Additional custom map features (#15806)
* Added options on edges for a label and to show graphs as bps as well as percentages I think that vis.js needs to be updated to allow both bps and percentages at the same time. * Add database migration * Try to avoid putting multiple mid points in the same position * Added a URL parameter for screenshot mode, where node labels are blanked out Also fixed up the node labels in the editor * Added legend to the editor as well as database options for reversing arrows and adjusting the edge separation All features have been implemented in the editor, but need to be implemented in the viewer * Fix missing defaults on the edit map list page Added arrow reverse code to the viewer Added legend code to the viewer Added code to the editor to correclty handle moving the legend * Formatting fixes and DB schema update * Remove view from database schema
This commit is contained in:
parent
d6ce29c052
commit
cb09ae0d54
|
@ -30,6 +30,7 @@ use App\Http\Requests\CustomMapSettingsRequest;
|
|||
use App\Models\CustomMap;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use LibreNMS\Config;
|
||||
|
@ -47,6 +48,16 @@ class CustomMapController extends Controller
|
|||
'maps' => CustomMap::orderBy('name')->get(['custom_map_id', 'name']),
|
||||
'name' => 'New Map',
|
||||
'node_align' => 10,
|
||||
'edge_separation' => 10,
|
||||
'reverse_arrows' => 0,
|
||||
'legend' => [
|
||||
'x' => -1,
|
||||
'y' => -1,
|
||||
'steps' => 7,
|
||||
'hide_invalid' => 0,
|
||||
'hide_overspeed' => 0,
|
||||
'font_size' => 14,
|
||||
],
|
||||
'background' => null,
|
||||
'map_conf' => [
|
||||
'height' => '800px',
|
||||
|
@ -75,8 +86,14 @@ class CustomMapController extends Controller
|
|||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
public function show(CustomMap $map): View
|
||||
public function show(Request $request, CustomMap $map): View
|
||||
{
|
||||
$request->validate([
|
||||
'screenshot' => 'nullable|in:yes',
|
||||
]);
|
||||
|
||||
$screenshot = $request->input('screenshot') === 'yes' ? 1 : 0;
|
||||
|
||||
$map_conf = $map->options;
|
||||
$map_conf['width'] = $map->width;
|
||||
$map_conf['height'] = $map->height;
|
||||
|
@ -84,6 +101,8 @@ class CustomMapController extends Controller
|
|||
'edit' => false,
|
||||
'map_id' => $map->custom_map_id,
|
||||
'name' => $map->name,
|
||||
'reverse_arrows' => $map->reverse_arrows,
|
||||
'legend' => $this->legendConfig($map),
|
||||
'background' => (bool) $map->background_suffix,
|
||||
'bgversion' => $map->background_version,
|
||||
'page_refresh' => Config::get('page_refresh', 300),
|
||||
|
@ -93,6 +112,7 @@ class CustomMapController extends Controller
|
|||
'newnode_conf' => $map->newnodeconfig,
|
||||
'vmargin' => 20,
|
||||
'hmargin' => 20,
|
||||
'screenshot' => $screenshot,
|
||||
];
|
||||
|
||||
return view('map.custom-view', $data);
|
||||
|
@ -104,6 +124,9 @@ class CustomMapController extends Controller
|
|||
'map_id' => $map->custom_map_id,
|
||||
'name' => $map->name,
|
||||
'node_align' => $map->node_align,
|
||||
'edge_separation' => $map->edge_separation,
|
||||
'reverse_arrows' => $map->reverse_arrows,
|
||||
'legend' => $this->legendConfig($map),
|
||||
'newedge_conf' => $map->newedgeconfig,
|
||||
'newnode_conf' => $map->newnodeconfig,
|
||||
'map_conf' => $map->options,
|
||||
|
@ -142,6 +165,8 @@ class CustomMapController extends Controller
|
|||
'name' => $map->name,
|
||||
'width' => $map->width,
|
||||
'height' => $map->height,
|
||||
'reverse_arrows' => $map->reverse_arrows,
|
||||
'edge_separation' => $map->edge_separation,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -164,4 +189,21 @@ class CustomMapController extends Controller
|
|||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the legend config
|
||||
*/
|
||||
private function legendConfig(CustomMap $map): array
|
||||
{
|
||||
$legend = [
|
||||
'x' => $map->legend_x,
|
||||
'y' => $map->legend_y,
|
||||
'steps' => $map->legend_steps,
|
||||
'hide_invalid' => $map->legend_hide_invalid,
|
||||
'hide_overspeed' => $map->legend_hide_overspeed,
|
||||
'font_size' => $map->legend_font_size,
|
||||
];
|
||||
|
||||
return $legend;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ class CustomMapDataController extends Controller
|
|||
'reverse' => $edge->reverse,
|
||||
'style' => $edge->style,
|
||||
'showpct' => $edge->showpct,
|
||||
'showbps' => $edge->showbps,
|
||||
'label' => $edge->label,
|
||||
'text_face' => $edge->text_face,
|
||||
'text_size' => $edge->text_size,
|
||||
'text_colour' => $edge->text_colour,
|
||||
|
@ -122,6 +124,8 @@ class CustomMapDataController extends Controller
|
|||
$edges[$edgeid]['colour_to'] = $this->speedColour($edges[$edgeid]['port_topct']);
|
||||
$edges[$edgeid]['colour_from'] = $this->speedColour($edges[$edgeid]['port_frompct']);
|
||||
}
|
||||
$edges[$edgeid]['port_tobps'] = $this->rateString($rateto);
|
||||
$edges[$edgeid]['port_frombps'] = $this->rateString($ratefrom);
|
||||
$edges[$edgeid]['width_to'] = $this->speedWidth($speedto);
|
||||
$edges[$edgeid]['width_from'] = $this->speedWidth($speedfrom);
|
||||
}
|
||||
|
@ -185,11 +189,20 @@ class CustomMapDataController extends Controller
|
|||
'newedgeconf' => 'array',
|
||||
'nodes' => 'array',
|
||||
'edges' => 'array',
|
||||
'legend_x' => 'integer',
|
||||
'legend_y' => 'integer',
|
||||
]);
|
||||
|
||||
$map->load(['nodes', 'edges']);
|
||||
|
||||
DB::transaction(function () use ($map, $data) {
|
||||
if ($map->legend_x != $data['legend_x'] || $map->legend_y != $data['legend_y']) {
|
||||
$map->legend_x = $data['legend_x'];
|
||||
$map->legend_y = $data['legend_y'];
|
||||
|
||||
$map->save();
|
||||
}
|
||||
|
||||
$dbnodes = $map->nodes->keyBy('custom_map_node_id')->all();
|
||||
$dbedges = $map->edges->keyBy('custom_map_edge_id')->all();
|
||||
|
||||
|
@ -249,6 +262,8 @@ class CustomMapDataController extends Controller
|
|||
$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->showbps = filter_var($edge['showbps'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
$dbedge->label = $edge['label'] ? $edge['label'] : '';
|
||||
$dbedge->style = $edge['style'];
|
||||
$dbedge->text_face = $edge['text_face'];
|
||||
$dbedge->text_size = $edge['text_size'];
|
||||
|
@ -274,6 +289,23 @@ class CustomMapDataController extends Controller
|
|||
return response()->json(['id' => $map->custom_map_id]);
|
||||
}
|
||||
|
||||
private function rateString(int $rate): string
|
||||
{
|
||||
if ($rate < 1000) {
|
||||
return $rate . ' bps';
|
||||
} elseif ($rate < 1000000) {
|
||||
return intval($rate / 1000) . ' kbps';
|
||||
} elseif ($rate < 1000000000) {
|
||||
return intval($rate / 1000000) . ' Mbps';
|
||||
} elseif ($rate < 1000000000000) {
|
||||
return intval($rate / 1000000000) . ' Gbps';
|
||||
} elseif ($rate < 1000000000000000) {
|
||||
return intval($rate / 1000000000000) . ' Tbps';
|
||||
} else {
|
||||
return intval($rate / 1000000000000000) . ' Pbps';
|
||||
}
|
||||
}
|
||||
|
||||
private function snmpSpeed(string $speeds): int
|
||||
{
|
||||
// Only succeed if the string startes with a number optionally followed by a unit
|
||||
|
|
|
@ -25,6 +25,8 @@ class CustomMapSettingsRequest extends FormRequest
|
|||
return [
|
||||
'name' => 'required|string',
|
||||
'node_align' => 'integer',
|
||||
'reverse_arrows' => 'boolean',
|
||||
'edge_separation' => 'integer',
|
||||
'width_type' => 'in:px,%',
|
||||
'width' => [
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
|
@ -49,6 +51,12 @@ class CustomMapSettingsRequest extends FormRequest
|
|||
}
|
||||
},
|
||||
],
|
||||
'legend_x' => 'integer',
|
||||
'legend_y' => 'integer',
|
||||
'legend_steps' => 'integer',
|
||||
'legend_font_size' => 'integer',
|
||||
'legend_hide_invalid' => 'boolean',
|
||||
'legend_hide_overspeed' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,14 @@ class CustomMap extends BaseModel
|
|||
'width',
|
||||
'height',
|
||||
'node_align',
|
||||
'reverse_arrows',
|
||||
'edge_separation',
|
||||
'legend_x',
|
||||
'legend_y',
|
||||
'legend_steps',
|
||||
'legend_font_size',
|
||||
'legend_hide_invalid',
|
||||
'legend_hide_overspeed',
|
||||
'background_suffix',
|
||||
'background_version',
|
||||
];
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('custom_map_edges', function (Blueprint $table) {
|
||||
$table->boolean('showbps')->default(0)->after('showpct');
|
||||
$table->string('label', 255)->default('')->after('showbps');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('custom_map_edges', function (Blueprint $table) {
|
||||
$table->dropColumn(['showbps', 'label']);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('custom_maps', function (Blueprint $table) {
|
||||
$table->boolean('reverse_arrows')->default(0)->after('node_align');
|
||||
$table->smallInteger('edge_separation')->default(10)->after('reverse_arrows');
|
||||
$table->integer('legend_x')->default(-1)->after('edge_separation');
|
||||
$table->integer('legend_y')->default(-1)->after('legend_x');
|
||||
$table->smallInteger('legend_steps')->default(7)->after('legend_y');
|
||||
$table->smallInteger('legend_font_size')->default(14)->after('legend_steps');
|
||||
$table->boolean('legend_hide_invalid')->default(0)->after('legend_font_size');
|
||||
$table->boolean('legend_hide_overspeed')->default(0)->after('legend_hide_invalid');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('custom_maps', function (Blueprint $table) {
|
||||
$table->dropColumn(['reverse_arrows', 'edge_separation', 'legend_x', 'legend_y', 'legend_steps', 'legend_steps', 'legend_hide_invalid', 'legend_hide_overspeed']);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -25,6 +25,19 @@ Some key points about the viewer are:
|
|||
- Red at 100% utilisation, with a gradual change to
|
||||
- Purple at 150% utilisation and above
|
||||
|
||||
### Viewer URL options
|
||||
|
||||
You can manually add the following parameters to a URL to alter the display of a
|
||||
custom map.
|
||||
|
||||
The following URL options are available:
|
||||
|
||||
- bare=yes : Removes the control bar from the top of the page.
|
||||
- screenshot=yes : Removes all labels from the nodes and links
|
||||
|
||||
e.g. If you want bare and screenshot enabled, https://_nmsserver_/maps/custom/2
|
||||
becomes https://_nmsserver_/maps/custom/2?bare=yes&screenshot=yes
|
||||
|
||||
## Editor
|
||||
|
||||
To access the custom map editor, a user must be an admin. The editor
|
||||
|
|
|
@ -36,6 +36,9 @@ return [
|
|||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'alignment' => 'Node Alignment',
|
||||
'edgeseparation' => 'Link Separation',
|
||||
'reverse' => 'Reverse Arrows',
|
||||
'enable_legend' => 'Enable Legend',
|
||||
'saving' => 'Saving...',
|
||||
'save_errors' => 'Save failed due to the following errors:',
|
||||
'save_error' => 'Save failed. Server returned error response code: :code',
|
||||
|
@ -45,6 +48,12 @@ return [
|
|||
'edit' => 'Edit Map Settings',
|
||||
'rerender' => 'Re-Render Map',
|
||||
'save' => 'Save Map',
|
||||
'legend' => [
|
||||
'font_size' => 'Legend Text Size',
|
||||
'steps' => 'Legend Steps',
|
||||
'hideinvalid' => 'Hide Invalid',
|
||||
'hideoverspeed' => 'Hide 100%+',
|
||||
],
|
||||
],
|
||||
'node' => [
|
||||
'new' => 'New Node',
|
||||
|
@ -126,6 +135,8 @@ return [
|
|||
'cubicBezier' => 'Cubic Bezier',
|
||||
],
|
||||
'show_usage_percent' => 'Show percent usage',
|
||||
'show_usage_bps' => 'Show bps usage',
|
||||
'label' => 'Label',
|
||||
'recenter' => 'Recenter Line',
|
||||
],
|
||||
'validate' => [
|
||||
|
|
|
@ -522,6 +522,14 @@ custom_maps:
|
|||
- { Field: width, Type: varchar(10), 'Null': false, Extra: '' }
|
||||
- { Field: height, Type: varchar(10), 'Null': false, Extra: '' }
|
||||
- { Field: node_align, Type: smallint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: reverse_arrows, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: edge_separation, Type: smallint, 'Null': false, Extra: '', Default: '10' }
|
||||
- { Field: legend_x, Type: int, 'Null': false, Extra: '', Default: '-1' }
|
||||
- { Field: legend_y, Type: int, 'Null': false, Extra: '', Default: '-1' }
|
||||
- { Field: legend_steps, Type: smallint, 'Null': false, Extra: '', Default: '7' }
|
||||
- { Field: legend_font_size, Type: smallint, 'Null': false, Extra: '', Default: '14' }
|
||||
- { Field: legend_hide_invalid, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: legend_hide_overspeed, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: background_suffix, Type: varchar(10), 'Null': true, Extra: '' }
|
||||
- { Field: background_version, Type: 'int unsigned', 'Null': false, Extra: '' }
|
||||
- { Field: options, Type: longtext, 'Null': true, Extra: '' }
|
||||
|
@ -553,6 +561,8 @@ custom_map_edges:
|
|||
- { Field: reverse, Type: tinyint, 'Null': false, Extra: '' }
|
||||
- { Field: style, Type: varchar(50), 'Null': false, Extra: '' }
|
||||
- { Field: showpct, Type: tinyint, 'Null': false, Extra: '' }
|
||||
- { Field: showbps, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: label, Type: varchar(255), 'Null': false, Extra: '', Default: '' }
|
||||
- { Field: text_face, Type: varchar(50), 'Null': false, Extra: '' }
|
||||
- { Field: text_size, Type: int, 'Null': false, Extra: '' }
|
||||
- { Field: text_colour, Type: varchar(10), 'Null': false, Extra: '' }
|
||||
|
|
|
@ -62,12 +62,24 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="form-group row single-node">
|
||||
<label for="edgetextshow" class="col-sm-3 control-label">{{ __('map.custom.edit.edge.show_usage_percent') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="edgetextshow">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="edgebpsshow" class="col-sm-3 control-label">{{ __('map.custom.edit.edge.show_usage_bps') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="edgebpsshow">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="edgelabel" class="col-sm-3 control-label">{{ __('map.custom.edit.edge.label') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type=text id="edgelabel" class="form-control input-sm" value="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="edgetextface" class="col-sm-3 control-label">{{ __('map.custom.edit.text_font') }}</label>
|
||||
<div class="col-sm-9">
|
||||
|
|
|
@ -59,12 +59,136 @@
|
|||
var network;
|
||||
var network_height;
|
||||
var network_width;
|
||||
var node_align = {{$node_align}};
|
||||
var network_nodes = new vis.DataSet({queue: {delay: 100}});
|
||||
var network_edges = new vis.DataSet({queue: {delay: 100}});
|
||||
var edge_nodes_map = [];
|
||||
var node_device_map = {};
|
||||
var custom_image_base = "{{ $base_url }}images/custommap/icons/";
|
||||
|
||||
function edgeNodesRemove(nm_id, edgeid) {
|
||||
// Remove old item from map if it exists
|
||||
if (nm_id in edge_nodes_map) {
|
||||
const edge_idx = edge_nodes_map[nm_id].indexOf(edgeid);
|
||||
if (edge_idx >= 0) {
|
||||
edge_nodes_map[nm_id].splice(edge_idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function edgeNodesUpdate(edgeid, node1_id, node2_id, old_node1_id, old_node2_id) {
|
||||
var nm_id = node1_id < node2_id ? node1_id + '.' + node2_id : node2_id + '.' + node1_id;
|
||||
var old_nm_id = old_node1_id < old_node2_id ? old_node1_id + '.' + old_node2_id : old_node2_id + '.' + old_node1_id;
|
||||
|
||||
// No update is needed if the new and old are the same
|
||||
if (nm_id == old_nm_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (old_node1_id > 0 && old_node2_id > 0) {
|
||||
edgeNodesRemove(old_nm_id, edgeid);
|
||||
}
|
||||
|
||||
if (!(nm_id in edge_nodes_map)) {
|
||||
edge_nodes_map[nm_id] = [];
|
||||
}
|
||||
edge_nodes_map[nm_id].push(edgeid);
|
||||
}
|
||||
|
||||
function getMidOffests(pos1, pos2) {
|
||||
// First work out which pos is on the left-hand side
|
||||
var left_pos;
|
||||
var right_pos;
|
||||
if(pos1.x < pos2.x) {
|
||||
left_pos = pos1;
|
||||
right_pos = pos2;
|
||||
} else {
|
||||
left_pos = pos2;
|
||||
right_pos = pos1;
|
||||
}
|
||||
|
||||
// The X axis needs to move left/right based on whether the line rises or falls
|
||||
var x_diff = right_pos.y - left_pos.y;
|
||||
// The Y axis needs to move up always based on how far apart the left and right nodes are
|
||||
var y_diff = left_pos.x - right_pos.x;
|
||||
|
||||
// Calculate how far each mid point needs to move
|
||||
var tot_diff = Math.abs(x_diff) + Math.abs(y_diff);
|
||||
return {x: Math.round(edge_sep * (x_diff / tot_diff)), y: Math.round(edge_sep * (y_diff / tot_diff))};
|
||||
}
|
||||
|
||||
function getMidPos(edgeid, from_id, to_id) {
|
||||
var nm_id = from_id < to_id ? from_id + '.' + to_id : to_id + '.' + from_id;
|
||||
const node_links = nm_id in edge_nodes_map ? edge_nodes_map[nm_id] : [];
|
||||
|
||||
var node_offsets = [];
|
||||
node_links.forEach((link_edgeid) => {
|
||||
// Ignore the edge we are creating
|
||||
if (link_edgeid == edgeid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the offset in the hash
|
||||
let link_mid = network_nodes.get(link_edgeid + "_mid");
|
||||
let link_mid_offset = link_mid.x + '.' + link_mid.y;
|
||||
node_offsets[link_mid_offset] = true;
|
||||
});
|
||||
|
||||
var pos = network.getPositions([from_id, to_id]);
|
||||
|
||||
const offsets = getMidOffests(pos[from_id], pos[to_id]);
|
||||
|
||||
// Calculate the center point
|
||||
var mid_center = {x: (pos[from_id].x + pos[to_id].x) >> 1, y: (pos[from_id].y + pos[to_id].y) >> 1};
|
||||
var mids = [mid_center];
|
||||
for (let i = 1; i < node_links.length; i++) {
|
||||
let multiplier = ((i + 1) >> 1);
|
||||
let this_x = mid_center.x;
|
||||
let this_y = mid_center.y;
|
||||
if(i & 1) {
|
||||
// Odd numbers go the normal direction
|
||||
mids.push({x: mid_center.x + (multiplier * offsets.x), y: mid_center.y + (multiplier * offsets.y)});
|
||||
} else {
|
||||
// Even numbers go the opposite direction
|
||||
mids.push({x: mid_center.x - (multiplier * offsets.x), y: mid_center.y - (multiplier * offsets.y)});
|
||||
}
|
||||
}
|
||||
|
||||
// Find the first unused mid point from the center
|
||||
for (let i = 0; i < mids.length; i++) {
|
||||
let this_offset = mids[i].x + '.' + mids[i].y;
|
||||
if (!(this_offset in node_offsets)) {
|
||||
return {x: mids[i].x, y: mids[i].y};
|
||||
}
|
||||
}
|
||||
|
||||
// Default to mid point
|
||||
return {x: mid_center.x, y: mid_center.y};
|
||||
}
|
||||
|
||||
function fixNodePos(nodeid, node) {
|
||||
var move=false;
|
||||
if ( node_align && !nodeid.endsWith("_mid")) {
|
||||
node.x = Math.round(node.x / node_align) * node_align;
|
||||
node.y = Math.round(node.y / node_align) * node_align;
|
||||
move = true;
|
||||
}
|
||||
if ( node.x < {{ $hmargin }} ) {
|
||||
node.x = {{ $hmargin }};
|
||||
move = true;
|
||||
} else if ( node.x > network_width - {{ $hmargin }} ) {
|
||||
node.x = network_width - {{ $hmargin }};
|
||||
move = true;
|
||||
}
|
||||
if ( node.y < {{ $vmargin }} ) {
|
||||
node.y = {{ $vmargin }};
|
||||
move = true;
|
||||
} else if ( node.y > network_height - {{ $vmargin }} ) {
|
||||
node.y = network_height - {{ $vmargin }};
|
||||
move = true;
|
||||
}
|
||||
return move;
|
||||
}
|
||||
|
||||
function CreateNetwork() {
|
||||
// Flush the nodes and edges so they are rendered immediately
|
||||
network_nodes.flush();
|
||||
|
@ -120,12 +244,15 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var pos = network.getPositions([data.from, data.to]);
|
||||
var mid_x = (pos[data.from].x + pos[data.to].x) >> 1;
|
||||
var mid_y = (pos[data.from].y + pos[data.to].y) >> 1;
|
||||
|
||||
var edgeid = "new" + newcount++;
|
||||
|
||||
edgeNodesUpdate(edgeid, data.from, data.to, -1, -1);
|
||||
const mid_pos = getMidPos(edgeid, data.from, data.to);
|
||||
|
||||
// Default to using the center point
|
||||
var mid_x = mid_pos.x;
|
||||
var mid_y = mid_pos.y;
|
||||
|
||||
var mid = {id: edgeid + "_mid", shape: "dot", size: 3, x: mid_x, y: mid_y};
|
||||
|
||||
var edge1 = structuredClone(newedgeconf);
|
||||
|
@ -185,26 +312,21 @@
|
|||
// Make sure a node is not dragged outside the canvas
|
||||
nodepos = network.getPositions(data.nodes);
|
||||
$.each( nodepos, function( nodeid, node ) {
|
||||
move = false;
|
||||
if ( node_align && !nodeid.endsWith("_mid")) {
|
||||
node.x = Math.round(node.x / node_align) * node_align;
|
||||
node.y = Math.round(node.y / node_align) * node_align;
|
||||
move = true;
|
||||
}
|
||||
if ( node.x < {{ $hmargin }} ) {
|
||||
node.x = {{ $hmargin }};
|
||||
move = true;
|
||||
} else if ( node.x > network_width - {{ $hmargin }} ) {
|
||||
node.x = network_width - {{ $hmargin }};
|
||||
move = true;
|
||||
}
|
||||
if ( node.y < {{ $vmargin }} ) {
|
||||
node.y = {{ $vmargin }};
|
||||
move = true;
|
||||
} else if ( node.y > network_height - {{ $vmargin }} ) {
|
||||
node.y = network_height - {{ $vmargin }};
|
||||
move = true;
|
||||
if ( nodeid == "legend_header" ) {
|
||||
// If the legend header was moved, just redraw it
|
||||
fixNodePos(nodeid, node);
|
||||
legend.x = node.x;
|
||||
legend.y = node.y;
|
||||
|
||||
redrawLegend();
|
||||
return;
|
||||
} else if ( nodeid.startsWith("legend_") ) {
|
||||
// Get the original node and move it back
|
||||
node = network_nodes.get(nodeid);
|
||||
network_nodes.update(node);
|
||||
return;
|
||||
}
|
||||
let move = fixNodePos(nodeid, node);
|
||||
if ( move ) {
|
||||
network.moveNode(nodeid, node.x, node.y);
|
||||
}
|
||||
|
@ -242,12 +364,87 @@
|
|||
window.location.href = "{{ route('maps.custom.index') }}";
|
||||
}
|
||||
|
||||
function swapArrows(reverse) {
|
||||
var arrows;
|
||||
if (reverse) {
|
||||
arrows = {from: {enabled: true, scaleFactor: 0.6}, to: {enabled: false}};
|
||||
} else {
|
||||
arrows = {to: {enabled: true, scaleFactor: 0.6}, from: {enabled: false}};
|
||||
}
|
||||
network_edges.forEach((edge) => {
|
||||
edge.arrows = arrows;
|
||||
network_edges.update(edge);
|
||||
});
|
||||
network_edges.flush();
|
||||
}
|
||||
|
||||
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 editMapSuccess(data) {
|
||||
$("#title").text(data.name);
|
||||
$("#savemap-alert").attr("class", "col-sm-12");
|
||||
$("#savemap-alert").text("");
|
||||
network.setSize(data.width, data.height);
|
||||
|
||||
edge_sep = data.edge_separation;
|
||||
if(reverse_arrows != parseInt(data.reverse_arrows)) {
|
||||
swapArrows(Boolean(parseInt(data.reverse_arrows)));
|
||||
}
|
||||
reverse_arrows = parseInt(data.reverse_arrows);
|
||||
redrawLegend();
|
||||
|
||||
editMapCancel();
|
||||
}
|
||||
|
||||
|
@ -261,11 +458,13 @@
|
|||
var edges = {};
|
||||
|
||||
$.each(network_nodes.get(), function (node_idx, node) {
|
||||
if(node.id.endsWith("_mid")) {
|
||||
if(node.id.startsWith("legend_")) {
|
||||
return;
|
||||
} else if(node.id.endsWith("_mid")) {
|
||||
edgeid = node.id.split("_")[0];
|
||||
edge1 = network_edges.get(edgeid + "_from");
|
||||
edge2 = network_edges.get(edgeid + "_to");
|
||||
edges[edgeid] = {id: edgeid, text_colour: edge1.font.color, text_size: edge1.font.size, text_face: edge1.font.face, from: edge1.from, to: edge2.from, showpct: (edge1.label ? true : false), port_id: edge1.title, style: edge1.smooth.type, mid_x: node.x, mid_y: node.y, reverse: (edgeid in edge_port_map ? edge_port_map[edgeid].reverse : false)};
|
||||
edges[edgeid] = {id: edgeid, text_colour: edge1.font.color, text_size: edge1.font.size, text_face: edge1.font.face, from: edge1.from, to: edge2.from, showpct: (edge1.label != null && edge1.label.includes("xx%")), showbps: (edge1.label != null && edge1.label.includes("bps")), label: (node.label || ''), port_id: edge1.title, style: edge1.smooth.type, mid_x: node.x, mid_y: node.y, reverse: (edgeid in edge_port_map ? edge_port_map[edgeid].reverse : false)};
|
||||
} else {
|
||||
if(node.icon.code) {
|
||||
node.icon = node.icon.code.charCodeAt(0).toString(16);
|
||||
|
@ -290,6 +489,8 @@
|
|||
newedgeconf: newedgeconf,
|
||||
nodes: nodes,
|
||||
edges: edges,
|
||||
legend_x: legend.x,
|
||||
legend_y: legend.y,
|
||||
},
|
||||
dataType: 'json',
|
||||
type: 'POST'
|
||||
|
@ -646,7 +847,8 @@
|
|||
$("#edgetextface").val(newedgeconf.font.face);
|
||||
$("#edgetextsize").val(newedgeconf.font.size);
|
||||
$("#edgetextcolour").val(newedgeconf.font.color);
|
||||
$("#edgetextshow").bootstrapSwitch('state', Boolean(newedgeconf.label));
|
||||
$("#edgetextshow").bootstrapSwitch('state', (newedgeconf.label.includes('xx%') || newedgeconf.label.includes('true')));
|
||||
$("#edgebpsshow").bootstrapSwitch('state', (newedgeconf.label.includes('bps')));
|
||||
$('#edgecolourtextreset').attr('disabled', 'disabled');
|
||||
|
||||
$("#edge-saveButton").hide();
|
||||
|
@ -654,13 +856,30 @@
|
|||
$('#edgeModal').modal({backdrop: 'static', keyboard: false}, 'show');
|
||||
}
|
||||
|
||||
function edgeLabel(show_pct, show_bps, default_val) {
|
||||
var label = '';
|
||||
if(show_pct) {
|
||||
label = 'xx%';
|
||||
}
|
||||
if(show_bps) {
|
||||
if(Boolean(label.length)) {
|
||||
label += "\n";
|
||||
}
|
||||
label += 'xx bps';
|
||||
}
|
||||
if(Boolean(label.length)) {
|
||||
return label;
|
||||
}
|
||||
return default_val;
|
||||
}
|
||||
|
||||
function editEdgeDefaultsSave() {
|
||||
editEdgeHide();
|
||||
newedgeconf.smooth.type = $("#edgestyle").val();
|
||||
newedgeconf.font.face = $("#edgetextface").val();
|
||||
newedgeconf.font.size = $("#edgetextsize").val();
|
||||
newedgeconf.font.color = $("#edgetextcolour").val();
|
||||
newedgeconf.label = $("#edgetextshow").prop('checked');
|
||||
newedgeconf.label = edgeLabel($("#edgetextshow").prop('checked'), $("#edgebpsshow").prop('checked'), '');
|
||||
$("#map-saveDataButton").show();
|
||||
}
|
||||
|
||||
|
@ -690,7 +909,8 @@
|
|||
$("#edgetextface").val(edgedata.edge1.font.face);
|
||||
$("#edgetextsize").val(edgedata.edge1.font.size);
|
||||
$("#edgetextcolour").val(edgedata.edge1.font.color);
|
||||
$("#edgetextshow").bootstrapSwitch('state', Boolean(edgedata.edge1.label));
|
||||
$("#edgetextshow").bootstrapSwitch('state', (edgedata.edge1.label != null && edgedata.edge1.label.includes('xx%')));
|
||||
$("#edgebpsshow").bootstrapSwitch('state', (edgedata.edge1.label != null && edgedata.edge1.label.includes('bps')));
|
||||
|
||||
$("#edgeRecenterRow").show();
|
||||
$("#divEdgeFrom").show();
|
||||
|
@ -705,6 +925,8 @@
|
|||
function editEdgeSave(event) {
|
||||
edgedata = event.data.data;
|
||||
|
||||
edgeNodesUpdate(edgedata.id, $("#edgefrom").val(), $("#edgeto").val(), edgedata.edge1.from, edgedata.edge2.from);
|
||||
|
||||
editEdgeHide();
|
||||
edgedata.edge1.smooth.type = $("#edgestyle").val();
|
||||
edgedata.edge2.smooth.type = $("#edgestyle").val();
|
||||
|
@ -713,8 +935,9 @@
|
|||
edgedata.edge1.font.face = edgedata.edge2.font.face = $("#edgetextface").val();
|
||||
edgedata.edge1.font.size = edgedata.edge2.font.size = $("#edgetextsize").val();
|
||||
edgedata.edge1.font.color = edgedata.edge2.font.color = $("#edgetextcolour").val();
|
||||
edgedata.edge1.label = edgedata.edge2.label = $("#edgetextshow").prop('checked') ? "xx%" : null;
|
||||
edgedata.edge1.label = edgedata.edge2.label = edgeLabel($("#edgetextshow").prop('checked'), $("#edgebpsshow").prop('checked'), null);
|
||||
edgedata.edge1.title = edgedata.edge2.title = $("#port_id").val();
|
||||
edgedata.mid.label = ($("#edgelabel").val() || '');
|
||||
|
||||
if(edgedata.id) {
|
||||
if($("#port_id").val()) {
|
||||
|
@ -738,14 +961,14 @@
|
|||
network_edges.flush();
|
||||
} else {
|
||||
network_edges.update([edgedata.edge1, edgedata.edge2]);
|
||||
network_nodes.update([edgedata.mid]);
|
||||
|
||||
if($("#edgerecenter").is(":checked")) {
|
||||
var pos = network.getPositions([edgedata.edge1.from, edgedata.edge2.from]);
|
||||
var mid_x = (pos[edgedata.edge1.from].x + pos[edgedata.edge2.from].x) >> 1;
|
||||
var mid_y = (pos[edgedata.edge1.from].y + pos[edgedata.edge2.from].y) >> 1;
|
||||
const mid_pos = getMidPos(edgedata.id, edgedata.edge1.from, edgedata.edge2.from);
|
||||
|
||||
edgedata.mid.x = mid_x;
|
||||
edgedata.mid.y = mid_y;
|
||||
edgedata.mid.x = mid_pos.x;
|
||||
edgedata.mid.y = mid_pos.y;
|
||||
network_nodes.update([edgedata.mid]);
|
||||
$("#map-renderButton").show();
|
||||
}
|
||||
|
@ -794,6 +1017,10 @@
|
|||
}
|
||||
|
||||
function deleteEdge(edgeid) {
|
||||
const edge1 = network_edges.get(edgeid + "_from");
|
||||
const edge2 = network_edges.get(edgeid + "_to");
|
||||
var nm_id = edge1.from < edge2.from ? edge1.from + '.' + edge2.from : edge2.from + '.' + edge1.from;
|
||||
edgeNodesRemove(nm_id, edgeid);
|
||||
network_edges.remove(edgeid + "_to");
|
||||
network_edges.remove(edgeid + "_from");
|
||||
network_edges.flush();
|
||||
|
@ -803,6 +1030,7 @@
|
|||
}
|
||||
|
||||
function refreshMap() {
|
||||
edge_nodes_map = [];
|
||||
$.get( '{{ route('maps.custom.data', ['map' => $map_id]) }}')
|
||||
.done(function( data ) {
|
||||
// Add/update nodes
|
||||
|
@ -853,14 +1081,23 @@
|
|||
});
|
||||
|
||||
$.each( data.edges, function( edgeid, edge) {
|
||||
edgeNodesUpdate(edgeid, edge.custom_map_node1_id, edge.custom_map_node2_id, -1, -1);
|
||||
|
||||
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};
|
||||
var mid = {id: edgeid + "_mid", shape: "dot", size: 0, x: mid_x, y: mid_y, label: edge.label};
|
||||
mid.size = 3;
|
||||
|
||||
var edge1 = {id: edgeid + "_from", from: edge.custom_map_node1_id, to: edgeid + "_mid", arrows: {to: {enabled: true, scaleFactor: 0.6}}, 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: {to: {enabled: true, scaleFactor: 0.6}}, font: {face: edge.text_face, size: edge.text_size, color: edge.text_colour}, smooth: {type: edge.style}};
|
||||
var arrows;
|
||||
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") {
|
||||
|
@ -874,11 +1111,7 @@
|
|||
} else {
|
||||
edge1.title = edge2.title = '';
|
||||
}
|
||||
if(edge.showpct) {
|
||||
edge1.label = edge2.label = 'xx%';
|
||||
} else {
|
||||
edge1.label = edge2.label = '';
|
||||
}
|
||||
edge1.label = edge2.label = edgeLabel(edge.showpct, edge.showbps, '');
|
||||
if (network_nodes.get(mid.id)) {
|
||||
network_nodes.update(mid);
|
||||
network_edges.update(edge1);
|
||||
|
@ -905,6 +1138,9 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Add the legend back to the map
|
||||
redrawLegend();
|
||||
|
||||
// Flush in order to make sure nodes exist for edges to connect to
|
||||
network_nodes.flush();
|
||||
network_edges.flush();
|
||||
|
|
|
@ -33,6 +33,48 @@
|
|||
<input type="number" id="mapnodealign" name="mapnodealign" class="form-control input-sm" value="{{ $node_align ?? 10 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="mapedgesep" class="col-sm-3 control-label">{{ __('map.custom.edit.map.edgeseparation') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="number" id="mapedgesep" name="mapedgesep" class="form-control input-sm" value="{{ $edge_separation ?? 10 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="mapreversearrows" class="col-sm-3 control-label">{{ __('map.custom.edit.map.reverse') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="mapreversearrows">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="maplegend" class="col-sm-3 control-label">{{ __('map.custom.edit.map.enable_legend') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="maplegend" onChange="toggleMapLegend()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row maplegend">
|
||||
<label for="maplegendfontsize" class="col-sm-4 control-label">{{ __('map.custom.edit.map.legend.font_size') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input type=number id="maplegendfontsize" class="form-control input-sm" value={{ $legend['font_size'] }} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row maplegend">
|
||||
<label for="maplegendsteps" class="col-sm-4 control-label">{{ __('map.custom.edit.map.legend.steps') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input type=number id="maplegendsteps" class="form-control input-sm" value={{ $legend['steps'] }} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row maplegend">
|
||||
<label for="maplegendhideinvalid" class="col-sm-4 control-label">{{ __('map.custom.edit.map.legend.hideinvalid') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="maplegendhideinvalid">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row maplegend">
|
||||
<label for="maplegendhideoverspeed" class="col-sm-4 control-label">{{ __('map.custom.edit.map.legend.hideoverspeed') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="maplegendhideoverspeed">
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-sm-12" id="savemap-alert">
|
||||
|
@ -53,6 +95,11 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
var node_align = {{$node_align}};
|
||||
var edge_sep = {{$edge_separation}};
|
||||
var reverse_arrows = {{$reverse_arrows}};
|
||||
var legend = @json($legend);
|
||||
|
||||
function saveMapSettings() {
|
||||
$("#map-saveButton").attr('disabled','disabled');
|
||||
$("#savemap-alert").text('{{ __('map.custom.edit.map.saving') }}');
|
||||
|
@ -63,6 +110,34 @@
|
|||
var height = $("#mapheight").val();
|
||||
var node_align = $("#mapnodealign").val();
|
||||
|
||||
var mapwdith = 100;
|
||||
if (!isNaN(width)) {
|
||||
mapwidth = width;
|
||||
} else if (width.includes("px")) {
|
||||
mapwidth = width.replace("px", "");
|
||||
} else if (width.includes("%")) {
|
||||
mapwidth = window.innerWidth * width.replace("%", "") / 100;
|
||||
}
|
||||
|
||||
// Update the x and y coordinates
|
||||
if ($("#maplegend").prop('checked')) {
|
||||
if (legend.x < 0) {
|
||||
legend.x = mapwidth - 50;
|
||||
legend.y = 100;
|
||||
}
|
||||
} else {
|
||||
legend.x = -1;
|
||||
legend.y = -1;
|
||||
}
|
||||
|
||||
legend.font_size = parseInt($("#maplegendfontsize").val());
|
||||
legend.steps = parseInt($("#maplegendsteps").val());
|
||||
legend.hide_invalid = $("#maplegendhideinvalid").prop('checked') ? 1 : 0;
|
||||
legend.hide_overspeed = $("#maplegendhideoverspeed").prop('checked') ? 1 : 0;
|
||||
|
||||
var map_reverse_arrows = $("#mapreversearrows").prop('checked') ? 1 : 0;
|
||||
var map_edge_sep = $("#mapedgesep").val();
|
||||
|
||||
if(!isNaN(width)) {
|
||||
width = width + "px";
|
||||
}
|
||||
|
@ -84,7 +159,15 @@
|
|||
name: name,
|
||||
width: width,
|
||||
height: height,
|
||||
node_align: node_align
|
||||
node_align: node_align,
|
||||
reverse_arrows: map_reverse_arrows,
|
||||
edge_separation: map_edge_sep,
|
||||
legend_x: legend.x,
|
||||
legend_y: legend.y,
|
||||
legend_steps: legend.steps,
|
||||
legend_font_size: legend.font_size,
|
||||
legend_hide_invalid: legend.hide_invalid,
|
||||
legend_hide_overspeed: legend.hide_overspeed,
|
||||
},
|
||||
dataType: 'json',
|
||||
type: method
|
||||
|
@ -105,4 +188,22 @@
|
|||
$("#map-saveButton").removeAttr('disabled');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleMapLegend() {
|
||||
if($("#maplegend").prop('checked')) {
|
||||
$(".maplegend").show();
|
||||
} else {
|
||||
$(".maplegend").hide();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
if(legend.x < 0 || legend.y < 0) {
|
||||
$(".maplegend").hide();
|
||||
}
|
||||
$("#mapreversearrows").bootstrapSwitch('state', Boolean(reverse_arrows));
|
||||
$("#maplegend").bootstrapSwitch('state', (legend.x >= 0 && legend.y >= 0));
|
||||
$("#maplegendhideinvalid").bootstrapSwitch('state', Boolean(legend.hide_invalid));
|
||||
$("#maplegendhideoverspeed").bootstrapSwitch('state', Boolean(legend.hide_overspeed));
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
@section('scripts')
|
||||
<script type="text/javascript">
|
||||
var bgimage = {{ $background ? "true" : "false" }};
|
||||
var screenshot = {{ $screenshot ? "true" : "false" }};
|
||||
var reverse_arrows = {{$reverse_arrows}};
|
||||
var legend = @json($legend);
|
||||
var network;
|
||||
var network_height;
|
||||
var network_width;
|
||||
|
@ -36,6 +39,60 @@
|
|||
var node_link_map = {};
|
||||
var custom_image_base = "{{ $base_url }}images/custommap/icons/";
|
||||
|
||||
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();
|
||||
|
@ -95,7 +152,7 @@
|
|||
} else {
|
||||
node_cfg.title = null;
|
||||
}
|
||||
node_cfg.label = node.label;
|
||||
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;
|
||||
|
@ -134,10 +191,16 @@
|
|||
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};
|
||||
var mid = {id: edgeid + "_mid", shape: "dot", size: 0, x: mid_x, y: mid_y, label: screenshot ? '' : edge.label};
|
||||
|
||||
var edge1 = {id: edgeid + "_from", from: edge.custom_map_node1_id, to: edgeid + "_mid", arrows: {to: {enabled: true, scaleFactor: 0.6}}, 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: {to: {enabled: true, scaleFactor: 0.6}}, font: {face: edge.text_face, size: edge.text_size, color: edge.text_colour}, smooth: {type: edge.style}};
|
||||
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") {
|
||||
|
@ -146,15 +209,35 @@
|
|||
edge2.smooth.type = "curvedCW";
|
||||
}
|
||||
if(edge.port_id) {
|
||||
edge1.title = edge2.title = edge.port_info;
|
||||
if(edge.showpct) {
|
||||
edge1.label = edge.port_frompct + "%";
|
||||
edge2.label = edge.port_topct + "%";
|
||||
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;
|
||||
}
|
||||
edge1.color = {color: edge.colour_from};
|
||||
edge1.width = edge.width_from;
|
||||
edge2.color = {color: edge.colour_to};
|
||||
edge2.width = edge.width_to;
|
||||
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 {
|
||||
|
@ -186,6 +269,9 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Re-draw the legend
|
||||
redrawLegend();
|
||||
|
||||
// Flush in order to make sure nodes exist for edges to connect to
|
||||
network_nodes.flush();
|
||||
network_edges.flush();
|
||||
|
|
Loading…
Reference in New Issue