Custom Maps: geo map and color backgrounds (#16020)

* Custom Maps: geo map and color background
tabs blade component
geo-map blade component and related script enhancements

* Update css/js

* style fixes

* update db_schema.yaml

* fix db_schema hand edit

* ignore phpstan being wrong

* Handle null

* another possible null spot

* Use standard file cache for custom map background images

* Create map->image as jpeg so we can compress it

* whitespace fix

* Fix background cancel button when other type is selected than the saved type

* Save and restore layer

* Map must exist before creating static image

* Don't show set as image button for Google and Bing.
Bing gives an odd error, but Google won't work.
This commit is contained in:
Tony Murray
2024-05-13 08:12:59 -05:00
committed by GitHub
parent 1e3e60d59b
commit 0d246a6ffc
29 changed files with 2082 additions and 863 deletions

View File

@@ -11,15 +11,15 @@ class LocationController extends Controller
{
public function index()
{
$maps_api = Config::get('geoloc.api_key');
$maps_config = ['tile_url' => Config::get('leaflet.tile_url', '{s}.tile.openstreetmap.org')];
$data = [
'maps_api' => $maps_api,
'maps_engine' => $maps_api ? Config::get('geoloc.engine') : '',
'maps_config' => $maps_config,
'maps_config' => [
'engine' => Config::get('geoloc.engine'),
'api_key' => Config::get('geoloc.api_key'),
'tile_url' => Config::get('leaflet.tile_url', '{s}.tile.openstreetmap.org'),
],
'graph_template' => '',
];
$data['graph_template'] = '';
Config::set('enable_lazy_load', false);
$graph_array = [
'type' => 'location_bits',

View File

@@ -29,7 +29,7 @@ use App\Http\Controllers\Controller;
use App\Models\CustomMap;
use App\Models\CustomMapBackground;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Cache;
class CustomMapBackgroundController extends Controller
{
@@ -37,58 +37,89 @@ class CustomMapBackgroundController extends Controller
{
$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),
]);
if ($map->background_type !== 'image') {
abort(404);
}
abort(404);
// explicitly use file cache
try {
$imageContent = Cache::driver('file')
->remember($this->getCacheKey($map), new \DateInterval('P30D'), fn () => $map->background->background_image);
} catch (\ErrorException $e) {
// if cache fails, just load from database :(
$imageContent = $map->background->background_image;
}
if (empty($imageContent)) {
abort(404);
}
return response($imageContent, headers: [
'Content-Type' => $map->background_data['mime'] ?? getimagesizefromstring($imageContent)['mime'] ?? 'image/jpeg',
]);
}
public function save(FormRequest $request, CustomMap $map)
{
$this->authorize('update', $map);
$this->validate($request, [
'type' => 'in:image,color,map,none',
'image' => 'required_if:type,image|mimes:png,jpg,svg,gif',
'color' => 'required_if:type,color|regex:/^#[0-9a-f]{6,8}$/',
'lat' => 'required_if:type,map|numeric|between:-90,90',
'lng' => 'required_if:type,map|numeric|between:-180,180',
'zoom' => 'required_if:type,map|integer|between:0,19',
'layer' => 'string|regex:/^[a-zA-Z]*$/',
]);
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();
}
$map->background_type = $request->type;
$this->updateBackgroundImage($map, $request);
$map->background_data = array_merge($map->background_data ?? [], $request->only([
'color',
'lat',
'lng',
'zoom',
'layer',
]));
$map->save();
return response()->json([
'bgimage' => $map->background_suffix ? true : false,
'bgversion' => $map->background_version,
'bgtype' => $map->background_type,
'bgdata' => $map->getBackgroundConfig(),
]);
}
private function checkImageCache(CustomMap $map): ?string
private function updateBackgroundImage(CustomMap $map, FormRequest $request): void
{
if (! $map->background_suffix) {
return null;
if ($map->background_type == 'image') {
if ($request->image) {
// if image type and we have image data (new image) save it
$background = $map->background ?? new CustomMapBackground;
$background->background_image = $request->image->getContent();
$map->background()->save($background);
Cache::driver('file')->forget($this->getCacheKey($map)); // clear old image cache if present
$map->background_data = array_merge($map->background_data ?? [], [
'version' => md5($background->background_image),
'original_filename' => $request->image->getClientOriginalName(),
'mime' => $request->image->getMimeType(),
]);
}
} elseif ($map->getOriginal('background_type') == 'image') {
// if no longer image, clean up. if there are multiple web servers, it will only clear from the local.
Cache::driver('file')->forget($this->getCacheKey($map));
$map->background()->delete();
// remove image keys from background data
$map->background_data = array_diff_key($map->background_data ?? [], [
'version' => 1,
'original_filename' => 1,
'mime' => 1,
]);
}
}
$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;
private function getCacheKey(CustomMap $map): string
{
return 'custommap_background_' . $map->custom_map_id . ':' . ($map->background_data['version'] ?? '');
}
}

View File

@@ -59,7 +59,7 @@ class CustomMapController extends Controller
'hide_overspeed' => 0,
'font_size' => 14,
],
'background' => null,
'background_type' => null,
'map_conf' => [
'height' => '800px',
'width' => '1800px',
@@ -98,15 +98,16 @@ class CustomMapController extends Controller
$map_conf = $map->options;
$map_conf['width'] = $map->width;
$map_conf['height'] = $map->height;
$data = [
return view('map.custom-view', [
'edit' => false,
'map_id' => $map->custom_map_id,
'name' => $map->name,
'menu_group' => $map->menu_group,
'reverse_arrows' => $map->reverse_arrows,
'legend' => $this->legendConfig($map),
'background' => (bool) $map->background_suffix,
'bgversion' => $map->background_version,
'background_type' => $map->background_type,
'background_config' => $map->getBackgroundConfig(),
'page_refresh' => Config::get('page_refresh', 300),
'map_conf' => $map_conf,
'base_url' => Config::get('base_url'),
@@ -115,9 +116,7 @@ class CustomMapController extends Controller
'vmargin' => 20,
'hmargin' => 20,
'screenshot' => $screenshot,
];
return view('map.custom-view', $data);
]);
}
public function edit(CustomMap $map): View
@@ -133,8 +132,8 @@ class CustomMapController extends Controller
'newedge_conf' => $map->newedgeconfig,
'newnode_conf' => $map->newnodeconfig,
'map_conf' => $map->options,
'background' => (bool) $map->background_suffix,
'bgversion' => $map->background_version,
'background_type' => $map->background_type,
'background_config' => $map->getBackgroundConfig(),
'edit' => true,
'vmargin' => 20,
'hmargin' => 20,

View File

