Former-commit-id: 56da7f09fa90543ae4b07af79da5dd308a4aa395
This commit is contained in:
marcos.alconada 2019-04-25 16:05:27 +02:00
commit da8507c4b4
99 changed files with 21793 additions and 674 deletions

View File

@ -10,5 +10,16 @@
"require": { "require": {
"mpdf/mpdf": "^7.1", "mpdf/mpdf": "^7.1",
"swiftmailer/swiftmailer": "^6.0" "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/"
}
} }
} }

View File

@ -1090,10 +1090,11 @@ function readFields() {
var text = tinymce.get("text-label").getContent(); var text = tinymce.get("text-label").getContent();
values["label"] = text; values["label"] = text;
values["percentile_label_color"] = $(
"input[name=percentile_label_color]"
).val();
if ($("input[name=percentile_label]").val().length > 0) { if ($("input[name=percentile_label]").val().length > 0) {
values["percentile_label_color"] = $(
"input[name=percentile_label_color]"
).val();
values["label"] = values["label"] =
"<span style='color:" + "<span style='color:" +
values["percentile_label_color"] + values["percentile_label_color"] +

View File

@ -70,10 +70,6 @@ $values[SECONDS_5MINUTES] = human_time_description_raw(SECONDS_5MINUTES);
$values[SECONDS_10MINUTES] = human_time_description_raw(SECONDS_10MINUTES); $values[SECONDS_10MINUTES] = human_time_description_raw(SECONDS_10MINUTES);
$values[SECONDS_30MINUTES] = human_time_description_raw(SECONDS_30MINUTES); $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][0] = __('Paginated module view');
$table_behaviour->data[$row][1] = html_print_checkbox_switch( $table_behaviour->data[$row][1] = html_print_checkbox_switch(
'paginate_module', 'paginate_module',
@ -908,6 +904,24 @@ $row++;
$table_vc->size[0] = '50%'; $table_vc->size[0] = '50%';
$table_vc->data = []; $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[0] = __('Classic view');
$vc_favourite_view_array[1] = __('View of favorites'); $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); $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'>"; $table_vc->data[$row][1] = "<input type ='number' value=".$config['vc_menu_items']." size='5' name='vc_menu_items' min='0' max='25'>";
$row++; $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][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>'; echo '<fieldset>';

View File

@ -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. * By default, it will return all the agents where the user has reading access.
* *
* @param array filter options in an indexed array. See * @param array $filter Filter options in an indexed array.
* db_format_array_where_clause_sql() * See db_format_array_where_clause_sql().
* @param array Fields to get. * @param array $fields DB fields to get.
* @param string Access needed in the agents groups. * @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 array $order The order of agents, by default is upward
* @param boolean $return Whether to return array with agents or false, or sql string statement * 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( function agents_get_agents(
$filter=false, $filter=false,
@ -346,7 +353,8 @@ function agents_get_agents(
'order' => 'ASC', 'order' => 'ASC',
], ],
$return=false, $return=false,
$disabled_agent=0 $disabled_agent=0,
$use_meta_table=false
) { ) {
global $config; global $config;
@ -563,11 +571,15 @@ function agents_get_agents(
); );
} }
$table_name = ($use_meta_table === true) ? 'tmetaconsole_agent' : 'tagente';
$sql = sprintf( $sql = sprintf(
'SELECT DISTINCT %s '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', WHERE %s %s',
implode(',', $fields), implode(',', $fields),
$table_name,
$where, $where,
$order $order
); );

View File

@ -980,7 +980,11 @@ function config_update_config()
$error_update[] = __('Custom support url'); $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'); $error_update[] = __('Default interval for refresh on Visual Console');
} }
@ -2415,10 +2419,18 @@ function config_process_config()
config_update_value('dbtype', 'mysql'); config_update_value('dbtype', 'mysql');
} }
if (!isset($config['legacy_vc'])) {
config_update_value('legacy_vc', 0);
}
if (!isset($config['vc_refr'])) { if (!isset($config['vc_refr'])) {
config_update_value('vc_refr', 300); 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'])) { if (!isset($config['agent_size_text_small'])) {
config_update_value('agent_size_text_small', 18); config_update_value('agent_size_text_small', 18);
} }

