Widget hot refresh & worldmap cleanup (#16053)

* Iterate in javascript and separate processing

* Widget refresh/destroy events

* Remove old dom and unbind events

* fix whitespace

* Fix up bootgrid tables, they inserted a div before the first div breaking event propagation
switch to regular js function to scope variables instead of jquery

* Handle settings the same way as the normal widget

* Use standard init_map and add layer control

* May need L.Control.Locate now

* Set maxZoom for marker cluster

* Try setMaxZoom

* worldmap size 100 and resize on refresh/widget resize

* Add resize event (and throttle it a bit)

* Further worldmap cleanup

* Move most javascript to common js, will cause js errors until page is reloaded, but better in the long run
This commit is contained in:
Tony Murray 2024-05-22 21:23:39 -05:00 committed by GitHub
parent df6a42f2a3
commit f0966f4d23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 542 additions and 365 deletions

View File

@ -26,8 +26,10 @@
namespace App\Http\Controllers\Widgets;
use App\Models\Device;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use LibreNMS\Config;
use LibreNMS\Util\Url;
class WorldMapController extends WidgetController
{
@ -37,11 +39,11 @@ class WorldMapController extends WidgetController
{
$this->defaults = [
'title' => null,
'title_url' => Config::get('leaflet.tile_url', '{s}.tile.openstreetmap.org'),
'init_lat' => Config::get('leaflet.default_lat', 51.4800),
'init_lng' => Config::get('leaflet.default_lng', 0),
'init_zoom' => Config::get('leaflet.default_zoom', 2),
'group_radius' => Config::get('leaflet.group_radius', 80),
'init_lat' => Config::get('leaflet.default_lat'),
'init_lng' => Config::get('leaflet.default_lng'),
'init_zoom' => Config::get('leaflet.default_zoom'),
'init_layer' => Config::get('geoloc.layer'),
'group_radius' => Config::get('leaflet.group_radius'),
'status' => '0,1',
'device_group' => null,
];
@ -50,17 +52,39 @@ class WorldMapController extends WidgetController
public function getView(Request $request)
{
$settings = $this->getSettings();
$status = explode(',', $settings['status']);
$settings['dimensions'] = $request->get('dimensions');
$settings['status'] = array_map('intval', explode(',', $settings['status']));
$settings['map_config'] = [
'engine' => Config::get('geoloc.engine'),
'api_key' => Config::get('geoloc.api_key'),
'tile_url' => Config::get('leaflet.tile_url'),
'lat' => $settings['init_lat'],
'lng' => $settings['init_lng'],
'zoom' => $settings['init_zoom'],
'layer' => $settings['init_layer'],
];
$devices = Device::hasAccess($request->user())
return view('widgets.worldmap', $settings);
}
public function getData(Request $request): JsonResponse
{
$this->validate($request, [
'status' => 'array',
'status.*' => 'int',
'device_group' => 'int',
]);
return response()->json($this->getMarkerData($request, $request->status ?? [0, 1], $request->device_group ?? 0));
}
public function getMarkerData(Request $request, array $status, int $device_group_id): array
{
return Device::hasAccess($request->user())
->with('location')
->isActive()
->whereIn('status', $status)
->when($settings['device_group'], function ($query) use ($settings) {
$query->inDeviceGroup($settings['device_group']);
})
->when($device_group_id, fn ($q) => $q->inDeviceGroup($device_group_id))
->get()
->filter(function ($device) use ($status) {
/** @var Device $device */
@ -68,31 +92,23 @@ class WorldMapController extends WidgetController
return false;
}
// add extra data
/** @phpstan-ignore-next-line */
$device->markerIcon = 'greenMarker';
/** @phpstan-ignore-next-line */
$device->zOffset = 0;
if ($device->status == 0) {
$device->markerIcon = 'redMarker';
$device->zOffset = 10000;
if ($device->isUnderMaintenance()) {
if (in_array(0, $status)) {
return false;
}
$device->markerIcon = 'blueMarker';
$device->zOffset = 5000;
}
// hide devices under maintenance if only showing down devices
if ($status == [0] && $device->isUnderMaintenance()) {
return false;
}
return true;
});
$settings['devices'] = $devices;
return view('widgets.worldmap', $settings);
})->map(function (Device $device) {
return [
'name' => $device->displayName(),
'lat' => $device->location->lat,
'lng' => $device->location->lng,
'icon' => $device->icon,
'url' => Url::deviceUrl($device),
// status: 0 = down, 1 = up, 3 = down + under maintenance
'status' => (int) ($device->status ?: ($device->isUnderMaintenance() ? 3 : 0)),
];
})->values()->all();
}
public function getSettingsView(Request $request)

View File

@ -47,6 +47,7 @@
'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '', // Instance of StorageInterface for custom driver
'open' => true,
],
/*

View File

@ -1776,10 +1776,8 @@ tr.search:nth-child(odd) {
background-color: #000000;
color: #ffffff;
text-align: center;
-webkit-border-top-left-radius: 4px;
-moz-border-top-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.mapTooltip {
@ -1966,10 +1964,12 @@ label {
.edit-widget, .close-widget { cursor: pointer; }
.widget_body {
padding: 0.8em;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
height: calc(100% - 38px);
height: calc(100% - 2.6em);
cursor: auto;
text-align: center;
}
@ -2016,6 +2016,20 @@ label {
margin:-8px;
}
.widget_body:has(> .worldmap_widget) {
padding: 0;
}
.worldmap_widget {
width: 100%;
height: 100%;
text-align: left;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.worldmap_widget a:hover {
text-decoration: none;
}
.report-up {
color:#5CB85C;
}

View File

@ -303,6 +303,7 @@ function init_map(id, config = {}) {
let baseMaps = {};
if (config.engine === 'google' && config.api_key) {
leaflet.setMaxZoom(21);
loadjs('https://maps.googleapis.com/maps/api/js?key=' + config.api_key, function () {
loadjs('js/Leaflet.GoogleMutant.js', function () {
const roads = L.gridLayer.googleMutant({
@ -322,6 +323,7 @@ function init_map(id, config = {}) {
});
});
} else if (config.engine === 'bing' && config.api_key) {
leaflet.setMaxZoom(18);
loadjs('js/leaflet-bing-layer.min.js', function () {
const roads = L.tileLayer.bing({
bingMapsKey: config.api_key,
@ -341,6 +343,7 @@ function init_map(id, config = {}) {
leaflet.layerControl._container.style.display = (config.readonly ? 'none' : 'block');
});
} else if (config.engine === 'mapquest' && config.api_key) {
leaflet.setMaxZoom(20);
loadjs('https://www.mapquestapi.com/sdk/leaflet/v2.2/mq-map.js?key=' + config.api_key, function () {
const roads = MQ.mapLayer();
const satellite = MQ.hybridLayer();
@ -354,22 +357,12 @@ function init_map(id, config = {}) {
leaflet.layerControl._container.style.display = (config.readonly ? 'none' : 'block');
});
} else {
leaflet.setMaxZoom(20);
const tile_url = config.tile_url ? config.tile_url : '{s}.tile.openstreetmap.org';
const osm = L.tileLayer('//' + tile_url + '/{z}/{x}/{y}.png', {
L.tileLayer('//' + tile_url + '/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
// var esri = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
// attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
// });
//
// baseMaps = {
// "OpenStreetMap": osm,
// "Satellite": esri
// };
// leaflet.layerControl = L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
osm.addTo(leaflet);
}).addTo(leaflet);
}
// disable all interaction
@ -399,6 +392,83 @@ function destroy_map(id) {
}
}
function populate_map_markers(map_id, group_radius = 10, status = [0,1], device_group = 0) {
$.ajax({
type: "GET",
url: ajax_url + '/dash/worldmap',
dataType: "json",
data: { status: status, device_group: device_group },
success: function (data) {
var redMarker = L.AwesomeMarkers.icon({
icon: 'server',
markerColor: 'red', prefix: 'fa', iconColor: 'white'
});
var blueMarker = L.AwesomeMarkers.icon({
icon: 'server',
markerColor: 'blue', prefix: 'fa', iconColor: 'white'
});
var greenMarker = L.AwesomeMarkers.icon({
icon: 'server',
markerColor: 'green', prefix: 'fa', iconColor: 'white'
});
var markers = data.map((device) => {
var markerData = {title: device.name};
switch (device.status) {
case 0: // down
markerData.icon = redMarker;
markerData.zIndexOffset = 5000;
break;
case 3: // down + maintenance
markerData.icon = blueMarker;
markerData.zIndexOffset = 10000;
break;
default: // up
markerData.icon = greenMarker;
markerData.zIndexOffset = 0;
}
var marker = L.marker(new L.LatLng(device.lat, device.lng), markerData);
marker.bindPopup(`<a href="${device.url}"><img src="${device.icon}" width="32" height="32" alt=""> ${device.name}</a>`);
return marker;
});
var map = get_map(map_id);
if (! map.markerCluster) {
map.markerCluster = L.markerClusterGroup({
maxClusterRadius: group_radius,
iconCreateFunction: function (cluster) {
var markers = cluster.getAllChildMarkers();
var color = "green";
var newClass = "Cluster marker-cluster marker-cluster-small leaflet-zoom-animated leaflet-clickable";
for (var i = 0; i < markers.length; i++) {
if (markers[i].options.icon.options.markerColor == "blue" && color != "red") {
color = "blue";
}
if (markers[i].options.icon.options.markerColor == "red") {
color = "red";
}
}
return L.divIcon({
html: cluster.getChildCount(),
className: color + newClass,
iconSize: L.point(40, 40)
});
}
});
map.addLayer(map.markerCluster);
}
map.markerCluster.clearLayers();
map.markerCluster.addLayers(markers);
},
error: function(error){
toastr.error(error.statusText);
}
});
}
function disable_map_interaction(leaflet) {
leaflet.zoomControl?.remove();
delete leaflet.zoomControl;

View File

@ -763,6 +763,10 @@ return [
'description' => 'Attempt to Geocode Locations',
'help' => 'Try to lookup latitude and longitude via geocoding API during polling',
],
'layer' => [
'description' => 'Initial Map Layer',
'help' => 'Initial map layer to display when showing various Geo Maps',
],
],
'graphite' => [
'enable' => [

View File

@ -1875,6 +1875,26 @@
]
}
},
"geoloc.layer": {
"default": "Streets",
"group": "external",
"section": "location",
"order": 7,
"type": "select",
"options": {
"Streets": "Streets",
"Sattelite": "Sattelite"
},
"when": {
"setting": "geoloc.engine",
"operator": "in",
"value": [
"google",
"mapquest",
"bing"
]
}
},
"github_api": {
"default": "https://api.github.com/repos/librenms/librenms/",
"type": "text"

View File

@ -42,7 +42,7 @@
<link href="{{ asset('css/query-builder.default.min.css') }}" rel="stylesheet">
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=20230928" rel="stylesheet">
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=22052024" rel="stylesheet">
<link href="{{ asset('css/' . LibreNMS\Config::get('applied_site_style', 'light') . '.css?ver=632417643') }}" rel="stylesheet">
@foreach(LibreNMS\Config::get('webui.custom_css', []) as $custom_css)
<link href="{{ $custom_css }}" rel="stylesheet">
@ -76,7 +76,7 @@
});
var ajax_url = "{{ url('/ajax') }}";
</script>
<script src="{{ asset('js/librenms.js?ver=12052024') }}"></script>
<script src="{{ asset('js/librenms.js?ver=22052024') }}"></script>
<script type="text/javascript" src="{{ asset('js/overlib_mini.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/flasher.min.js?ver=0.6.1') }}"></script>
<script type="text/javascript" src="{{ asset('js/toastr.min.js?ver=05072021') }}"></script>

View File

@ -322,7 +322,7 @@
} else {
obj.data('settings','1');
}
widget_reload(obj.attr('id'),obj.data('type'));
widget_reload(obj.attr('id'), obj.data('type'), true);
});
@ -582,18 +582,14 @@
return false;
}
function widget_reload(id, data_type, forceDomInject) {
function widget_reload(id, data_type, forceDomInject = false) {
const $widget_body = $('#widget_body_' + id);
const $widget_bootgrid = $('#widget_body_' + id + ' .bootgrid-table');
const settings = $widget_body.parent().data('settings') == 1 ? 1 : 0;
const $widget = $widget_body.children().first();
if (settings === 1 || forceDomInject) {
$widget_bootgrid.bootgrid('destroy');
$('#widget_body_' + id + ' *').off();
} else if ($widget_bootgrid[0] && $widget_bootgrid.data('ajax') === true) {
// Check to see if a bootgrid already exists and has ajax reloading enabled.
// If so, use bootgrid to refresh the data instead of injecting the DOM in request.
return $widget_bootgrid.bootgrid('reload');
// skip html reload and sned refresh event instead
if (!forceDomInject && $widget.data('reload') === false) {
$widget.trigger('refresh', $widget); // send refresh event
return; // skip html reload
}
$.ajax({
@ -602,11 +598,14 @@
data: {
id: id,
dimensions: {x: $widget_body.width(), y: $widget_body.height()},
settings: settings
settings: $widget_body.parent().data('settings') == 1 ? 1 : 0
},
dataType: 'json',
success: function (data) {
if (data.status === 'ok') {
$widget.trigger('destroy', $widget); // send destroy event
$widget_body.children().unbind().html("").remove(); // clear old contents and unbind events
$('#widget_title_' + id).html(data.title);
$widget_body.html(data.html);
$widget_body.parent().data('settings', data.show_settings).data('refresh', data.settings.refresh);
@ -621,19 +620,25 @@
}
function grab_data(id, data_type) {
const $parent = $('#widget_body_' + id).parent();
if($parent.data('settings') == 0) {
widget_reload(id, data_type);
}
const refresh = $('#widget_body_' + id).parent().data('refresh');
widget_reload(id, data_type);
setTimeout(function () {
grab_data(id, data_type);
}, ($parent.data('refresh') > 0 ? $parent.data('refresh') : 60) * 1000);
}, (refresh > 0 ? refresh : 60) * 1000);
}
// make sure gridster stays disabled when the window is resized
var resizeTrigger = null;
addEvent(window, "resize", function(event) {
// emit resize event, but only once every 100ms
if (resizeTrigger === null) {
resizeTrigger = setTimeout(() => {
resizeTrigger = null;
$('.widget_body').children().first().trigger('resize');
}, 100);
}
setTimeout(function(){
if(!gridster_state) {
gridster.disable();

View File

@ -1,59 +1,64 @@
<div class="table-responsive">
<table id="alertlog_{{ $id }}" class="table table-hover table-condensed alerts" data-ajax="true">
<thead>
<tr>
<th data-column-id="status" data-sortable="false"></th>
<th data-column-id="time_logged" data-order="desc">{{ __('Timestamp') }}</th>
<th data-column-id="details" data-sortable="false">&nbsp;</th>
<th data-column-id="hostname">{{ __('Device') }}</th>
<th data-column-id="alert">{{ __('Alert') }}</th>
</tr>
</thead>
</table>
<div id="alertlog_container-{{ $id }}" data-reload="false">
<div class="table-responsive">
<table id="alertlog_{{ $id }}" class="table table-hover table-condensed alerts">
<thead>
<tr>
<th data-column-id="status" data-sortable="false"></th>
<th data-column-id="time_logged" data-order="desc">{{ __('Timestamp') }}</th>
<th data-column-id="details" data-sortable="false">&nbsp;</th>
<th data-column-id="hostname">{{ __('Device') }}</th>
<th data-column-id="alert">{{ __('Alert') }}</th>
</tr>
</thead>
</table>
</div>
</div>
<script>
var grid = $("#alertlog_{{ $id }}").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function () {
return {
id: "alertlog",
device_id: "",
device_group: "{{ $device_group }}",
state: '{{ $state }}',
min_severity: '{{ $min_severity }}',
};
},
url: "ajax_table.php"
}).on("loaded.rs.jquery.bootgrid", function () {
var results = $("div.infos").text().split(" ");
low = results[1] - 1;
high = results[3];
max = high - low;
search = $('.search-field').val();
grid.find(".incident-toggle").each(function () {
$(this).parent().addClass('incident-toggle-td');
}).on("click", function (e) {
var target = $(this).data("target");
$(target).collapse('toggle');
$(this).toggleClass('fa-plus fa-minus');
});
grid.find(".incident").each(function () {
$(this).parent().addClass('col-lg-4 col-md-4 col-sm-4 col-xs-4');
$(this).parent().parent().on("mouseenter", function () {
$(this).find(".incident-toggle").fadeIn(200);
}).on("mouseleave", function () {
$(this).find(".incident-toggle").fadeOut(200);
}).on("click", "td:not(.incident-toggle-td)", function () {
var target = $(this).parent().find(".incident-toggle").data("target");
if ($(this).parent().find(".incident-toggle").hasClass('fa-plus')) {
$(this).parent().find(".incident-toggle").toggleClass('fa-plus fa-minus');
$(target).collapse('toggle');
}
(function () {
var grid = $("#alertlog_{{ $id }}").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function () {
return {
id: "alertlog",
device_id: "",
device_group: "{{ $device_group }}",
state: '{{ $state }}',
min_severity: '{{ $min_severity }}',
};
},
url: "ajax_table.php"
}).on("loaded.rs.jquery.bootgrid", function () {
grid.find(".incident-toggle").each(function () {
$(this).parent().addClass('incident-toggle-td');
}).on("click", function (e) {
var target = $(this).data("target");
$(target).collapse('toggle');
$(this).toggleClass('fa-plus fa-minus');
});
grid.find(".incident").each(function () {
$(this).parent().addClass('col-lg-4 col-md-4 col-sm-4 col-xs-4');
$(this).parent().parent().on("mouseenter", function () {
$(this).find(".incident-toggle").fadeIn(200);
}).on("mouseleave", function () {
$(this).find(".incident-toggle").fadeOut(200);
}).on("click", "td:not(.incident-toggle-td)", function () {
var target = $(this).parent().find(".incident-toggle").data("target");
if ($(this).parent().find(".incident-toggle").hasClass('fa-plus')) {
$(this).parent().find(".incident-toggle").toggleClass('fa-plus fa-minus');
$(target).collapse('toggle');
}
});
});
});
});
$('#alertlog_container-{{ $id }}').on('refresh', function (event) {
grid.bootgrid('reload');
});
$('#alertlog_container-{{ $id }}').on('destroy', function (event) {
grid.bootgrid('destroy');
delete grid;
});
})();
</script>

View File

@ -1,27 +1,39 @@
<div class="table-responsive">
<table id="alertlog-stats_{{ $id }}" class="table table-hover table-condensed table-striped" data-ajax="true">
<thead>
<tr>
<th data-column-id="count">{{ __('Count') }}</th>
<th data-column-id="hostname">{{ __('Device') }}</th>
<th data-column-id="alert_rule">{{ __('Alert rule') }}</th>
</tr>
</thead>
</table>
<div id="alertlog_stats_container-{{ $id }}" data-reload="false">
<div class="table-responsive">
<table id="alertlog_stats-{{ $id }}" class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th data-column-id="count">{{ __('Count') }}</th>
<th data-column-id="hostname">{{ __('Device') }}</th>
<th data-column-id="alert_rule">{{ __('Alert rule') }}</th>
</tr>
</thead>
</table>
</div>
</div>
<script>
$("#alertlog-stats_{{ $id }}").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function () {
return {
id: "alertlog-stats",
device_id: "",
min_severity: '{{ $min_severity }}',
time_interval: '{{ $time_interval }}'
};
},
url: "ajax_table.php"
});
(function () {
var grid = $("#alertlog_stats-{{ $id }}").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function () {
return {
id: "alertlog-stats",
device_id: "",
min_severity: '{{ $min_severity }}',
time_interval: '{{ $time_interval }}'
};
},
url: "ajax_table.php"
});
$('#alertlog_stats_container-{{ $id }}').on('refresh', function (event) {
grid.bootgrid('reload');
});
$('#alertlog_stats_container-{{ $id }}').on('destroy', function (event) {
grid.bootgrid('destroy');
delete grid;
});
})();
</script>

View File

@ -1,75 +1,87 @@
<div class="table-responsive">
<table id="alerts_{{ $id }}" class="table table-hover table-condensed alerts" data-ajax="true">
<thead>
<tr>
<th data-column-id="severity"></th>
<th data-column-id="timestamp">{{ __('Timestamp') }}</th>
<th data-column-id="rule">{{ __('Rule') }}</th>
<th data-column-id="details" data-sortable="false"></th>
<th data-column-id="hostname">{{ __('Hostname') }}</th>
<th data-column-id="location" data-visible="{{ $location ? 'true' : 'false' }}">{{ __('Location') }}</th>
<th data-column-id="ack_ico" data-sortable="false">{{ __('ACK') }}</th>
<th data-column-id="notes" data-sortable="false">{{ __('Notes') }}</th>
<th data-column-id="proc" data-sortable="false" data-visible="{{ $proc ? 'true' : 'false' }}">URL</th>
</tr>
</thead>
</table>
<div id="alerts_container-{{ $id }}" data-reload="false">
<div class="table-responsive">
<table id="alerts-{{ $id }}" class="table table-hover table-condensed alerts">
<thead>
<tr>
<th data-column-id="severity"></th>
<th data-column-id="timestamp">{{ __('Timestamp') }}</th>
<th data-column-id="rule">{{ __('Rule') }}</th>
<th data-column-id="details" data-sortable="false"></th>
<th data-column-id="hostname">{{ __('Hostname') }}</th>
<th data-column-id="location" data-visible="{{ $location ? 'true' : 'false' }}">{{ __('Location') }}</th>
<th data-column-id="ack_ico" data-sortable="false">{{ __('ACK') }}</th>
<th data-column-id="notes" data-sortable="false">{{ __('Notes') }}</th>
<th data-column-id="proc" data-sortable="false" data-visible="{{ $proc ? 'true' : 'false' }}">URL</th>
</tr>
</thead>
</table>
</div>
</div>
<script>
var alerts_grid = $("#alerts_{{ $id }}").bootgrid({
ajax: true,
requestHandler: request => ({
...request,
id: "alerts",
acknowledged: '{{ $acknowledged }}',
unreachable: '{{ $unreachable }}',
fired: '{{ $fired }}',
min_severity: '{{ $min_severity }}',
group: '{{ $device_group }}',
proc: '{{ $proc }}',
sort: '{{ $sort }}',
uncollapse_key_count: '{{ $uncollapse_key_count }}',
device_id: '{{ $device }}'
}),
responseHandler: response => {
$("#widget_title_counter_{{ $id }}").text(response.total ? ` (${response.total})` : '')
(function () {
var alerts_grid = $("#alerts-{{ $id }}").bootgrid({
ajax: true,
requestHandler: request => ({
...request,
id: "alerts",
acknowledged: '{{ $acknowledged }}',
unreachable: '{{ $unreachable }}',
fired: '{{ $fired }}',
min_severity: '{{ $min_severity }}',
group: '{{ $device_group }}',
proc: '{{ $proc }}',
sort: '{{ $sort }}',
uncollapse_key_count: '{{ $uncollapse_key_count }}',
device_id: '{{ $device }}'
}),
responseHandler: response => {
$("#widget_title_counter_{{ $id }}").text(response.total ? ` (${response.total})` : '')
return response
},
url: "ajax_table.php",
navigation: ! {{ $hidenavigation }},
rowCount: [50, 100, 250, -1]
}).on("loaded.rs.jquery.bootgrid", function() {
alerts_grid = $(this);
alerts_grid.find(".incident-toggle").each( function() {
$(this).parent().addClass('incident-toggle-td');
}).on("click", function(e) {
var target = $(this).data("target");
$(target).collapse('toggle');
$(this).toggleClass('fa-plus fa-minus');
});
alerts_grid.find(".incident").each( function() {
$(this).parent().addClass('col-lg-4 col-md-4 col-sm-4 col-xs-4');
$(this).parent().parent().on("mouseenter", function() {
$(this).find(".incident-toggle").fadeIn(200);
}).on("mouseleave", function() {
$(this).find(".incident-toggle").fadeOut(200);
return response
},
url: "ajax_table.php",
navigation: ! {{ $hidenavigation }},
rowCount: [50, 100, 250, -1]
}).on("loaded.rs.jquery.bootgrid", function() {
alerts_grid = $(this);
alerts_grid.find(".incident-toggle").each( function() {
$(this).parent().addClass('incident-toggle-td');
}).on("click", function(e) {
var target = $(this).data("target");
$(target).collapse('toggle');
$(this).toggleClass('fa-plus fa-minus');
});
alerts_grid.find(".incident").each( function() {
$(this).parent().addClass('col-lg-4 col-md-4 col-sm-4 col-xs-4');
$(this).parent().parent().on("mouseenter", function() {
$(this).find(".incident-toggle").fadeIn(200);
}).on("mouseleave", function() {
$(this).find(".incident-toggle").fadeOut(200);
});
});
alerts_grid.find(".command-ack-alert").on("click", function(e) {
e.preventDefault();
var alert_state = $(this).data("alert_state");
var alert_id = $(this).data('alert_id');
$('#ack_alert_id').val(alert_id);
$('#ack_alert_state').val(alert_state);
$('#ack_msg').val('');
$("#alert_ack_modal").modal('show');
});
alerts_grid.find(".command-alert-note").on("click", function(e) {
e.preventDefault();
var alert_id = $(this).data('alert_id');
$('#alert_id').val(alert_id);
$("#alert_notes_modal").modal('show');
});
});
alerts_grid.find(".command-ack-alert").on("click", function(e) {
e.preventDefault();
var alert_state = $(this).data("alert_state");
var alert_id = $(this).data('alert_id');
$('#ack_alert_id').val(alert_id);
$('#ack_alert_state').val(alert_state);
$('#ack_msg').val('');
$("#alert_ack_modal").modal('show');
$('#alerts_container-{{ $id }}').on('refresh', function (event) {
alerts_grid.bootgrid('reload');
});
alerts_grid.find(".command-alert-note").on("click", function(e) {
e.preventDefault();
var alert_id = $(this).data('alert_id');
$('#alert_id').val(alert_id);
$("#alert_notes_modal").modal('show');
$('#alerts_container-{{ $id }}').on('destroy', function (event) {
alerts_grid.bootgrid('destroy');
delete alerts_grid;
});
});
})();
</script>

View File

@ -1,29 +1,41 @@
<div class="table-responsive">
<table id="eventlog" class="table table-hover table-condensed table-striped" data-ajax="true">
<thead>
<tr>
<th data-column-id="datetime" data-order="desc">{{ __('Timestamp') }}</th>
<th data-column-id="type">{{ __('Type') }}</th>
<th data-column-id="device_id">{{ __('Hostname') }}</th>
<th data-column-id="message">{{ __('Message') }}</th>
<th data-column-id="username">{{ __('User') }}</th>
</tr>
</thead>
</table>
<div id="eventlog_container-{{ $id }}" data-reload="false">
<div class="table-responsive">
<table id="eventlog-{{ $id }}" class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th data-column-id="datetime" data-order="desc">{{ __('Timestamp') }}</th>
<th data-column-id="type">{{ __('Type') }}</th>
<th data-column-id="device_id">{{ __('Hostname') }}</th>
<th data-column-id="message">{{ __('Message') }}</th>
<th data-column-id="username">{{ __('User') }}</th>
</tr>
</thead>
</table>
</div>
</div>
<script>
$("#eventlog").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function ()
{
return {
device: "{{ $device }}",
device_group: "{{ $device_group }}",
eventtype: "{{ $eventtype }}"
};
},
url: "{{ url('/ajax/table/eventlog') }}"
$(function () {
var grid = $("#eventlog-{{ $id }}").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function ()
{
return {
device: "{{ $device }}",
device_group: "{{ $device_group }}",
eventtype: "{{ $eventtype }}"
};
},
url: "{{ url('/ajax/table/eventlog') }}"
});
$('#eventlog_container-{{ $id }}').on('refresh', function (event) {
grid.bootgrid('reload');
});
$('#eventlog_container-{{ $id }}').on('destroy', function (event) {
grid.bootgrid('destroy');
delete grid;
});
});
</script>

View File

@ -1,42 +1,54 @@
<div class="table-responsive">
<table id="graylog-{{ $id }}" class="table table-hover table-condensed graylog" data-ajax="true">
<thead>
<tr>
<th data-column-id="severity" data-sortable="false"></th>
<th data-column-id="origin">{{ __('Origin') }}</th>
<th data-column-id="timestamp" data-formatter="browserTime">{{ __('Timestamp') }}</th>
<th data-column-id="level" data-sortable="false">{{ __('Level') }}</th>
<th data-column-id="source">{{ __('Source') }}</th>
<th data-column-id="message" data-sortable="false">{{ __('Message') }}</th>
<th data-column-id="facility" data-sortable="false">{{ __('Facility') }}</th>
</tr>
</thead>
</table>
<div id="graylog_container-{{ $id }}" data-reload="false">
<div class="table-responsive">
<table id="graylog-{{ $id }}" class="table table-hover table-condensed graylog">
<thead>
<tr>
<th data-column-id="severity" data-sortable="false"></th>
<th data-column-id="origin">{{ __('Origin') }}</th>
<th data-column-id="timestamp" data-formatter="browserTime">{{ __('Timestamp') }}</th>
<th data-column-id="level" data-sortable="false">{{ __('Level') }}</th>
<th data-column-id="source">{{ __('Source') }}</th>
<th data-column-id="message" data-sortable="false">{{ __('Message') }}</th>
<th data-column-id="facility" data-sortable="false">{{ __('Facility') }}</th>
</tr>
</thead>
</table>
</div>
</div>
<script>
$("#graylog-{{ $id }}").bootgrid({
ajax: true,
rowCount: ['{{ $limit }}', 25,50,100,250,-1],
navigation: ! {{ $hidenavigation }},
formatters: {
"browserTime": function(column, row) {
@config('graylog.timezone')
return row.timestamp;
@else
return moment.parseZone(row.timestamp).local().format("YYYY-MM-DD HH:MM:SS");
@endconfig
}
},
post: function ()
{
return {
stream: "{{ $stream }}",
device: "{{ $device }}",
range: "{{ $range }}",
loglevel: "{{ $loglevel }}"
};
},
url: "{{ url('/ajax/table/graylog') }}"
});
(function () {
var grid = $("#graylog-{{ $id }}").bootgrid({
ajax: true,
rowCount: ['{{ $limit }}', 25,50,100,250,-1],
navigation: ! {{ $hidenavigation }},
formatters: {
"browserTime": function(column, row) {
@config('graylog.timezone')
return row.timestamp;
@else
return moment.parseZone(row.timestamp).local().format("YYYY-MM-DD HH:MM:SS");
@endconfig
}
},
post: function ()
{
return {
stream: "{{ $stream }}",
device: "{{ $device }}",
range: "{{ $range }}",
loglevel: "{{ $loglevel }}"
};
},
url: "{{ url('/ajax/table/graylog') }}"
});
$('#graylog_container-{{ $id }}').on('refresh', function (event) {
grid.bootgrid('reload');
});
$('#graylog_container-{{ $id }}').on('destroy', function (event) {
grid.bootgrid('destroy');
delete grid;
});
})();
</script>

View File

@ -1,4 +1,4 @@
<form role="form" class="dashboard-widget-settings" onsubmit="widget_settings(this); return false;">
<form role="form" class="dashboard-widget-settings" onsubmit="widget_settings(this); return false;" data-reload="false">
@csrf
@yield('form')

View File

@ -22,6 +22,14 @@
<input class="form-control" name="init_zoom" id="init_zoom-{{ $id }}" type="number" min="0" max="18" step="0.1" value="{{ $init_zoom }}" placeholder="{{ __('ie. 5.8') }}">
</div>
<div class="form-group">
<label for="init_layer-{{ $id }}" class="control-label">{{ __('Initial Layer') }}</label>
<select class="form-control" name="init_layer" id="init_layer-{{ $id }}">
<option value="Streets" @if($init_layer == 'Streets') selected @endif>{{ __('Streets') }}</option>
<option value="Satellite" @if($init_layer == 'Satellite') selected @endif>{{ __('Satellite') }}</option>
</select>
</div>
<div class="form-group">
<label for="group_radius-{{ $id }}" class="control-label">{{ __('Grouping radius') }}</label>
<input class="form-control" name="group_radius" id="group_radius-{{ $id }}" type="number" value="{{ $group_radius }}" placeholder="{{ __('default 80') }}">

View File

@ -1,31 +1,43 @@
<div class="table-responsive">
<table id="syslog" class="table table-hover table-condensed table-striped" data-ajax="true">
<thead>
<tr>
<th data-column-id="label"></th>
<th data-column-id="timestamp" data-order="desc">{{ __('Timestamp') }}</th>
<th data-column-id="level">{{ __('Level') }}</th>
<th data-column-id="device_id">{{ __('Hostname') }}</th>
<th data-column-id="program">{{ __('Program') }}</th>
<th data-column-id="msg">{{ __('Message') }}</th>
<th data-column-id="priority">{{ __('Priority') }}</th>
</tr>
</thead>
</table>
<div id="syslog_container-{{ $id }}" data-reload="false">
<div class="table-responsive">
<table id="syslog-{{ $id }}" class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th data-column-id="label"></th>
<th data-column-id="timestamp" data-order="desc">{{ __('Timestamp') }}</th>
<th data-column-id="level">{{ __('Level') }}</th>
<th data-column-id="device_id">{{ __('Hostname') }}</th>
<th data-column-id="program">{{ __('Program') }}</th>
<th data-column-id="msg">{{ __('Message') }}</th>
<th data-column-id="priority">{{ __('Priority') }}</th>
</tr>
</thead>
</table>
</div>
</div>
<script type="application/javascript">
$("#syslog").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function ()
{
return {
device: '{{ $device ?: '' }}',
device_group: '{{ $device_group }}',
level: '{{ $level }}'
};
},
url: "{{ url('/ajax/table/syslog') }}"
});
(function () {
var grid = $("#syslog-{{ $id }}").bootgrid({
ajax: true,
rowCount: [50, 100, 250, -1],
navigation: ! {{ $hidenavigation }},
post: function ()
{
return {
device: '{{ $device ?: '' }}',
device_group: '{{ $device_group }}',
level: '{{ $level }}'
};
},
url: "{{ url('/ajax/table/syslog') }}"
});
$('#syslog_container-{{ $id }}').on('refresh', function (event) {
grid.bootgrid('reload');
});
$('#syslog_container-{{ $id }}').on('destroy', function (event) {
grid.bootgrid('destroy');
delete grid;
});
})();
</script>

View File

@ -1,63 +1,36 @@
<div id="leaflet-map-{{ $id }}" style="width: {{ $dimensions['x'] }}px; height: {{ $dimensions['y'] }}px;"></div>
<div id="worldmap_widget-{{ $id }}" class="worldmap_widget" data-reload="false"></div>
<script type="application/javascript">
loadjs('js/leaflet.js', function() {
loadjs('js/leaflet.markercluster.js', function () {
loadjs('js/leaflet.awesome-markers.min.js', function () {
var map = L.map('leaflet-map-{{ $id }}', { zoomSnap: 0.1 } ).setView(['{{ $init_lat }}', '{{ $init_lng }}'], '{{ sprintf('%01.1f', $init_zoom) }}');
L.tileLayer('//{{ $title_url }}/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
(function () {
const map_id = 'worldmap_widget-{{ $id }}';
const status = {{ Js::from($status) }};
const device_group = {{ (int) $device_group }};
const map_config = {{ Js::from($map_config) }};
const group_radius = {{ (int) $group_radius }};
var markers = L.markerClusterGroup({
maxClusterRadius: '{{ $group_radius }}',
iconCreateFunction: function (cluster) {
var markers = cluster.getAllChildMarkers();
var color = "green";
var newClass = "Cluster marker-cluster marker-cluster-small leaflet-zoom-animated leaflet-clickable";
for (var i = 0; i < markers.length; i++) {
if (markers[i].options.icon.options.markerColor == "blue" && color != "red") {
color = "blue";
}
if (markers[i].options.icon.options.markerColor == "red") {
color = "red";
}
}
return L.divIcon({ html: cluster.getChildCount(), className: color+newClass, iconSize: L.point(40, 40) });
}
});
var redMarker = L.AwesomeMarkers.icon({
icon: 'server',
markerColor: 'red', prefix: 'fa', iconColor: 'white'
});
var blueMarker = L.AwesomeMarkers.icon({
icon: 'server',
markerColor: 'blue', prefix: 'fa', iconColor: 'white'
});
var greenMarker = L.AwesomeMarkers.icon({
icon: 'server',
markerColor: 'green', prefix: 'fa', iconColor: 'white'
});
loadjs('js/leaflet.js', function () {
loadjs('js/leaflet.markercluster.js', function () {
loadjs('js/leaflet.awesome-markers.min.js', function () {
loadjs('js/L.Control.Locate.min.js', function () {
init_map(map_id, map_config).scrollWheelZoom.disable();
populate_map_markers(map_id, group_radius, status, device_group);
@foreach($devices as $device)
@if($status != '0' or !$device->isUnderMaintenance())
var title = '<a href="@deviceUrl($device)"><img src="{{ $device->icon }}" width="32" height="32" alt=""> {{ $device->displayName() }}</a>';
var tooltip = '{{ $device->displayName() }}';
var marker = L.marker(new L.LatLng('{{ $device->location->lat }}', '{{ $device->location->lng }}'), {title: tooltip, icon: {{ $device->markerIcon }}, zIndexOffset: {{ $device->zOffset }}});
marker.bindPopup(title);
markers.addLayer(marker);
@endif
@endforeach
map.addLayer(markers);
map.scrollWheelZoom.disable();
$(document).ready(function() {
$("#leaflet-map-{{ $id }}").on("click", function (event) {
map.scrollWheelZoom.enable();
}).on("mouseleave", function (event) {
map.scrollWheelZoom.disable();
// register listeners
$('#' + map_id).on('click', function (event) {
get_map(map_id).scrollWheelZoom.enable();
}).on('mouseleave', function (event) {
get_map(map_id).scrollWheelZoom.disable();
}).on('resize', function (event) {
get_map(map_id).invalidateSize();
}).on('refresh', function (event) {
get_map(map_id).invalidateSize();
populate_map_markers(map_id, group_radius, status, device_group);
}).on('destroy', function (event) {
destroy_map(map_id);
});
});
});
});
});
});});});
})();
</script>

View File

@ -237,7 +237,8 @@ Route::middleware(['auth'])->group(function () {
Route::post('top-devices', 'TopDevicesController');
Route::post('top-interfaces', 'TopInterfacesController');
Route::post('top-errors', 'TopErrorsController');
Route::post('worldmap', 'WorldMapController');
Route::post('worldmap', 'WorldMapController')->name('widget.worldmap');
Route::get('worldmap', 'WorldMapController@getData')->name('widget.worldmap.data');
Route::post('alertlog-stats', 'AlertlogStatsController');
});
});