@@ -38,6 +38,7 @@ class CustomMap extends BaseModel
'options' => 'array',
'newnodeconfig' => 'array',
'newedgeconfig' => 'array',
'background_data' => 'array',
];
protected $fillable = [
'name',
@@ -53,8 +54,8 @@ class CustomMap extends BaseModel
'legend_font_size',
'legend_hide_invalid',
'legend_hide_overspeed',
'background_suffix',
'background_version',
'background_type',
'background_data',
];
// default values for attributes
@@ -62,9 +63,23 @@ class CustomMap extends BaseModel
'options' => '{"interaction":{"dragNodes":false,"dragView":false,"zoomView":false},"manipulation":{"enabled":false},"physics":{"enabled":false}}',
'newnodeconfig' => '{"borderWidth":1,"color":{"border":"#2B7CE9","background":"#D2E5FF"},"font":{"color":"#343434","size":14,"face":"arial"},"icon":[],"label":true,"shape":"box","size":25}',
'newedgeconfig' => '{"arrows":{"to":{"enabled":true}},"smooth":{"type":"dynamic"},"font":{"color":"#343434","size":12,"face":"arial"},"label":true}',
'background_version' => 0,
];
/**
* Get background data intended to be passed to javascript to configure the background
*/
public function getBackgroundConfig(): array
{
$config = $this->background_data ?? [];
$config['engine'] = \LibreNMS\Config::get('geoloc.engine');
$config['api_key'] = \LibreNMS\Config::get('geoloc.api_key');
$config['tile_url'] = \LibreNMS\Config::get('leaflet.tile_url');
/* @phpstan-ignore-next-line seems to think version is not in array 100% of the time... which is wrong */
$config['image_url'] = route('maps.custom.background', ['map' => $this->custom_map_id]) . '?version=' . ($config['version'] ?? 0);
return $config;
}
public function hasAccess(): bool
{
return false; // TODO calculate based on device access

View File

@@ -31,6 +31,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class CustomMapBackground extends BaseModel
{
use HasFactory;
protected $primaryKey = 'custom_map_background_id';
public function map(): BelongsTo

View File

@@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// check for existence if migration fails
if (! Schema::hasColumn('custom_maps', 'background_data')) {
Schema::table('custom_maps', function (Blueprint $table) {
$table->string('background_type', 16)->default('none');
$table->text('background_data')->nullable();
});
}
// migrate data
DB::table('custom_maps')->select(['custom_map_id', 'background_suffix', 'background_version'])->get()->map(function ($map) {
if ($map->background_suffix) {
DB::table('custom_maps')->where('custom_map_id', $map->custom_map_id)->update([
'background_type' => 'image',
'background_data' => json_encode([
'suffix' => $map->background_suffix,
'version' => $map->background_version,
]),
]);
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// migrate data
DB::table('custom_maps')->select(['custom_map_id', 'background_type', 'background_data'])->get()->map(function ($map) {
if ($map->background_type == 'image' && $map->background_data) {
$data = json_decode($map->background_data, true);
DB::table('custom_maps')->where('custom_map_id', $map->custom_map_id)->update([
'background_suffix' => $data['suffix'],
'background_version' => $data['version'],
]);
}
});
Schema::table('custom_maps', function (Blueprint $table) {
$table->dropColumn(['background_type', 'background_data']);
});
}
};

View File

@@ -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_maps', function (Blueprint $table) {
$table->dropColumn(['background_suffix', 'background_version']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('custom_maps', function (Blueprint $table) {
$table->string('background_suffix', 10)->nullable()->after('legend_hide_overspeed');
$table->integer('background_version')->unsigned()->after('background_suffix');
});
}
};

View File

@@ -1,2 +1 @@
.leaflet-control-locate a{font-size:1.4em;color:#444;cursor:pointer}.leaflet-control-locate.active a{color:#2074B6}.leaflet-control-locate.active.following a{color:#FC8428}.leafet-control-locate-location circle{animation:leaflet-control-locate-throb 4s ease infinite}@keyframes leaflet-control-locate-throb{0%{r:9;stroke-width:1}50%{r:7;stroke-width:3}100%{r:9;stroke-width:1}}
/*# sourceMappingURL=L.Control.Locate.min.css.map */
.leaflet-control-locate a{cursor:pointer}.leaflet-control-locate a .leaflet-control-locate-location-arrow{display:inline-block;width:16px;height:16px;margin:7px;background-image:url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M445 4 29 195c-48 23-32 93 19 93h176v176c0 51 70 67 93 19L508 67c16-38-25-79-63-63z"/></svg>')}.leaflet-control-locate a .leaflet-control-locate-spinner{display:inline-block;width:16px;height:16px;margin:7px;background-image:url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="black" d="M304 48a48 48 0 1 1-96 0 48 48 0 0 1 96 0zm-48 368a48 48 0 1 0 0 96 48 48 0 0 0 0-96zm208-208a48 48 0 1 0 0 96 48 48 0 0 0 0-96zM96 256a48 48 0 1 0-96 0 48 48 0 0 0 96 0zm13 99a48 48 0 1 0 0 96 48 48 0 0 0 0-96zm294 0a48 48 0 1 0 0 96 48 48 0 0 0 0-96zM109 61a48 48 0 1 0 0 96 48 48 0 0 0 0-96z"/></svg>');animation:leaflet-control-locate-spin 2s linear infinite}.leaflet-control-locate.active a .leaflet-control-locate-location-arrow{background-image:url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="rgb(32, 116, 182)" d="M445 4 29 195c-48 23-32 93 19 93h176v176c0 51 70 67 93 19L508 67c16-38-25-79-63-63z"/></svg>')}.leaflet-control-locate.following a .leaflet-control-locate-location-arrow{background-image:url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="rgb(252, 132, 40)" d="M445 4 29 195c-48 23-32 93 19 93h176v176c0 51 70 67 93 19L508 67c16-38-25-79-63-63z"/></svg>')}.leaflet-touch .leaflet-bar .leaflet-locate-text-active{width:100%;max-width:200px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;padding:0 10px}.leaflet-touch .leaflet-bar .leaflet-locate-text-active .leaflet-locate-icon{padding:0 5px 0 0}.leaflet-control-locate-location circle{animation:leaflet-control-locate-throb 4s ease infinite}@keyframes leaflet-control-locate-throb{0%{stroke-width:1}50%{stroke-width:3;transform:scale(0.8, 0.8)}100%{stroke-width:1}}@keyframes leaflet-control-locate-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}/*# sourceMappingURL=L.Control.Locate.min.css.map */

View File

@@ -1,7 +1 @@
{
"version": 3,
"mappings": "AAEE,yBAAE,CACA,SAAS,CAAE,KAAK,CAChB,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,OAAO,CAGf,gCAAE,CACA,KAAK,CAAE,OAAO,CAEhB,0CAAc,CACZ,KAAK,CAAE,OAAO,CAKpB,sCAAuC,CACrC,SAAS,CAAE,6CAA6C,CAG1D,uCAIC,CAHG,EAAG,CAAE,CAAC,CAAE,CAAC,CAAE,YAAY,CAAE,CAAC,CAC3B,GAAI,CAAE,CAAC,CAAE,CAAC,CAAE,YAAY,CAAE,CAAC,CAC5B,IAAK,CAAE,CAAC,CAAE,CAAC,CAAE,YAAY,CAAE,CAAC",
"sources": ["../src/L.Control.Locate.scss"],
"names": [],
"file": "L.Control.Locate.min.css"
}
{"version":3,"sourceRoot":"","sources":["../src/L.Control.Locate.scss"],"names":[],"mappings":"AASE,0BACE,eAEA,iEACE,qBACA,WACA,YACA,WACA,0OAGF,0DACE,qBACA,WACA,YACA,WACA,6bACA,yDAIJ,wEACE,sPAGF,2EACE,sPAIJ,wDACE,WACA,gBACA,uBACA,mBACA,gBACA,eAEA,6EACE,kBAIJ,wCACE,wDAGF,wCACE,GACE,eAGF,IACE,eACA,0BAGF,KACE,gBAIJ,uCACE,GACE,uBAGF,KACE","file":"L.Control.Locate.min.css"}

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,10 @@
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
@@ -41,7 +45,10 @@
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg,
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
@@ -49,8 +56,15 @@
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
@@ -162,9 +176,6 @@
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile {
will-change: opacity;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
@@ -179,9 +190,10 @@
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
svg.leaflet-zoom-animated {
will-change: transform;
}
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
@@ -237,7 +249,8 @@
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
@@ -246,14 +259,11 @@
.leaflet-container {
background: #ddd;
outline: 0;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
@@ -262,7 +272,10 @@
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
@@ -272,8 +285,7 @@
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
@@ -290,7 +302,8 @@
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
@@ -380,6 +393,8 @@
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
@@ -388,7 +403,7 @@
}
/* Default icon URLs */
.leaflet-default-icon-path {
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
@@ -397,23 +412,27 @@
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
@@ -426,14 +445,11 @@
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
@@ -469,17 +485,22 @@
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 18px 0;
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
@@ -490,6 +511,7 @@
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
@@ -506,28 +528,25 @@
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
border: none;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
@@ -536,9 +555,6 @@
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
@@ -573,7 +589,7 @@
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-clickable {
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
@@ -633,3 +649,13 @@
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

File diff suppressed because it is too large Load Diff

388
html/js/leaflet-image.js Normal file
View File

@@ -0,0 +1,388 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.leafletImage = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global L */
var queue = require('d3-queue').queue;
var cacheBusterDate = +new Date();
// leaflet-image
module.exports = function leafletImage(map, callback) {
var hasMapbox = !!L.mapbox;
var dimensions = map.getSize(),
layerQueue = new queue(1);
var canvas = document.createElement('canvas');
canvas.width = dimensions.x;
canvas.height = dimensions.y;
var ctx = canvas.getContext('2d');
// dummy canvas image when loadTile get 404 error
// and layer don't have errorTileUrl
var dummycanvas = document.createElement('canvas');
dummycanvas.width = 1;
dummycanvas.height = 1;
var dummyctx = dummycanvas.getContext('2d');
dummyctx.fillStyle = 'rgba(0,0,0,0)';
dummyctx.fillRect(0, 0, 1, 1);
// layers are drawn in the same order as they are composed in the DOM:
// tiles, paths, and then markers
map.eachLayer(drawTileLayer);
map.eachLayer(drawEsriDynamicLayer);
if (map._pathRoot) {
layerQueue.defer(handlePathRoot, map._pathRoot);
} else if (map._panes) {
var firstCanvas = map._panes.overlayPane.getElementsByTagName('canvas').item(0);
if (firstCanvas) { layerQueue.defer(handlePathRoot, firstCanvas); }
}
map.eachLayer(drawMarkerLayer);
layerQueue.awaitAll(layersDone);
function drawTileLayer(l) {
if (l instanceof L.TileLayer) layerQueue.defer(handleTileLayer, l);
else if (l._heat) layerQueue.defer(handlePathRoot, l._canvas);
}
function drawMarkerLayer(l) {
if (l instanceof L.Marker && l.options.icon instanceof L.Icon) {
layerQueue.defer(handleMarkerLayer, l);
}
}
function drawEsriDynamicLayer(l) {
if (!L.esri) return;
if (l instanceof L.esri.DynamicMapLayer) {
layerQueue.defer(handleEsriDymamicLayer, l);
}
}
function done() {
callback(null, canvas);
}
function layersDone(err, layers) {
if (err) throw err;
layers.forEach(function (layer) {
if (layer && layer.canvas) {
ctx.drawImage(layer.canvas, 0, 0);
}
});
done();
}
function handleTileLayer(layer, callback) {
// `L.TileLayer.Canvas` was removed in leaflet 1.0
var isCanvasLayer = (L.TileLayer.Canvas && layer instanceof L.TileLayer.Canvas),
canvas = document.createElement('canvas');
canvas.width = dimensions.x;
canvas.height = dimensions.y;
var ctx = canvas.getContext('2d'),
bounds = map.getPixelBounds(),
zoom = map.getZoom(),
tileSize = layer.options.tileSize;
if (zoom > layer.options.maxZoom ||
zoom < layer.options.minZoom ||
// mapbox.tileLayer
(hasMapbox &&
layer instanceof L.mapbox.tileLayer && !layer.options.tiles)) {
return callback();
}
var tileBounds = L.bounds(
bounds.min.divideBy(tileSize)._floor(),
bounds.max.divideBy(tileSize)._floor()),
tiles = [],
j, i,
tileQueue = new queue(1);
for (j = tileBounds.min.y; j <= tileBounds.max.y; j++) {
for (i = tileBounds.min.x; i <= tileBounds.max.x; i++) {
tiles.push(new L.Point(i, j));
}
}
tiles.forEach(function (tilePoint) {
var originalTilePoint = tilePoint.clone();
if (layer._adjustTilePoint) {
layer._adjustTilePoint(tilePoint);
}
var tilePos = originalTilePoint
.scaleBy(new L.Point(tileSize, tileSize))
.subtract(bounds.min);
if (tilePoint.y >= 0) {
if (isCanvasLayer) {
var tile = layer._tiles[tilePoint.x + ':' + tilePoint.y];
tileQueue.defer(canvasTile, tile, tilePos, tileSize);
} else {
var url = addCacheString(layer.getTileUrl(tilePoint));
tileQueue.defer(loadTile, url, tilePos, tileSize);
}
}
});
tileQueue.awaitAll(tileQueueFinish);
function canvasTile(tile, tilePos, tileSize, callback) {
callback(null, {
img: tile,
pos: tilePos,
size: tileSize
});
}
function loadTile(url, tilePos, tileSize, callback) {
var im = new Image();
im.crossOrigin = '';
im.onload = function () {
callback(null, {
img: this,
pos: tilePos,
size: tileSize
});
};
im.onerror = function (e) {
// use canvas instead of errorTileUrl if errorTileUrl get 404
if (layer.options.errorTileUrl != '' && e.target.errorCheck === undefined) {
e.target.errorCheck = true;
e.target.src = layer.options.errorTileUrl;
} else {
callback(null, {
img: dummycanvas,
pos: tilePos,
size: tileSize
});
}
};
im.src = url;
}
function tileQueueFinish(err, data) {
data.forEach(drawTile);
callback(null, { canvas: canvas });
}
function drawTile(d) {
ctx.drawImage(d.img, Math.floor(d.pos.x), Math.floor(d.pos.y),
d.size, d.size);
}
}
function handlePathRoot(root, callback) {
var bounds = map.getPixelBounds(),
origin = map.getPixelOrigin(),
canvas = document.createElement('canvas');
canvas.width = dimensions.x;
canvas.height = dimensions.y;
var ctx = canvas.getContext('2d');
var pos = L.DomUtil.getPosition(root).subtract(bounds.min).add(origin);
try {
ctx.drawImage(root, pos.x, pos.y, canvas.width - (pos.x * 2), canvas.height - (pos.y * 2));
callback(null, {
canvas: canvas
});
} catch(e) {
console.error('Element could not be drawn on canvas', root); // eslint-disable-line no-console
}
}
function handleMarkerLayer(marker, callback) {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
pixelBounds = map.getPixelBounds(),
minPoint = new L.Point(pixelBounds.min.x, pixelBounds.min.y),
pixelPoint = map.project(marker.getLatLng()),
isBase64 = /^data\:/.test(marker._icon.src),
url = isBase64 ? marker._icon.src : addCacheString(marker._icon.src),
im = new Image(),
options = marker.options.icon.options,
size = options.iconSize,
pos = pixelPoint.subtract(minPoint),
anchor = L.point(options.iconAnchor || size && size.divideBy(2, true));
if (size instanceof L.Point) size = [size.x, size.y];
var x = Math.round(pos.x - size[0] + anchor.x),
y = Math.round(pos.y - anchor.y);
canvas.width = dimensions.x;
canvas.height = dimensions.y;
im.crossOrigin = '';
im.onload = function () {
ctx.drawImage(this, x, y, size[0], size[1]);
callback(null, {
canvas: canvas
});
};
im.src = url;
if (isBase64) im.onload();
}
function handleEsriDymamicLayer(dynamicLayer, callback) {
var canvas = document.createElement('canvas');
canvas.width = dimensions.x;
canvas.height = dimensions.y;
var ctx = canvas.getContext('2d');
var im = new Image();
im.crossOrigin = '';
im.src = addCacheString(dynamicLayer._currentImage._image.src);
im.onload = function() {
ctx.drawImage(im, 0, 0);
callback(null, {
canvas: canvas
});
};
}
function addCacheString(url) {
// If it's a data URL we don't want to touch this.
if (isDataURL(url) || url.indexOf('mapbox.com/styles/v1') !== -1) {
return url;
}
return url + ((url.match(/\?/)) ? '&' : '?') + 'cache=' + cacheBusterDate;
}
function isDataURL(url) {
var dataURLRegex = /^\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i;
return !!url.match(dataURLRegex);
}
};
},{"d3-queue":2}],2:[function(require,module,exports){
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.d3_queue = global.d3_queue || {})));
}(this, function (exports) { 'use strict';
var version = "2.0.3";
var slice = [].slice;
var noabort = {};
function Queue(size) {
if (!(size >= 1)) throw new Error;
this._size = size;
this._call =
this._error = null;
this._tasks = [];
this._data = [];
this._waiting =
this._active =
this._ended =
this._start = 0; // inside a synchronous task callback?
}
Queue.prototype = queue.prototype = {
constructor: Queue,
defer: function(callback) {
if (typeof callback !== "function" || this._call) throw new Error;
if (this._error != null) return this;
var t = slice.call(arguments, 1);
t.push(callback);
++this._waiting, this._tasks.push(t);
poke(this);
return this;
},
abort: function() {
if (this._error == null) abort(this, new Error("abort"));
return this;
},
await: function(callback) {
if (typeof callback !== "function" || this._call) throw new Error;
this._call = function(error, results) { callback.apply(null, [error].concat(results)); };
maybeNotify(this);
return this;
},
awaitAll: function(callback) {
if (typeof callback !== "function" || this._call) throw new Error;
this._call = callback;
maybeNotify(this);
return this;
}
};
function poke(q) {
if (!q._start) try { start(q); } // let the current task complete
catch (e) { if (q._tasks[q._ended + q._active - 1]) abort(q, e); } // task errored synchronously
}
function start(q) {
while (q._start = q._waiting && q._active < q._size) {
var i = q._ended + q._active,
t = q._tasks[i],
j = t.length - 1,
c = t[j];
t[j] = end(q, i);
--q._waiting, ++q._active;
t = c.apply(null, t);
if (!q._tasks[i]) continue; // task finished synchronously
q._tasks[i] = t || noabort;
}
}
function end(q, i) {
return function(e, r) {
if (!q._tasks[i]) return; // ignore multiple callbacks
--q._active, ++q._ended;
q._tasks[i] = null;
if (q._error != null) return; // ignore secondary errors
if (e != null) {
abort(q, e);
} else {
q._data[i] = r;
if (q._waiting) poke(q);
else maybeNotify(q);
}
};
}
function abort(q, e) {
var i = q._tasks.length, t;
q._error = e; // ignore active callbacks
q._data = undefined; // allow gc
q._waiting = NaN; // prevent starting
while (--i >= 0) {
if (t = q._tasks[i]) {
q._tasks[i] = null;
if (t.abort) try { t.abort(); }
catch (e) { /* ignore */ }
}
}
q._active = NaN; // allow notification
maybeNotify(q);
}
function maybeNotify(q) {
if (!q._active && q._call) q._call(q._error, q._data);
}
function queue(concurrency) {
return new Queue(arguments.length ? +concurrency : Infinity);
}
exports.version = version;
exports.queue = queue;
}));
},{}]},{},[1])(1)
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,5 @@
window.maps = {};
function override_config(event, state, tmp_this) {
event.preventDefault();
var $this = tmp_this;
@@ -285,18 +287,28 @@ function loadjs(filename, func){
}
}
function init_map(id, engine, api_key, config) {
var leaflet = L.map(id);
var baseMaps = {};
leaflet.setView([0, 0], 15);
function init_map(id, config = {}) {
let leaflet = get_map(id)
if (leaflet) {
// return existing map
return leaflet;
}
if (engine === 'google') {
loadjs('https://maps.googleapis.com/maps/api/js?key=' + api_key, function () {
leaflet = L.map(id, {
preferCanvas: true,
zoom: config.zoom !== undefined ? config.zoom : 3,
center: (config.lat !== undefined && config.lng !== undefined) ? [config.lat, config.lng] : [40,-20]
});
window.maps[id] = leaflet;
let baseMaps = {};
if (config.engine === 'google' && config.api_key) {
loadjs('https://maps.googleapis.com/maps/api/js?key=' + config.api_key, function () {
loadjs('js/Leaflet.GoogleMutant.js', function () {
var roads = L.gridLayer.googleMutant({
const roads = L.gridLayer.googleMutant({
type: 'roadmap' // valid values are 'roadmap', 'satellite', 'terrain' and 'hybrid'
});
var satellite = L.gridLayer.googleMutant({
const satellite = L.gridLayer.googleMutant({
type: 'satellite'
});
@@ -304,18 +316,19 @@ function init_map(id, engine, api_key, config) {
"Streets": roads,
"Satellite": satellite
};
L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
roads.addTo(leaflet);
leaflet.layerControl = L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
(config.layer in baseMaps ? baseMaps[config.layer] : roads).addTo(leaflet);
leaflet.layerControl._container.style.display = (config.readonly ? 'none' : 'block');
});
});
} else if (engine === 'bing') {
} else if (config.engine === 'bing' && config.api_key) {
loadjs('js/leaflet-bing-layer.min.js', function () {
var roads = L.tileLayer.bing({
bingMapsKey: api_key,
const roads = L.tileLayer.bing({
bingMapsKey: config.api_key,
imagerySet: 'RoadOnDemand'
});
var satellite = L.tileLayer.bing({
bingMapsKey: api_key,
const satellite = L.tileLayer.bing({
bingMapsKey: config.api_key,
imagerySet: 'AerialWithLabelsOnDemand'
});
@@ -323,23 +336,26 @@ function init_map(id, engine, api_key, config) {
"Streets": roads,
"Satellite": satellite
};
L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
roads.addTo(leaflet);
leaflet.layerControl = L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
(config.layer in baseMaps ? baseMaps[config.layer] : roads).addTo(leaflet);
leaflet.layerControl._container.style.display = (config.readonly ? 'none' : 'block');
});
} else if (engine === 'mapquest') {
loadjs('https://www.mapquestapi.com/sdk/leaflet/v2.2/mq-map.js?key=' + api_key, function () {
var roads = MQ.mapLayer();
var satellite = MQ.hybridLayer();
} else if (config.engine === 'mapquest' && config.api_key) {
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();
baseMaps = {
"Streets": roads,
"Satellite": satellite
};
L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
roads.addTo(leaflet);
leaflet.layerControl = L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
(config.layer in baseMaps ? baseMaps[config.layer] : roads).addTo(leaflet);
leaflet.layerControl._container.style.display = (config.readonly ? 'none' : 'block');
});
} else {
var osm = L.tileLayer('//' + config.tile_url + '/{z}/{x}/{y}.png', {
const tile_url = config.tile_url ? config.tile_url : '{s}.tile.openstreetmap.org';
const osm = L.tileLayer('//' + tile_url + '/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
@@ -352,20 +368,79 @@ function init_map(id, engine, api_key, config) {
// "OpenStreetMap": osm,
// "Satellite": esri
// };
// L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
// leaflet.layerControl = L.control.layers(baseMaps, null, {position: 'bottomleft'}).addTo(leaflet);
osm.addTo(leaflet);
}
if (location.protocol === 'https:') {
// disable all interaction
if (config.readonly === true) {
disable_map_interaction(leaflet)
} else if (location.protocol === 'https:') {
// can't request location permission without https
L.control.locate().addTo(leaflet);
leaflet.locateControl = L.control.locate().addTo(leaflet);
}
return leaflet;
}
function get_map(id) {
if (window.maps) {
return window.maps[id];
}
}
function destroy_map(id) {
const leaflet = get_map(id);
if(id in window.maps) {
leaflet.off();
leaflet._container.classList.remove('leaflet-container', 'leaflet-touch', 'leaflet-retina', 'leaflet-fade-anim');
leaflet.remove();
delete window.maps[id];
}
}
function disable_map_interaction(leaflet) {
leaflet.zoomControl?.remove();
delete leaflet.zoomControl;
leaflet.locateControl?.stop();
leaflet.locateControl?.remove();
delete leaflet.locateControl;
if (leaflet.layerControl) {
leaflet.layerControl._container.style.display = 'none';
}
leaflet.dragging.disable();
leaflet.touchZoom.disable();
leaflet.doubleClickZoom.disable();
leaflet.scrollWheelZoom.disable();
leaflet.boxZoom.disable();
leaflet.keyboard.disable();
leaflet.tap?.disable();
leaflet._container.style.cursor = 'default';
}
function enable_map_interaction(leaflet) {
if (! leaflet.zoomControl) {
leaflet.zoomControl = L.control.zoom().addTo(leaflet);
}
if (location.protocol === 'https:' && ! leaflet.locateControl) {
// can't request location permission without https
leaflet.locateControl = L.control.locate().addTo(leaflet);
}
if (leaflet.layerControl) {
leaflet.layerControl._container.style.display = 'block';
}
leaflet.dragging.enable();
leaflet.touchZoom.enable();
leaflet.doubleClickZoom.enable();
leaflet.scrollWheelZoom.enable();
leaflet.boxZoom.enable();
leaflet.keyboard.enable();
leaflet.tap?.enable();
leaflet._container.style.cursor = 'pointer';
}
function init_map_marker(leaflet, latlng) {
var marker = L.marker(latlng);
let marker = L.marker(latlng);
marker.addTo(leaflet);
leaflet.setView(latlng);
@@ -381,6 +456,33 @@ function init_map_marker(leaflet, latlng) {
return marker;
}
function setCustomMapBackground(id, type, data) {
let image = '';
let color = '';
if(type === 'image') {
image = `url(${data.image_url})`;
} else if(type === 'color') {
color = data.color;
}
$(`#${id} .vis-network canvas`)
.css('background-image', image)
.css('background-size', 'cover')
.css('background-color', color);
const mapBackgroundId = `${id}-bg-geo-map`;
if (type === 'map') {
$(`#${id}-bg-geo-map`).show();
let config = data;
config['readonly'] = true;
init_map(mapBackgroundId, config)
.setView(L.latLng(data.lat, data.lng), data.zoom);
} else {
// destroy the map if it exists
destroy_map(mapBackgroundId)
}
}
function update_location(id, latlng, callback) {
$.ajax({
method: 'PATCH',

View File

@@ -2,15 +2,15 @@
"/js/app.js": "/js/app.js?id=1ecd9b13d60fe23a9729684f4d9dc663",
"/js/manifest.js": "/js/manifest.js?id=2eb19d92c19953027907b72ff5963ebb",
"/css/vendor.css": "/css/vendor.css?id=d520734ded0ec75b0a572aa8db1c2161",
"/css/app.css": "/css/app.css?id=61739943d1d595fd4afe2157f24f0316",
"/css/app.css": "/css/app.css?id=30306ab2ec735e188cf480f67548406b",
"/js/vendor.js": "/js/vendor.js?id=3b22b85b4e5a64e37dd954c0b147b3f3",
"/js/lang/de.js": "/js/lang/de.js?id=9a6f9c23a4b209504cce12ce85315a3c",
"/js/lang/en.js": "/js/lang/en.js?id=43cfd926c2a415bdbb2e59676ab29875",
"/js/lang/fr.js": "/js/lang/fr.js?id=d9dd782bb64e09dcca29d784c0417779",
"/js/lang/it.js": "/js/lang/it.js?id=40ad82368018e52347e3808571866e69",
"/js/lang/de.js": "/js/lang/de.js?id=f80b2c49bd4d1587d4747d189c566ffa",
"/js/lang/en.js": "/js/lang/en.js?id=cece9b44445c5e2d9d1d819a6b37ec74",
"/js/lang/fr.js": "/js/lang/fr.js?id=7e43fd1965beef315f0b416fd8607231",
"/js/lang/it.js": "/js/lang/it.js?id=7827375adf92766a477291c48fa1b360",
"/js/lang/ru.js": "/js/lang/ru.js?id=f6b7c078755312a0907c4f983991cc52",
"/js/lang/sr.js": "/js/lang/sr.js?id=388e38b41f63e35175061e849bf0d8e5",
"/js/lang/uk.js": "/js/lang/uk.js?id=85ef43c7afe57a42b774f3cbae5a77e5",
"/js/lang/zh-CN.js": "/js/lang/zh-CN.js?id=6fdbd03cdf6b4868de1d3b5b557e5e13",
"/js/lang/zh-CN.js": "/js/lang/zh-CN.js?id=13d99410e49647e05664cd14056c2473",
"/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=2cf0d871ec12cbd5ccb746b983d127df"
}

View File

@@ -22,6 +22,10 @@ return [
'bg' => [
'title' => 'Set Background',
'background' => 'Background',
'color' => 'Color',
'image' => 'Image',
'map' => 'Map',
'none' => 'None',
'clear_bg' => 'Clear BG',
'clear_background' => 'Clear Background',
'keep_background' => 'Keep Background',
@@ -29,6 +33,13 @@ return [
'save_errors' => 'Save failed due to the following errors:',
'save_error' => 'Save failed. Server returned error response code: :code',
'save' => 'Save Background',
'lat' => 'Latitude',
'lng' => 'Longitude',
'zoom' => 'Zoom',
'adjust_map' => 'Adjust Map',
'adjust_map_finish' => 'Done Adjusting Map',
'as_image' => 'Set as Image',
'as_image_hint' => 'Setting the map as an image background will be static, but have improved performance and work without connection to the map tile server',
],
'map' => [
'settings_title' => 'Map Settings',

View File

@@ -530,14 +530,14 @@ custom_maps:
- { 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: '' }
- { Field: newnodeconfig, Type: longtext, 'Null': false, Extra: '' }
- { Field: newedgeconfig, Type: longtext, 'Null': false, Extra: '' }
- { Field: created_at, Type: timestamp, 'Null': true, Extra: '' }
- { Field: updated_at, Type: timestamp, 'Null': true, Extra: '' }
- { Field: menu_group, Type: varchar(100), 'Null': true, Extra: '' }
- { Field: background_type, Type: varchar(16), 'Null': false, Extra: '', Default: 'none' }
- { Field: background_data, Type: 'text', 'Null': true, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [custom_map_id], Unique: true, Type: BTREE }
custom_map_backgrounds:

306
package-lock.json generated
View File

@@ -82,21 +82,21 @@
}
},
"node_modules/@babel/core": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz",
"integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
"integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
"@babel/generator": "^7.24.4",
"@babel/generator": "^7.24.5",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helpers": "^7.24.4",
"@babel/parser": "^7.24.4",
"@babel/helper-module-transforms": "^7.24.5",
"@babel/helpers": "^7.24.5",
"@babel/parser": "^7.24.5",
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.1",
"@babel/types": "^7.24.0",
"@babel/traverse": "^7.24.5",
"@babel/types": "^7.24.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -121,12 +121,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz",
"integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
"integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.24.0",
"@babel/types": "^7.24.5",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
@@ -185,19 +185,19 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz",
"integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz",
"integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-member-expression-to-functions": "^7.23.0",
"@babel/helper-member-expression-to-functions": "^7.24.5",
"@babel/helper-optimise-call-expression": "^7.22.5",
"@babel/helper-replace-supers": "^7.24.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-split-export-declaration": "^7.24.5",
"semver": "^6.3.1"
},
"engines": {
@@ -293,12 +293,12 @@
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
"integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz",
"integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.23.0"
"@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -317,16 +317,16 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
"integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-simple-access": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-validator-identifier": "^7.22.20"
"@babel/helper-module-imports": "^7.24.3",
"@babel/helper-simple-access": "^7.24.5",
"@babel/helper-split-export-declaration": "^7.24.5",
"@babel/helper-validator-identifier": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -348,9 +348,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
"integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
"integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -391,12 +391,12 @@
}
},
"node_modules/@babel/helper-simple-access": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
"integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -415,12 +415,12 @@
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
"integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -436,9 +436,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -454,40 +454,40 @@
}
},
"node_modules/@babel/helper-wrap-function": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz",
"integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz",
"integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==",
"dev": true,
"dependencies": {
"@babel/helper-function-name": "^7.22.5",
"@babel/template": "^7.22.15",
"@babel/types": "^7.22.19"
"@babel/helper-function-name": "^7.23.0",
"@babel/template": "^7.24.0",
"@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz",
"integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
"integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
"dev": true,
"dependencies": {
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.1",
"@babel/types": "^7.24.0"
"@babel/traverse": "^7.24.5",
"@babel/types": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
"integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
"integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-validator-identifier": "^7.24.5",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
@@ -559,9 +559,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
"integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -570,13 +570,13 @@
}
},
"node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz",
"integrity": "sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz",
"integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -966,12 +966,12 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz",
"integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz",
"integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1014,18 +1014,18 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz",
"integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz",
"integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.5",
"@babel/helper-replace-supers": "^7.24.1",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-split-export-declaration": "^7.24.5",
"globals": "^11.1.0"
},
"engines": {
@@ -1052,12 +1052,12 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz",
"integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz",
"integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1371,15 +1371,15 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
"integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz",
"integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==",
"dev": true,
"dependencies": {
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.5",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-transform-parameters": "^7.24.1"
"@babel/plugin-transform-parameters": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1421,12 +1421,12 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz",
"integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz",
"integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
},
@@ -1438,12 +1438,12 @@
}
},
"node_modules/@babel/plugin-transform-parameters": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
"integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz",
"integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1469,14 +1469,14 @@
}
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz",
"integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz",
"integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-create-class-features-plugin": "^7.24.1",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-create-class-features-plugin": "^7.24.5",
"@babel/helper-plugin-utils": "^7.24.5",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
},
"engines": {
@@ -1623,12 +1623,12 @@
}
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz",
"integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz",
"integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1701,16 +1701,16 @@
}
},
"node_modules/@babel/preset-env": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.4.tgz",
"integrity": "sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz",
"integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.24.4",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.5",
"@babel/helper-validator-option": "^7.23.5",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.4",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1",
@@ -1737,12 +1737,12 @@
"@babel/plugin-transform-async-generator-functions": "^7.24.3",
"@babel/plugin-transform-async-to-generator": "^7.24.1",
"@babel/plugin-transform-block-scoped-functions": "^7.24.1",
"@babel/plugin-transform-block-scoping": "^7.24.4",
"@babel/plugin-transform-block-scoping": "^7.24.5",
"@babel/plugin-transform-class-properties": "^7.24.1",
"@babel/plugin-transform-class-static-block": "^7.24.4",
"@babel/plugin-transform-classes": "^7.24.1",
"@babel/plugin-transform-classes": "^7.24.5",
"@babel/plugin-transform-computed-properties": "^7.24.1",
"@babel/plugin-transform-destructuring": "^7.24.1",
"@babel/plugin-transform-destructuring": "^7.24.5",
"@babel/plugin-transform-dotall-regex": "^7.24.1",
"@babel/plugin-transform-duplicate-keys": "^7.24.1",
"@babel/plugin-transform-dynamic-import": "^7.24.1",
@@ -1762,13 +1762,13 @@
"@babel/plugin-transform-new-target": "^7.24.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
"@babel/plugin-transform-numeric-separator": "^7.24.1",
"@babel/plugin-transform-object-rest-spread": "^7.24.1",
"@babel/plugin-transform-object-rest-spread": "^7.24.5",
"@babel/plugin-transform-object-super": "^7.24.1",
"@babel/plugin-transform-optional-catch-binding": "^7.24.1",
"@babel/plugin-transform-optional-chaining": "^7.24.1",
"@babel/plugin-transform-parameters": "^7.24.1",
"@babel/plugin-transform-optional-chaining": "^7.24.5",
"@babel/plugin-transform-parameters": "^7.24.5",
"@babel/plugin-transform-private-methods": "^7.24.1",
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
"@babel/plugin-transform-private-property-in-object": "^7.24.5",
"@babel/plugin-transform-property-literals": "^7.24.1",
"@babel/plugin-transform-regenerator": "^7.24.1",
"@babel/plugin-transform-reserved-words": "^7.24.1",
@@ -1776,7 +1776,7 @@
"@babel/plugin-transform-spread": "^7.24.1",
"@babel/plugin-transform-sticky-regex": "^7.24.1",
"@babel/plugin-transform-template-literals": "^7.24.1",
"@babel/plugin-transform-typeof-symbol": "^7.24.1",
"@babel/plugin-transform-typeof-symbol": "^7.24.5",
"@babel/plugin-transform-unicode-escapes": "^7.24.1",
"@babel/plugin-transform-unicode-property-regex": "^7.24.1",
"@babel/plugin-transform-unicode-regex": "^7.24.1",
@@ -1825,9 +1825,9 @@
"dev": true
},
"node_modules/@babel/runtime": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
"integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -1850,19 +1850,19 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
"integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
"integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.24.1",
"@babel/generator": "^7.24.1",
"@babel/code-frame": "^7.24.2",
"@babel/generator": "^7.24.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.24.1",
"@babel/types": "^7.24.0",
"@babel/helper-split-export-declaration": "^7.24.5",
"@babel/parser": "^7.24.5",
"@babel/types": "^7.24.5",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -1871,13 +1871,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
"integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-string-parser": "^7.24.1",
"@babel/helper-validator-identifier": "^7.24.5",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -3460,9 +3460,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001612",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz",
"integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==",
"version": "1.0.30001614",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz",
"integrity": "sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog==",
"dev": true,
"funding": [
{
@@ -4465,9 +4465,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.4.748",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.748.tgz",
"integrity": "sha512-VWqjOlPZn70UZ8FTKUOkUvBLeTQ0xpty66qV0yJcAGY2/CthI4xyW9aEozRVtuwv3Kpf5xTesmJUcPwuJmgP4A==",
"version": "1.4.751",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.751.tgz",
"integrity": "sha512-2DEPi++qa89SMGRhufWTiLmzqyuGmNF3SK4+PQetW1JKiZdEpF4XQonJXJCzyuYSA6mauiMhbyVhqYAP45Hvfw==",
"dev": true
},
"node_modules/elliptic": {
@@ -4538,9 +4538,9 @@
}
},
"node_modules/envinfo": {
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.12.0.tgz",
"integrity": "sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==",
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz",
"integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==",
"dev": true,
"bin": {
"envinfo": "dist/cli.js"
@@ -4580,9 +4580,9 @@
}
},
"node_modules/es-module-lexer": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
"integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==",
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz",
"integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==",
"dev": true
},
"node_modules/es6-object-assign": {
@@ -7094,9 +7094,9 @@
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
@@ -9226,9 +9226,9 @@
}
},
"node_modules/tailwindcss/node_modules/yaml": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
"integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
@@ -9247,9 +9247,9 @@
}
},
"node_modules/terser": {
"version": "5.30.4",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz",
"integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==",
"version": "5.31.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz",
"integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
@@ -10279,9 +10279,9 @@
"dev": true
},
"node_modules/ws": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"dev": true,
"engines": {
"node": ">=10.0.0"

View File

@@ -0,0 +1,33 @@
@props([
'id' => 'geo-map',
'init' => true,
'width' => '200px',
'height' => '100px',
'lat' => null,
'lng' => null,
'zoom' => null,
'layer' => null,
'readonly' => false,
'config' => [],
])
@php
$config['readonly'] = $readonly;
$config['lat'] = $lat ?? $config['lat'] ?? 40;
$config['lng'] = $lng ?? $config['lng'] ?? 40;
$config['zoom'] = $zoom ?? $config['zoom'] ?? 3;
$config['layer'] = $layer ?? $config['layer'] ?? null;
$config['engine'] ??= \LibreNMS\Config::get('geoloc.engine');
$config['api_key'] ??= \LibreNMS\Config::get('geoloc.api_key');
$config['tile_url'] ??= \LibreNMS\Config::get('leaflet.tile_url', '{s}.tile.openstreetmap.org');
@endphp
<div id="{{ $id }}" style="width: {{ $width }};height: {{ $height }}" {{ $attributes }}></div>
@if($init)
<script>
loadjs('js/leaflet.js', function () {
init_map(@json($id), @json($config))
})
</script>
@endif

View File

@@ -0,0 +1,12 @@
@props([
'id',
'label' => null,
'value' => null,
'type' => 'text',
])
<label for="{{ $id }}" class="tw-block tw-mb-1 tw-mt-2 tw-font-medium tw-text-gray-900 dark:tw-text-white">{{ $label }}</label>
<input type="{{ $type }}"
id="{{ $id }}"
{{ $attributes->merge(['class' => 'tw-mb-2 tw-bg-gray-50 tw-border tw-border-gray-300 tw-text-gray-900 tw-rounded-lg focus:tw-ring-blue-500 focus:tw-border-blue-500 tw-block tw-w-full tw-p-2.5 dark:tw-bg-gray-700 dark:tw-border-gray-600 dark:tw-placeholder-gray-400 dark:tw-text-white dark:tw-focus:ring-blue-500 dark:tw-focus:border-blue-500']) }}
/>

View File

@@ -0,0 +1,16 @@
@props(['name', 'value' => null])
<div x-data="{
id: '',
name: {{ Js::from($name) }},
value: {{ Js::from($value ?: $name) }}
}"
x-show="value === activeTab"
role="tabpanel"
:aria-labelledby="`tab-${id}`"
:id="`tab-panel-${id}`"
x-init="id = registerTab(name, value)"
{{ $attributes }}
>
{{ $slot }}
</div>

View File

@@ -0,0 +1,41 @@
@props(['active' => ''])
<div x-data="{
activeTab: '{{ $active }}',
tabs: [],
registerTab(name, value) {
this.tabs.push({name: name, value: value});
if (! this.activeTab) {
this.changeTab(value)
}
return this.tabs.length;
},
changeTab(tabValue) {
this.activeTab = tabValue;
this.$dispatch('tab-change', tabValue);
}
}"
{{ $attributes }}
>
<ul role="tablist" class="tw-flex tw-flex-wrap -tw-mb-px tw-list-none tw-text-center tw-text-gray-500 dark:tw-text-gray-400">
<template x-for="(tab, index) in tabs" :key="index">
<li class="tw-me-2"
@click="changeTab(tab.value)"
:id="`tab-${index + 1}`"
role="tab"
:aria-selected="(tab.value === activeTab).toString()"
:aria-controls="`tab-panel-${index + 1}`"
>
<div
x-text="tab.name"
class="tw-inline-block tw-p-3 tw-border-b-2 tw-rounded-t-lg tw-cursor-pointer"
:class="tab.value === activeTab ? 'tw-text-blue-600 tw-border-blue-600 active dark:tw-text-blue-500 dark:tw-border-blue-500' : 'tw-border-transparent hover:tw-text-gray-600 hover:tw-border-gray-300 dark:hover:tw-text-gray-300'"
></div>
</li>
</template>
</ul>
<div x-ref="tabs">
{{ $slot }}
</div>
</div>

View File

@@ -76,7 +76,7 @@
});
var ajax_url = "{{ url('/ajax') }}";
</script>
<script src="{{ asset('js/librenms.js?ver=14042024') }}"></script>
<script src="{{ asset('js/librenms.js?ver=12052024') }}"></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

@@ -149,7 +149,7 @@
if (locationMap === null) {
config = {{ Js::from($maps_config) }}
locationMap = init_map('location-edit-map', '{{ $maps_engine }}', '{{ $maps_api }}', config);
locationMap = init_map('location-edit-map', config);
locationMarker = init_map_marker(locationMap, location);
}

View File

@@ -1,118 +1,199 @@
<div class="modal fade" id="bgModal" tabindex="-1" role="dialog" aria-labelledby="bgModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-content" x-data="backgroundModalData()" x-init="resetBackground">
<div class="modal-header">
<h5 class="modal-title" id="bgModalLabel">{{ __('map.custom.edit.bg.title') }}</h5>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="well well-lg">
<div class="form-group row" id="mapBackgroundRow">
<label for="selectbackground" class="col-sm-3 control-label">{{ __('map.custom.edit.bg.background') }}</label>
<div class="col-sm-9">
<input id="mapBackgroundSelect" type="file" name="selectbackground" accept="image/png,image/jpeg,image/svg+xml,image/gif" class="form-control" onchange="mapChangeBackground();">
<button id="mapBackgroundCancel" type="button" name="cancelbackground" class="btn btn-primary" onclick="mapChangeBackgroundCancel();" style="display:none">{{ __('Cancel') }}</button>
</div>
</div>
<div class="form-group row" id="mapBackgroundClearRow">
<label for="clearbackground" class="col-sm-3 control-label">{{ __('map.custom.edit.bg.clear_bg') }}</label>
<div class="col-sm-9">
<input type="hidden" id="mapBackgroundClearVal">
<button id="mapBackgroundClear" type="button" name="clearbackground" class="btn btn-primary" onclick="mapClearBackground();">{{ __('map.custom.edit.bg.clear_background') }}</button>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-12" id="savebg-alert">
</div>
</div>
<div class="modal-body tw-p-10">
<x-tabs class="tw-text-2xl" @tab-change="type=$event.detail" x-effect="activeTab = type">
<x-tab value="image" name="{{ __('map.custom.edit.bg.image') }}" class="tw-mt-10">
<x-input id="bgimage"
x-ref="bgimage"
type="file"
label="{{ __('map.custom.edit.bg.background') }}"
accept="image/png,image/jpeg,image/svg+xml,image/gif"
x-show="!image"
x-on:change="setImage($event)"></x-input>
<div x-show="image">
<span x-text="image"></span>
<button type="button" class="btn btn-danger" @click="clearImage">{{ __('map.custom.edit.bg.clear_background') }}</button>
</div>
</div>
</x-tab>
<x-tab value="color" name="{{ __('map.custom.edit.bg.color') }}" class="tw-mt-10">
<x-input id="bg-color" type="color" x-model="color"
class="tw-cursor-pointer tw-h-24 tw-w-48"
></x-input>
</x-tab>
<x-tab value="map" name="{{ __('map.custom.edit.bg.map') }}" class="tw-mt-5">
<x-input id="bg-lat" label="{{ __('map.custom.edit.bg.lat') }}" x-model="lat"></x-input>
<x-input id="bg-lng" label="{{ __('map.custom.edit.bg.lng') }}" x-model="lng"></x-input>
<x-input id="bg-zoom" label="{{ __('map.custom.edit.bg.zoom') }}" x-model="zoom"></x-input>
<button type="button" class="btn btn-primary tw-mt-2" @click="adjustMap">{{ __('map.custom.edit.bg.adjust_map') }}</button>
<button type="button" class="btn btn-primary tw-mt-2" @click="setMapAsImage" title="{{ __('map.custom.edit.bg.as_image_hint') }}" :disabled="saving_map_as_image" x-show="show_image_export">
<i class="fa-solid fa-circle-notch fa-spin" x-show="saving_map_as_image"></i>
{{ __('map.custom.edit.bg.as_image') }}
</button>
</x-tab>
<x-tab value="none" name="{{ __('map.custom.edit.bg.none') }}"></x-tab>
</x-tabs>
<div x-show="error">
<div class="tw-text-red-600" x-text="error"></div>
</div>
</div>
<div class="modal-footer">
<center>
<button type=button value="save" id="map-savebgButton" class="btn btn-primary" onclick="saveMapBackground()">{{ __('Save') }}</button>
<button type=button value="cancel" id="map-cancelbgButton" class="btn btn-primary" onclick="editMapBackgroundCancel()">{{ __('Cancel') }}</button>
</center>
<button type=button class="btn btn-primary" @click="saveBackground">{{ __('Save') }}</button>
<button type=button class="btn btn-default" @click="closeBackgroundModal">{{ __('Cancel') }}</button>
</div>
</div>
</div>
</div>
<script>
function mapChangeBackground() {
$("#mapBackgroundCancel").show();
}
function backgroundModalData() {
return {
initial_data: {{ Js::from($background_config) }},
initial_type: {{ Js::from($background_type) }},
type: 'none',
color: null,
lat: null,
lng: null,
zoom: null,
layer: null,
image: null,
show_image_export: true,
image_content: null,
saving_map_as_image: false,
error: '',
resetBackground() {
this.type = this.initial_type;
this.color = 'color' in this.initial_data ? this.initial_data.color : '#badaee';
this.lat = 'lat' in this.initial_data ? this.initial_data.lat : 40;
this.lng = 'lng' in this.initial_data ? this.initial_data.lng : -20;
this.zoom = 'zoom' in this.initial_data ? this.initial_data.zoom : 3;
this.layer = 'layer' in this.initial_data ? this.initial_data.layer : null;
this.image = this.initial_data['original_filename'];
this.image_content = null;
this.show_image_export = (! 'engine' in this.initial_data) || ! ['google', 'bing'].includes(this.initial_data['engine']);
this.error = '';
function mapChangeBackgroundCancel() {
$("#mapBackgroundCancel").hide();
$("#mapBackgroundSelect").val(null);
}
setCustomMapBackground('custom-map', this.type, this.initial_data);
// stop map interaction
document.getElementById('custom-map-bg-geo-map').style.zIndex = '1';
const leaflet = get_map('custom-map-bg-geo-map');
if (leaflet) {
disable_map_interaction(leaflet)
leaflet.off('zoomend');
leaflet.off('moveend');
leaflet.off('baselayerchange');
leaflet.setView(L.latLng(this.lat, this.lng), this.zoom);
}
},
setImage(event) {
this.image_content = event.target.files[0];
},
clearImage() {
this.image = null;
this.image_content = null;
},
setMapAsImage() {
setCustomMapBackground('custom-map', this.type, this.initial_data);
this.saving_map_as_image = true;
leafletImage(get_map('custom-map-bg-geo-map'), (err, canvas) => {
if (! canvas) {
this.error = err;
return;
}
function mapClearBackground() {
if($('#mapBackgroundClearVal').val()) {
$('#mapBackgroundClear').text('{{ __('map.custom.edit.bg.clear_background') }}');
$('#mapBackgroundClearVal').val('');
} else {
$('#mapBackgroundClear').text('{{ __('map.custom.edit.bg.keep_background') }}');
$('#mapBackgroundClearVal').val('clear');
this.type = 'image';
this.image = 'geo-map.jpg';
canvas.toBlob((blob) => this.image_content = blob, 'image/jpeg', 0.5);
this.saving_map_as_image = false;
});
},
saveBackground() {
if (this.type === 'image' && ! this.image_content) {
// change to none type when saving bg image with no file
// helps with mental work flow of clicking clear image -> save.
this.type = 'none';
}
let fd = new FormData();
fd.append('type', this.type);
if (this.type === 'color') {
fd.append('color', this.color);
} else if (this.type === 'image') {
fd.append('image', this.image_content, this.image);
}
if (this.type === 'map' || this.image === 'geo-map.png') {
// include map data when we converted a map to a static image
fd.append('lat', this.lat);
fd.append('lng', this.lng);
fd.append('zoom', this.zoom);
fd.append('layer', this.layer);
}
fetch({{ Js::from(route('maps.custom.background.save', ['map' => $map_id])) }}, {
method: 'POST',
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.head.querySelector('meta[name=\'csrf-token\']').content
},
body: fd
}).then((response) => {
if (response.status === 413) {
this.error = response.statusText;
return;
}
response.json().then(data => {
if (data.message) {
this.error = data.message;
} else {
setCustomMapBackground('custom-map', data.bgtype, data.bgdata);
this.initial_type = data.bgtype;
this.initial_data = data.bgdata;
// update jquery code
if (bgtype) {
bgtype = data.bgtype;
}
if (bgdata) {
bgdata = data.bgdata;
}
this.closeBackgroundModal();
}
})
})
.catch(() => {
this.error = 'Ooops! Something went wrong!'
});
},
adjustMap() {
let leaflet = init_map('custom-map-bg-geo-map', this.initial_data);
let adjustValues = () => {
const center = leaflet.getCenter();
this.lat = center.lat;
this.lng = center.lng;
this.zoom = leaflet.getZoom();
}
let layerChange = (event) => {this.layer = event.name};
leaflet._container.style.zIndex = '3';
enable_map_interaction(leaflet);
leaflet.on({
zoomend: adjustValues,
moveend: adjustValues,
baselayerchange: layerChange,
});
startBackgroundMapAdjust();
$('#bgModal').modal('hide');
},
closeBackgroundModal() {
$('#bgModal').modal('hide');
this.resetBackground();
}
}
}
function editMapBackgroundCancel() {
$('#mapBackgroundClear').text('{{ __('map.custom.edit.bg.clear_background') }}');
$('#mapBackgroundClearVal').val('');
$("#mapBackgroundCancel").hide();
$("#mapBackgroundSelect").val(null);
$('#bgModal').modal('hide');
}
function saveMapBackground() {
$("#map-savebgButton").attr('disabled','disabled');
$("#savebg-alert").text('{{ __('map.custom.edit.bg.saving') }}');
$("#savebg-alert").attr("class", "col-sm-12 alert alert-info");
var clearbackground = $('#mapBackgroundClearVal').val() ? 1 : 0;
var newbackground = $('#mapBackgroundSelect').prop('files').length ? $('#mapBackgroundSelect').prop('files')[0] : '';
var url = '{{ route('maps.custom.background.save', ['map' => $map_id]) }}';
var fd = new FormData();
fd.append('bgclear', clearbackground);
fd.append('bgimage', newbackground);
$.ajax({
url: url,
data: fd,
processData: false,
contentType: false,
type: 'POST'
}).done(function (data, status, resp) {
canvas = $("#custom-map").children()[0].canvas;
if(data['bgimage']) {
$(canvas).css('background-image','url({{ route('maps.custom.background', ['map' => $map_id]) }}?ver=' + data['bgversion'] + ')').css('background-size', 'cover');
bgimage = true;
} else {
$(canvas).css('background-image','');
bgimage = false;
}
$("#savebg-alert").attr("class", "col-sm-12");
$("#savebg-alert").text("");
editMapBackgroundCancel();
}).fail(function (resp, status, error) {
var data = resp.responseJSON;
if (data['message']) {
let alert_content = $("#savebg-alert");
alert_content.text(data['message']);
alert_content.attr("class", "col-sm-12 alert alert-danger");
} else {
let alert_content = $("#savebg-alert");
alert_content.text('{{ __('map.custom.edit.bg.save_error', ['code' => '?']) }}'.replace('?', resp.status));
alert_content.attr("class", "col-sm-12 alert alert-danger");
}
}).always(function (resp, status, error) {
$("#map-savebgButton").removeAttr('disabled');
});
}
</script>

View File

@@ -13,10 +13,11 @@
<div class="container-fluid">
<div class="row" id="control-row">
<div class="col-md-5">
<button type=button value="mapedit" id="map-editButton" class="btn btn-primary" onclick="editMapSettings();">{{ __('map.custom.edit.map.edit') }}</button>
<button type=button value="mapbg" id="map-bgButton" class="btn btn-primary" onclick="editMapBackground();">{{ __('map.custom.edit.bg.title') }}</button>
<button type=button value="editnodedefaults" id="map-nodeDefaultsButton" class="btn btn-primary" onclick="editNodeDefaults();">{{ __('map.custom.edit.node.edit_defaults') }}</button>
<button type=button value="editedgedefaults" id="map-edgeDefaultsButton" class="btn btn-primary" onclick="editEdgeDefaults();">{{ __('map.custom.edit.edge.edit_defaults') }}</button>
<button type=button value="mapedit" id="map-editButton" class="btn btn-primary" onclick="editMapSettings()">{{ __('map.custom.edit.map.edit') }}</button>
<button type=button value="mapbg" id="map-bgButton" class="btn btn-primary" onclick="editMapBackground()">{{ __('map.custom.edit.bg.title') }}</button>
<button type=button value="mapbg" id="map-bgEndAdjustButton" class="btn btn-primary" onclick="endBackgroundMapAdjust()" style="display:none">{{ __('map.custom.edit.bg.adjust_map_finish') }}</button>
<button type=button value="editnodedefaults" id="map-nodeDefaultsButton" class="btn btn-primary" onclick="editNodeDefaults()">{{ __('map.custom.edit.node.edit_defaults') }}</button>
<button type=button value="editedgedefaults" id="map-edgeDefaultsButton" class="btn btn-primary" onclick="editEdgeDefaults()">{{ __('map.custom.edit.edge.edit_defaults') }}</button>
</div>
<div class="col-md-2">
<center>
@@ -41,9 +42,15 @@
</div>
<div class="row">
<div class="col-md-12">
<center>
<div id="custom-map"></div>
</center>
<div id="map-container">
<div id="custom-map"></div>
<x-geo-map id="custom-map-bg-geo-map"
:init="$background_type == 'map'"
:width="$map_conf['width']"
:height="$map_conf['height']"
:config="$background_config"
readonly
/>
</div>
</div>
</div>
@@ -51,11 +58,35 @@
@section('javascript')
<script type="text/javascript" src="{{ asset('js/vis.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/leaflet.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/L.Control.Locate.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/leaflet-image.js') }}"></script>
@endsection
@push('styles')
<style>
#map-container {
display: grid;
grid-template: 1fr / 1fr;
place-items: center;
}
#custom-map {
grid-column: 1 / 1;
grid-row: 1 / 1;
z-index: 2;
}
#custom-map-bg-geo-map {
grid-column: 1 / 1;
grid-row: 1 / 1;
z-index: 1;
}
</style>
@endpush
@section('scripts')
<script type="text/javascript">
var bgimage = {{ $background ? "true" : "false" }};
var bgtype = {{ Js::from($background_type) }};
var bgdata = {{ Js::from($background_config) }};
var network;
var network_height;
var network_width;
@@ -64,6 +95,7 @@
var edge_nodes_map = [];
var node_device_map = {};
var custom_image_base = "{{ $base_url }}images/custommap/icons/";
var network_options = {{ Js::from($map_conf) }}
function edgeNodesRemove(nm_id, edgeid) {
// Remove old item from map if it exists
@@ -195,7 +227,7 @@
network_edges.flush();
var container = document.getElementById('custom-map');
var options = {!! json_encode($map_conf) !!};
var options = network_options;
// Set up the triggers for adding and editing map items
options['manipulation']['addNode'] = function (data, callback) {
@@ -280,17 +312,15 @@
};
network = new vis.Network(container, {nodes: network_nodes, edges: network_edges, stabilize: true}, options);
// width/height might be % get values in pixels
network_height = $($(container).children(".vis-network")[0]).height();
network_width = $($(container).children(".vis-network")[0]).width();
var centreY = parseInt(network_height / 2);
var centreX = parseInt(network_width / 2);
var centreY = Math.round(network_height / 2);
var centreX = Math.round(network_width / 2);
network.moveTo({position: {x: centreX, y: centreY}, scale: 1});
if(bgimage) {
canvas = $("#custom-map").children()[0].canvas;
$(canvas).css('background-image','url({{ route('maps.custom.background', ['map' => $map_id]) }}?ver={{$bgversion}})').css('background-size', 'cover');
}
setCustomMapBackground('custom-map', bgtype, bgdata);
network.on('doubleClick', function (properties) {
edge_id = null;
@@ -453,6 +483,11 @@
reverse_arrows = parseInt(data.reverse_arrows);
redrawLegend();
// update dimensions
network_options.width = data.width;
network_options.height = data.height;
$("#custom-map-bg-geo-map").css('width', data.width).css('height', data.height);
// Re-create the network because network.setSize() blanks out the map
CreateNetwork();
@@ -529,15 +564,7 @@
}
function editMapBackground() {
$("#mapBackgroundCancel").hide();
$("#mapBackgroundSelect").val(null);
if($("#custom-map").children()[0].canvas.style.backgroundImage) {
$("#mapBackgroundClearRow").show();
} else {
$("#mapBackgroundClearRow").hide();
}
$('#bgModal').modal({backdrop: 'static', keyboard: false}, 'show');
$('#bgModal').modal('show');
}
function nodeStyleChange() {
@@ -1192,6 +1219,23 @@
}).observe(targetNode, {attributes: false, childList: true, subtree: false});
}
function startBackgroundMapAdjust() {
$('#map-editButton,#map-nodeDefaultsButton,#map-edgeDefaultsButton,#map-bgButton').hide();
$('#map-bgEndAdjustButton').show();
}
function endBackgroundMapAdjust() {
$('#map-editButton,#map-nodeDefaultsButton,#map-edgeDefaultsButton,#map-bgButton').show();
$('#map-bgEndAdjustButton').hide();
document.getElementById('custom-map-bg-geo-map').style.zIndex = '1';
const leaflet = get_map('custom-map-bg-geo-map');
if (leaflet) {
disable_map_interaction(leaflet)
}
editMapBackground();
}
$(document).ready(function () {
init_select2('#devicesearch', 'device', {limit: 100}, '', '{{ __('map.custom.edit.node.device_select') }}', {dropdownParent: $('#nodeModal')});
$("#devicesearch").on("select2:select", nodeDeviceSelect);

View File

@@ -11,9 +11,16 @@
</div>
<div class="row">
<div class="col-md-12">
<center>
<div id="map-container">
<div id="custom-map"></div>
</center>
<x-geo-map id="custom-map-bg-geo-map"
:init="$background_type == 'map'"
:width="$map_conf['width']"
:height="$map_conf['height']"
:config="$background_config"
readonly
/>
</div>
</div>
</div>
</div>
@@ -21,11 +28,34 @@
@section('javascript')
<script type="text/javascript" src="{{ asset('js/vis.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/leaflet.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/L.Control.Locate.min.js') }}"></script>
@endsection
@push('styles')
<style>
#map-container {
display: grid;
grid-template: 1fr / 1fr;
place-items: center;
}
#custom-map {
grid-column: 1 / 1;
grid-row: 1 / 1;
z-index: 2;
}
#custom-map-bg-geo-map {
grid-column: 1 / 1;
grid-row: 1 / 1;
z-index: 1;
}
</style>
@endpush
@section('scripts')
<script type="text/javascript">
var bgimage = {{ $background ? "true" : "false" }};
var bgtype = {{ Js::from($background_type) }};
var bgdata = {{ Js::from($background_config) }};
var screenshot = {{ $screenshot ? "true" : "false" }};
var reverse_arrows = {{$reverse_arrows}};
var legend = @json($legend);
@@ -38,6 +68,7 @@
var node_device_map = {};
var node_link_map = {};
var custom_image_base = "{{ $base_url }}images/custommap/icons/";
var network_options = {{ Js::from($map_conf) }};
function legendPctColour(pct) {
if (pct < 0) {
@@ -99,20 +130,16 @@
network_edges.flush();
var container = document.getElementById('custom-map');
var options = {!! json_encode($map_conf) !!};
network = new vis.Network(container, {nodes: network_nodes, edges: network_edges, stabilize: true}, network_options);
network = new vis.Network(container, {nodes: network_nodes, edges: network_edges, stabilize: true}, options);
// width/height might be % get values in pixels
network_height = $($(container).children(".vis-network")[0]).height();
network_width = $($(container).children(".vis-network")[0]).width();
var centreY = parseInt(network_height / 2);
var centreX = parseInt(network_width / 2);
var centreY = Math.round(network_height / 2);
var centreX = Math.round(network_width / 2);
network.moveTo({position: {x: centreX, y: centreY}, scale: 1});
if(bgimage) {
canvas = $("#custom-map").children()[0].canvas;
$(canvas).css('background-image','url({{ route('maps.custom.background', ['map' => $map_id]) }}?ver={{$bgversion}})').css('background-size', 'cover');
}
setCustomMapBackground('custom-map', bgtype, bgdata);
network.on('doubleClick', function (properties) {
edge_id = null;