Merge branch 'ent-6384-consola-visual-nuevo-elemento-interfaz-de-red' into 'develop'

First steps VC network link

See merge request artica/pandorafms!3521
This commit is contained in:
Daniel Rodriguez 2021-02-03 13:29:44 +01:00
commit aba8813095
35 changed files with 2473 additions and 441 deletions

View File

@ -119,6 +119,7 @@ foreach ($layoutDatas as $layoutData) {
}
switch ($layoutData['type']) {
case NETWORK_LINK:
case LINE_ITEM:
visual_map_print_user_line_handles($layoutData);
visual_map_print_user_lines($layoutData);

View File

@ -271,6 +271,7 @@ foreach ($layoutDatas as $layoutData) {
);
break;
case NETWORK_LINK:
case LINE_ITEM:
$table->data[($i + 1)]['icon'] = html_print_image(
'images/line_item.png',
@ -303,6 +304,7 @@ foreach ($layoutDatas as $layoutData) {
switch ($layoutData['type']) {
case ICON:
case BOX_ITEM:
case NETWORK_LINK:
case LINE_ITEM:
// hasn't the label.
$table->data[($i + 1)][0] = '';
@ -345,6 +347,7 @@ foreach ($layoutDatas as $layoutData) {
// Width and height
switch ($layoutData['type']) {
case NETWORK_LINK:
case LINE_ITEM:
// hasn't the width and height.
$table->data[($i + 1)][2] = '';
@ -361,6 +364,7 @@ foreach ($layoutDatas as $layoutData) {
// Position
switch ($layoutData['type']) {
case NETWORK_LINK:
case LINE_ITEM:
// hasn't the width and height.
$table->data[($i + 1)][3] = '';
@ -375,6 +379,7 @@ foreach ($layoutDatas as $layoutData) {
// Parent
switch ($layoutData['type']) {
case BOX_ITEM:
case NETWORK_LINK:
case LINE_ITEM:
case COLOR_CLOUD:
$table->data[($i + 1)][4] = '';
@ -434,6 +439,7 @@ foreach ($layoutDatas as $layoutData) {
case BOX_ITEM:
case ICON:
case LABEL:
case NETWORK_LINK:
case LINE_ITEM:
$table->data[($i + 2)][0] = '';
break;
@ -494,6 +500,7 @@ foreach ($layoutDatas as $layoutData) {
case ICON:
case LABEL:
case BOX_ITEM:
case NETWORK_LINK:
case LINE_ITEM:
case GROUP_ITEM:
$table->data[($i + 2)][1] = '';
@ -598,6 +605,7 @@ foreach ($layoutDatas as $layoutData) {
// Map linked
switch ($layoutData['type']) {
case NETWORK_LINK:
case LINE_ITEM:
case BOX_ITEM:
case AUTO_SLA_GRAPH:

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1449,6 +1449,16 @@ switch ($action) {
}
switch ($type) {
case 'network_link':
$values['type'] = NETWORK_LINK;
$values['border_width'] = $line_width;
$values['border_color'] = $line_color;
$values['pos_x'] = $line_start_x;
$values['pos_y'] = $line_start_y;
$values['width'] = $line_end_x;
$values['height'] = $line_end_y;
break;
case 'line_item':
$values['type'] = LINE_ITEM;
$values['border_width'] = $line_width;

View File

@ -232,6 +232,7 @@ define('DONUT_GRAPH', 17);
define('BARS_GRAPH', 18);
define('CLOCK', 19);
define('COLOR_CLOUD', 20);
define('NETWORK_LINK', 21);
// Some styles.
define('MIN_WIDTH', 300);
define('MIN_HEIGHT', 120);

View File

@ -3670,6 +3670,7 @@ function visual_map_print_visual_map(
$layout_data['label'] = visual_map_macro($layout_data['label'], $layout_data['id_agente_modulo']);
switch ($layout_data['type']) {
case NETWORK_LINK:
case LINE_ITEM:
visual_map_print_user_lines($layout_data, $proportion);
break;
@ -4397,6 +4398,9 @@ function visual_map_type_in_js($type)
case LINE_ITEM:
return 'line_item';
case NETWORK_LINK:
return 'network_link';
case COLOR_CLOUD:
return 'color_cloud';

View File

@ -1308,6 +1308,13 @@ function visual_map_editor_print_toolbox()
visual_map_print_button_editor('box_item', __('Box'), 'left', false, 'box_item_min', true);
visual_map_print_button_editor('line_item', __('Line'), 'left', false, 'line_item_min', true);
visual_map_print_button_editor('color_cloud', __('Color cloud'), 'left', false, 'color_cloud_min', true);
if (isset($config['legacy_vc']) === false
|| (bool) $config['legacy_vc'] === false
) {
// Applies only on modern VC.
visual_map_print_button_editor('network_link', __('Network link'), 'left', false, 'network_link_min', true);
}
if (defined('METACONSOLE')) {
echo '<a href="javascript:" class="tip"><img src="'.$config['homeurl_static'].'/images/tip.png" data-title="The data displayed in editor mode is not real" data-use_title_for_force_title="1"
class="forced_title" alt="The data displayed in editor mode is not real"></a>';

View File

@ -87,7 +87,12 @@ function load_modal(settings) {
div.id = "div-modal-" + uniq;
div.style.display = "none";
document.getElementById("main").append(div);
if (document.getElementById("main") == null) {
// MC env.
document.getElementById("page").append(div);
} else {
document.getElementById("main").append(div);
}
var id_modal_target = "#div-modal-" + uniq;
@ -281,8 +286,6 @@ function load_modal(settings) {
data: formdata,
dataType: settings.onsubmit.dataType,
success: function(data) {
console.log("successsssssssssssss");
console.log(data);
if (settings.ajax_callback != undefined) {
if (settings.idMsgCallback != undefined) {
settings.ajax_callback(data, settings.idMsgCallback);
@ -379,6 +382,8 @@ function load_modal(settings) {
//$(".ui-dialog-titlebar-close").hide();
},
close: function() {
$(this).dialog("destroy");
if (id_modal_target != undefined) {
$(id_modal_target).remove();
}
@ -386,14 +391,12 @@ function load_modal(settings) {
if (settings.cleanup != undefined) {
settings.cleanup();
}
$(this).dialog("destroy");
},
beforeClose: settings.beforeClose()
});
},
error: function(data) {
// console.log(data);
console.error(data);
}
});
}

View File

@ -186,13 +186,44 @@ function createVisualConsole(
var item = e.item || {};
var meta = item.meta || {};
if ((meta.editMode || meta.lineMode) && !meta.isUpdating) {
if (meta.editMode && !meta.isUpdating) {
createOrUpdateVisualConsoleItem(
visualConsole,
asyncTaskManager,
baseUrl,
item
);
} else if (meta.lineMode && item.props.type == 21) {
load_modal({
url: baseUrl + "/ajax.php",
modal: {
title: "NetworkLink information",
ok: "Ok"
},
extradata: [
{
name: "from",
value: item.props.linkedStart
},
{
name: "to",
value: item.props.linkedEnd
}
],
onshow: {
page: "include/rest-api/index",
method: "networkLinkPopup"
}
});
// confirmDialog({
// title: "todo",
// message:
// "<pre>" +
// item.props.labelStart +
// "</pre><br><pre>" +
// item.props.labelEnd +
// "</pre>"
// });
}
});
// VC Item moved.
@ -203,7 +234,7 @@ function createVisualConsole(
y: e.newPosition.y,
type: e.item.props.type
};
if (e.item.props.type === 13) {
if (e.item.props.type === 13 || e.item.props.type === 21) {
var startIsLeft =
e.item.props.startPosition.x - e.item.props.endPosition.x <= 0;
var startIsTop =
@ -279,6 +310,7 @@ function createVisualConsole(
endX: e.endPosition.x,
endY: e.endPosition.y
};
var taskId = "visual-console-item-update-" + id;
// Persist the new position.
@ -290,18 +322,14 @@ function createVisualConsole(
id,
data,
function(error, data) {
// if (!error && !data) return;
if (error || !data) {
console.log(
"[ERROR]",
"[VISUAL-CONSOLE-CLIENT]",
"[API]",
error ? error.message : "Invalid response"
);
if (!error && !data) return;
// TODO: Move the element to its initial position.
try {
var decoded_data = JSON.parse(data);
visualConsole.updateElement(decoded_data);
} catch (error) {
console.error(error);
}
done();
}
);
@ -427,6 +455,7 @@ function createVisualConsole(
},
createItem: function(typeString) {
var type;
console.log(typeString);
switch (typeString) {
case "STATIC_GRAPH":
type = 0;
@ -479,6 +508,9 @@ function createVisualConsole(
case "COLOR_CLOUD":
type = 20;
break;
case "NETWORK_LINK":
type = 21;
break;
default:
type = 0;
}
@ -565,7 +597,7 @@ function createVisualConsole(
item.setMeta({ isUpdating: false });
var itemRetrieved = item.props;
if (itemRetrieved["type"] == 13) {
if (itemRetrieved["type"] == 13 || itemRetrieved["type"] == 21) {
var startIsLeft =
itemRetrieved["startPosition"]["x"] -
itemRetrieved["endPosition"]["x"] <=
@ -1179,6 +1211,9 @@ function createOrUpdateVisualConsoleItem(
case 20:
nameType = "Color Cloud";
break;
case 21:
nameType = "Network Link";
break;
default:
nameType = "Static graph";
@ -1259,7 +1294,8 @@ function createOrUpdateVisualConsoleItem(
tinyMCE != undefined &&
tinyMCE.editors.length > 0 &&
item.itemProps.type != 12 &&
item.itemProps.type != 13
item.itemProps.type != 13 &&
item.itemProps.type != 21
) {
// Content tiny.
var label = tinyMCE.activeEditor.getContent();

View File

@ -419,13 +419,126 @@ class Agent extends Entity
}
/**
* Return a list of interfaces.
*
* @param array $filter Filter interfaces by name in array.
*
* @return array Of interfaces and modules PandoraFMS\Modules.
*/
public function getInterfaces(array $filter=[])
{
$modules = $this->searchModules(
['nombre' => '%ifOperStatus%']
);
$interfaces = [];
foreach ($modules as $module) {
$matches = [];
if (preg_match(
'/^(.*?)_ifOperStatus$/',
$module->name(),
$matches
) > 0
) {
$interface = $matches[1];
}
if (empty($interface) === true) {
continue;
}
if (empty($filter) === false
&& in_array($interface, $filter) !== true
) {
continue;
}
$name_filters = [
'ifOperStatus' => ['nombre' => $interface.'_ifOperStatus'],
'ifInOctets' => ['nombre' => $interface.'_ifInOctets'],
'ifOutOctets' => ['nombre' => $interface.'_ifOutOctets'],
'ifHCInOctets' => ['nombre' => $interface.'_ifHCInOctets'],
'ifHCOutOctets' => ['nombre' => $interface.'_ifHCOutOctets'],
];
$ifOperStatus = $this->searchModules(
$name_filters['ifOperStatus']
);
$ifInOctets = $this->searchModules(
$name_filters['ifInOctets']
);
$ifOutOctets = $this->searchModules(
$name_filters['ifOutOctets']
);
$ifHCInOctets = $this->searchModules(
$name_filters['ifHCInOctets']
);
$ifHCOutOctets = $this->searchModules(
$name_filters['ifHCOutOctets']
);
$interfaces[$interface] = [
'ifOperStatus' => array_shift($ifOperStatus),
'ifInOctets' => array_shift($ifInOctets),
'ifOutOctets' => array_shift($ifOutOctets),
'ifHCInOctets' => array_shift($ifHCInOctets),
'ifHCOutOctets' => array_shift($ifHCOutOctets),
];
}
return $interfaces;
}
/**
* Retrieves status, in and out modules from given interface name.
*
* @param string $interface Interface name.
*
* @return array|null With status, in and out modules. Null if no iface.
*/
public function getInterfaceMetrics(string $interface):?array
{
$modules = $this->getInterfaces([$interface]);
if (empty($modules) === true) {
return null;
}
$modules = $modules[$interface];
$in = null;
$out = null;
$status = $modules['ifOperStatus'];
if (empty($modules['ifHCInOctets']) === false) {
$in = $modules['ifHCInOctets'];
} else if (empty($modules['ifInOctets']) === false) {
$in = $modules['ifInOctets'];
}
if (empty($modules['ifHCOutOctets']) === false) {
$out = $modules['ifHCOutOctets'];
} else if (empty($modules['ifOutOctets']) === false) {
$out = $modules['ifOutOctets'];
}
return [
'in' => $in,
'out' => $out,
'status' => $status,
];
}
/**
* Search for modules into this agent.
*
* @param array $filter Filters.
* @param integer $limit Limit search results.
*
* @return PandoraFMS\Module Module found.
* @return array Of PandoraFMS\Module Modules found.
*/
public function searchModules(array $filter, int $limit=0)
{
@ -452,7 +565,12 @@ class Agent extends Entity
return $results;
} else {
// Search in db.
return Module::search($filter, $limit);
$return = Module::search($filter, $limit);
if (is_array($return) === false) {
return [];
}
return $return;
}
}

View File

@ -39,6 +39,12 @@ use PandoraFMS\ModuleType;
class Module extends Entity
{
const INTERFACE_STATUS = 1;
const INTERFACE_INOCTETS = 2;
const INTERFACE_OUTOCTETS = 3;
const INTERFACE_HC_INOCTETS = 4;
const INTERFACE_HC_OUTOCTETS = 5;
/**
* Module status (From tagente_estado).
*
@ -753,6 +759,84 @@ class Module extends Entity
}
/**
* Return true if module represents an interface (operStatus, in/outOctets)
*
* @return integer > 0 if interface module, 0 if not.
*/
public function isInterfaceModule():int
{
if (strstr($this->name(), '_ifOperStatus') !== false) {
return self::INTERFACE_STATUS;
}
if (strstr($this->name(), '_ifInOctets') !== false) {
return self::INTERFACE_INOCTETS;
}
if (strstr($this->name(), '_ifOutOctets') !== false) {
return self::INTERFACE_OUTOCTETS;
}
if (strstr($this->name(), '_ifHCInOctets') !== false) {
return self::INTERFACE_HC_INOCTETS;
}
if (strstr($this->name(), '_ifHCOutOctets') !== false) {
return self::INTERFACE_HC_OUTOCTETS;
}
return 0;
}
/**
* Return interface name if module represents an interface module.
*
* @return string|null Interface name or null.
*/
public function getInterfaceName():?string
{
$label = null;
switch ($this->isInterfaceModule()) {
case self::INTERFACE_STATUS:
$label = '_ifOperStatus';
break;
case self::INTERFACE_INOCTETS:
$label = '_ifInOctets';
break;
case self::INTERFACE_OUTOCTETS:
$label = '_ifOutOctets';
break;
case self::INTERFACE_HC_INOCTETS:
$label = '_ifHCInOctets';
break;
case self::INTERFACE_HC_OUTOCTETS:
$label = '_ifHCOutOctets';
break;
default:
// Not an interface module.
return null;
}
if (preg_match(
'/^(.*?)'.$label.'$/',
$this->name(),
$matches
) > 0
) {
return $matches[1];
}
return null;
}
/**
* Transforms configuration data into an array.
*

View File

@ -9,6 +9,10 @@ if (!is_ajax()) {
require_once $config['homedir'].'/vendor/autoload.php';
// Require also some stuff from Pandora FMS.
enterprise_include('include/functions_metaconsole.php');
use Models\VisualConsole\Container as VisualConsole;
use Models\VisualConsole\View as Viewer;
use Models\VisualConsole\Item as Item;
@ -109,7 +113,7 @@ if ($getVisualConsole === true) {
$ratio
);
echo '['.implode($vcItems, ',').']';
echo '['.implode(',', $vcItems).']';
return;
} else if ($getVisualConsoleItem === true
|| $updateVisualConsoleItem === true
@ -121,6 +125,7 @@ if ($getVisualConsole === true) {
} catch (Throwable $e) {
// Bad params.
http_response_code(400);
hd($e);
return;
}
@ -245,7 +250,9 @@ if ($getVisualConsole === true) {
$item = VisualConsole::getItemFromDB($itemId);
$data = $item->toArray();
$data['id_layout'] = $visualConsoleId;
if ($data['type'] === LINE_ITEM) {
if ($data['type'] === LINE_ITEM
|| $data['type'] === NETWORK_LINK
) {
$data['endX'] = ($data['endX'] + 20);
$data['endY'] = ($data['endY'] + 20);
$data['startX'] = ($data['startX'] + 20);

View File

@ -366,6 +366,9 @@ final class Container extends Model
case COLOR_CLOUD:
return Items\ColorCloud::class;
case NETWORK_LINK:
return Items\NetworkLink::class;
default:
return Item::class;
}
@ -481,6 +484,7 @@ final class Container extends Model
$item = $class::fromDB($row);
} catch (\Throwable $e) {
// TODO: Log this?
error_log(obhd($e));
}
return $item;

View File

@ -196,6 +196,7 @@ class Item extends CachedModel
{
$decodedData = [
'id' => (int) $data['id'],
'colorStatus' => (string) COL_UNKNOWN,
'type' => (int) $data['type'],
'label' => static::extractLabel($data),
'labelPosition' => static::extractLabelPosition($data),
@ -1461,6 +1462,11 @@ class Item extends CachedModel
$result['linked_layout_node_id'] = $linked_layout_node_id;
}
if ($id_layout_linked > 0) {
// If VC linked, force link status to enabled.
$result['enable_link'] = 1;
}
$linked_layout_status_type = static::notEmptyStringOr(
static::issetInArray(
$data,
@ -2030,8 +2036,8 @@ class Item extends CachedModel
break;
default:
// Line not parent.
break;
// Lines could not be parents.
continue 2;
}
if (isset($data['agentAlias']) === true
@ -2204,7 +2210,7 @@ class Item extends CachedModel
*
* @return array Array all VCs.
*/
public function getAllVisualConsole(int $id):array
public static function getAllVisualConsole(int $id):array
{
// Extract all VC except own.
$result = db_get_all_rows_filter(
@ -2219,7 +2225,7 @@ class Item extends CachedModel
// Extract all VC for each node.
if (is_metaconsole() === true) {
enterprise_include_once('include/functions_metaconsole.php');
$meta_servers = metaconsole_get_servers();
$meta_servers = (array) metaconsole_get_servers();
foreach ($meta_servers as $server) {
if (metaconsole_load_external_db($server) !== NOERR) {
metaconsole_restore_db();
@ -2257,7 +2263,7 @@ class Item extends CachedModel
}
}
if ($result === false && $result === '') {
if ($result === false || $result === '') {
$result = [];
}
@ -2280,14 +2286,18 @@ class Item extends CachedModel
if ($fields === false) {
$fields = [];
} else {
$fields = \array_reduce(
$fields,
function ($carry, $item) {
$carry[$item['id']] = $item['name'];
return $carry;
},
[]
);
$rs = [];
foreach ($fields as $k => $v) {
if (isset($v['id']) === true && isset($v['name']) === true) {
// Modern environments use id-name format.
$rs[$v['id']] = $v;
} else {
// In MC environments is key-value.
$rs[$k] = $v;
}
}
$fields = $rs;
}
$getAllVisualConsoleValue = $values['linkedLayoutId'];

View File

@ -0,0 +1,958 @@
<?php
declare(strict_types=1);
namespace Models\VisualConsole\Items;
use Models\Model;
/**
* Model of a line item of the Visual Console.
*/
final class NetworkLink 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' => NETWORK_LINK,
'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),
'labelStart' => static::extractLabelStart($data),
'labelEnd' => static::extractLabelEnd($data),
'labelStartWidth' => static::extractlabelStartWidth($data),
'labelEndWidth' => static::extractlabelEndWidth($data),
'labelStartHeight' => static::extractlabelStartHeight($data),
'labelEndHeight' => static::extractlabelEndHeight($data),
'linkedStart' => static::extractlinkedStart($data),
'linkedEnd' => static::extractlinkedEnd($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
);
}
/**
* Extract information to fullfil labels in NetworkLinks.
*
* @param string $ref Sub-data to extract from "label".
* @param array $data Unknown input data structure.
*
* @return mixed Reference from json encoded data stored in db.
*/
private static function extractExtra(?string $ref, array $data)
{
if ($data['label'] === null) {
return null;
}
$return = json_decode($data['label'], true);
if (json_last_error() === JSON_ERROR_NONE) {
if ($ref !== null) {
return $return[$ref];
}
return $return;
}
return null;
}
/**
* Extract label Start.
*
* @param array $data Unknown input data structure.
*
* @return mixed String representing label Start or null.
*/
private static function extractLabelStart(array $data)
{
return static::extractExtra(
'labelStart',
$data
);
}
/**
* Extract label End.
*
* @param array $data Unknown input data structure.
*
* @return mixed String representing label End or null.
*/
private static function extractLabelEnd(array $data)
{
return static::extractExtra(
'labelEnd',
$data
);
}
/**
* Extract label StartWidth.
*
* @param array $data Unknown input data structure.
*
* @return mixed Float representing label StartWidth or null.
*/
private static function extractlabelStartWidth(array $data)
{
return static::extractExtra(
'labelStartWidth',
$data
);
}
/**
* Extract label EndWidth.
*
* @param array $data Unknown input data structure.
*
* @return mixed Float representing label EndWidth or null.
*/
private static function extractlabelEndWidth(array $data)
{
return static::extractExtra(
'labelEndWidth',
$data
);
}
/**
* Extract label StartHeight.
*
* @param array $data Unknown input data structure.
*
* @return mixed Float representing label StartHeight or null.
*/
private static function extractlabelStartHeight(array $data)
{
return static::extractExtra(
'labelStartHeight',
$data
);
}
/**
* Extract label EndHeight.
*
* @param array $data Unknown input data structure.
*
* @return mixed Float representing label EndHeight or null.
*/
private static function extractlabelEndHeight(array $data)
{
return static::extractExtra(
'labelEndHeight',
$data
);
}
/**
* Extract label StartHeight.
*
* @param array $data Unknown input data structure.
*
* @return mixed Float representing label StartHeight or null.
*/
private static function extractlinkedStart(array $data)
{
return static::extractExtra(
'linkedStart',
$data
);
}
/**
* Extract label EndHeight.
*
* @param array $data Unknown input data structure.
*
* @return mixed Float representing label EndHeight or null.
*/
private static function extractlinkedEnd(array $data)
{
return static::extractExtra(
'linkedEnd',
$data
);
}
/**
* Obtain a vc item data structure from the database using a filter.
*
* @param array $filter Filter of the Visual Console Item.
* @param float $ratio Adjustment ratio factor.
*
* @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,
?float $ratio=0
): 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');
}
if ($ratio != 0) {
$row['width'] = ($row['width'] * $ratio);
$row['height'] = ($row['height'] * $ratio);
$row['pos_x'] = ($row['pos_x'] * $ratio);
$row['pos_y'] = ($row['pos_y'] * $ratio);
}
$row['label'] = static::buildLabels($row);
return $row;
}
/**
* Calculates linked elements given line data.
*
* @param array $data Input data (item).
*
* @return array With start and end elements.
*/
private static function getLinkedItems(array $data)
{
$startX = $data['startX'];
$startY = $data['startY'];
$endX = $data['endX'];
$endY = $data['endY'];
$linked_start = static::extractlinkedStart($data);
$linked_end = static::extractlinkedEnd($data);
$start = false;
$end = false;
if ($linked_start !== null) {
$start = \db_get_row_filter(
'tlayout_data',
[
'id_layout' => $data['id_layout'],
'id' => $linked_start,
]
);
}
if ($linked_end !== null) {
$end = \db_get_row_filter(
'tlayout_data',
[
'id_layout' => $data['id_layout'],
'id' => $linked_end,
]
);
}
if (isset($data['width']) === true) {
// Row format.
$startX = $data['pos_x'];
$startY = $data['pos_y'];
$endX = $data['width'];
$endY = $data['height'];
}
if ($start === false) {
$start = \db_get_row_filter(
'tlayout_data',
[
'id_layout' => $data['id_layout'],
'pos_x' => '<'.$startX,
'pos_y' => '<'.$startY,
'pos_x`+`width' => '>'.$startX,
'pos_y`+`height' => '>'.$startY,
'type' => '!'.NETWORK_LINK,
'order' => [
[
'field' => 'show_on_top',
'order' => 'desc',
],
[
'field' => 'id',
'order' => 'desc',
],
],
]
);
}
if ($end === false) {
$end = \db_get_row_filter(
'tlayout_data',
[
'id_layout' => $data['id_layout'],
'pos_x' => '<'.$endX,
'pos_y' => '<'.$endY,
'pos_x`+`width' => '>'.$endX,
'pos_y`+`height' => '>'.$endY,
'type' => '!'.NETWORK_LINK,
'order' => [
[
'field' => 'show_on_top',
'order' => 'desc',
],
[
'field' => 'id',
'order' => 'desc',
],
],
]
);
}
return [
'start' => $start,
'end' => $end,
];
}
/**
* Builds a label depending on the information available.
*
* @param array $data Input data.
*
* @return string JSON encoded results to be stored in DB.
* @throws \Exception If cannot connect to node if needed.
*/
private static function buildLabels(array $data)
{
$links = self::getLinkedItems($data);
$labelStart = null;
$labelEnd = null;
$linkedStart = null;
$linkedEnd = null;
/*
* If start['id_agente_modulo'] its a network module (in/out/status)
* then:
*
* start => outOctets
*
* If end['id_agente_modulo'] its a network module (in/out/status)
* then:
* end => inOctets
*
*/
try {
if (isset($links['start']) === true) {
$linkedStart = $links['start']['id'];
if (is_numeric($links['start']['id_agente_modulo']) === true
&& $links['start']['id_agente_modulo'] > 0
) {
if (isset($links['start']['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
$cnn = \enterprise_hook(
'metaconsole_get_connection_by_id',
[ $links['start']['id_metaconsole'] ]
);
if (\enterprise_hook('metaconsole_connect', [$cnn]) !== NOERR) {
throw new \Exception(__('Failed to connect to node'));
}
}
$module = new \PandoraFMS\Module(
(int) $links['start']['id_agente_modulo']
);
if ((bool) $module->isInterfaceModule() === true) {
$interface_name = $module->getInterfaceName();
$interface = array_shift(
$module->agent()->getInterfaces(
[$interface_name]
)
);
$outOctets = 0;
$inOctets = 0;
$unitIn = '';
$unitOut = '';
if (isset($interface['ifOutOctets']) === true) {
$outOctets = $interface['ifOutOctets']->lastValue();
$unitOut = $interface['ifOutOctets']->unit();
} else if (isset($interface['ifHCOutOctets']) === true) {
$outOctets = $interface['ifHCOutOctets']->lastValue();
$unitOut = $interface['ifHCOutOctets']->unit();
}
if (isset($interface['ifInOctets']) === true) {
$inOctets = $interface['ifInOctets']->lastValue();
$unitIn = $interface['ifInOctets']->unit();
} else if (isset($interface['ifHCInOctets']) === true) {
$inOctets = $interface['ifHCInOctets']->lastValue();
$unitIn = $interface['ifHCInOctets']->unit();
}
if (empty($outOctets) === true) {
$outOctets = 0;
}
if (empty($inOctets) === true) {
$inOctets = 0;
}
$outOctets = sprintf('%0.3f %s', $outOctets, $unitOut);
$inOctets = sprintf('%0.3f %s', $inOctets, $unitIn);
$labelStart = $interface_name;
$labelStart .= ' (in): '.$inOctets;
$labelStart .= '<br>'.$interface_name;
$labelStart .= ' (out): '.$outOctets;
}
if (isset($links['start']['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
\enterprise_hook('metaconsole_restore_db');
}
}
}
} catch (\Exception $e) {
$labelStart = $e->getMessage();
}
try {
if (isset($links['end']) === true) {
$linkedEnd = $links['end']['id'];
if (is_numeric($links['end']['id_agente_modulo']) === true
&& $links['end']['id_agente_modulo'] > 0
) {
if (isset($links['end']['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
$cnn = \enterprise_hook(
'metaconsole_get_connection_by_id',
[$links['end']['id_metaconsole']]
);
if (\enterprise_hook('metaconsole_connect', [$cnn]) !== NOERR) {
throw new \Exception(__('Failed to connect to node'));
}
}
$module = new \PandoraFMS\Module(
(int) $links['end']['id_agente_modulo']
);
if ((bool) $module->isInterfaceModule() === true) {
$interface_name = $module->getInterfaceName();
$interface = array_shift(
$module->agent()->getInterfaces(
[$interface_name]
)
);
$outOctets = 0;
$inOctets = 0;
$unitIn = '';
$unitOut = '';
if (isset($interface['ifOutOctets']) === true) {
$outOctets = $interface['ifOutOctets']->lastValue();
$unitOut = $interface['ifOutOctets']->unit();
} else if (isset($interface['ifHCOutOctets']) === true) {
$outOctets = $interface['ifHCOutOctets']->lastValue();
$unitOut = $interface['ifHCOutOctets']->unit();
}
if (isset($interface['ifInOctets']) === true) {
$inOctets = $interface['ifInOctets']->lastValue();
$unitIn = $interface['ifInOctets']->unit();
} else if (isset($interface['ifHCInOctets']) === true) {
$inOctets = $interface['ifHCInOctets']->lastValue();
$unitIn = $interface['ifHCInOctets']->unit();
}
if (empty($outOctets) === true) {
$outOctets = 0;
}
if (empty($inOctets) === true) {
$inOctets = 0;
}
$outOctets = sprintf('%0.3f %s', $outOctets, $unitOut);
$inOctets = sprintf('%0.3f %s', $inOctets, $unitIn);
$labelEnd = $interface_name;
$labelEnd .= ' (in): '.$inOctets;
$labelEnd .= '<br>'.$interface_name;
$labelEnd .= ' (out): '.$outOctets;
}
if (isset($links['end']['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
\enterprise_hook('metaconsole_restore_db');
}
}
}
} catch (\Exception $e) {
$labelEnd = $e->getMessage();
}
return json_encode(
[
'labelStart' => io_safe_output($labelStart),
'labelEnd' => io_safe_output($labelEnd),
'linkedStart' => $linkedStart,
'linkedEnd' => $linkedEnd,
// 'labelStartWidth' => 105,
// 'labelStartHeight' => 105,
// 'labelEndWidth' => 105,
// 'labelEndHeight' => 105,
]
);
}
/**
* Return a valid representation of a record in database.
*
* @param array $data Input data.
*
* @return array Data structure representing a record in database.
*
* @overrides Model::encode.
*/
protected function encode(array $data): array
{
$result = [];
$result['type'] = NETWORK_LINK;
$id = static::getId($data);
if ($id) {
$result['id'] = $id;
}
$layoutId = static::getIdLayout($data);
if ($layoutId > 0) {
$result['id_layout'] = $layoutId;
}
$startX = static::parseIntOr(
static::issetInArray($data, ['pos_x', 'startX']),
null
);
if ($startX !== null) {
$result['pos_x'] = $startX;
}
$startY = static::parseIntOr(
static::issetInArray($data, ['pos_y', 'startY']),
null
);
if ($startY !== null) {
$result['pos_y'] = $startY;
}
$endX = static::parseIntOr(
static::issetInArray($data, ['width', 'endX']),
null
);
if ($endX !== null) {
$result['width'] = $endX;
}
$endY = static::parseIntOr(
static::issetInArray($data, ['height', 'endY']),
null
);
if ($endY !== null) {
$result['height'] = $endY;
}
$borderWidth = static::getBorderWidth($data);
if ($borderWidth !== null) {
if ($borderWidth < 1) {
$borderWidth = 1;
}
$result['border_width'] = $borderWidth;
}
$borderColor = static::extractBorderColor($data);
if ($borderColor !== null) {
$result['border_color'] = $borderColor;
}
// Build labels.
$result['label'] = static::buildLabels($data);
$showOnTop = static::issetInArray(
$data,
[
'isOnTop',
'show_on_top',
'showOnTop',
]
);
if ($showOnTop !== null) {
$result['show_on_top'] = static::parseBool($showOnTop);
}
return $result;
}
/**
* Extract item id.
*
* @param array $data Unknown input data structure.
*
* @return integer Item id. 0 by default.
*/
private static function getId(array $data): int
{
return static::parseIntOr(
static::issetInArray($data, ['id', 'itemId']),
0
);
}
/**
* Extract layout id.
*
* @param array $data Unknown input data structure.
*
* @return integer Item id. 0 by default.
*/
private static function getIdLayout(array $data): int
{
return static::parseIntOr(
static::issetInArray($data, ['id_layout', 'idLayout', 'layoutId']),
0
);
}
/**
* Extract a border width value.
*
* @param array $data Unknown input data structure.
*
* @return integer Valid border width.
*/
private static function getBorderWidth(array $data)
{
return static::parseIntOr(
static::issetInArray($data, ['border_width', 'borderWidth']),
null
);
}
/**
* Insert or update an item in the database
*
* @param array $data Unknown input data structure.
*
* @return integer The modeled element data structure stored into the DB.
*
* @overrides Model::save.
*/
public function save(array $data=[]): int
{
if (empty($data) === false) {
if (empty($data['id']) === true) {
// Insert.
$save = static::encode($data);
$result = \db_process_sql_insert('tlayout_data', $save);
if ($result !== false) {
$item = static::fromDB(['id' => $result]);
$item->setData($item->toArray());
}
} else {
// Update.
$dataModelEncode = $this->encode($this->toArray());
$dataEncode = $this->encode($data);
$save = array_merge($dataModelEncode, $dataEncode);
$result = \db_process_sql_update(
'tlayout_data',
$save,
['id' => $save['id']]
);
// Invalidate the item's cache.
if ($result !== false && $result > 0) {
$item = static::fromDB(['id' => $save['id']]);
// Update the model.
if (empty($item) === false) {
$this->setData($item->toArray());
}
}
}
}
return $result;
}
/**
* Delete a line in the database
*
* @param integer $itemId Identifier of the Item.
*
* @return boolean The modeled element data structure stored into the DB.
*
* @overrides Model::delete.
*/
public function delete(int $itemId): bool
{
$result = db_process_sql_delete(
'tlayout_data',
['id' => $itemId]
);
return (bool) $result;
}
/**
* Generates inputs for form (global, common).
*
* @param array $values Default values.
*
* @return array Of inputs.
*/
public static function getFormInputs(array $values): array
{
$inputs = [];
if ($values['tabSelected'] === 'specific') {
// Width.
if ($values['borderWidth'] === null) {
$values['borderWidth'] = 5;
}
if ($values['borderWidth'] < 1) {
$values['borderWidth'] = 1;
}
$inputs[] = [
'label' => __('Width'),
'arguments' => [
'name' => 'borderWidth',
'type' => 'number',
'value' => $values['borderWidth'],
'return' => true,
'min' => 1,
],
];
// Color.
$inputs[] = [
'label' => __('Color'),
'arguments' => [
'wrapper' => 'div',
'name' => 'borderColor',
'type' => 'color',
'value' => $values['borderColor'],
'return' => true,
],
];
// Show on top.
$inputs[] = [
'label' => __('Show on top'),
'arguments' => [
'name' => 'isOnTop',
'id' => 'isOnTop',
'type' => 'switch',
'value' => $values['isOnTop'],
],
];
}
return $inputs;
}
}

View File

@ -53,6 +53,7 @@ final class StaticGraph extends Item
static::issetInArray($data, ['lastValue']),
null
);
$return['colorStatus'] = $data['colorStatus'];
return $return;
}
@ -181,6 +182,35 @@ final class StaticGraph extends Item
false
);
$status = \visual_map_get_status_element($data);
// Magic numbers from the hell.
switch ($status) {
case 1:
case 4:
// Critical or critical alert (BAD).
$data['colorStatus'] = COL_CRITICAL;
break;
case 0:
// Normal (OK).
$data['colorStatus'] = COL_NORMAL;
break;
case 2:
case 10:
// Warning or warning alert.
$data['colorStatus'] = COL_WARNING;
break;
case 3:
// Unknown.
default:
// Default is Grey (Other).
$data['colorStatus'] = COL_UNKNOWN;
break;
}
// If the width or the height are equal to 0 we will extract them
// from the real image size.
$width = (int) $data['width'];

View File

@ -30,6 +30,8 @@
namespace Models\VisualConsole;
use Models\VisualConsole\Container as VisualConsole;
define('__DEBUG', 0);
global $config;
require_once $config['homedir'].'/include/class/HTML.class.php';
enterprise_include_once('include/functions_metaconsole.php');
@ -65,12 +67,14 @@ class View extends \HTML
'id' => 'tab-label',
'href' => $url.'&tabSelected=label',
'img' => 'label-settings.png',
],[
],
[
'name' => __('General settings'),
'id' => 'tab-general',
'href' => $url.'&tabSelected=general',
'img' => 'general-settings.png',
],[
],
[
'name' => __('Specific settings'),
'id' => 'tab-specific',
'href' => $url.'&tabSelected=specific',
@ -81,7 +85,9 @@ class View extends \HTML
$activetabs = 2;
if ($type === LABEL) {
$activetabs = 0;
} else if ($type === LINE_ITEM) {
} else if ($type === LINE_ITEM
|| $type === NETWORK_LINK
) {
$activetabs = 0;
$tabs = [
[
@ -99,7 +105,8 @@ class View extends \HTML
'id' => 'tab-general',
'href' => $url.'&tabSelected=general',
'img' => 'pencil.png',
],[
],
[
'name' => __('Specific settings'),
'id' => 'tab-specific',
'href' => $url.'&tabSelected=specific',
@ -242,7 +249,7 @@ class View extends \HTML
true
);
return $form.$jsforms;
return $form;
}
@ -306,7 +313,7 @@ class View extends \HTML
);
} else {
// Only Create, settings default values if not enter tab general.
if ($itemId === 0 && $type != LINE_ITEM) {
if ($itemId === 0 && $type != LINE_ITEM && $type != NETWORK_LINK) {
$class = VisualConsole::getItemClass((int) $type);
$data = $class::getDefaultGeneralValues($data);
}
@ -491,6 +498,17 @@ class View extends \HTML
$data['isLinkEnabled'] = true;
break;
case NETWORK_LINK:
$data['borderColor'] = \get_parameter('borderColor');
$data['borderWidth'] = \get_parameter('borderWidth');
$data['isOnTop'] = \get_parameter_switch('isOnTop');
// Insert line default position ball end.
if ($itemId === 0) {
$data['height'] = 100;
$data['width'] = 100;
}
break;
default:
// Not posible.
break;
@ -503,8 +521,12 @@ class View extends \HTML
// Save the new item.
$data['id_layout'] = $vCId;
$itemId = $class::save($data);
} catch (\Throwable $th) {
} catch (\Exception $e) {
// Bad params.
if (__DEBUG === 1) {
error_log($e->getMessage());
}
http_response_code(400);
return false;
}
@ -513,8 +535,12 @@ class View extends \HTML
try {
$item = VisualConsole::getItemFromDB($itemId);
$result = $item->toArray();
} catch (Throwable $e) {
} catch (\Exception $e) {
// Bad params.
if (__DEBUG === 1) {
error_log($e->getMessage());
}
http_response_code(400);
return false;
}
@ -522,8 +548,12 @@ class View extends \HTML
// UpdateVC.
try {
$item = VisualConsole::getItemFromDB($itemId);
} catch (Throwable $e) {
} catch (\Exception $e) {
// Bad params.
if (__DEBUG === 1) {
error_log($e->getMessage());
}
http_response_code(400);
return false;
}
@ -583,4 +613,214 @@ class View extends \HTML
}
/**
* Returns a popup for networkLink viewer.
*
* @return void
*/
public function networkLinkPopup()
{
global $config;
try {
include_once $config['homedir'].'/include/functions_graph.php';
$item_idFrom = get_parameter('from');
$item_idTo = get_parameter('to');
$itemFrom = db_get_row_filter(
'tlayout_data',
['id' => $item_idFrom]
);
$itemTo = db_get_row_filter(
'tlayout_data',
['id' => $item_idTo]
);
// Interface chart base configuration.
$params = [
'period' => SECONDS_6HOURS,
'width' => '90%',
'height' => 150,
'date' => time(),
'homeurl' => $config['homeurl'],
];
if ($config['type_interface_charts'] == 'line') {
$stacked = CUSTOM_GRAPH_LINE;
} else {
$stacked = CUSTOM_GRAPH_AREA;
}
$params_combined = [
'weight_list' => [],
'projection' => false,
'from_interface' => true,
'return' => 0,
'stacked' => $stacked,
];
// Interface FROM.
if (isset($itemFrom['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
$cnn = \enterprise_hook(
'metaconsole_get_connection_by_id',
[ $itemFrom['id_metaconsole'] ]
);
if (\enterprise_hook('metaconsole_connect', [$cnn]) !== NOERR) {
throw new \Exception(__('Failed to connect to node'));
}
$params['server_id'] = $itemFrom['id_metaconsole'];
} else {
$params['server_id'] = null;
}
$from = new \PandoraFMS\Module((int) $itemFrom['id_agente_modulo']);
if ((bool) $from->isInterfaceModule() === true) {
$interface_name = $from->getInterfaceName();
if ($interface_name !== null) {
$data = $from->agent()->getInterfaceMetrics(
$interface_name
);
echo '<h3 class="center">'.__('NetworkLink from').'</h3>';
echo '<div class="margin-top-10 interface-status from w90p centered flex-row-vcenter">';
ui_print_module_status($data['status']->lastStatus());
echo '<span style="margin-left: 1em;">';
echo __('Interface %s status', $interface_name);
echo '</span>';
echo '</div>';
$interface_traffic_modules = [
__('In') => $data['in']->id_agente_modulo(),
__('Out') => $data['out']->id_agente_modulo(),
];
$params['unit_name'] = array_fill(
0,
count($interface_traffic_modules),
$config['interface_unit']
);
$params_combined['labels'] = array_keys(
$interface_traffic_modules
);
$params_combined['modules_series'] = array_values(
$interface_traffic_modules
);
// Graph.
echo '<div id="stat-win-interface-graph from">';
if (isset($itemFrom['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
\enterprise_hook('metaconsole_restore_db');
}
\graphic_combined_module(
array_values($interface_traffic_modules),
$params,
$params_combined
);
echo '</div>';
}
} else {
if (isset($itemFrom['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
\enterprise_hook('metaconsole_restore_db');
}
}
// Interface TO.
if (isset($itemTo['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
$cnn = \enterprise_hook(
'metaconsole_get_connection_by_id',
[ $itemTo['id_metaconsole'] ]
);
if (\enterprise_hook('metaconsole_connect', [$cnn]) !== NOERR) {
throw new \Exception(__('Failed to connect to node'));
}
$params['server_id'] = $itemTo['id_metaconsole'];
} else {
$params['server_id'] = null;
}
$to = new \PandoraFMS\Module((int) $itemTo['id_agente_modulo']);
if ((bool) $to->isInterfaceModule() === true) {
$interface_name = $to->getInterfaceName();
if ($interface_name !== null) {
$data = $to->agent()->getInterfaceMetrics(
$interface_name
);
echo '<h3 class="center">'.__('NetworkLink to').'</h3>';
echo '<div class="interface-status from w90p centered flex-row-vcenter">';
ui_print_module_status($data['status']->lastStatus());
echo '<span style="margin-left: 1em;">';
echo __('Interface %s status', $interface_name);
echo '</span>';
echo '</div>';
$interface_traffic_modules = [
__('In') => $data['in']->id_agente_modulo(),
__('Out') => $data['out']->id_agente_modulo(),
];
$params['unit_name'] = array_fill(
0,
count($interface_traffic_modules),
$config['interface_unit']
);
$params_combined['labels'] = array_keys(
$interface_traffic_modules
);
$params_combined['modules_series'] = array_values(
$interface_traffic_modules
);
// Graph.
echo '<div id="stat-win-interface-graph to">';
if (isset($itemTo['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
\enterprise_hook('metaconsole_restore_db');
}
\graphic_combined_module(
array_values($interface_traffic_modules),
$params,
$params_combined
);
echo '</div>';
}
} else {
if (isset($itemTo['id_metaconsole']) === true
&& (bool) is_metaconsole() === true
) {
\enterprise_hook('metaconsole_restore_db');
}
}
} catch (\Exception $e) {
echo __('Failed to generate charts: %s', $e->getMessage());
}
}
}

View File

@ -771,6 +771,9 @@ p.center {
.center {
text-align: center;
}
.centered {
margin: 0 auto;
}
.margin-top-10 {
margin-top: 10px;

View File

@ -234,7 +234,12 @@ input.service_min {
input.service_min[disabled] {
background: url(../../images/box.disabled.png) no-repeat center;
}
input.network_link_min {
background: url(../../images/network_link_item.png) no-repeat center;
}
input.network_link_min[disabled] {
background: url(../../images/network_link_item.disabled.png) no-repeat center;
}
input.group_item_min {
background: url(../../images/group_green.png) no-repeat center;
}

View File

@ -9,21 +9,11 @@
.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;
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;
align-items: center;
user-select: text;
z-index: 1;
}
@ -31,19 +21,24 @@
z-index: 2;
}
.visual-console-item * {
overflow: visible;
}
.visual-console-item.is-editing {
border: 2px dashed #b2b2b2;
-webkit-transform: translateX(-2px) translateY(-2px);
transform: translateX(-2px) translateY(-2px);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
transform: translateX(-2px) translateY(-2px);
user-select: none;
}
.visual-console-item.is-editing:hover {
border-color: #82b92e;
}
.visual-console-item.is-editing.is-selected {
border: 2px dashed #2b2b2b;
cursor: move;
z-index: 10;
}
.visual-console-item.is-editing > .resize-draggable {
float: right;
@ -60,25 +55,12 @@
pointer-events: none;
}
@-webkit-keyframes spinner-loading {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(1turn);
transform: rotate(1turn);
}
}
@keyframes spinner-loading {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(1turn);
transform: rotate(1turn);
transform: rotate(1turn);
}
}
@ -90,15 +72,10 @@
border-bottom: 5px solid rgb(82, 85, 87);
border-left: 5px solid rgba(82, 85, 87, 0.2);
-webkit-animation-name: spinner-loading;
animation-name: spinner-loading;
-webkit-animation-duration: 0.8s;
animation-duration: 0.8s;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
animation-name: spinner-loading;
animation-duration: 0.8s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
.visual-console-spinner,
@ -119,12 +96,8 @@
position: absolute;
width: 100%;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
align-items: center;
opacity: 0.7;
background: rgb(212, 215, 218);
z-index: 2;
@ -140,18 +113,10 @@
/*Forms*/
.div-input-group label {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
font-size: 12pt;
font-family: "lato-bolder", "Open Sans", sans-serif;
font-weight: 600;
@ -173,8 +138,7 @@
font-family: "lato-bolder", "Open Sans", sans-serif;
font-weight: lighter;
padding: 0px 0px 2px 0px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
box-sizing: border-box;
margin-right: 10px;
padding-left: 2px;
}
@ -192,15 +156,9 @@
}
.input-groups {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
flex-direction: column;
flex-wrap: wrap;
}
.input-group {
@ -210,18 +168,10 @@
}
.div-ranges-input-group {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
flex-direction: column;
flex-wrap: wrap;
align-items: flex-start;
}
.div-ranges-input-group > div {
@ -231,18 +181,10 @@
.div-input-group,
.div-input-group div div {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
}
.div-input-group h3 {
@ -259,21 +201,11 @@
}
.div-input-group-autocomplete-agent {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
flex-direction: column;
flex-wrap: wrap;
align-items: flex-start;
justify-content: space-between;
height: 70px;
}
@ -336,34 +268,19 @@ p.error-p-validate {
}
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
animation: fa-spin 1s infinite steps(8);
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
transform: rotate(360deg);
}
}
@ -417,19 +334,11 @@ p.error-p-validate {
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;
flex-direction: column;
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;
align-content: center;
align-items: center;
}
.visual-console-item .digital-clock > span {
@ -459,18 +368,15 @@ p.error-p-validate {
}
.visual-console-item .analogic-clock .hour-hand {
-webkit-animation: rotate-hour 43200s infinite linear;
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;
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;
animation: rotate-second 60s infinite linear;
}
#html-tabs .ui-widget-header {
@ -480,19 +386,10 @@ p.error-p-validate {
#html-tabs .ui-tabs-anchor {
float: none;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
#html-tabs .ui-tabs-anchor img {
@ -526,22 +423,13 @@ li#li-position-item > label:not(:first-child) {
}
li#li-image-item label {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
flex-direction: row;
justify-content: flex-end;
}
li#li-image-item label img {
-webkit-box-flex: initial;
-ms-flex: initial;
flex: initial;
flex: initial;
}
.discovery.modal * {
@ -570,9 +458,7 @@ li#li-image-item label img {
li#li-timeZone-item > label:not(:first-child),
.discovery.modal li#div-textarea-label > label {
-webkit-box-flex: inherit;
-ms-flex: inherit;
flex: inherit;
flex: inherit;
}
li#li-timeZone-item > select:not(:first-child) {
@ -585,13 +471,8 @@ li#li-timeZone-item > select:not(:first-child) {
/*style item group show statistic*/
.group-container {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
flex-direction: column;
width: 100%;
height: 100%;
}
@ -601,120 +482,63 @@ li#li-timeZone-item > select:not(:first-child) {
background-color: #9d9ea0;
color: black;
font-weight: bold;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
flex-direction: row;
align-items: center;
justify-content: center;
}
.group-container .group-item-info {
width: 100%;
height: 70%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
flex-direction: row;
flex-wrap: wrap;
padding: 2%;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
align-items: center;
}
.group-container .group-item-info .group-item-info-container {
-webkit-box-flex: 1;
-ms-flex: 1 1 20%;
flex: 1 1 20%;
display: -webkit-box;
display: -ms-flexbox;
flex: 1 1 20%;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
flex-direction: row;
border-radius: 2px;
max-height: 50px;
margin: 1%;
}
.group-container .group-item-info .group-item-info-container .value-style {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
flex: 1;
color: #fff;
font-size: 100%;
padding: 5%;
width: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
flex-direction: row;
align-items: center;
justify-content: center;
}
.group-container .group-item-info .group-item-info-container .name-style {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
flex: 1;
background-color: white;
color: black;
font-size: 100%;
padding: 5%;
width: 100%;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
flex-direction: row;
align-items: center;
justify-content: center;
}
div.label,
div.simple-value {
min-width: -webkit-fit-content;
min-width: -moz-fit-content;
min-width: fit-content;
min-height: -webkit-fit-content;
min-height: -moz-fit-content;
min-height: fit-content;
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;
flex-direction: column;
justify-content: center;
}
div.simple-value > div {
@ -728,27 +552,15 @@ div.module-graph .parent_graph p table tr {
div.module-graph {
width: 100%;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
div.module-graph .gauge_d3_class {
-webkit-box-flex: 1;
-ms-flex: 1 1 100px;
flex: 1 1 100px;
flex: 1 1 100px;
float: none !important;
overflow: inherit !important;
text-align: center;
@ -758,6 +570,14 @@ div.module-graph .gauge_d3_class {
text-decoration: none;
}
.vc-item-nl-label {
border-radius: 5px;
background-color: #fff;
padding: 5px;
padding-left: 1em;
font-size: 14px;
}
/* Styles for the solid icons */
.fa {
@ -786,34 +606,19 @@ div.module-graph .gauge_d3_class {
}
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
animation: fa-spin 1s infinite steps(8);
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
transform: rotate(360deg);
}
}
@ -862,22 +667,12 @@ div.module-graph .gauge_d3_class {
/* 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;
flex-direction: column;
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;
align-content: center;
align-items: center;
}
.visual-console-item .digital-clock > span {
@ -908,18 +703,15 @@ div.module-graph .gauge_d3_class {
}
.visual-console-item .analogic-clock .hour-hand {
-webkit-animation: rotate-hour 43200s infinite linear;
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;
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;
animation: rotate-second 60s infinite linear;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -280,6 +280,11 @@ if ($pure === false) {
__('Color cloud'),
'color_cloud_min link-create-item'
);
visual_map_print_button_editor_refactor(
'NETWORK_LINK',
__('Network link'),
'network_link_min link-create-item'
);
enterprise_include_once('include/functions_visual_map_editor.php');
enterprise_hook(
'enterprise_visual_map_editor_print_toolbox_refactor'

View File

@ -47,5 +47,9 @@
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.3.1"
}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

View File

@ -0,0 +1,5 @@
{
"trailingComma": "none",
"singleQuote": false,
"arrowParens": "avoid"
}

View File

@ -46,7 +46,8 @@ export const enum ItemType {
DONUT_GRAPH = 17,
BARS_GRAPH = 18,
CLOCK = 19,
COLOR_CLOUD = 20
COLOR_CLOUD = 20,
NETWORK_LINK = 21
}
// Base item properties. This interface should be extended by the item implementations.
@ -61,6 +62,7 @@ export interface ItemProps extends Position, Size {
parentId: number | null;
aclGroupId: number | null;
cacheExpiration: number | null;
colorStatus: string;
}
export interface ItemClickEvent {
@ -136,6 +138,7 @@ export function itemBasePropsDecoder(data: AnyObject): ItemProps | never {
parentId: parseIntOr(data.parentId, null),
aclGroupId: parseIntOr(data.aclGroupId, null),
cacheExpiration: parseIntOr(data.cacheExpiration, null),
colorStatus: notEmptyStringOr(data.colorStatus, "#CCC"),
...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.
};
@ -208,6 +211,9 @@ export function titleItem(id: number): string {
case ItemType.COLOR_CLOUD:
title = t("Color cloud");
break;
case ItemType.NETWORK_LINK:
title = t("Network link");
break;
default:
title = t("Item");
break;
@ -293,6 +299,14 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
* @param element Element to move inside its container.
*/
private initMovementListener(element: HTMLElement): void {
// Avoid line movement as 'block' force using circles.
if (
this.props.type == ItemType.LINE_ITEM ||
this.props.type == ItemType.NETWORK_LINK
) {
return;
}
this.removeMovement = addMovementListener(
element,
(x: Position["x"], y: Position["y"]) => {
@ -372,6 +386,12 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
* @param element Element to move inside its container.
*/
protected initResizementListener(element: HTMLElement): void {
if (
this.props.type == ItemType.LINE_ITEM ||
this.props.type == ItemType.NETWORK_LINK
) {
return;
}
this.removeResizement = addResizementListener(
element,
(width: Size["width"], height: Size["height"]) => {
@ -839,6 +859,7 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
this.elementRef.classList.remove("is-editing");
}
}
if (!prevMeta || prevMeta.isFetching !== this.meta.isFetching) {
if (this.meta.isFetching) {
this.elementRef.classList.add("is-fetching");
@ -999,8 +1020,13 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
*/
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;
if (
this.props.type != ItemType.LINE_ITEM &&
this.props.type != ItemType.NETWORK_LINK
) {
this.childElementRef.style.width = width > 0 ? `${width}px` : "0";
this.childElementRef.style.height = height > 0 ? `${height}px` : "0";
}
if (this.props.label && this.props.label.length > 0) {
// Ugly table to show the label as its legacy counterpart.
@ -1011,11 +1037,11 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
switch (this.props.labelPosition) {
case "up":
case "down":
table.style.width = width > 0 ? `${width}px` : null;
table.style.width = width > 0 ? `${width}px` : "0";
break;
case "left":
case "right":
table.style.height = height > 0 ? `${height}px` : null;
table.style.height = height > 0 ? `${height}px` : "0";
break;
}
}
@ -1178,7 +1204,10 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
};
this.initMovementListener(this.elementRef);
if (this.props.type !== 13) {
if (
this.props.type !== ItemType.LINE_ITEM &&
this.props.type !== ItemType.NETWORK_LINK
) {
this.initResizementListener(this.elementRef);
}
}
@ -1194,7 +1223,7 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
};
this.stopMovementListener();
if (this.props.type !== 13) {
if (this.props.type !== ItemType.LINE_ITEM) {
this.stopResizementListener();
}
}

View File

@ -6,7 +6,8 @@ import {
notEmptyStringOr,
itemMetaDecoder,
t,
ellipsize
ellipsize,
debounce
} from "./lib";
import Item, {
ItemType,
@ -20,6 +21,7 @@ import Item, {
import StaticGraph, { staticGraphPropsDecoder } from "./items/StaticGraph";
import Icon, { iconPropsDecoder } from "./items/Icon";
import ColorCloud, { colorCloudPropsDecoder } from "./items/ColorCloud";
import NetworkLink, { networkLinkPropsDecoder } from "./items/NetworkLink";
import Group, { groupPropsDecoder } from "./items/Group";
import Clock, { clockPropsDecoder } from "./items/Clock";
import Box, { boxPropsDecoder } from "./items/Box";
@ -35,7 +37,6 @@ import DonutGraph, { donutGraphPropsDecoder } from "./items/DonutGraph";
import BarsGraph, { barsGraphPropsDecoder } from "./items/BarsGraph";
import ModuleGraph, { moduleGraphPropsDecoder } from "./items/ModuleGraph";
import Service, { servicePropsDecoder } from "./items/Service";
import { FormContainer } from "./Form";
// TODO: Document.
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -82,6 +83,8 @@ function itemInstanceFrom(data: AnyObject) {
return new Clock(clockPropsDecoder(data), meta);
case ItemType.COLOR_CLOUD:
return new ColorCloud(colorCloudPropsDecoder(data), meta);
case ItemType.NETWORK_LINK:
return new NetworkLink(networkLinkPropsDecoder(data), meta);
default:
throw new TypeError("item not found");
}
@ -130,6 +133,8 @@ function decodeProps(data: AnyObject) {
return clockPropsDecoder(data);
case ItemType.COLOR_CLOUD:
return colorCloudPropsDecoder(data);
case ItemType.NETWORK_LINK:
return networkLinkPropsDecoder(data);
default:
throw new TypeError("decoder not found");
}
@ -206,6 +211,16 @@ export default class VisualConsole {
private relations: {
[key: string]: Line;
} = {};
// Dictionary which store the related items (by ID).
private lineLinks: {
[key: number]: { [key: number]: { [key: string]: number } };
} = {};
private lines: {
[key: number]: { [key: string]: number };
} = {};
// Event manager for click events.
private readonly clickEventManager = new TypedEvent<ItemClickEvent>();
// Event manager for double click events.
@ -266,6 +281,9 @@ export default class VisualConsole {
}
});
// Move lines conneted with this item.
this.updateLinesConnected(e.item.props, e.newPosition, false);
// console.log(`Moved element #${e.item.props.id}`, e);
};
@ -275,9 +293,36 @@ export default class VisualConsole {
*/
private handleElementMovementFinished: (e: ItemMovedEvent) => void = e => {
this.movedEventManager.emit(e);
// Move lines conneted with this item.
this.updateLinesConnected(e.item.props, e.newPosition, true);
// console.log(`Movement finished for element #${e.item.props.id}`, e);
};
/**
* Verifies if x,y are inside item coordinates.
* @param x Coordinate X
* @param y Coordinate Y
* @param item ItemProps instance.
*/
private coordinatesInItem(x: number, y: number, props: ItemProps) {
if (
props.type == ItemType.LINE_ITEM ||
props.type == ItemType.NETWORK_LINK
) {
return false;
}
if (
x > props.x &&
x < props.x + props.width &&
y > props.y &&
y < props.y + props.height
) {
return true;
}
return false;
}
/**
* React to a line movement.
* @param e Event object.
@ -285,7 +330,12 @@ export default class VisualConsole {
private handleLineElementMovementFinished: (
e: LineMovedEvent
) => void = e => {
// Update links.
this.refreshLink(e.item);
// Build line relationships between items and lines.
this.lineMovedEventManager.emit(e);
// console.log(`Movement finished for element #${e.item.props.id}`, e);
};
@ -373,6 +423,205 @@ export default class VisualConsole {
this.unSelectItems();
};
/**
* Refresh link for given line.
*
* @param line Line.
*/
protected refreshLink(l: Line) {
let line: number = l.props.id;
let itemAtStart = 0;
let itemAtEnd = 0;
try {
for (let i in this.elementsById) {
if (
this.coordinatesInItem(
l.props.startPosition.x,
l.props.startPosition.y,
this.elementsById[i].props
)
) {
// Start position at element i.
itemAtStart = parseInt(i);
}
if (
this.coordinatesInItem(
l.props.endPosition.x,
l.props.endPosition.y,
this.elementsById[i].props
)
) {
// Start position at element i.
itemAtEnd = parseInt(i);
}
}
if (this.lineLinks == null) {
this.lineLinks = {};
}
if (this.lines == null) {
this.lines = {};
}
if (itemAtStart == line) {
itemAtStart = 0;
}
if (itemAtEnd == line) {
itemAtEnd = 0;
}
// Initialize line if not registered.
if (this.lines[line] == null) {
this.lines[line] = {
start: itemAtStart,
end: itemAtEnd
};
}
// Register 'start' side of the line.
if (itemAtStart > 0) {
// Initialize.
if (this.lineLinks[itemAtStart] == null) {
this.lineLinks[itemAtStart] = {};
}
// Assign.
this.lineLinks[itemAtStart][line] = {
start: itemAtStart,
end: itemAtEnd
};
// Register line if not exists prviously.
} else {
// Clean previous line relationship.
if (this.lines[line]["start"] > 0) {
this.lineLinks[this.lines[line]["start"]][line]["start"] = 0;
this.lines[line]["start"] = 0;
}
}
if (itemAtEnd > 0) {
if (this.lineLinks[itemAtEnd] == null) {
this.lineLinks[itemAtEnd] = {};
}
this.lineLinks[itemAtEnd][line] = {
start: itemAtStart,
end: itemAtEnd
};
} else {
// Clean previous line relationship.
if (this.lines[line]["end"] > 0) {
this.lineLinks[this.lines[line]["end"]][line]["end"] = 0;
this.lines[line]["end"] = 0;
}
}
this.lines[line] = {
start: itemAtStart,
end: itemAtEnd
};
// Cleanup.
for (let i in this.lineLinks) {
if (this.lineLinks[i][line]) {
if (
this.lineLinks[i][line].start == 0 &&
this.lineLinks[i][line].end == 0
) {
// Object not connected to a line.
delete this.lineLinks[i][line];
if (Object.keys(this.lineLinks[i]).length === 0) {
delete this.lineLinks[i];
}
}
}
}
} catch (error) {
console.error(error);
}
}
/**
* Updates lines connected to this item.
*
* @param item Item moved.
* @param newPosition New location for item.
* @param oldPosition Old location for item.
* @param save Save to ajax or not.
*/
protected updateLinesConnected(item: ItemProps, to: Position, save: boolean) {
if (this.lineLinks[item.id] == null) {
return;
}
Object.keys(this.lineLinks[item.id]).forEach(i => {
let lineId = parseInt(i);
let line = this.elementsById[lineId] as Line;
if (line.props) {
let startX = line.props.startPosition.x;
let startY = line.props.startPosition.y;
let endX = line.props.endPosition.x;
let endY = line.props.endPosition.y;
if (item.id == this.lineLinks[item.id][lineId]["start"]) {
startX = to.x + item.width / 2;
startY = to.y + item.height / 2;
}
if (item.id == this.lineLinks[item.id][lineId]["end"]) {
endX = to.x + item.width / 2;
endY = to.y + item.height / 2;
}
// Update line movement.
this.updateElement({
...line.props,
startX: startX,
startY: startY,
endX: endX,
endY: endY
});
if (save) {
let debouncedLinePositionSave = debounce(
500,
(options: AnyObject) => {
this.lineMovedEventManager.emit({
item: options.line,
startPosition: {
x: options.startX,
y: options.startY
},
endPosition: {
x: options.endX,
y: options.endY
}
});
}
);
// Save line positon.
debouncedLinePositionSave({
line: line,
startX: startX,
startY: startY,
endX: endX,
endY: endY
});
}
}
});
// Update parents...
this.buildRelations(item.id, to.x + item.width / 2, to.y + item.height / 2);
}
public constructor(
container: HTMLElement,
props: AnyObject,
@ -397,6 +646,13 @@ export default class VisualConsole {
// Create lines.
this.buildRelations();
// Re-attach all connected lines if any.
this.elements.forEach(item => {
if (item instanceof Line) {
this.refreshLink(item);
}
});
this.containerRef.addEventListener("click", this.handleContainerClick);
}
@ -424,17 +680,18 @@ export default class VisualConsole {
// Item event handlers.
itemInstance.onRemove(context.handleElementRemove);
itemInstance.onSelectionChanged(context.handleElementSelectionChanged);
// TODO:Continue
itemInstance.onClick(context.handleElementClick);
itemInstance.onDblClick(context.handleElementDblClick);
itemInstance.onMoved(context.handleElementMovement);
itemInstance.onMovementFinished(context.handleElementMovementFinished);
// TODO:Continue
if (itemInstance instanceof Line) {
itemInstance.onLineMovementFinished(
context.handleLineElementMovementFinished
);
this.refreshLink(itemInstance);
} else {
itemInstance.onMoved(context.handleElementMovement);
itemInstance.onMovementFinished(context.handleElementMovementFinished);
itemInstance.onResized(context.handleElementResizement);
itemInstance.onResizeFinished(context.handleElementResizementFinished);
}
@ -443,7 +700,7 @@ export default class VisualConsole {
context.containerRef.append(itemInstance.elementRef);
return itemInstance;
} catch (error) {
console.log("Error creating a new element:", error.message);
console.error("Error creating a new element:", error.message);
}
return;
}
@ -480,7 +737,7 @@ export default class VisualConsole {
try {
this.elementsById[item.id].props = decodeProps(item);
} catch (error) {
console.log("Error updating an element:", error.message);
console.error("Error updating an element:", error.message);
}
}
}
@ -497,9 +754,11 @@ export default class VisualConsole {
public updateElement(item: AnyObject): void {
// Update item.
try {
this.elementsById[item.id].props = decodeProps(item);
this.elementsById[item.id].props = {
...decodeProps(item)
};
} catch (error) {
console.log("Error updating element:", error.message);
console.error("Error updating element:", error.message);
}
// Re-build relations.
@ -538,24 +797,28 @@ export default class VisualConsole {
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.props.backgroundURL)
this.containerRef.style.backgroundImage =
this.props.backgroundURL !== null
? `url(${this.props.backgroundURL})`
: "";
}
if (this.props.backgroundColor != 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;
if (this.props.backgroundURL)
this.containerRef.style.backgroundImage =
this.props.backgroundURL !== null
? `url(${this.props.backgroundURL})`
: "";
this.containerRef.style.backgroundColor = this.props.backgroundColor;
if (this.props.backgroundColor)
this.containerRef.style.backgroundColor = this.props.backgroundColor;
this.resizeElement(this.props.width, this.props.height);
}
}
@ -614,8 +877,11 @@ export default class VisualConsole {
/**
* Create line elements which connect the elements with their parents.
*
* When itemId is being moved, overwrite position of the 'parent' or 'child'
* endpoints of the line, using X and Y values.
*/
public buildRelations(): void {
public buildRelations(itemId?: number, x?: number, y?: number): void {
// Clear relations.
this.clearRelations();
// Add relations.
@ -623,7 +889,23 @@ export default class VisualConsole {
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);
if (parent && child) {
if (itemId != undefined) {
if (item.props.parentId == itemId) {
// Update parent line position.
this.addRelationLine(parent, child, x, y);
} else if (item.props.id == itemId) {
// Update child line position.
this.addRelationLine(parent, child, undefined, undefined, x, y);
} else {
this.addRelationLine(parent, child);
}
} else {
// No movements default behaviour.
this.addRelationLine(parent, child);
}
}
}
});
}
@ -749,7 +1031,11 @@ export default class VisualConsole {
*/
private addRelationLine(
parent: Item<ItemProps>,
child: Item<ItemProps>
child: Item<ItemProps>,
parentX?: number,
parentY?: number,
childX?: number,
childY?: number
): Line {
const identifier = `${parent.props.id}|${child.props.id}`;
if (this.relations[identifier] != null) {
@ -757,9 +1043,27 @@ export default class VisualConsole {
}
// Get the items center.
const { x: startX, y: startY } = this.getVisualCenter(parent.props, parent);
const { x: endX, y: endY } = this.getVisualCenter(child.props, child);
let { x: startX, y: startY } = this.getVisualCenter(parent.props, parent);
let { x: endX, y: endY } = this.getVisualCenter(child.props, child);
// Overwrite positions if needed (while moving it!).
if (parentX != null) {
startX = parentX;
}
if (parentY != null) {
startY = parentY;
}
if (childX != null) {
endX = childX;
}
if (childY != null) {
endY = childY;
}
// Line inherits child element status.
const line = new Line(
linePropsDecoder({
id: 0,
@ -771,7 +1075,7 @@ export default class VisualConsole {
width: 0,
height: 0,
lineWidth: this.props.relationLineWidth,
color: "#CCCCCC"
color: notEmptyStringOr(child.props.colorStatus, "#CCC")
}),
itemMetaDecoder({
receivedAt: new Date()
@ -973,7 +1277,8 @@ export default class VisualConsole {
[ItemType.DONUT_GRAPH]: DonutGraph,
[ItemType.BARS_GRAPH]: BarsGraph,
[ItemType.CLOCK]: Clock,
[ItemType.COLOR_CLOUD]: ColorCloud
[ItemType.COLOR_CLOUD]: ColorCloud,
[ItemType.NETWORK_LINK]: NetworkLink
};
/**

View File

@ -8,9 +8,9 @@ import {
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
import TypedEvent, { Listener, Disposable } from "../lib/TypedEvent";
interface LineProps extends ItemProps {
export interface LineProps extends ItemProps {
// Overrided properties.
readonly type: ItemType.LINE_ITEM;
type: number;
label: null;
isLinkEnabled: false;
parentId: null;
@ -20,6 +20,16 @@ interface LineProps extends ItemProps {
endPosition: Position;
lineWidth: number;
color: string | null;
viewportOffsetX: number;
viewportOffsetY: number;
labelEnd: string;
labelStart: string;
linkedEnd: number | null;
linkedStart: number | null;
labelEndWidth: number;
labelEndHeight: number;
labelStartWidth: number;
labelStartHeight: number;
}
/**
@ -54,7 +64,17 @@ export function linePropsDecoder(data: AnyObject): LineProps | never {
y: parseIntOr(data.endY, 0)
},
lineWidth: parseIntOr(data.lineWidth || data.borderWidth, 1),
color: notEmptyStringOr(data.borderColor || data.color, null)
color: notEmptyStringOr(data.borderColor || data.color, null),
viewportOffsetX: 0,
viewportOffsetY: 0,
labelEnd: notEmptyStringOr(data.labelEnd, ""),
labelEndWidth: parseIntOr(data.labelEndWidth, 0),
linkedEnd: data.linkedEnd,
linkedStart: data.linkedStart,
labelEndHeight: parseIntOr(data.labelEndHeight, 0),
labelStart: notEmptyStringOr(data.labelStart, ""),
labelStartWidth: parseIntOr(data.labelStartWidth, 0),
labelStartHeight: parseIntOr(data.labelStartHeight, 0)
};
/*
@ -82,20 +102,20 @@ export interface LineMovedEvent {
}
export default class Line extends Item<LineProps> {
private circleRadius = 8;
protected circleRadius = 8;
// To control if the line movement is enabled.
private moveMode: boolean = false;
protected moveMode: boolean = false;
// To control if the line is moving.
private isMoving: boolean = false;
protected isMoving: boolean = false;
// Event manager for moved events.
private readonly lineMovedEventManager = new TypedEvent<LineMovedEvent>();
public readonly lineMovedEventManager = new TypedEvent<LineMovedEvent>();
// List of references to clean the event listeners.
private readonly lineMovedEventDisposables: Disposable[] = [];
protected readonly lineMovedEventDisposables: Disposable[] = [];
// This function will only run the 2nd arg function after the time
// of the first arg have passed after its last execution.
private debouncedStartPositionMovementSave = debounce(
protected debouncedStartPositionMovementSave = debounce(
500, // ms.
(x: Position["x"], y: Position["y"]) => {
this.isMoving = false;
@ -110,13 +130,13 @@ export default class Line extends Item<LineProps> {
);
// This property will store the function
// to clean the movement listener.
private removeStartPositionMovement: Function | null = null;
protected removeStartPositionMovement: Function | null = null;
/**
* Start the movement funtionality for the start position.
* @param element Element to move inside its container.
*/
private initStartPositionMovementListener(
protected initStartPositionMovementListener(
element: HTMLElement,
container: HTMLElement
): void {
@ -124,8 +144,8 @@ export default class Line extends Item<LineProps> {
element,
(x: Position["x"], y: Position["y"]) => {
// Calculate the center of the circle.
x += this.circleRadius;
y += this.circleRadius;
x += this.circleRadius - this.props.viewportOffsetX / 2;
y += this.circleRadius - this.props.viewportOffsetY / 2;
const startPosition = { x, y };
@ -153,7 +173,7 @@ export default class Line extends Item<LineProps> {
// This function will only run the 2nd arg function after the time
// of the first arg have passed after its last execution.
private debouncedEndPositionMovementSave = debounce(
protected debouncedEndPositionMovementSave = debounce(
500, // ms.
(x: Position["x"], y: Position["y"]) => {
this.isMoving = false;
@ -168,13 +188,13 @@ export default class Line extends Item<LineProps> {
);
// This property will store the function
// to clean the movement listener.
private removeEndPositionMovement: Function | null = null;
protected removeEndPositionMovement: Function | null = null;
/**
* End the movement funtionality for the end position.
* @param element Element to move inside its container.
*/
private initEndPositionMovementListener(
protected initEndPositionMovementListener(
element: HTMLElement,
container: HTMLElement
): void {
@ -182,8 +202,8 @@ export default class Line extends Item<LineProps> {
element,
(x: Position["x"], y: Position["y"]) => {
// Calculate the center of the circle.
x += this.circleRadius;
y += this.circleRadius;
x += this.circleRadius - this.props.viewportOffsetX / 2;
y += this.circleRadius - this.props.viewportOffsetY / 2;
this.isMoving = true;
this.props = {
@ -231,6 +251,11 @@ export default class Line extends Item<LineProps> {
this.moveMode = meta.editMode;
this.init();
super.resizeElement(
Math.max(props.width, props.viewportOffsetX),
Math.max(props.height, props.viewportOffsetY)
);
}
/**
@ -272,27 +297,33 @@ export default class Line extends Item<LineProps> {
const element: HTMLDivElement = document.createElement("div");
element.className = "line";
const {
let {
x, // Box x
y, // Box y
width, // Box width
height, // Box height
lineWidth, // Line thickness
lineWidth, // Line thickness,
viewportOffsetX, // viewport width,
viewportOffsetY, // viewport heigth,
startPosition, // Line start position
endPosition, // Line end position
color // Line color
} = this.props;
const x1 = startPosition.x - x + lineWidth / 2;
const y1 = startPosition.y - y + lineWidth / 2;
const x2 = endPosition.x - x + lineWidth / 2;
const y2 = endPosition.y - y + lineWidth / 2;
width = width + viewportOffsetX;
height = height + viewportOffsetY;
const x1 = startPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
const y1 = startPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
const x2 = endPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
const y2 = endPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
// SVG container.
const svg = document.createElementNS(svgNS, "svg");
// Set SVG size.
svg.setAttribute("width", `${width + lineWidth}`);
svg.setAttribute("height", `${height + lineWidth}`);
const line = document.createElementNS(svgNS, "line");
line.setAttribute("x1", `${x1}`);
line.setAttribute("y1", `${y1}`);
@ -308,21 +339,26 @@ export default class Line extends Item<LineProps> {
}
protected updateDomElement(element: HTMLElement): void {
const {
let {
x, // Box x
y, // Box y
width, // Box width
height, // Box height
lineWidth, // Line thickness
viewportOffsetX, // viewport width,
viewportOffsetY, // viewport heigth,
startPosition, // Line start position
endPosition, // Line end position
color // Line color
} = this.props;
const x1 = startPosition.x - x + lineWidth / 2;
const y1 = startPosition.y - y + lineWidth / 2;
const x2 = endPosition.x - x + lineWidth / 2;
const y2 = endPosition.y - y + lineWidth / 2;
width = width + viewportOffsetX;
height = height + viewportOffsetY;
const x1 = startPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
const y1 = startPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
const x2 = endPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
const y2 = endPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
const svgs = element.getElementsByTagName("svg");
@ -352,9 +388,6 @@ export default class Line extends Item<LineProps> {
}
if (this.moveMode) {
const startIsLeft = startPosition.x - endPosition.x <= 0;
const startIsTop = startPosition.y - endPosition.y <= 0;
let startCircle: HTMLElement = document.createElement("div");
let endCircle: HTMLElement = document.createElement("div");
@ -384,12 +417,8 @@ export default class Line extends Item<LineProps> {
startCircle.style.borderRadius = "50%";
startCircle.style.backgroundColor = `${color}`;
startCircle.style.position = "absolute";
startCircle.style.left = startIsLeft
? `-${this.circleRadius}px`
: `${width + lineWidth - this.circleRadius}px`;
startCircle.style.top = startIsTop
? `-${this.circleRadius}px`
: `${height + lineWidth - this.circleRadius}px`;
startCircle.style.left = `${x1 - this.circleRadius}px`;
startCircle.style.top = `${y1 - this.circleRadius}px`;
endCircle.classList.add(
"visual-console-item-line-circle",
@ -400,12 +429,8 @@ export default class Line extends Item<LineProps> {
endCircle.style.borderRadius = "50%";
endCircle.style.backgroundColor = `${color}`;
endCircle.style.position = "absolute";
endCircle.style.left = startIsLeft
? `${width + lineWidth - 8}px`
: `-${this.circleRadius}px`;
endCircle.style.top = startIsTop
? `${height + lineWidth - this.circleRadius}px`
: `-${this.circleRadius}px`;
endCircle.style.left = `${x2 - this.circleRadius}px`;
endCircle.style.top = `${y2 - this.circleRadius}px`;
if (element.parentElement !== null) {
const circles = element.parentElement.getElementsByClassName(

View File

@ -0,0 +1,314 @@
import { AnyObject, Position, ItemMeta } from "../lib/types";
import { debounce, notEmptyStringOr, parseIntOr } from "../lib";
import { ItemType } from "../Item";
import Line, { LineProps, linePropsDecoder } from "./Line";
const svgNS = "http://www.w3.org/2000/svg";
export interface NetworkLinkProps extends LineProps {
// Overrided properties.
type: number;
labelStart: string;
labelEnd: string;
}
/**
* 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 networkLinkPropsDecoder(
data: AnyObject
): NetworkLinkProps | never {
return {
...linePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
type: ItemType.NETWORK_LINK,
viewportOffsetX: 0,
viewportOffsetY: 0,
labelEnd: notEmptyStringOr(data.labelEnd, ""),
labelEndWidth: parseIntOr(data.labelEndWidth, 0),
labelEndHeight: parseIntOr(data.labelEndHeight, 0),
labelStart: notEmptyStringOr(data.labelStart, ""),
labelStartWidth: parseIntOr(data.labelStartWidth, 0),
labelStartHeight: parseIntOr(data.labelStartHeight, 0)
};
}
export default class NetworkLink extends Line {
/**
* @override
*/
public constructor(props: NetworkLinkProps, meta: ItemMeta) {
/*
* 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
},
{
...meta
}
);
this.render();
}
/**
* @override
*/
protected debouncedStartPositionMovementSave = debounce(
50, // ms.
(x: Position["x"], y: Position["y"]) => {
this.isMoving = false;
const startPosition = { x, y };
// Re-Paint after move.
this.render();
// Emit the movement event.
this.lineMovedEventManager.emit({
item: this,
startPosition,
endPosition: this.props.endPosition
});
}
);
protected debouncedEndPositionMovementSave = debounce(
50, // ms.
(x: Position["x"], y: Position["y"]) => {
this.isMoving = false;
const endPosition = { x, y };
// Re-Paint after move.
this.render();
// Emit the movement event.
this.lineMovedEventManager.emit({
item: this,
endPosition,
startPosition: this.props.startPosition
});
}
);
protected updateDomElement(element: HTMLElement): void {
super.updateDomElement(element);
let {
x, // Box x
y, // Box y
lineWidth, // Line thickness
viewportOffsetX, // viewport width,
viewportOffsetY, // viewport heigth,
startPosition, // Line start position
endPosition, // Line end position
color, // Line color
labelEnd,
labelStart,
labelEndWidth,
labelEndHeight,
labelStartWidth,
labelStartHeight
} = this.props;
const svgs = element.getElementsByTagName("svg");
let line;
let svg;
if (svgs.length > 0) {
svg = svgs.item(0);
if (svg != null) {
// Set SVG size.
const lines = svg.getElementsByTagNameNS(svgNS, "line");
let groups = svg.getElementsByTagNameNS(svgNS, "g");
while (groups.length > 0) {
groups[0].remove();
}
if (lines.length > 0) {
line = lines.item(0);
}
}
} else {
// No line or svg, no more actions are required.
return;
}
if (svg == null || line == null) {
// No more actionas are required.
return;
}
// Font size and text adjustments.
const fontsize = 10;
const adjustment = 25;
const lineX1 = startPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
const lineY1 = startPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
const lineX2 = endPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
const lineY2 = endPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
let x1 = startPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
let y1 = startPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
let x2 = endPosition.x - x + lineWidth / 2 + viewportOffsetX / 2;
let y2 = endPosition.y - y + lineWidth / 2 + viewportOffsetY / 2;
// Calculate angle (rotation).
let rad = Math.atan2(lineY2 - lineY1, lineX2 - lineX1);
let g = (rad * 180) / Math.PI;
// Calculate effective 'text' box sizes.
const fontheight = 25;
if (labelStartWidth <= 0) {
let lines = labelStart.split("<br>");
labelStartWidth = 0;
lines.forEach(l => {
if (l.length > labelStartWidth) {
labelStartWidth = l.length * fontsize;
}
});
if (labelStartHeight <= 0) {
labelStartHeight = lines.length * fontheight;
}
}
if (labelEndWidth <= 0) {
let lines = labelEnd.split("<br>");
labelEndWidth = 0;
lines.forEach(l => {
if (l.length > labelEndWidth) {
labelEndWidth = l.length * fontsize;
}
});
if (labelEndHeight <= 0) {
labelEndHeight = lines.length * fontheight;
}
}
if (x1 < x2) {
// x1 on left of x2.
x1 += adjustment;
x2 -= adjustment + labelEndWidth;
}
if (x1 > x2) {
// x1 on right of x2.
x1 -= adjustment + labelStartWidth;
x2 += adjustment;
}
if (y1 < y2) {
// y1 on y2.
y1 += adjustment;
y2 -= adjustment + labelEndHeight;
}
if (y1 > y2) {
// y1 under y2.
y1 -= adjustment + labelStartHeight;
y2 += adjustment;
}
if (typeof color == "undefined") {
color = "#000";
}
// Clean.
if (element.parentElement !== null) {
const labels = element.parentElement.getElementsByClassName(
"vc-item-nl-label"
);
while (labels.length > 0) {
const label = labels.item(0);
if (label) label.remove();
}
const arrows = element.parentElement.getElementsByClassName(
"vc-item-nl-arrow"
);
while (arrows.length > 0) {
const arrow = arrows.item(0);
if (arrow) arrow.remove();
}
}
let arrowSize = lineWidth * 2;
let arrowPosX = lineX1 + (lineX2 - lineX1) / 2 - arrowSize;
let arrowPosY = lineY1 + (lineY2 - lineY1) / 2 - arrowSize;
let arrowStart: HTMLElement = document.createElement("div");
arrowStart.classList.add("vc-item-nl-arrow");
arrowStart.style.position = "absolute";
arrowStart.style.border = `${arrowSize}px solid transparent`;
arrowStart.style.borderBottom = `${arrowSize}px solid ${color}`;
arrowStart.style.left = `${arrowPosX}px`;
arrowStart.style.top = `${arrowPosY}px`;
arrowStart.style.transform = `rotate(${90 + g}deg)`;
let arrowEnd: HTMLElement = document.createElement("div");
arrowEnd.classList.add("vc-item-nl-arrow");
arrowEnd.style.position = "absolute";
arrowEnd.style.border = `${arrowSize}px solid transparent`;
arrowEnd.style.borderBottom = `${arrowSize}px solid ${color}`;
arrowEnd.style.left = `${arrowPosX}px`;
arrowEnd.style.top = `${arrowPosY}px`;
arrowEnd.style.transform = `rotate(${270 + g}deg)`;
if (element.parentElement !== null) {
element.parentElement.appendChild(arrowStart);
element.parentElement.appendChild(arrowEnd);
}
if (labelStart != "") {
let htmlLabelStart: HTMLElement = document.createElement("div");
try {
htmlLabelStart.innerHTML = labelStart;
htmlLabelStart.style.position = "absolute";
htmlLabelStart.style.left = `${x1}px`;
htmlLabelStart.style.top = `${y1}px`;
htmlLabelStart.style.width = `${labelStartWidth}px`;
htmlLabelStart.style.border = `2px solid ${color}`;
htmlLabelStart.classList.add("vc-item-nl-label", "label-start");
} catch (error) {
console.error(error);
}
if (element.parentElement !== null) {
element.parentElement.appendChild(htmlLabelStart);
}
}
if (labelEnd != "") {
let htmlLabelEnd: HTMLElement = document.createElement("div");
try {
htmlLabelEnd.innerHTML = labelEnd;
htmlLabelEnd.style.position = "absolute";
htmlLabelEnd.style.left = `${x2}px`;
htmlLabelEnd.style.top = `${y2}px`;
htmlLabelEnd.style.width = `${labelEndWidth}px`;
htmlLabelEnd.style.border = `2px solid ${color}`;
htmlLabelEnd.classList.add("vc-item-nl-label", "label-end");
} catch (error) {
console.error(error);
}
if (element.parentElement !== null) {
element.parentElement.appendChild(htmlLabelEnd);
}
}
}
}

View File

@ -71,6 +71,8 @@ export default class StaticGraph extends Item<StaticGraphProps> {
const imgSrc = this.props.statusImageSrc || this.props.imageSrc;
const element = document.createElement("div");
element.className = "static-graph";
element.setAttribute("ondragstart", "return false;");
element.setAttribute("draggable", "false");
element.style.backgroundImage = `url(${imgSrc})`;
element.style.backgroundRepeat = "no-repeat";
element.style.backgroundSize = "contain";

View File

@ -553,6 +553,10 @@ export function addMovementListener(
// Disable the drag temporarily.
element.draggable = false;
// Fix for Firefox browser.
element.setAttribute("ondragstart", "return false;");
element.setAttribute("draggable", "false");
// Store the difference between the cursor and
// the initial coordinates of the element.
const elementOffset = getOffset(element, container);

View File

@ -21,15 +21,24 @@
z-index: 2;
}
.visual-console-item * {
overflow: visible;
}
.visual-console-item.is-editing {
border: 2px dashed #b2b2b2;
transform: translateX(-2px) translateY(-2px);
user-select: none;
}
.visual-console-item.is-editing:hover {
border-color: #82b92e;
}
.visual-console-item.is-editing.is-selected {
border: 2px dashed #2b2b2b;
cursor: move;
z-index: 10;
}
.visual-console-item.is-editing > .resize-draggable {
float: right;
@ -560,3 +569,11 @@ div.module-graph .gauge_d3_class {
.textDecorationNone:hover {
text-decoration: none;
}
.vc-item-nl-label {
border-radius: 5px;
background-color: #fff;
padding: 5px;
padding-left: 1em;
font-size: 14px;
}

View File

@ -59,10 +59,11 @@ module.exports = {
loader: "postcss-loader",
options: {
plugins: () => [
// To improve the support for old browsers.
require("autoprefixer")({
browsers: ["> 1%", "last 2 versions"]
})
// Moved to package.json (?)
// // To improve the support for old browsers.
// require("autoprefixer")({
// browsers: ["> 1%", "last 2 versions"]
// })
]
}
}