mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
* Custom Maps: geo map and color background tabs blade component geo-map blade component and related script enhancements * Update css/js * style fixes * update db_schema.yaml * fix db_schema hand edit * ignore phpstan being wrong * Handle null * another possible null spot * Use standard file cache for custom map background images * Create map->image as jpeg so we can compress it * whitespace fix * Fix background cancel button when other type is selected than the saved type * Save and restore layer * Map must exist before creating static image * Don't show set as image button for Google and Bing. Bing gives an odd error, but Google won't work.
347 lines
14 KiB
PHP
347 lines
14 KiB
PHP
@extends('layouts.librenmsv1')
|
|
|
|
@section('title', __('map.custom.title.view', ['name' => $name]))
|
|
|
|
@section('content')
|
|
<div class="container-fluid">
|
|
<div class="row" id="alert-row">
|
|
<div class="col-md-12">
|
|
<div class="alert alert-warning" role="alert" id="alert">{{ __('map.custom.view.loading') }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div id="map-container">
|
|
<div id="custom-map"></div>
|
|
<x-geo-map id="custom-map-bg-geo-map"
|
|
:init="$background_type == 'map'"
|
|
:width="$map_conf['width']"
|
|
:height="$map_conf['height']"
|
|
:config="$background_config"
|
|
readonly
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('javascript')
|
|
<script type="text/javascript" src="{{ asset('js/vis.min.js') }}"></script>
|
|
<script type="text/javascript" src="{{ asset('js/leaflet.js') }}"></script>
|
|
<script type="text/javascript" src="{{ asset('js/L.Control.Locate.min.js') }}"></script>
|
|
@endsection
|
|
|
|
@push('styles')
|
|
<style>
|
|
#map-container {
|
|
display: grid;
|
|
grid-template: 1fr / 1fr;
|
|
place-items: center;
|
|
}
|
|
#custom-map {
|
|
grid-column: 1 / 1;
|
|
grid-row: 1 / 1;
|
|
z-index: 2;
|
|
}
|
|
#custom-map-bg-geo-map {
|
|
grid-column: 1 / 1;
|
|
grid-row: 1 / 1;
|
|
z-index: 1;
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('scripts')
|
|
<script type="text/javascript">
|
|
var bgtype = {{ Js::from($background_type) }};
|
|
var bgdata = {{ Js::from($background_config) }};
|
|
var screenshot = {{ $screenshot ? "true" : "false" }};
|
|
var reverse_arrows = {{$reverse_arrows}};
|
|
var legend = @json($legend);
|
|
var network;
|
|
var network_height;
|
|
var network_width;
|
|
var network_nodes = new vis.DataSet({queue: {delay: 100}});
|
|
var network_edges = new vis.DataSet({queue: {delay: 100}});
|
|
var edge_port_map = {};
|
|
var node_device_map = {};
|
|
var node_link_map = {};
|
|
var custom_image_base = "{{ $base_url }}images/custommap/icons/";
|
|
var network_options = {{ Js::from($map_conf) }};
|
|
|
|
function legendPctColour(pct) {
|
|
if (pct < 0) {
|
|
return "black";
|
|
} else if (pct < 50) {
|
|
// 100% green and slowly increase the red until we get to yellow
|
|
return '#' + parseInt(5.1 * pct).toString(16).padStart(2, 0) + 'ff00';
|
|
} else if (pct < 100) {
|
|
// 100% red and slowly remove green to go from yellow to red
|
|
return '#ff' + parseInt(5.1 * (100.0 - pct)).toString(16).padStart(2, 0) + '00';
|
|
} else if (pct < 150) {
|
|
// 100% red and slowly increase blue to go purple
|
|
return '#ff00' + parseInt(5.1 * (pct - 100.0)).toString(16).padStart(2, 0);
|
|
}
|
|
|
|
// Default to purple for links over 150%
|
|
return '#ff00ff';
|
|
}
|
|
|
|
function redrawLegend() {
|
|
// Clear out the old legend
|
|
old_nodes = network_nodes.get({filter: function(node) { return node.id.startsWith("legend_") }});
|
|
old_nodes.forEach((node) => {
|
|
network_nodes.remove(node.id);
|
|
});
|
|
if (legend.x >= 0) {
|
|
let y_pos = legend.y;
|
|
let y_inc = legend.font_size + 10;
|
|
|
|
let legend_header = {id: "legend_header", label: "<b>Legend</b>", shape: "box", borderWidth: 0, x: legend.x, y: y_pos, font: {multi: 'html', size: legend.font_size}, color: {background: "white"}};
|
|
network_nodes.add(legend_header);
|
|
y_pos += y_inc;
|
|
|
|
if (!(Boolean(legend.hide_invalid))) {
|
|
let legend_invalid = {id: "legend_invalid", label: "???", title: "Link is down or link speed is not defined", shape: "box", borderWidth: 0, x: legend.x, y: y_pos, font: {face: 'courier new', size: legend.font_size, color: "white"}, color: {background: "black"}};
|
|
y_pos += y_inc;
|
|
network_nodes.add(legend_invalid);
|
|
}
|
|
|
|
let pct_step;
|
|
if (Boolean(legend.hide_overspeed)) {
|
|
pct_step = 100.0 / (legend.steps - 1);
|
|
} else {
|
|
pct_step = 150.0 / (legend.steps - 1);
|
|
}
|
|
for (let i=0; i < legend.steps; i++) {
|
|
let this_pct = Math.round(pct_step * i);
|
|
let legend_step = {id: "legend_" + i.toString(), label: this_pct.toString().padStart(3, " ") + "%", shape: "box", borderWidth: 0, x: legend.x, y: y_pos, font: {face: 'courier new', size: legend.font_size, color: "black"}, color: {background: legendPctColour(this_pct)}};
|
|
network_nodes.add(legend_step);
|
|
y_pos += y_inc;
|
|
}
|
|
network_nodes.flush();
|
|
}
|
|
}
|
|
|
|
function CreateNetwork() {
|
|
// Flush the nodes and edges so they are rendered immediately
|
|
network_nodes.flush();
|
|
network_edges.flush();
|
|
|
|
var container = document.getElementById('custom-map');
|
|
network = new vis.Network(container, {nodes: network_nodes, edges: network_edges, stabilize: true}, network_options);
|
|
|
|
// width/height might be % get values in pixels
|
|
network_height = $($(container).children(".vis-network")[0]).height();
|
|
network_width = $($(container).children(".vis-network")[0]).width();
|
|
var centreY = Math.round(network_height / 2);
|
|
var centreX = Math.round(network_width / 2);
|
|
network.moveTo({position: {x: centreX, y: centreY}, scale: 1});
|
|
|
|
setCustomMapBackground('custom-map', bgtype, bgdata);
|
|
|
|
network.on('doubleClick', function (properties) {
|
|
edge_id = null;
|
|
if (properties.nodes.length > 0) {
|
|
if(properties.nodes[0] in node_device_map) {
|
|
window.location.href = "device/"+node_device_map[properties.nodes[0]].device_id;
|
|
} else if (properties.nodes[0] in node_link_map) {
|
|
window.location.href = '{{ route('maps.custom.show', ['map' => '?']) }}'.replace('?', node_link_map[properties.nodes[0]]);
|
|
} else if (properties.nodes[0].endsWith('_mid')) {
|
|
edge_id = properties.nodes[0].split("_")[0];
|
|
}
|
|
} else if (properties.edges.length > 0) {
|
|
edge_id = properties.edges[0].split("_")[0];
|
|
}
|
|
|
|
if (edge_id && (edge_id in edge_port_map)) {
|
|
window.location.href = 'device/device=' + edge_port_map[edge_id].device_id + '/tab=port/port=' + edge_port_map[edge_id].port_id + '/';
|
|
}
|
|
});
|
|
}
|
|
var Countdown;
|
|
function refreshMap() {
|
|
$.get( '{{ route('maps.custom.data', ['map' => $map_id]) }}')
|
|
.done(function( data ) {
|
|
// Add/update nodes
|
|
$.each( data.nodes, function( nodeid, node) {
|
|
var node_cfg = {};
|
|
node_cfg.id = nodeid;
|
|
if(node.device_id) {
|
|
node_device_map[nodeid] = {device_id: node.device_id, device_name: node.device_name};
|
|
delete node_link_map[nodeid];
|
|
node_cfg.title = node.device_info;
|
|
} else if(node.linked_map_name) {
|
|
delete node_device_map[nodeid];
|
|
node_link_map[nodeid] = node.linked_map_id;
|
|
node_cfg.title = "Go to " + node.linked_map_name;
|
|
} else {
|
|
node_cfg.title = null;
|
|
}
|
|
node_cfg.label = screenshot ? node.label.replace(/./g, ' ') : node.label;
|
|
node_cfg.shape = node.style;
|
|
node_cfg.borderWidth = node.border_width;
|
|
node_cfg.x = node.x_pos;
|
|
node_cfg.y = node.y_pos;
|
|
node_cfg.font = {face: node.text_face, size: node.text_size, color: node.text_colour};
|
|
node_cfg.size = node.size;
|
|
node_cfg.color = {background: node.colour_bg_view, border: node.colour_bdr_view};
|
|
if(node.style == "icon") {
|
|
node_cfg.icon = {face: 'FontAwesome', code: String.fromCharCode(parseInt(node.icon, 16)), size: node.size, color: node.colour_bdr};
|
|
} else {
|
|
node_cfg.icon = {};
|
|
}
|
|
if(node.style == "image" || node.style == "circularImage") {
|
|
if(node.image) {
|
|
node_cfg.image = {unselected: custom_image_base + node.image};
|
|
} else if (node.device_image) {
|
|
node_cfg.image = {unselected: node.device_image};
|
|
} else {
|
|
// If we do not get a valid image from the database, use defaults
|
|
node_cfg.shape = newnodeconf.shape;
|
|
node_cfg.icon = newnodeconf.icon;
|
|
node_cfg.image = newnodeconf.image;
|
|
}
|
|
} else {
|
|
node_cfg.image = {};
|
|
}
|
|
|
|
if (network_nodes.get(nodeid)) {
|
|
network_nodes.update(node_cfg);
|
|
} else {
|
|
network_nodes.add([node_cfg]);
|
|
}
|
|
});
|
|
|
|
$.each( data.edges, function( edgeid, edge) {
|
|
var mid_x = edge.mid_x;
|
|
var mid_y = edge.mid_y;
|
|
|
|
var mid = {id: edgeid + "_mid", shape: "dot", size: 0, x: mid_x, y: mid_y, label: screenshot ? '' : edge.label};
|
|
|
|
if (Boolean(reverse_arrows)) {
|
|
arrows = {from: {enabled: true, scaleFactor: 0.6}, to: {enabled: false}};
|
|
} else {
|
|
arrows = {to: {enabled: true, scaleFactor: 0.6}, from: {enabled: false}};
|
|
}
|
|
|
|
var edge1 = {id: edgeid + "_from", from: edge.custom_map_node1_id, to: edgeid + "_mid", arrows: arrows, font: {face: edge.text_face, size: edge.text_size, color: edge.text_colour}, smooth: {type: edge.style}};
|
|
var edge2 = {id: edgeid + "_to", from: edge.custom_map_node2_id, to: edgeid + "_mid", arrows: arrows, font: {face: edge.text_face, size: edge.text_size, color: edge.text_colour}, smooth: {type: edge.style}};
|
|
|
|
// Special case for curved lines
|
|
if(edge2.smooth.type == "curvedCW") {
|
|
edge2.smooth.type = "curvedCCW";
|
|
} else if (edge2.smooth.type == "curvedCCW") {
|
|
edge2.smooth.type = "curvedCW";
|
|
}
|
|
if(edge.port_id) {
|
|
var edge_port_from;
|
|
var edge_port_to;
|
|
if (Boolean(reverse_arrows)) {
|
|
port_from = edge2;
|
|
port_to = edge1;
|
|
} else {
|
|
port_from = edge1;
|
|
port_to = edge2;
|
|
}
|
|
port_from.title = port_to.title = edge.port_info;
|
|
if(edge.showpct) {
|
|
port_from.label = edge.port_frompct + "%";
|
|
port_to.label = edge.port_topct + "%";
|
|
}
|
|
if(edge.showbps) {
|
|
if(port_from.label == null) {
|
|
port_from.label = '';
|
|
port_to.label = '';
|
|
} else {
|
|
port_from.label += "\n";
|
|
port_to.label += "\n";
|
|
}
|
|
port_from.label += edge.port_frombps;
|
|
port_to.label += edge.port_tobps;
|
|
}
|
|
port_from.color = {color: edge.colour_from};
|
|
port_from.width = edge.width_from;
|
|
port_to.color = {color: edge.colour_to};
|
|
port_to.width = edge.width_to;
|
|
|
|
edge_port_map[edgeid] = {device_id: edge.device_id, port_id: edge.port_id};
|
|
} else {
|
|
delete edge_port_map[edgeid];
|
|
}
|
|
if (network_nodes.get(mid.id)) {
|
|
network_nodes.update(mid);
|
|
network_edges.update(edge1);
|
|
network_edges.update(edge2);
|
|
} else {
|
|
network_nodes.add([mid]);
|
|
network_edges.add([edge1, edge2]);
|
|
}
|
|
});
|
|
|
|
// Remove any nodes that are not in the database, includes edges
|
|
$.each( network_nodes.getIds(), function( node_idx, nodeid ) {
|
|
if(nodeid.endsWith('_mid')) {
|
|
edgeid = nodeid.split("_")[0];
|
|
if(! (edgeid in data.edges)) {
|
|
network_nodes.remove(edgeid + "_mid");
|
|
network_edges.remove(edgeid + "_to");
|
|
network_edges.remove(edgeid + "_from");
|
|
}
|
|
} else {
|
|
if(! (nodeid in data.nodes)) {
|
|
network_nodes.remove(nodeid);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Re-draw the legend
|
|
redrawLegend();
|
|
|
|
// Flush in order to make sure nodes exist for edges to connect to
|
|
network_nodes.flush();
|
|
network_edges.flush();
|
|
if (Object.keys(data).length == 0) {
|
|
$("#alert").text('{{ __('map.custom.view.no_devices') }}');
|
|
$("#alert-row").show();
|
|
} else {
|
|
$("#alert").text("");
|
|
$("#alert-row").hide();
|
|
}
|
|
});
|
|
|
|
// Initialise map if it does not exist
|
|
if (! network) {
|
|
CreateNetwork();
|
|
}
|
|
}
|
|
|
|
$(document).ready(function () {
|
|
Countdown = {
|
|
sec: {{$page_refresh}},
|
|
|
|
Start: function () {
|
|
var cur = this;
|
|
this.interval = setInterval(function () {
|
|
cur.sec -= 1;
|
|
if (cur.sec <= 0) {
|
|
refreshMap();
|
|
cur.sec = {{$page_refresh}};
|
|
}
|
|
}, 1000);
|
|
},
|
|
|
|
Pause: function () {
|
|
clearInterval(this.interval);
|
|
delete this.interval;
|
|
},
|
|
};
|
|
|
|
Countdown.Start();
|
|
refreshMap();
|
|
});
|
|
</script>
|
|
@endsection
|
|
|