From 071076149ae984807a3012df415a0fddcf6d93f6 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Mon, 9 Sep 2024 02:09:19 -0500 Subject: [PATCH] Improved module controls (#16372) * Improved module controls Ability to clear device module overrides from webui Ability to clear all database data for a module (helpful for module you have disabled that still have data) Database reset only works for modern modules. * Update functions.php --- LibreNMS/Interfaces/Module.php | 7 +- LibreNMS/Modules/Availability.php | 9 +- LibreNMS/Modules/Core.php | 9 +- LibreNMS/Modules/EntityPhysical.php | 9 +- LibreNMS/Modules/Isis.php | 29 ++-- LibreNMS/Modules/LegacyModule.php | 9 +- LibreNMS/Modules/Mempools.php | 9 +- LibreNMS/Modules/Mpls.php | 32 +++-- LibreNMS/Modules/Nac.php | 9 +- LibreNMS/Modules/Netstats.php | 9 +- LibreNMS/Modules/Os.php | 9 +- LibreNMS/Modules/Ospf.php | 20 ++- LibreNMS/Modules/PortsStack.php | 9 +- LibreNMS/Modules/PrinterSupplies.php | 9 +- LibreNMS/Modules/Slas.php | 9 +- LibreNMS/Modules/Stp.php | 13 +- LibreNMS/Modules/Vminfo.php | 9 +- LibreNMS/Modules/Xdsl.php | 13 +- .../Device/Tabs/ModuleController.php | 59 ++++++++ app/Models/Device.php | 4 +- .../forms/discovery-module-update.inc.php | 27 ---- .../html/forms/poller-module-update.inc.php | 26 ---- .../html/pages/device/edit/modules.inc.php | 133 +++++++++++++++--- routes/web.php | 2 + 24 files changed, 342 insertions(+), 131 deletions(-) create mode 100644 app/Http/Controllers/Device/Tabs/ModuleController.php delete mode 100644 includes/html/forms/discovery-module-update.inc.php delete mode 100644 includes/html/forms/poller-module-update.inc.php diff --git a/LibreNMS/Interfaces/Module.php b/LibreNMS/Interfaces/Module.php index 3a493a73c2..35c6078dd6 100644 --- a/LibreNMS/Interfaces/Module.php +++ b/LibreNMS/Interfaces/Module.php @@ -65,13 +65,18 @@ interface Module */ public function poll(OS $os, DataStorageInterface $datastore): void; + /** + * Check if data exists for this module + */ + public function dataExists(Device $device): bool; + /** * Remove all DB data for this module. * This will be run when the module is disabled. * * @param \App\Models\Device $device */ - public function cleanup(Device $device): void; + public function cleanup(Device $device): int; /** * Dump current module data for the given device for tests. diff --git a/LibreNMS/Modules/Availability.php b/LibreNMS/Modules/Availability.php index 8654c54b37..ee283453cc 100644 --- a/LibreNMS/Modules/Availability.php +++ b/LibreNMS/Modules/Availability.php @@ -100,12 +100,17 @@ class Availability implements Module $os->getDevice()->availability()->whereNotIn('availability_id', $valid_ids)->delete(); } + public function dataExists(Device $device): bool + { + return $device->availability()->exists(); + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->availability()->delete(); + return $device->availability()->delete(); } /** diff --git a/LibreNMS/Modules/Core.php b/LibreNMS/Modules/Core.php index 4a34a414df..281af8bf32 100644 --- a/LibreNMS/Modules/Core.php +++ b/LibreNMS/Modules/Core.php @@ -125,9 +125,14 @@ class Core implements Module $device->save(); } - public function cleanup(Device $device): void + public function dataExists(Device $device): bool { - // nothing to cleanup + return false; // no module specific data + } + + public function cleanup(Device $device): int + { + return 0; // nothing to cleanup } /** diff --git a/LibreNMS/Modules/EntityPhysical.php b/LibreNMS/Modules/EntityPhysical.php index 4996c247f8..8bb32ab9cc 100644 --- a/LibreNMS/Modules/EntityPhysical.php +++ b/LibreNMS/Modules/EntityPhysical.php @@ -58,12 +58,17 @@ class EntityPhysical implements Module // no polling } + public function dataExists(Device $device): bool + { + return $device->entityPhysical()->exists(); + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->entityPhysical()->delete(); + return $device->entityPhysical()->delete(); } /** diff --git a/LibreNMS/Modules/Isis.php b/LibreNMS/Modules/Isis.php index 8da0665932..5166b6c97f 100644 --- a/LibreNMS/Modules/Isis.php +++ b/LibreNMS/Modules/Isis.php @@ -107,18 +107,6 @@ class Isis implements Module $updated->each->save(); } - /** - * Remove all DB data for this module. - * This will be run when the module is disabled. - */ - public function cleanup(Device $device): void - { - $device->isisAdjacencies()->delete(); - - // clean up legacy components from old code - $device->components()->where('type', 'ISIS')->delete(); - } - public function discoverIsIsMib(OS $os): Collection { // Check if the device has any ISIS enabled interfaces @@ -197,6 +185,23 @@ class Isis implements Module return (int) (max($data['isisISAdjLastUpTime'] ?? 1, 1) / 100); } + public function dataExists(Device $device): bool + { + return $device->isisAdjacencies()->exists() || $device->components()->where('type', 'ISIS')->exists(); + } + + /** + * Remove all DB data for this module. + * This will be run when the module is disabled. + */ + public function cleanup(Device $device): int + { + // clean up legacy components from old code + $legacyDeleted = $device->components()->where('type', 'ISIS')->delete(); + + return $device->isisAdjacencies()->delete() + $legacyDeleted; + } + /** * @inheritDoc */ diff --git a/LibreNMS/Modules/LegacyModule.php b/LibreNMS/Modules/LegacyModule.php index c87d9e3af5..9640822312 100644 --- a/LibreNMS/Modules/LegacyModule.php +++ b/LibreNMS/Modules/LegacyModule.php @@ -117,9 +117,14 @@ class LegacyModule implements Module Debug::enableErrorReporting(); // and back to normal } - public function cleanup(Device $device): void + public function dataExists(Device $device): bool { - // TODO: Implement cleanup() method. + return false; // impossible to determine for legacy modules + } + + public function cleanup(Device $device): int + { + return 0; // Not possible to cleanup legacy modules } /** diff --git a/LibreNMS/Modules/Mempools.php b/LibreNMS/Modules/Mempools.php index ce4c22e9dc..111dc3d055 100644 --- a/LibreNMS/Modules/Mempools.php +++ b/LibreNMS/Modules/Mempools.php @@ -148,9 +148,14 @@ class Mempools implements Module return $mempools; } - public function cleanup(Device $device): void + public function dataExists(Device $device): bool { - $device->mempools()->delete(); + return $device->mempools()->exists(); + } + + public function cleanup(Device $device): int + { + return $device->mempools()->delete(); } /** diff --git a/LibreNMS/Modules/Mpls.php b/LibreNMS/Modules/Mpls.php index 9afa7b4d84..b2cf1bf1c4 100644 --- a/LibreNMS/Modules/Mpls.php +++ b/LibreNMS/Modules/Mpls.php @@ -165,20 +165,34 @@ class Mpls implements Module } } + public function dataExists(Device $device): bool + { + return $device->mplsLsps()->exists() + || $device->mplsLspPaths()->exists() + || $device->mplsSdps()->exists() + || $device->mplsServices()->exists() + || $device->mplsSaps()->exists() + || $device->mplsSdpBinds()->exists() + || $device->mplsTunnelArHops()->exists() + || $device->mplsTunnelCHops()->exists(); + } + /** * Remove all DB data for this module. * This will be run when the module is disabled. */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->mplsLsps()->delete(); - $device->mplsLspPaths()->delete(); - $device->mplsSdps()->delete(); - $device->mplsServices()->delete(); - $device->mplsSaps()->delete(); - $device->mplsSdpBinds()->delete(); - $device->mplsTunnelArHops()->delete(); - $device->mplsTunnelCHops()->delete(); + $deleted = $device->mplsLsps()->delete(); + $deleted += $device->mplsLspPaths()->delete(); + $deleted += $device->mplsSdps()->delete(); + $deleted += $device->mplsServices()->delete(); + $deleted += $device->mplsSaps()->delete(); + $deleted += $device->mplsSdpBinds()->delete(); + $deleted += $device->mplsTunnelArHops()->delete(); + $deleted += $device->mplsTunnelCHops()->delete(); + + return $deleted; } /** diff --git a/LibreNMS/Modules/Nac.php b/LibreNMS/Modules/Nac.php index e83c904af5..9f830cf977 100644 --- a/LibreNMS/Modules/Nac.php +++ b/LibreNMS/Modules/Nac.php @@ -113,13 +113,18 @@ class Nac implements Module } } + public function dataExists(Device $device): bool + { + return $device->portsNac()->exists(); + } + /** * Remove all DB data for this module. * This will be run when the module is disabled. */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->portsNac()->delete(); + return $device->portsNac()->delete(); } /** diff --git a/LibreNMS/Modules/Netstats.php b/LibreNMS/Modules/Netstats.php index 912480f884..dbff4387c7 100644 --- a/LibreNMS/Modules/Netstats.php +++ b/LibreNMS/Modules/Netstats.php @@ -227,12 +227,17 @@ class Netstats implements Module } } + public function dataExists(Device $device): bool + { + return false; // no database data + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - // no cleanup + return 0; // no cleanup } /** diff --git a/LibreNMS/Modules/Os.php b/LibreNMS/Modules/Os.php index 8b00046c3d..8ae3ef75d4 100644 --- a/LibreNMS/Modules/Os.php +++ b/LibreNMS/Modules/Os.php @@ -109,12 +109,17 @@ class Os implements Module $this->handleChanges($os); } + public function dataExists(Device $device): bool + { + return false; // data part of device + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - // no cleanup needed + return 0; // no cleanup needed } /** diff --git a/LibreNMS/Modules/Ospf.php b/LibreNMS/Modules/Ospf.php index 321b4b840e..a839e593c1 100644 --- a/LibreNMS/Modules/Ospf.php +++ b/LibreNMS/Modules/Ospf.php @@ -243,15 +243,25 @@ class Ospf implements Module } } + public function dataExists(Device $device): bool + { + return $device->ospfPorts()->exists() + || $device->ospfNbrs()->exists() + || $device->ospfAreas()->exists() + || $device->ospfInstances()->exists(); + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->ospfPorts()->delete(); - $device->ospfNbrs()->delete(); - $device->ospfAreas()->delete(); - $device->ospfInstances()->delete(); + $deleted = $device->ospfPorts()->delete(); + $deleted += $device->ospfNbrs()->delete(); + $deleted += $device->ospfAreas()->delete(); + $deleted += $device->ospfInstances()->delete(); + + return $deleted; } /** diff --git a/LibreNMS/Modules/PortsStack.php b/LibreNMS/Modules/PortsStack.php index fe70a2147c..13fb79edb5 100644 --- a/LibreNMS/Modules/PortsStack.php +++ b/LibreNMS/Modules/PortsStack.php @@ -101,12 +101,17 @@ class PortsStack implements Module // no polling } + public function dataExists(Device $device): bool + { + return $device->portsStack()->exists(); + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->portsStack()->delete(); + return $device->portsStack()->delete(); } /** diff --git a/LibreNMS/Modules/PrinterSupplies.php b/LibreNMS/Modules/PrinterSupplies.php index 5812efbeba..39e845198f 100644 --- a/LibreNMS/Modules/PrinterSupplies.php +++ b/LibreNMS/Modules/PrinterSupplies.php @@ -135,13 +135,18 @@ class PrinterSupplies implements Module } } + public function dataExists(Device $device): bool + { + return $device->printerSupplies()->exists(); + } + /** * Remove all DB data for this module. * This will be run when the module is disabled. */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->printerSupplies()->delete(); + return $device->printerSupplies()->delete(); } /** diff --git a/LibreNMS/Modules/Slas.php b/LibreNMS/Modules/Slas.php index 123da19de2..8d9c092c1a 100644 --- a/LibreNMS/Modules/Slas.php +++ b/LibreNMS/Modules/Slas.php @@ -102,13 +102,18 @@ class Slas implements Module } } + public function dataExists(Device $device): bool + { + return $device->slas()->exists(); + } + /** * Remove all DB data for this module. * This will be run when the module is disabled. */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->slas()->delete(); + return $device->slas()->delete(); } /** diff --git a/LibreNMS/Modules/Stp.php b/LibreNMS/Modules/Stp.php index 4d00d43db0..4755e123d0 100644 --- a/LibreNMS/Modules/Stp.php +++ b/LibreNMS/Modules/Stp.php @@ -88,10 +88,17 @@ class Stp implements Module $this->syncModels($device, 'stpPorts', $ports); } - public function cleanup(Device $device): void + public function dataExists(Device $device): bool { - $device->stpInstances()->delete(); - $device->stpPorts()->delete(); + return $device->stpInstances()->exists() || $device->stpPorts()->exists(); + } + + public function cleanup(Device $device): int + { + $deleted = $device->stpInstances()->delete(); + $deleted += $device->stpPorts()->delete(); + + return $deleted; } /** diff --git a/LibreNMS/Modules/Vminfo.php b/LibreNMS/Modules/Vminfo.php index c8cebc9b3b..077f653bdb 100644 --- a/LibreNMS/Modules/Vminfo.php +++ b/LibreNMS/Modules/Vminfo.php @@ -93,12 +93,17 @@ class Vminfo implements \LibreNMS\Interfaces\Module $this->discover($os); } + public function dataExists(Device $device): bool + { + return $device->vminfo()->exists(); + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->vminfo()->delete(); + return $device->vminfo()->delete(); } /** diff --git a/LibreNMS/Modules/Xdsl.php b/LibreNMS/Modules/Xdsl.php index 16950881e0..bdd6c37396 100644 --- a/LibreNMS/Modules/Xdsl.php +++ b/LibreNMS/Modules/Xdsl.php @@ -101,13 +101,20 @@ class Xdsl implements Module } } + public function dataExists(Device $device): bool + { + return $device->portsAdsl()->exists() || $device->portsVdsl()->exists(); + } + /** * @inheritDoc */ - public function cleanup(Device $device): void + public function cleanup(Device $device): int { - $device->portsAdsl()->delete(); - $device->portsVdsl()->delete(); + $deleted = $device->portsAdsl()->delete(); + $deleted += $device->portsVdsl()->delete(); + + return $deleted; } /** diff --git a/app/Http/Controllers/Device/Tabs/ModuleController.php b/app/Http/Controllers/Device/Tabs/ModuleController.php new file mode 100644 index 0000000000..8d642736cc --- /dev/null +++ b/app/Http/Controllers/Device/Tabs/ModuleController.php @@ -0,0 +1,59 @@ +validate($request, [ + 'discovery' => 'in:true,false,clear', + 'polling' => 'in:true,false,clear', + ]); + + if ($request->has('discovery')) { + $discovery = $request->get('discovery'); + if ($discovery == 'clear') { + $device->forgetAttrib('discover_' . $module); + } else { + $device->setAttrib('discover_' . $module, $discovery == 'true' ? 1 : 0); + } + } + + if ($request->has('polling')) { + $polling = $request->get('polling'); + if ($polling == 'clear') { + $device->forgetAttrib('poll_' . $module); + } else { + $device->setAttrib('poll_' . $module, $polling == 'true' ? 1 : 0); + } + } + + // return the module status + return response()->json([ + 'discovery' => (bool) $device->getAttrib('discover_' . $module, Config::getCombined($device->os, 'discovery_modules')[$module] ?? false), + 'polling' => (bool) $device->getAttrib('poll_' . $module, Config::getCombined($device->os, 'poller_modules')[$module] ?? false), + ]); + } + + public function delete(Device $device, string $module): JsonResponse + { + Gate::authorize('delete', $device); + + $deleted = Module::fromName($module)->cleanup($device); + + return response()->json([ + 'deleted' => $deleted, + ]); + } +} diff --git a/app/Models/Device.php b/app/Models/Device.php index 842e5a7715..d43fd1f4bb 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -406,9 +406,9 @@ class Device extends BaseModel $this->save(); } - public function getAttrib($name) + public function getAttrib($name, $default = null) { - return $this->attribs->pluck('attrib_value', 'attrib_type')->get($name); + return $this->attribs->pluck('attrib_value', 'attrib_type')->get($name, $default); } public function setAttrib($name, $value) diff --git a/includes/html/forms/discovery-module-update.inc.php b/includes/html/forms/discovery-module-update.inc.php deleted file mode 100644 index cf36002ad0..0000000000 --- a/includes/html/forms/discovery-module-update.inc.php +++ /dev/null @@ -1,27 +0,0 @@ -hasGlobalAdmin()) { - exit('ERROR: You need to be admin'); -} - -$device['device_id'] = $_POST['device_id']; -$module = 'discover_' . $_POST['discovery_module']; - -if (! isset($module) && validate_device_id($device['device_id']) === false) { - echo 'error with data'; - exit; -} else { - if ($_POST['state'] == 'true') { - $state = 1; - } elseif ($_POST['state'] == 'false') { - $state = 0; - } else { - $state = 0; - } - - set_dev_attrib($device, $module, $state); -} diff --git a/includes/html/forms/poller-module-update.inc.php b/includes/html/forms/poller-module-update.inc.php deleted file mode 100644 index d8f6e9f7e4..0000000000 --- a/includes/html/forms/poller-module-update.inc.php +++ /dev/null @@ -1,26 +0,0 @@ -hasGlobalAdmin()) { - exit('ERROR: You need to be admin'); -} - -// FUA -$device['device_id'] = $_POST['device_id']; -$module = 'poll_' . $_POST['poller_module']; - -if (! isset($module) && validate_device_id($device['device_id']) === false) { - echo 'error with data'; - exit; -} else { - if ($_POST['state'] == 'true') { - $state = 1; - } elseif ($_POST['state'] == 'false') { - $state = 0; - } else { - $state = 0; - } - - set_dev_attrib($device, $module, $state); -} diff --git a/includes/html/pages/device/edit/modules.inc.php b/includes/html/pages/device/edit/modules.inc.php index 1b12a626f2..4f8de0bdf7 100644 --- a/includes/html/pages/device/edit/modules.inc.php +++ b/includes/html/pages/device/edit/modules.inc.php @@ -14,16 +14,18 @@ Global OS Device + Override getAttribs(); $poller_module_names = $settings['poller_modules']; $discovery_module_names = $settings['discovery_modules']; @@ -87,11 +89,20 @@ foreach ($poller_modules as $module => $module_status) { '; - echo ''; + echo ''; echo ' + '; + + echo ''; + + $moduleInstance = Module::fromName($module); + if ($moduleInstance->dataExists(DeviceCache::getPrimary())) { + echo ''; + } + +echo ' '; } @@ -107,6 +118,7 @@ foreach ($poller_modules as $module => $module_status) { Global OS Device + Override @@ -172,16 +184,45 @@ foreach ($discovery_modules as $module => $module_status) { '; - echo ''; + echo ''; echo ' + '; + + echo ''; + + $moduleInstance = Module::fromName($module); + if ($moduleInstance->dataExists(DeviceCache::getPrimary())) { + echo ''; + } + + echo ' '; } echo ' + '; ?> @@ -190,14 +231,12 @@ echo ' $("[name='poller-module']").bootstrapSwitch('offColor','danger'); $('input[name="poller-module"]').on('switchChange.bootstrapSwitch', function(event, state) { event.preventDefault(); - var $this = $(this); - var poller_module = $(this).data("poller_module"); - var device_id = $(this).data("device_id"); + var poller_module = $(this).attr('id').replace('poller-toggle-', ''); $.ajax({ - type: 'POST', - url: 'ajax_form.php', - data: { type: "poller-module-update", poller_module: poller_module, device_id: device_id, state: state}, - dataType: "html", + type: 'PUT', + url: ' $device['device_id'], 'module' => ':module']) ?>'.replace(':module', poller_module), + data: { polling: state}, + dataType: "json", success: function(data){ //alert('good'); if(state) @@ -212,6 +251,7 @@ echo ' $('#poller-module-'+poller_module).addClass('text-danger'); $('#poller-module-'+poller_module).html('Disabled'); } + $('#poller-reset-button-' + poller_module).css('visibility', 'visible'); }, error:function(){ //alert('bad'); @@ -221,14 +261,12 @@ echo ' $("[name='discovery-module']").bootstrapSwitch('offColor','danger'); $('input[name="discovery-module"]').on('switchChange.bootstrapSwitch', function(event, state) { event.preventDefault(); - var $this = $(this); - var discovery_module = $(this).data("discovery_module"); - var device_id = $(this).data("device_id"); + var discovery_module = $(this).attr('id').replace('discovery-toggle-', ''); $.ajax({ - type: 'POST', - url: 'ajax_form.php', - data: { type: "discovery-module-update", discovery_module: discovery_module, device_id: device_id, state: state}, - dataType: "html", + type: 'PUT', + url: ' $device['device_id'], 'module' => ':module']) ?>'.replace(':module', discovery_module), + data: { discovery: state}, + dataType: "json", success: function(data){ //alert('good'); if(state) @@ -243,10 +281,67 @@ echo ' $('#discovery-module-'+discovery_module).addClass('text-danger'); $('#discovery-module-'+discovery_module).html('Disabled'); } + $('#discovery-reset-button-' + discovery_module).css('visibility', 'visible'); }, error:function(){ //alert('bad'); } }); }); + $('#delete-module-data').on('show.bs.modal', function (event) { + $('.dialog-module-name').text($(event.relatedTarget).data('module-name')); + $('#module-data-delete-button').data('module', $(event.relatedTarget).data('module')); + }); + $('.poller-reset-button').on('click', function (event) { + var poller_module = $(this).attr('id').replace('poller-reset-button-', ''); + $.ajax({ + type: 'PUT', + url: ' $device['device_id'], 'module' => ':module']) ?>'.replace(':module', poller_module), + data: { polling: 'clear'}, + dataType: "json", + success: function(data){ + $('#poller-toggle-'+poller_module).bootstrapSwitch('state', data.polling, true); + $('#poller-module-'+poller_module).removeClass('text-danger'); + $('#poller-module-'+poller_module).removeClass('text-success'); + $('#poller-module-'+poller_module).html('Unset'); + $('#poller-reset-button-'+poller_module).css('visibility', 'hidden'); + }, + error:function(){ + } + }); + }); + $('.discovery-reset-button').on('click', function (event) { + var discovery_module = $(this).attr('id').replace('discovery-reset-button-', ''); + $.ajax({ + type: 'PUT', + url: ' $device['device_id'], 'module' => ':module']) ?>'.replace(':module', discovery_module), + data: { discovery: 'clear'}, + dataType: "json", + success: function(data){ + $('#discovery-toggle-'+discovery_module).bootstrapSwitch('state', data.discovery, true); + $('#discovery-module-'+discovery_module).removeClass('text-danger'); + $('#discovery-module-'+discovery_module).removeClass('text-success'); + $('#discovery-module-'+discovery_module).html('Unset'); + $('#discovery-reset-button-'+discovery_module).css('visibility', 'hidden'); + }, + error:function(){ + } + }); + }); + $('#module-data-delete-button').on('click', function (event) { + var module = $(this).data('module'); + $.ajax({ + type: 'DELETE', + url: ' $device['device_id'], 'module' => ':module']) ?>'.replace(':module', module), + data: {}, + dataType: "json", + success: function(data){ + console.log('Deleted: ' + data.deleted); + $('#delete-module-data').modal('hide'); + $('.delete-button-' + module).remove(); + }, + error:function(){ + } + }); + }) diff --git a/routes/web.php b/routes/web.php index fe17661b3b..134b512f39 100644 --- a/routes/web.php +++ b/routes/web.php @@ -75,6 +75,8 @@ Route::middleware(['auth'])->group(function () { // Device Tabs Route::prefix('device/{device}')->namespace('Device\Tabs')->name('device.')->group(function () { Route::put('notes', 'NotesController@update')->name('notes.update'); + Route::put('module/{module}', [\App\Http\Controllers\Device\Tabs\ModuleController::class, 'update'])->name('module.update'); + Route::delete('module/{module}', [\App\Http\Controllers\Device\Tabs\ModuleController::class, 'delete'])->name('module.delete'); }); Route::match(['get', 'post'], 'device/{device}/{tab?}/{vars?}', 'DeviceController@index')