View File

@ -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 * 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) * are OK. If any of them is down, then result is down (0)
* *
* @param int Id of the layout * @param integer $layout_id Id of the layout.
* @param array Information about the status calculation of the item * @param array $status_data Information about the status calculation of the
* @param int Depth (for recursion control) * 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) function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
{ {
global $config; global $config;
// TODO: Implement this limit into the setup // TODO: Implement this limit into the setup.
if ($depth > 10) { if ($depth > 10) {
return VISUAL_MAP_STATUS_UNKNOWN; 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) { if ($layout_items === false) {
return VISUAL_MAP_STATUS_UNKNOWN; 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 = []; $valid_layout_items = [];
foreach ($layout_items as $layout_item_data) { foreach ($layout_items as $layout_item_data) {
if (($layout_item_data['type'] == GROUP_ITEM if (($layout_item_data['type'] == GROUP_ITEM
&& !empty($layout_item_data['id_group']) && !empty($layout_item_data['id_group'])
&& check_acl($config['id_user'], $layout_item_data['id_group'], 'VR') && check_acl(
&& check_acl($config['id_user'], $layout_item_data['element_group'], 'VR')) $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_layout_linked'])
|| !empty($layout_item_data['id_agente_modulo']) || !empty($layout_item_data['id_agente_modulo'])
|| !empty($layout_item_data['id_agent'])) || !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; $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; 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()) { if (is_metaconsole()) {
sort_by_column($valid_layout_items, 'id_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) { if (empty($node_id) && $meta_connected_to) {
metaconsole_restore_db(); metaconsole_restore_db();
// Restore db connection // Restore db connection.
$meta_connected_to = null; $meta_connected_to = null;
} else if (!empty($node_id) && ( empty($meta_connected_to) } else if (!empty($node_id)
|| $meta_connected_to != $node_id) && (empty($meta_connected_to) || $meta_connected_to != $node_id)
) { ) {
if (!empty($meta_connected_to)) { if (!empty($meta_connected_to)) {
metaconsole_restore_db(); metaconsole_restore_db();
// Restore db connection // Restore db connection.
} }
$connection = metaconsole_get_connection_by_id($node_id); $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; $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) { if ($ent_element_status === ENTERPRISE_NOT_HOOK) {
$ent_element_status = false; $ent_element_status = false;
} }
// Enterprise element
if ($ent_element_status !== false) { if ($ent_element_status !== false) {
// Enterprise element.
$status = $ent_element_status; $status = $ent_element_status;
} } else {
// Other // Other.
else {
switch ($layout_item_data['type']) { switch ($layout_item_data['type']) {
case STATIC_GRAPH: case STATIC_GRAPH:
case PERCENTILE_BAR: case PERCENTILE_BAR:
case PERCENTILE_BUBBLE: case PERCENTILE_BUBBLE:
case CIRCULAR_PROGRESS_BAR: case CIRCULAR_PROGRESS_BAR:
case CIRCULAR_INTERIOR_PROGRESS_BAR: case CIRCULAR_INTERIOR_PROGRESS_BAR:
// Linked layout
if (!empty($layout_item_data['id_layout_linked'])) { 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)); // Linked layout.
} $status = visual_map_get_layout_status(
// Module $layout_item_data['id_layout_linked'],
else if (!empty($layout_item_data['id_agente_modulo'])) { $layout_item_data,
$module_status = modules_get_agentmodule_status($layout_item_data['id_agente_modulo']); ($depth + 1)
$status = visual_map_translate_module_status($module_status); );
} } else if (!empty($layout_item_data['id_agente_modulo'])) {
// Agent // Module.
else if (!empty($layout_item_data['id_agent'])) { $module_status = modules_get_agentmodule_status(
$agent_status = agents_get_status($layout_item_data['id_agent'], true); $layout_item_data['id_agente_modulo']
$status = visual_map_translate_agent_status($agent_status); );
} $status = visual_map_translate_module_status(
// Unknown $module_status
else { );
} 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; $status = VISUAL_MAP_STATUS_UNKNOWN;
} }
break; break;
case GROUP_ITEM: 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); $status = visual_map_translate_agent_status($group_status);
break; break;
default: default:
// If it's a graph, a progress bar or a data tag, ALWAYS report status OK // If it's a graph, a progress bar or a data tag,
// (=0) to avoid confussions here. // ALWAYS report status OK (=0) to avoid confussions here.
$status = VISUAL_MAP_STATUS_NORMAL; $status = VISUAL_MAP_STATUS_NORMAL;
break; break;
} }
} }
// When the status calculation type is 'default', only one critical element is required to // When the status calculation type is 'default', only one critical
// set the layout status as critical, so we can return the critical status right now. // element is required to set the layout status as critical, so we can
if ($status_data['linked_layout_status_type'] === 'default' && ( $status == VISUAL_MAP_STATUS_CRITICAL_BAD // 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) || $status == VISUAL_MAP_STATUS_CRITICAL_ALERT)
) { ) {
if (is_metaconsole() && $meta_connected_to) { if (is_metaconsole() && $meta_connected_to) {
// Restore db connection.
metaconsole_restore_db(); metaconsole_restore_db();
// Restore db connection
} }
return $status; return $status;
@ -4113,11 +4145,11 @@ function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
} }
if (is_metaconsole() && $meta_connected_to) { if (is_metaconsole() && $meta_connected_to) {
// Restore db connection.
metaconsole_restore_db(); metaconsole_restore_db();
// Restore db connection
} }
// Status calculation // Status calculation.
switch ($status_data['linked_layout_status_type']) { switch ($status_data['linked_layout_status_type']) {
default: default:
case 'default': case 'default':
@ -4149,7 +4181,7 @@ function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
if ($num_items_critical > 0 if ($num_items_critical > 0
&& ((($num_items_critical_alert + $num_items_critical) * 100) / $num_items) >= $weight && ((($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 } else if ($num_items_warning > 0
&& (($num_items_warning * 100) / $num_items) >= $weight && (($num_items_warning * 100) / $num_items) >= $weight
) { ) {
@ -4525,3 +4557,43 @@ function visual_map_get_color_cloud_element($data)
<?php <?php
return ob_get_clean(); 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);
}
}
}

View File

@ -688,7 +688,7 @@ function visual_map_editor_print_item_palette($visualConsole_id, $background)
'percentile_item', 'percentile_item',
'datos', '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( <td align="left">'.html_print_input_text_extended(
'percentile_label_color', 'percentile_label_color',
'#ffffff', '#ffffff',

View File

@ -2133,7 +2133,7 @@ function pandoraFlotArea(
var plot = $.plot($("#" + graph_id), datas, options); var plot = $.plot($("#" + graph_id), datas, options);
// Re-calculate the graph height with the legend height // Re-calculate the graph height with the legend height
if (dashboard || vconsole) { if (dashboard) {
$acum = 0; $acum = 0;
if (dashboard) $acum = 35; if (dashboard) $acum = 35;
var hDiff = var hDiff =

View File

@ -1748,7 +1748,9 @@ function round_with_decimals(value, multiplier) {
if (typeof multiplier === "undefined") multiplier = 1; if (typeof multiplier === "undefined") multiplier = 1;
// Return non numeric types without modification // 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 (value * multiplier == 0) return 0;
if (Math.abs(value) * multiplier >= 1) { if (Math.abs(value) * multiplier >= 1) {

View File

@ -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 These functions require jQuery library
**************************************/ **************************************/

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,
]
);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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*/

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -15,245 +15,9 @@
// The session is configured and started inside the config process. // The session is configured and started inside the config process.
require_once '../../include/config.php'; require_once '../../include/config.php';
// Set root on homedir, as defined in setup $legacy = (bool) get_parameter('legacy', $config['legacy_vc']);
chdir($config['homedir']); if ($legacy === false) {
include_once $config['homedir'].'/operation/visual_console/public_view.php';
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 { } 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>

View File

@ -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>

View File

@ -13,368 +13,9 @@
// GNU General Public License for more details. // GNU General Public License for more details.
global $config; global $config;
// Login check $legacy = (bool) get_parameter('legacy', $config['legacy_vc']);
require_once $config['homedir'].'/include/functions_visual_map.php'; if ($legacy === false) {
include_once $config['homedir'].'/operation/visual_console/view.php';
check_login();
if (!defined('METACONSOLE')) {
$id_layout = (int) get_parameter('id');
} else { } 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>

View File

@ -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>

View File

@ -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
);
}
}

View File

@ -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,
]
)
);
}
}

View File

@ -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+',
]
)
);
}
}

View File

@ -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,
]
)
);
}
}

