mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Add functionality for custom maps (weathermaps) (#15633)
* Initial commit with editor. * Added custom map models and database migrations. Modified the controller and view to support saving the custom map settings to the database * Added menu items and sorted out access permissions for maps and nodes * Cleaned up some of the conditions in the javascript section of the blade * Started work on the map data save * Save of map nodes and edges is complete * Got the map to load data on page load and added the delete functionality * Fixed a typo and made link colour black if intertface is down * Various usability fix-ups * Show the save button on node and edge delete * Fixed up access for users without global read * Increase typeahead search size and standardised the way modals are triggered. * Update data fetch to copy values into array so I can add more fields * Convert blank array check to use count() * Formatting changes * More formatting fixes * Formatting again * DB schema update * Revert previous commit * Pass device id to pages * Remove bad characters from javascript * Re-add the - character in search results * Update to avoid background colour being set to the current colour for offline devices * Fixed a bug in speed detection when no suffix is given * Fixed up the speed colour calculation and added comments * Update default edge font size to 12 * Reduce arrow size * Formatting fix * Update the custom map controller to handle null interface speeds * Alter JSON columns to be longtext instead * Only refresh map data on successful save * Update labels on default settings to make it clear that they are not saved * Added timestamps to all custom map tables Use HasFactory instead of static definitions for custom map tables convert JSON DB fields to longtext and updated PHP to do the appropriate JSON decoding as a result * Added missing vis.js images for the editor * Split the custom map blade into different pages * formatting fixes * Initial commit with editor. * Added custom map models and database migrations. Modified the controller and view to support saving the custom map settings to the database * Added menu items and sorted out access permissions for maps and nodes * Cleaned up some of the conditions in the javascript section of the blade * Started work on the map data save * Save of map nodes and edges is complete * Got the map to load data on page load and added the delete functionality * Various usability fix-ups * Show the save button on node and edge delete * Fixed up access for users without global read * Increase typeahead search size and standardised the way modals are triggered. * Convert blank array check to use count() * Formatting changes * More formatting fixes * Formatting again * DB schema update * Revert previous commit * Pass device id to pages * Remove bad characters from javascript * Re-add the - character in search results * Update to avoid background colour being set to the current colour for offline devices * Reduce arrow size * Only refresh map data on successful save * Update labels on default settings to make it clear that they are not saved * Added timestamps to all custom map tables Use HasFactory instead of static definitions for custom map tables convert JSON DB fields to longtext and updated PHP to do the appropriate JSON decoding as a result * Added missing vis.js images for the editor * Split the custom map blade into different pages * Updated the custom maps to use the select2 searches for ports and devices * Fix port search clearing with select2 * Update DB schema to add timestamps * Add the ability to set a node alignment value where nodes will align to a grid * Add a checkbox to re-center edge lines * Schema update for node alignment * Removed unused route * Fixups after rebase * Remove DevicePortSearchController * Rebase fixups * Remove unneeded controller * Formatting fixes * Update all network map documentation * Fixed typo in doc * Change background imgae database migration * Update migration for custom map background to fix schema error * Place a try/catch around the BLOB->MEDIUMBLOB migration * Formatting fix * Moved custom map background image location and added some SVG images to test as image options * Updated the editor to use a static set of device images * Update the image logic in the editor and added to the viewer * DB Schema update * Formatting * remove svg height/width attributes * Added some more icon options for arrows * Added database migration to allow nodes to link to another custom map Fixed an error in the image migration * Added the ability to link a node to another custom map * Formatting fixes * DB Schema update * Remove images-custom directory * Explicitly cast map ID to int * Made the image selection list dynamic based on the contents of the custom map icons directory * Formatting fix * Double-clicking on a link will take you to the link * Remove whitespace * Add translations fix an xss and hopefully not add any new ones refactor node image to use translations with fallback * split modals out into separate files return width/height to avoid js scope issues * Formatting fixes * refactor edit select page into a "manage" page Still left: validation/custom request Controller refactor ui tweaks * MapSettingsRequest * Refactor more routes, policy, controller I think this is the last refactor. Everything is now organized in a standard way. Missing a method to check if a user has access to a map * Fix booleans and style * Add versioning to the background image to prevent browser caching * Fixed the background image update by splitting it into a separate modal Changed the delete button on the map editor screen to return to the map list * Formatting fix * Added double-click actions in editor to edit nodes and edges --------- Co-authored-by: Tony Murray <murraytony@gmail.com>
This commit is contained in:
94
app/Http/Controllers/Maps/CustomMapBackgroundController.php
Normal file
94
app/Http/Controllers/Maps/CustomMapBackgroundController.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* CustomMapController.php
|
||||
*
|
||||
* Controller for custom maps
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @link https://www.librenms.org
|
||||
*
|
||||
* @copyright 2023 Steven Wilton
|
||||
* @author Steven Wilton <swilton@fluentit.com.au>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Maps;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CustomMap;
|
||||
use App\Models\CustomMapBackground;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CustomMapBackgroundController extends Controller
|
||||
{
|
||||
public function get(CustomMap $map)
|
||||
{
|
||||
$this->authorize('view', $map);
|
||||
|
||||
$background = $this->checkImageCache($map);
|
||||
if ($background) {
|
||||
$path = Storage::disk('base')->path('html/images/custommap/background/' . $background);
|
||||
|
||||
return response()->file($path, [
|
||||
'Content-Type' => Storage::mimeType($background),
|
||||
]);
|
||||
}
|
||||
abort(404);
|
||||
}
|
||||
|
||||
public function save(FormRequest $request, CustomMap $map)
|
||||
{
|
||||
$this->authorize('update', $map);
|
||||
|
||||
if ($request->bgimage) {
|
||||
$map->background_suffix = $request->bgimage->extension();
|
||||
if (! $map->background) {
|
||||
$background = new CustomMapBackground;
|
||||
$background->background_image = $request->bgimage->getContent();
|
||||
$map->background()->save($background);
|
||||
} else {
|
||||
$map->background->background_image = $request->bgimage->getContent();
|
||||
$map->background->save();
|
||||
}
|
||||
$map->background_version++;
|
||||
$map->save();
|
||||
} elseif ($request->bgclear) {
|
||||
if ($map->background) {
|
||||
$map->background->delete();
|
||||
}
|
||||
$map->background_suffix = null;
|
||||
$map->save();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'bgimage' => $map->background_suffix ? true : false,
|
||||
'bgversion' => $map->background_version,
|
||||
]);
|
||||
}
|
||||
|
||||
private function checkImageCache(CustomMap $map): ?string
|
||||
{
|
||||
if (! $map->background_suffix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$imageName = $map->custom_map_id . '_' . $map->background_version . '.' . $map->background_suffix;
|
||||
if (Storage::disk('base')->missing('html/images/custommap/background/' . $imageName)) {
|
||||
Storage::disk('base')->put('html/images/custommap/background/' . $imageName, $map->background->background_image);
|
||||
}
|
||||
|
||||
return $imageName;
|
||||
}
|
||||
}
|
165
app/Http/Controllers/Maps/CustomMapController.php
Normal file
165
app/Http/Controllers/Maps/CustomMapController.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* CustomMapController.php
|
||||
*
|
||||
* Controller for custom maps
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @link https://www.librenms.org
|
||||
*
|
||||
* @copyright 2023 Steven Wilton
|
||||
* @author Steven Wilton <swilton@fluentit.com.au>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Maps;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\CustomMapSettingsRequest;
|
||||
use App\Models\CustomMap;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use LibreNMS\Config;
|
||||
|
||||
class CustomMapController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(CustomMap::class, 'map');
|
||||
}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
return view('map.custom-manage', [
|
||||
'maps' => CustomMap::orderBy('name')->get(['custom_map_id', 'name']),
|
||||
'name' => 'New Map',
|
||||
'node_align' => 10,
|
||||
'background' => null,
|
||||
'map_conf' => [
|
||||
'height' => '800px',
|
||||
'width' => '1800px',
|
||||
'interaction' => [
|
||||
'dragNodes' => true,
|
||||
'dragView' => false,
|
||||
'zoomView' => false,
|
||||
],
|
||||
'manipulation' => [
|
||||
'enabled' => true,
|
||||
'initiallyActive' => true,
|
||||
],
|
||||
'physics' => [
|
||||
'enabled' => false,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(CustomMap $map): Response
|
||||
{
|
||||
$map->delete();
|
||||
|
||||
return response('Success', 200)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
public function show(CustomMap $map): View
|
||||
{
|
||||
$map_conf = $map->options;
|
||||
$map_conf['width'] = $map->width;
|
||||
$map_conf['height'] = $map->height;
|
||||
$data = [
|
||||
'edit' => false,
|
||||
'map_id' => $map->custom_map_id,
|
||||
'name' => $map->name,
|
||||
'background' => (bool) $map->background_suffix,
|
||||
'bgversion' => $map->background_version,
|
||||
'page_refresh' => Config::get('page_refresh', 300),
|
||||
'map_conf' => $map_conf,
|
||||
'newedge_conf' => $map->newedgeconfig,
|
||||
'newnode_conf' => $map->newnodeconfig,
|
||||
'vmargin' => 20,
|
||||
'hmargin' => 20,
|
||||
];
|
||||
|
||||
return view('map.custom-view', $data);
|
||||
}
|
||||
|
||||
public function edit(CustomMap $map): View
|
||||
{
|
||||
$data = [
|
||||
'map_id' => $map->custom_map_id,
|
||||
'name' => $map->name,
|
||||
'node_align' => $map->node_align,
|
||||
'newedge_conf' => $map->newedgeconfig,
|
||||
'newnode_conf' => $map->newnodeconfig,
|
||||
'map_conf' => $map->options,
|
||||
'background' => (bool) $map->background_suffix,
|
||||
'bgversion' => $map->background_version,
|
||||
'edit' => true,
|
||||
'vmargin' => 20,
|
||||
'hmargin' => 20,
|
||||
'images' => $this->listNodeImages(),
|
||||
'maps' => CustomMap::orderBy('name')->where('custom_map_id', '<>', $map->custom_map_id)->get(['custom_map_id', 'name']),
|
||||
];
|
||||
|
||||
$data['map_conf']['width'] = $map->width;
|
||||
$data['map_conf']['height'] = $map->height;
|
||||
// Override some settings for the editor
|
||||
$data['map_conf']['interaction'] = ['dragNodes' => true, 'dragView' => false, 'zoomView' => false];
|
||||
$data['map_conf']['manipulation'] = ['enabled' => true, 'initiallyActive' => true];
|
||||
$data['map_conf']['physics'] = ['enabled' => false];
|
||||
|
||||
return view('map.custom-edit', $data);
|
||||
}
|
||||
|
||||
public function store(CustomMapSettingsRequest $request): JsonResponse
|
||||
{
|
||||
return $this->update($request, new CustomMap);
|
||||
}
|
||||
|
||||
public function update(CustomMapSettingsRequest $request, CustomMap $map): JsonResponse
|
||||
{
|
||||
$map->fill($request->validated());
|
||||
$map->save(); // save to get ID
|
||||
|
||||
return response()->json([
|
||||
'id' => $map->custom_map_id,
|
||||
'name' => $map->name,
|
||||
'width' => $map->width,
|
||||
'height' => $map->height,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all available node images with a label.
|
||||
*/
|
||||
private function listNodeImages(): array
|
||||
{
|
||||
$images = [];
|
||||
$image_translations = __('map.custom.edit.node.image_options');
|
||||
|
||||
foreach (Storage::disk('base')->files('html/images/custommap/icons') as $image) {
|
||||
if (in_array(strtolower(pathinfo($image, PATHINFO_EXTENSION)), ['svg', 'png', 'jpg', 'gif'])) {
|
||||
$file = pathinfo($image, PATHINFO_BASENAME);
|
||||
$filename = pathinfo($image, PATHINFO_FILENAME);
|
||||
|
||||
$images[$file] = $image_translations[$filename] ?? ucwords(str_replace(['-', '_'], [' - ', ' '], $filename));
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
}
|
356
app/Http/Controllers/Maps/CustomMapDataController.php
Normal file
356
app/Http/Controllers/Maps/CustomMapDataController.php
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
/**
|
||||
* CustomMapController.php
|
||||
*
|
||||
* Controller for custom maps
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @link https://www.librenms.org
|
||||
*
|
||||
* @copyright 2023 Steven Wilton
|
||||
* @author Steven Wilton <swilton@fluentit.com.au>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Maps;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CustomMap;
|
||||
use App\Models\CustomMapEdge;
|
||||
use App\Models\CustomMapNode;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Util\Url;
|
||||
|
||||
class CustomMapDataController extends Controller
|
||||
{
|
||||
public function get(Request $request, CustomMap $map): JsonResponse
|
||||
{
|
||||
$this->authorize('view', $map);
|
||||
|
||||
$edges = [];
|
||||
$nodes = [];
|
||||
|
||||
foreach ($map->edges as $edge) {
|
||||
$edgeid = $edge->custom_map_edge_id;
|
||||
$edges[$edgeid] = [
|
||||
'custom_map_edge_id' => $edge->custom_map_edge_id,
|
||||
'custom_map_node1_id' => $edge->custom_map_node1_id,
|
||||
'custom_map_node2_id' => $edge->custom_map_node2_id,
|
||||
'port_id' => $edge->port_id,
|
||||
'reverse' => $edge->reverse,
|
||||
'style' => $edge->style,
|
||||
'showpct' => $edge->showpct,
|
||||
'text_face' => $edge->text_face,
|
||||
'text_size' => $edge->text_size,
|
||||
'text_colour' => $edge->text_colour,
|
||||
'mid_x' => $edge->mid_x,
|
||||
'mid_y' => $edge->mid_y,
|
||||
];
|
||||
if ($edge->port) {
|
||||
$edges[$edgeid]['device_id'] = $edge->port->device_id;
|
||||
$edges[$edgeid]['port_name'] = $edge->port->device->displayName() . ' - ' . $edge->port->getLabel();
|
||||
$edges[$edgeid]['port_info'] = Url::portLink($edge->port, null, null, false, true);
|
||||
|
||||
// Work out speed to and from
|
||||
$speedto = 0;
|
||||
$speedfrom = 0;
|
||||
$rateto = 0;
|
||||
$ratefrom = 0;
|
||||
|
||||
// Try to interpret the SNMP speeds
|
||||
if ($edge->port->port_descr_speed) {
|
||||
$speed_parts = explode('/', $edge->port->port_descr_speed, 2);
|
||||
|
||||
if (count($speed_parts) == 1) {
|
||||
$speedto = $this->snmpSpeed($speed_parts[0]);
|
||||
$speedfrom = $speedto;
|
||||
} elseif ($edge->reverse) {
|
||||
$speedto = $this->snmpSpeed($speed_parts[1]);
|
||||
$speedfrom = $this->snmpSpeed($speed_parts[0]);
|
||||
} else {
|
||||
$speedto = $this->snmpSpeed($speed_parts[0]);
|
||||
$speedfrom = $this->snmpSpeed($speed_parts[1]);
|
||||
}
|
||||
if ($speedto == 0 || $speedfrom == 0) {
|
||||
$speedto = 0;
|
||||
$speedfrom = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If we did not get a speed from the snmp desc, use the deteced speed
|
||||
if ($speedto == 0 && $edge->port->ifSpeed) {
|
||||
$speedto = $edge->port->ifSpeed;
|
||||
$speedfrom = $edge->port->ifSpeed;
|
||||
}
|
||||
|
||||
// Get the to/from rates
|
||||
if ($edge->reverse) {
|
||||
$ratefrom = $edge->port->ifInOctets_rate * 8;
|
||||
$rateto = $edge->port->ifOutOctets_rate * 8;
|
||||
} else {
|
||||
$ratefrom = $edge->port->ifOutOctets_rate * 8;
|
||||
$rateto = $edge->port->ifInOctets_rate * 8;
|
||||
}
|
||||
|
||||
if ($speedto == 0) {
|
||||
$edges[$edgeid]['port_topct'] = -1.0;
|
||||
$edges[$edgeid]['port_frompct'] = -1.0;
|
||||
} else {
|
||||
$edges[$edgeid]['port_topct'] = round($rateto / $speedto * 100.0, 2);
|
||||
$edges[$edgeid]['port_frompct'] = round($ratefrom / $speedfrom * 100.0, 2);
|
||||
}
|
||||
if ($edge->port->ifOperStatus != 'up') {
|
||||
// If the port is not online, show the same as speed unknown
|
||||
$edges[$edgeid]['colour_to'] = $this->speedColour(-1.0);
|
||||
$edges[$edgeid]['colour_from'] = $this->speedColour(-1.0);
|
||||
} else {
|
||||
$edges[$edgeid]['colour_to'] = $this->speedColour($edges[$edgeid]['port_topct']);
|
||||
$edges[$edgeid]['colour_from'] = $this->speedColour($edges[$edgeid]['port_frompct']);
|
||||
}
|
||||
$edges[$edgeid]['width_to'] = $this->speedWidth($speedto);
|
||||
$edges[$edgeid]['width_from'] = $this->speedWidth($speedfrom);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($map->nodes as $node) {
|
||||
$nodeid = $node->custom_map_node_id;
|
||||
$nodes[$nodeid] = [
|
||||
'custom_map_node_id' => $node->custom_map_node_id,
|
||||
'device_id' => $node->device_id,
|
||||
'linked_map_id' => $node->linked_custom_map_id,
|
||||
'linked_map_name' => $node->linked_map ? $node->linked_map->name : null,
|
||||
'label' => $node->label,
|
||||
'style' => $node->style,
|
||||
'icon' => $node->icon,
|
||||
'image' => $node->image,
|
||||
'size' => $node->size,
|
||||
'border_width' => $node->border_width,
|
||||
'text_face' => $node->text_face,
|
||||
'text_size' => $node->text_size,
|
||||
'text_colour' => $node->text_colour,
|
||||
'colour_bg' => $node->colour_bg,
|
||||
'colour_bdr' => $node->colour_bdr,
|
||||
'colour_bg_view' => $node->colour_bg,
|
||||
'colour_bdr_view' => $node->colour_bdr,
|
||||
'x_pos' => $node->x_pos,
|
||||
'y_pos' => $node->y_pos,
|
||||
];
|
||||
if ($node->device) {
|
||||
$nodes[$nodeid]['device_name'] = $node->device->hostname . '(' . $node->device->sysName . ')';
|
||||
$nodes[$nodeid]['device_image'] = $node->device->icon;
|
||||
$nodes[$nodeid]['device_info'] = Url::deviceLink($node->device, null, [], 0, 0, 0, 0);
|
||||
|
||||
if ($node->device->disabled) {
|
||||
$device_style = $this->nodeDisabledStyle();
|
||||
} elseif (! $node->device->status) {
|
||||
$device_style = $this->nodeDownStyle();
|
||||
} else {
|
||||
$device_style = $this->nodeUpStyle();
|
||||
}
|
||||
|
||||
if ($device_style['background']) {
|
||||
$nodes[$nodeid]['colour_bg_view'] = $device_style['background'];
|
||||
}
|
||||
|
||||
if ($device_style['border']) {
|
||||
$nodes[$nodeid]['colour_bdr_view'] = $device_style['border'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['nodes' => $nodes, 'edges' => $edges]);
|
||||
}
|
||||
|
||||
public function save(Request $request, CustomMap $map): JsonResponse
|
||||
{
|
||||
$this->authorize('update', $map);
|
||||
|
||||
$data = $this->validate($request, [
|
||||
'newnodeconf' => 'array',
|
||||
'newedgeconf' => 'array',
|
||||
'nodes' => 'array',
|
||||
'edges' => 'array',
|
||||
]);
|
||||
|
||||
$map->load(['nodes', 'edges']);
|
||||
|
||||
DB::transaction(function () use ($map, $data) {
|
||||
$dbnodes = $map->nodes->keyBy('custom_map_node_id')->all();
|
||||
$dbedges = $map->edges->keyBy('custom_map_edge_id')->all();
|
||||
|
||||
$nodesProcessed = [];
|
||||
$edgesProcessed = [];
|
||||
|
||||
$newNodes = [];
|
||||
|
||||
$map->newnodeconfig = $data['newnodeconf'];
|
||||
$map->newedgeconfig = $data['newedgeconf'];
|
||||
$map->save();
|
||||
|
||||
foreach ($data['nodes'] as $nodeid => $node) {
|
||||
if (strpos($nodeid, 'new') === 0) {
|
||||
$dbnode = new CustomMapNode;
|
||||
$dbnode->map()->associate($map);
|
||||
} else {
|
||||
$dbnode = $dbnodes[$nodeid];
|
||||
if (! $dbnode) {
|
||||
Log::error('Could not find existing node for node id ' . $nodeid);
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
$dbnode->device_id = is_numeric($node['title']) ? $node['title'] : null;
|
||||
$dbnode->linked_custom_map_id = str_starts_with($node['title'], 'map:') ? (int) str_replace('map:', '', $node['title']) : null;
|
||||
$dbnode->label = $node['label'];
|
||||
$dbnode->style = $node['shape'];
|
||||
$dbnode->icon = $node['icon'];
|
||||
$dbnode->image = $node['image']['unselected'] ?? '';
|
||||
$dbnode->size = $node['size'];
|
||||
$dbnode->text_face = $node['font']['face'];
|
||||
$dbnode->text_size = $node['font']['size'];
|
||||
$dbnode->text_colour = $node['font']['color'];
|
||||
$dbnode->colour_bg = $node['color']['background'] ?? null;
|
||||
$dbnode->colour_bdr = $node['color']['border'] ?? null;
|
||||
$dbnode->border_width = $node['borderWidth'];
|
||||
$dbnode->x_pos = intval($node['x']);
|
||||
$dbnode->y_pos = intval($node['y']);
|
||||
|
||||
$dbnode->save();
|
||||
$nodesProcessed[$dbnode->custom_map_node_id] = true;
|
||||
$newNodes[$nodeid] = $dbnode;
|
||||
}
|
||||
foreach ($data['edges'] as $edgeid => $edge) {
|
||||
if (strpos($edgeid, 'new') === 0) {
|
||||
$dbedge = new CustomMapEdge;
|
||||
$dbedge->map()->associate($map);
|
||||
} else {
|
||||
$dbedge = $dbedges[$edgeid];
|
||||
if (! $dbedge) {
|
||||
Log::error('Could not find existing edge for edge id ' . $edgeid);
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
$dbedge->custom_map_node1_id = strpos($edge['from'], 'new') == 0 ? $newNodes[$edge['from']]->custom_map_node_id : $edge['from'];
|
||||
$dbedge->custom_map_node2_id = strpos($edge['to'], 'new') == 0 ? $newNodes[$edge['to']]->custom_map_node_id : $edge['to'];
|
||||
$dbedge->port_id = $edge['port_id'] ? $edge['port_id'] : null;
|
||||
$dbedge->reverse = filter_var($edge['reverse'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
$dbedge->showpct = filter_var($edge['showpct'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
$dbedge->style = $edge['style'];
|
||||
$dbedge->text_face = $edge['text_face'];
|
||||
$dbedge->text_size = $edge['text_size'];
|
||||
$dbedge->text_colour = $edge['text_colour'];
|
||||
$dbedge->mid_x = intval($edge['mid_x']);
|
||||
$dbedge->mid_y = intval($edge['mid_y']);
|
||||
|
||||
$dbedge->save();
|
||||
$edgesProcessed[$dbedge->custom_map_edge_id] = true;
|
||||
}
|
||||
foreach ($map->edges as $edge) {
|
||||
if (! array_key_exists($edge->custom_map_edge_id, $edgesProcessed)) {
|
||||
$edge->delete();
|
||||
}
|
||||
}
|
||||
foreach ($map->nodes as $node) {
|
||||
if (! array_key_exists($node->custom_map_node_id, $nodesProcessed)) {
|
||||
$node->delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return response()->json(['id' => $map->custom_map_id]);
|
||||
}
|
||||
|
||||
private function snmpSpeed(string $speeds): int
|
||||
{
|
||||
// Only succeed if the string startes with a number optionally followed by a unit
|
||||
if (preg_match('/^(\d+)([kMGTP])?/', $speeds, $matches)) {
|
||||
$speed = (int) $matches[1];
|
||||
if (count($matches) < 3) {
|
||||
return $speed;
|
||||
} elseif ($matches[2] == 'k') {
|
||||
$speed *= 1000;
|
||||
} elseif ($matches[2] == 'M') {
|
||||
$speed *= 1000000;
|
||||
} elseif ($matches[2] == 'G') {
|
||||
$speed *= 1000000000;
|
||||
} elseif ($matches[2] == 'T') {
|
||||
$speed *= 1000000000000;
|
||||
} elseif ($matches[2] == 'P') {
|
||||
$speed *= 1000000000000000;
|
||||
}
|
||||
|
||||
return $speed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function speedColour(float $pct): string
|
||||
{
|
||||
// For the maths below, the 5.1 is worked out as 255 / 50
|
||||
// (255 being the max colour value and 50 is the max of the $pct calcluation)
|
||||
if ($pct < 0) {
|
||||
// Black if we can't determine the percentage (link down or speed 0)
|
||||
return '#000000';
|
||||
} elseif ($pct < 50) {
|
||||
// 100% green and slowly increase the red until we get to yellow
|
||||
return sprintf('#%02XFF00', (int) (5.1 * $pct));
|
||||
} elseif ($pct < 100) {
|
||||
// 100% red and slowly remove green to go from yellow to red
|
||||
return sprintf('#FF%02X00', (int) (5.1 * (100.0 - $pct)));
|
||||
} elseif ($pct < 150) {
|
||||
// 100% red and slowly increase blue to go purple
|
||||
return sprintf('#FF00%02X', (int) (5.1 * ($pct - 100.0)));
|
||||
}
|
||||
|
||||
// Default to purple for links over 150%
|
||||
return '#FF00FF';
|
||||
}
|
||||
|
||||
private function speedWidth(int $speed): float
|
||||
{
|
||||
if ($speed < 1000000) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return (strlen((string) $speed) - 5) / 2.0;
|
||||
}
|
||||
|
||||
protected function nodeDisabledStyle(): array
|
||||
{
|
||||
return [
|
||||
'border' => Config::get('network_map_legend.di.border'),
|
||||
'background' => Config::get('network_map_legend.di.node'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function nodeDownStyle(): array
|
||||
{
|
||||
return [
|
||||
'border' => Config::get('network_map_legend.dn.border'),
|
||||
'background' => Config::get('network_map_legend.dn.node'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function nodeUpStyle(): array
|
||||
{
|
||||
return [
|
||||
'border' => null,
|
||||
'background' => null,
|
||||
];
|
||||
}
|
||||
}
|
@@ -53,7 +53,7 @@ class DeviceController extends SelectController
|
||||
// list devices the user does not have access to
|
||||
if ($request->get('access') == 'inverted' && $user_id && $request->user()->isAdmin()) {
|
||||
return Device::query()
|
||||
->select('device_id', 'hostname', 'sysName', 'display')
|
||||
->select('device_id', 'hostname', 'sysName', 'display', 'icon')
|
||||
->whereNotIn('device_id', function ($query) use ($user_id) {
|
||||
$query->select('device_id')
|
||||
->from('devices_perms')
|
||||
@@ -63,7 +63,7 @@ class DeviceController extends SelectController
|
||||
}
|
||||
|
||||
return Device::hasAccess($request->user())
|
||||
->select('device_id', 'hostname', 'sysName', 'display')
|
||||
->select('device_id', 'hostname', 'sysName', 'display', 'icon')
|
||||
->orderBy('hostname');
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ class DeviceController extends SelectController
|
||||
return [
|
||||
'id' => $device->{$this->id},
|
||||
'text' => $device->displayName(),
|
||||
'icon' => $device->icon,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@ class PortController extends SelectController
|
||||
{
|
||||
return [
|
||||
'device' => 'nullable|int',
|
||||
'devices' => 'nullable|array',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -79,6 +80,10 @@ class PortController extends SelectController
|
||||
$query->where('ports.device_id', $device_id);
|
||||
}
|
||||
|
||||
if ($device_ids = $request->get('devices')) {
|
||||
$query->whereIn('ports.device_id', $device_ids);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -91,6 +96,7 @@ class PortController extends SelectController
|
||||
return [
|
||||
'id' => $port->port_id,
|
||||
'text' => $label . ' - ' . $port->device->shortDisplayName() . $description,
|
||||
'device_id' => $port->device_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
54
app/Http/Requests/CustomMapSettingsRequest.php
Normal file
54
app/Http/Requests/CustomMapSettingsRequest.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CustomMapSettingsRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()->isAdmin(); // TODO permissions
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string',
|
||||
'node_align' => 'integer',
|
||||
'width_type' => 'in:px,%',
|
||||
'width' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
if (! preg_match('/^(\d+)(px|%)$/', $value, $matches)) {
|
||||
$fail(__('map.custom.edit.validate.width_format'));
|
||||
} elseif ($matches[2] == 'px' && $matches[1] < 200) {
|
||||
$fail(__('map.custom.edit.validate.width_pixels'));
|
||||
} elseif ($matches[2] == '%' && ($matches[1] < 10 || $matches[1] > 100)) {
|
||||
$fail(__('map.custom.edit.validate.width_percent'));
|
||||
}
|
||||
},
|
||||
],
|
||||
'height_type' => 'in:px,%',
|
||||
'height' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
if (! preg_match('/^(\d+)(px|%)$/', $value, $matches)) {
|
||||
$fail(__('map.custom.edit.validate.height_format'));
|
||||
} elseif ($matches[2] == 'px' && $matches[1] < 200) {
|
||||
$fail(__('map.custom.edit.validate.height_pixels'));
|
||||
} elseif ($matches[2] == '%' && ($matches[1] < 10 || $matches[1] > 100)) {
|
||||
$fail(__('map.custom.edit.validate.height_percent'));
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ namespace App\Http\ViewComposers;
|
||||
|
||||
use App\Models\AlertRule;
|
||||
use App\Models\BgpPeer;
|
||||
use App\Models\CustomMap;
|
||||
use App\Models\Dashboard;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceGroup;
|
||||
@@ -76,6 +77,9 @@ class MenuComposer
|
||||
//Dashboards
|
||||
$vars['dashboards'] = Dashboard::select('dashboard_id', 'dashboard_name')->allAvailable($user)->orderBy('dashboard_name')->get();
|
||||
|
||||
//Custom Maps
|
||||
$vars['custommaps'] = CustomMap::select('custom_map_id', 'name')->hasAccess($user)->orderBy('name')->get();
|
||||
|
||||
// Device menu
|
||||
$vars['device_groups'] = DeviceGroup::hasAccess($user)->orderBy('name')->get(['device_groups.id', 'name', 'desc']);
|
||||
$vars['package_count'] = Package::hasAccess($user)->count();
|
||||
|
Reference in New Issue
Block a user