From 6242f941f6e3d27a7d47afb5d0c75bfdd094ad9d Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Mon, 24 Sep 2018 02:07:00 -0500 Subject: [PATCH] Update Eventlog WebUI/backend to use ajax (#9252) * WIP Eventlog table * Initial Eventlog rework * fromdevice is not a request parameter * updates * remove unneeded field * Cleanups --- LibreNMS/Util/Rewrite.php | 110 ++++++++++++++++ LibreNMS/Util/Url.php | 88 ++++++++++++- .../Controllers/Select/EventlogController.php | 74 +++++++++++ .../Controllers/Table/EventlogController.php | 119 ++++++++++++++++++ app/Models/Device.php | 22 ++++ app/Models/Eventlog.php | 56 +++++++++ app/Models/Port.php | 103 ++++++++++++++- app/Models/Sensor.php | 5 + app/Providers/AppServiceProvider.php | 11 ++ html/includes/common/eventlog.inc.php | 9 +- html/pages/eventlog.inc.php | 77 +++++++----- includes/rewrites.php | 52 +------- routes/web.php | 4 +- 13 files changed, 642 insertions(+), 88 deletions(-) create mode 100644 LibreNMS/Util/Rewrite.php create mode 100644 app/Http/Controllers/Select/EventlogController.php create mode 100644 app/Http/Controllers/Table/EventlogController.php create mode 100644 app/Models/Eventlog.php diff --git a/LibreNMS/Util/Rewrite.php b/LibreNMS/Util/Rewrite.php new file mode 100644 index 0000000000..3fd5e0f26b --- /dev/null +++ b/LibreNMS/Util/Rewrite.php @@ -0,0 +1,110 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2018 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Util; + +class Rewrite +{ + public static function normalizeIfType($type) + { + $rewrite_iftype = [ + 'frameRelay' => 'Frame Relay', + 'ethernetCsmacd' => 'Ethernet', + 'softwareLoopback' => 'Loopback', + 'tunnel' => 'Tunnel', + 'propVirtual' => 'Virtual Int', + 'ppp' => 'PPP', + 'ds1' => 'DS1', + 'pos' => 'POS', + 'sonet' => 'SONET', + 'slip' => 'SLIP', + 'mpls' => 'MPLS Layer', + 'l2vlan' => 'VLAN Subif', + 'atm' => 'ATM', + 'aal5' => 'ATM AAL5', + 'atmSubInterface' => 'ATM Subif', + 'propPointToPointSerial' => 'PtP Serial', + ]; + + if (isset($rewrite_iftype[$type])) { + return $rewrite_iftype[$type]; + } + + return $type; + } + + public static function normalizeIfName($name) + { + $rewrite_ifname = [ + 'ether' => 'Ether', + 'gig' => 'Gig', + 'fast' => 'Fast', + 'ten' => 'Ten', + '-802.1q vlan subif' => '', + '-802.1q' => '', + 'bvi' => 'BVI', + 'vlan' => 'Vlan', + 'tunnel' => 'Tunnel', + 'serial' => 'Serial', + '-aal5 layer' => ' aal5', + 'null' => 'Null', + 'atm' => 'ATM', + 'port-channel' => 'Port-Channel', + 'dial' => 'Dial', + 'hp procurve switch software loopback interface' => 'Loopback Interface', + 'control plane interface' => 'Control Plane', + 'loop' => 'Loop', + 'bundle-ether' => 'Bundle-Ether', + ]; + + return str_ireplace(array_keys($rewrite_ifname), array_values($rewrite_ifname), $name); + } + + public static function shortenIfName($name) + { + $rewrite_shortif = [ + 'tengigabitethernet' => 'Te', + 'ten-gigabitethernet' => 'Te', + 'tengige' => 'Te', + 'gigabitethernet' => 'Gi', + 'fastethernet' => 'Fa', + 'ethernet' => 'Et', + 'serial' => 'Se', + 'pos' => 'Pos', + 'port-channel' => 'Po', + 'atm' => 'Atm', + 'null' => 'Null', + 'loopback' => 'Lo', + 'dialer' => 'Di', + 'vlan' => 'Vlan', + 'tunnel' => 'Tunnel', + 'serviceinstance' => 'SI', + 'dwdm' => 'DWDM', + 'bundle-ether' => 'BE', + ]; + + return str_ireplace(array_keys($rewrite_shortif), array_values($rewrite_shortif), $name); + } +} diff --git a/LibreNMS/Util/Url.php b/LibreNMS/Util/Url.php index a30d0e386e..9cf2edbe98 100644 --- a/LibreNMS/Util/Url.php +++ b/LibreNMS/Util/Url.php @@ -26,6 +26,7 @@ namespace LibreNMS\Util; use App\Models\Device; +use App\Models\Port; use Auth; use Carbon\Carbon; use LibreNMS\Config; @@ -106,18 +107,76 @@ class Url $link = Url::overlibLink($url, $text, $contents, $class); } - if (Auth::user()->hasGlobalRead() || $device->users()->where('devices_perms.user_id', Auth::id())->exists()) { + if ($device->canAccess(Auth::user())) { return $link; } else { return $device->displayName(); } } + /** + * @param Port $port + * @param string $text + * @param string $type + * @param boolean $overlib + * @param boolean $single_graph + * @return mixed|string + */ + public static function portLink($port, $text = null, $type = null, $overlib = true, $single_graph = false) + { + + $label = Rewrite::normalizeIfName($port->getLabel()); + if (!$text) { + $text = $label; + } + + $content = '
' . addslashes(htmlentities($port->device->displayName() . ' - ' . $label)) . '
'; + if ($port->ifAlias) { + $content .= addslashes(htmlentities($port->ifAlias)) . '
'; + } + + $content .= "
"; + $graph_array = [ + 'type' => $type ?: 'port_bits', + 'legend' => 'yes', + 'height' => 100, + 'width' => 340, + 'to' => Carbon::now()->timestamp, + 'from' => Carbon::now()->subDay()->timestamp, + 'id' => $port->port_id, + ]; + + $content .= self::graphTag($graph_array); + if (!$single_graph) { + $graph_array['from'] = Carbon::now()->subWeek()->timestamp; + $content .= self::graphTag($graph_array); + $graph_array['from'] = Carbon::now()->subMonth()->timestamp; + $content .= self::graphTag($graph_array); + $graph_array['from'] = Carbon::now()->subYear()->timestamp; + $content .= self::graphTag($graph_array); + } + + $content .= '
'; + + if (!$overlib) { + return $content; + } elseif ($port->canAccess(Auth::user())) { + return self::overlibLink(self::portUrl($port), $text, $content, self::portLinkDisplayClass($port)); + } + + return Rewrite::normalizeIfName($text); + } + public static function deviceUrl($device, $vars = []) { return self::generate(['page' => 'device', 'device' => $device->device_id], $vars); } + public static function portUrl($port, $vars = []) + { + return self::generate(['page' => 'device', 'device' => $port->device_id, 'tab' => 'port', 'port' => $port->port_id], $vars); + } + public static function generate($vars, $new_vars = []) { $vars = array_merge($vars, $new_vars); @@ -134,6 +193,20 @@ class Url return $url; } + /** + * @param array $args + * @return string + */ + public static function graphTag($args) + { + $urlargs = []; + foreach ($args as $key => $arg) { + $urlargs[] = $key . '=' . urlencode($arg); + } + + return ''; + } + public static function overlibLink($url, $text, $contents, $class = null) { $contents = "
" . $contents . '
'; @@ -213,4 +286,17 @@ class Url return $device->status ? 'list-device' : 'list-device-down'; } + + private static function portLinkDisplayClass($port) + { + if ($port->ifAdminStatus == "down") { + return "interface-admindown"; + } + + if ($port->ifAdminStatus == "up" && $port->ifOperStatus == "down") { + return "interface-updown"; + } + + return "interface-upup"; + } } diff --git a/app/Http/Controllers/Select/EventlogController.php b/app/Http/Controllers/Select/EventlogController.php new file mode 100644 index 0000000000..d0385f4e3e --- /dev/null +++ b/app/Http/Controllers/Select/EventlogController.php @@ -0,0 +1,74 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2018 Tony Murray + * @author Tony Murray + */ + +namespace App\Http\Controllers\Select; + +use App\Models\Eventlog; + +class EventlogController extends SelectController +{ + /** + * Defines validation rules (will override base validation rules for select2 responses too) + * + * @return array + */ + public function rules() + { + return [ + 'field' => 'required|in:type', + 'device' => 'nullable|int', + ]; + } + + /** + * Defines search fields will be searched in order + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function searchFields($request) + { + return [$request->get('field')]; + } + + /** + * Defines the base query for this resource + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder + */ + public function baseQuery($request) + { + /** @var \Illuminate\Database\Eloquent\Builder $query */ + $query = Eventlog::hasAccess($request->user()) + ->select($request->get('field'))->distinct(); + + if ($device_id = $request->get('device')) { + $query->where('device_id', $device_id); + } + + return $query; + } +} diff --git a/app/Http/Controllers/Table/EventlogController.php b/app/Http/Controllers/Table/EventlogController.php new file mode 100644 index 0000000000..1be1fa00e5 --- /dev/null +++ b/app/Http/Controllers/Table/EventlogController.php @@ -0,0 +1,119 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2018 Tony Murray + * @author Tony Murray + */ + +namespace App\Http\Controllers\Table; + +use App\Models\Eventlog; +use Carbon\Carbon; +use LibreNMS\Config; +use LibreNMS\Util\Url; + +class EventlogController extends TableController +{ + public function rules() + { + return [ + 'device' => 'nullable|int', + 'eventtype' => 'nullable|string', + ]; + } + + /** + * Defines the base query for this resource + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder + */ + public function baseQuery($request) + { + $query = Eventlog::hasAccess($request->user())->with('device'); + + if ($device_id = $request->get('device')) { + $query->where('device_id', $device_id); + } + + if ($type = $request->get('eventtype')) { + $query->where('type', $type); + } + + return $query; + } + + public function formatItem($eventlog) + { + return [ + 'datetime' => $this->formatDatetime($eventlog), + 'device_id' => Url::deviceLink($eventlog->device, $eventlog->device->shortDisplayName()), + 'type' => $this->formatType($eventlog), + 'message' => htmlspecialchars($eventlog->message), + 'username' => $eventlog->username ?: 'System', + ]; + } + + private function formatType($eventlog) + { + if ($eventlog->type == 'interface') { + if (is_numeric($eventlog->reference)) { + $port = $eventlog->related; + return '' . Url::portLink($port, $port->getShortLabel()) . ''; + } + } + + return $eventlog->type; + } + + private function formatDatetime($eventlog) + { + $output = ""; + $output .= (new Carbon($eventlog->datetime))->format(Config::get('dateformat.compact')); + $output .= ""; + + return $output; + } + + /** + * @param int $eventlog_severity + * @return string $eventlog_severity_icon + */ + private function severityLabel($eventlog_severity) + { + switch ($eventlog_severity) { + case 1: + return "label-success"; //OK + case 2: + return "label-info"; //Informational + case 3: + return "label-primary"; //Notice + case 4: + return "label-warning"; //Warning + case 5: + return "label-danger"; //Critical + default: + return "label-default"; //Unknown + } + } // end eventlog_severity +} diff --git a/app/Models/Device.php b/app/Models/Device.php index 48fae8ec59..af4b103048 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -2,6 +2,7 @@ namespace App\Models; +use DB; use Fico7489\Laravel\Pivot\Traits\PivotEventTrait; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -175,6 +176,27 @@ class Device extends BaseModel return $name; } + /** + * Check if user can access this device. + * + * @param User $user + * @return bool + */ + public function canAccess($user) + { + if (!$user) { + return false; + } + + if ($user->hasGlobalRead()) { + return true; + } + + return DB::table('devices_perms') + ->where('user_id', $user->user_id) + ->where('device_id', $this->device_id)->exists(); + } + public function formatUptime($short = false) { $result = ''; diff --git a/app/Models/Eventlog.php b/app/Models/Eventlog.php new file mode 100644 index 0000000000..2488ebf33a --- /dev/null +++ b/app/Models/Eventlog.php @@ -0,0 +1,56 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2018 Tony Murray + * @author Tony Murray + */ + +namespace App\Models; + +class Eventlog extends BaseModel +{ + protected $table = 'eventlog'; + protected $primaryKey = 'event_id'; + public $timestamps = false; + + // ---- Query scopes ---- + + public function scopeHasAccess($query, User $user) + { + return $this->hasDeviceAccess($query, $user); + } + + // ---- Define Relationships ---- + + /** + * Returns the device this entry belongs to. + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function device() + { + return $this->belongsTo('App\Models\Device', 'device_id'); + } + + public function related() + { + return $this->morphTo('related', 'type', 'reference'); + } +} diff --git a/app/Models/Port.php b/app/Models/Port.php index 6cf33bc74c..f48825517e 100644 --- a/app/Models/Port.php +++ b/app/Models/Port.php @@ -2,6 +2,10 @@ namespace App\Models; +use DB; +use Illuminate\Database\Eloquent\Builder; +use LibreNMS\Util\Rewrite; + class Port extends BaseModel { public $timestamps = false; @@ -16,15 +20,70 @@ class Port extends BaseModel */ public function getLabel() { - if ($this->ifName) { - return $this->ifName; + $os = $this->device->os; + + if (\LibreNMS\Config::getOsSetting($os, 'ifname')) { + $label = $this->ifName; + } elseif (\LibreNMS\Config::getOsSetting($os, 'ifalias')) { + $label = $this->ifAlias; } - if ($this->ifDescr) { - return $this->ifDescr; + if (empty($label)) { + $label = $this->ifDescr; + + if (\LibreNMS\Config::getOsSetting($os, 'ifindex')) { + $label .= " $this->ifIndex"; + } } - return $this->ifIndex; + foreach ((array)\LibreNMS\Config::get('rewrite_if', []) as $src => $val) { + if (str_i_contains($label, $src)) { + $label = $val; + } + } + + foreach ((array)\LibreNMS\Config::get('rewrite_if_regexp', []) as $reg => $val) { + $label = preg_replace($reg.'i', $val, $label); + } + + return $label; + } + + /** + * Get the shortened label for this device. Replaces things like GigabitEthernet with GE. + * + * @return string + */ + public function getShortLabel() + { + return Rewrite::shortenIfName(Rewrite::normalizeIfName($this->getLabel())); + } + + /** + * Check if user can access this port. + * + * @param User $user + * @return bool + */ + public function canAccess($user) + { + if (!$user) { + return false; + } + + if ($user->hasGlobalRead()) { + return true; + } + + $port_query = DB::table('ports_perms') + ->where('user_id', $user->user_id) + ->where('port_id', $this->port_id); + + $device_query = DB::table('devices_perms') + ->where('user_id', $user->user_id) + ->where('device_id', $this->device_id); + + return $port_query->union($device_query)->exists(); } // ---- Accessors/Mutators ---- @@ -39,6 +98,10 @@ class Port extends BaseModel // ---- Query scopes ---- + /** + * @param Builder $query + * @return Builder + */ public function scopeIsDeleted($query) { return $query->where([ @@ -46,6 +109,10 @@ class Port extends BaseModel ]); } + /** + * @param Builder $query + * @return Builder + */ public function scopeIsNotDeleted($query) { return $query->where([ @@ -53,6 +120,10 @@ class Port extends BaseModel ]); } + /** + * @param Builder $query + * @return Builder + */ public function scopeIsUp($query) { return $query->where([ @@ -62,6 +133,10 @@ class Port extends BaseModel ]); } + /** + * @param Builder $query + * @return Builder + */ public function scopeIsDown($query) { return $query->where([ @@ -72,6 +147,10 @@ class Port extends BaseModel ]); } + /** + * @param Builder $query + * @return Builder + */ public function scopeIsIgnored($query) { return $query->where([ @@ -80,6 +159,10 @@ class Port extends BaseModel ]); } + /** + * @param Builder $query + * @return Builder + */ public function scopeIsDisabled($query) { return $query->where([ @@ -89,9 +172,14 @@ class Port extends BaseModel ]); } + /** + * @param Builder $query + * @return Builder + */ public function scopeHasErrors($query) { return $query->where(function ($query) { + /** @var Builder $query */ $query->where('ifInErrors_delta', '>', 0) ->orWhere('ifOutErrors_delta', '>', 0); }); @@ -109,6 +197,11 @@ class Port extends BaseModel return $this->belongsTo('App\Models\Device', 'device_id', 'device_id'); } + public function events() + { + return $this->morphMany(Eventlog::class, 'events', 'type', 'reference'); + } + public function users() { // FIXME does not include global read diff --git a/app/Models/Sensor.php b/app/Models/Sensor.php index 0d07257ad7..b1f34c8c80 100644 --- a/app/Models/Sensor.php +++ b/app/Models/Sensor.php @@ -64,4 +64,9 @@ class Sensor extends BaseModel { return $this->belongsTo('App\Models\Device', 'device_id'); } + + public function events() + { + return $this->morphMany(Eventlog::class, 'events', 'type', 'reference'); + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e4db0b8e69..3c5fcff984 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; @@ -49,6 +50,8 @@ class AppServiceProvider extends ServiceProvider return ""; }); + $this->configureMorphAliases(); + // Development service providers if ($this->app->environment() !== 'production') { if (class_exists(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class)) { @@ -73,4 +76,12 @@ class AppServiceProvider extends ServiceProvider { // } + + private function configureMorphAliases() + { + Relation::morphMap([ + 'interface' => \App\Models\Port::class, + 'sensor' => \App\Models\Sensor::class, + ]); + } } diff --git a/html/includes/common/eventlog.inc.php b/html/includes/common/eventlog.inc.php index f8a8d951f8..56c7c9db6e 100644 --- a/html/includes/common/eventlog.inc.php +++ b/html/includes/common/eventlog.inc.php @@ -20,7 +20,7 @@ $common_output[] = ' Timestamp Type - Hostname + Hostname Message User @@ -35,12 +35,11 @@ var eventlog_grid = $("#eventlog").bootgrid({ post: function () { return { - id: "eventlog", - device: "' . mres($vars['device']) . '", - eventtype: "' . mres($vars['eventtype']) . '", + device: "' . (int)($vars['device']) . '", + eventtype: "' . addcslashes($vars['eventtype'], '"') . '", }; }, - url: "ajax_table.php" + url: "ajax/table/eventlog" }); diff --git a/html/pages/eventlog.inc.php b/html/pages/eventlog.inc.php index d640be0105..b2b6e238ba 100644 --- a/html/pages/eventlog.inc.php +++ b/html/pages/eventlog.inc.php @@ -13,14 +13,12 @@ * @author LibreNMS Contributors */ -use LibreNMS\Authentication\LegacyAuth; +use App\Models\Device; $no_refresh = true; -$param = array(); - -if ($vars['action'] == 'expunge' && LegacyAuth::user()->hasGlobalAdmin()) { - dbQuery('TRUNCATE TABLE `eventlog`'); - print_message('Event log truncated'); +$param = []; +if ($device_id = (int)Request::get('device')) { + $device = Device::find($device_id); } $pagetitle[] = 'Eventlog'; @@ -41,50 +39,73 @@ $pagetitle[] = 'Eventlog'; $('.actionBar').append( '
' + '
' + - '
' + + '
' + '' + - '' + '' + " . format_hostname($data) . "' + "; - } + if ($device instanceof Device) { + echo "'' +"; } ?> '' + + '
    ' + ' + "; + echo "'  ' + "; } ?> - '
    ' + '
' + '' + '
  ' + - '' + + '' + '
' + '
' ); + + + $("#device").select2({ + theme: 'bootstrap', + dropdownAutoWidth : true, + width: "auto", + allowClear: true, + placeholder: "All Devices", + ajax: { + url: 'ajax/select/device', + delay: 200 + } + }); + + + $("#eventtype").select2({ + theme: 'bootstrap', + dropdownAutoWidth : true, + width: "auto", + allowClear: true, + placeholder: "All Types", + ajax: { + url: 'ajax/select/eventlog', + delay: 200, + data: function(params) { + return { + field: "type", + device: $('#device').val(), + term: params.term, + page: params.page || 1 + } + } + } + }); + diff --git a/includes/rewrites.php b/includes/rewrites.php index 088194ffe2..016dfb6d2d 100644 --- a/includes/rewrites.php +++ b/includes/rewrites.php @@ -1,6 +1,8 @@ 'Frame Relay', - '/^ethernetCsmacd$/' => 'Ethernet', - '/^softwareLoopback$/' => 'Loopback', - '/^tunnel$/' => 'Tunnel', - '/^propVirtual$/' => 'Virtual Int', - '/^ppp$/' => 'PPP', - '/^ds1$/' => 'DS1', - '/^pos$/' => 'POS', - '/^sonet$/' => 'SONET', - '/^slip$/' => 'SLIP', - '/^mpls$/' => 'MPLS Layer', - '/^l2vlan$/' => 'VLAN Subif', - '/^atm$/' => 'ATM', - '/^aal5$/' => 'ATM AAL5', - '/^atmSubInterface$/' => 'ATM Subif', - '/^propPointToPointSerial$/' => 'PtP Serial', - ); - - $type = array_preg_replace($rewrite_iftype, $type); - - return ($type); + return Rewrite::normalizeIfType($type); } function fixifName($inf) { - $rewrite_ifname = array( - 'ether' => 'Ether', - 'gig' => 'Gig', - 'fast' => 'Fast', - 'ten' => 'Ten', - '-802.1q vlan subif' => '', - '-802.1q' => '', - 'bvi' => 'BVI', - 'vlan' => 'Vlan', - 'tunnel' => 'Tunnel', - 'serial' => 'Serial', - '-aal5 layer' => ' aal5', - 'null' => 'Null', - 'atm' => 'ATM', - 'port-channel' => 'Port-Channel', - 'dial' => 'Dial', - 'hp procurve switch software loopback interface' => 'Loopback Interface', - 'control plane interface' => 'Control Plane', - 'loop' => 'Loop', - 'bundle-ether' => 'Bundle-Ether', - ); - - $inf = strtolower($inf); - $inf = array_str_replace($rewrite_ifname, $inf); - - return $inf; + return Rewrite::normalizeIfName($inf); } diff --git a/routes/web.php b/routes/web.php index 0f8023953d..a581245ee7 100644 --- a/routes/web.php +++ b/routes/web.php @@ -33,11 +33,13 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () { Route::post('set_resolution', 'ResolutionController@set'); Route::group(['prefix' => 'select', 'namespace' => 'Select'], function () { - Route::get('syslog', 'SyslogController'); Route::get('device', 'DeviceController'); + Route::get('eventlog', 'EventlogController'); + Route::get('syslog', 'SyslogController'); }); Route::group(['prefix' => 'table', 'namespace' => 'Table'], function () { + Route::post('eventlog', 'EventlogController'); Route::post('syslog', 'SyslogController'); }); });