View File

@ -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',
]
)
);
}
}

View File

@ -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,
]
);
}
}

View File

@ -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+',
]
)
);
}
}

View File

@ -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',
]
)
);
}
}

View File

@ -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,
]
)
);
}
}

View File

@ -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,
]
)
);
}
}

View File

@ -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,
]
)
);
}
}

View File

@ -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',
]
)
);
}
}

View File

@ -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',
]
)
);
}
}

View File

@ -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',
]
)
);
}
}

View File

@ -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',
]
);
}
}

View File

@ -0,0 +1,4 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
require_once __DIR__.'/../include/constants.php';

View File

@ -279,7 +279,7 @@ class ClassLoader
*/ */
public function setApcuPrefix($apcuPrefix) 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;
} }
/** /**

View File

@ -6,8 +6,11 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'Tests\\' => array($baseDir . '/tests'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Mpdf\\' => array($vendorDir . '/mpdf/mpdf/src'), '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'), 'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/EmailValidator'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), 'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
); );

View File

@ -12,6 +12,10 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
); );
public static $prefixLengthsPsr4 = array ( public static $prefixLengthsPsr4 = array (
'T' =>
array (
'Tests\\' => 6,
),
'P' => 'P' =>
array ( array (
'Psr\\Log\\' => 8, 'Psr\\Log\\' => 8,
@ -19,9 +23,11 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
'M' => 'M' =>
array ( array (
'Mpdf\\' => 5, 'Mpdf\\' => 5,
'Models\\' => 7,
), ),
'E' => 'E' =>
array ( array (
'Enterprise\\Models\\' => 18,
'Egulias\\EmailValidator\\' => 23, 'Egulias\\EmailValidator\\' => 23,
), ),
'D' => 'D' =>
@ -31,6 +37,10 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
); );
public static $prefixDirsPsr4 = array ( public static $prefixDirsPsr4 = array (
'Tests\\' =>
array (
0 => __DIR__ . '/../..' . '/tests',
),
'Psr\\Log\\' => 'Psr\\Log\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log', 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
@ -39,6 +49,14 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
array ( array (
0 => __DIR__ . '/..' . '/mpdf/mpdf/src', 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\\' => 'Egulias\\EmailValidator\\' =>
array ( array (
0 => __DIR__ . '/..' . '/egulias/email-validator/EmailValidator', 0 => __DIR__ . '/..' . '/egulias/email-validator/EmailValidator',

View File

@ -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"
}
}

27
visual_console_client/.gitignore vendored Normal file
View File

@ -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*

View File

@ -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}"
}
]
}

View File

@ -0,0 +1,10 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
}

View File

@ -0,0 +1 @@
# Visual Console Client

View File

@ -0,0 +1 @@
module.exports = "test-file-stub";

View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -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"
}
};

7957
visual_console_client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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));
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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());
}
}
}
}

View File

@ -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.

View File

@ -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.");
}
}
}

View File

@ -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");
});
});
});

View File

@ -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;
}

View File

@ -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);
});
});

View File

@ -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;
}
}

View File

@ -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());
}
}
}
}

View File

@ -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());
}
}
}
}

View File

@ -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);
});
});

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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)
};
}
}

View File

@ -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());
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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");
});
});

View File

@ -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
);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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"]
}

View File

@ -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"
}
};