Push Notifications (Mobile and PC) (#13277)

* Update manifest and add service worker
cleanup icons a bit

* Push notifications WIP

* navigate working

* cleanup

* acknowledge wired up

* Set VAPID keys on composer install

* Component to control notification permissions.

* Allow all user option to validate

* Enable on browser load if transport exists.

* Check for transport before showing user permissions
translations

* Documentation

* style fixes

* access via the attribute model

* fix alerting test

* update schema

* cleanup subscription on disable

* non-configurable db and table for webpush subscriptions (respect system connection)

* revert AlertTransport change
hopefully phpstan can figure it out

* phpstan fixes

* Support custom details display

* Match transport names to brand's preferred display

* less duplicate id errors

* Tests are done in Laravel code now so
remove legacy function usage... could be better, but ok

* Style fixes

* Style fixes 2

* Fix alert test

* Doc updates requires HTTPS and GMP

* unregister subscription when permission is set to denied

* cleanup after user deletion

* delete the right thing

* fix whitespace

* update install docs to include php-gmp

* suggest ext-gmp

* update javascript

* Update functions.php

Co-authored-by: Jellyfrog <Jellyfrog@users.noreply.github.com>
This commit is contained in:
Tony Murray 2021-10-06 07:29:47 -05:00 committed by GitHub
parent 83f847d92d
commit 0b8b97bb68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 1993 additions and 448 deletions

View File

@ -521,7 +521,7 @@ class RunAlerts
}
foreach ($transport_maps as $item) {
$class = 'LibreNMS\\Alert\\Transport\\' . ucfirst($item['transport_type']);
$class = Transport::getClass($item['transport_type']);
if (class_exists($class)) {
//FIXME remove Deprecated transport
$transport_title = "Transport {$item['transport_type']}";

View File

@ -2,6 +2,8 @@
namespace LibreNMS\Alert;
use App\Models\AlertTransport;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Str;
use LibreNMS\Config;
use LibreNMS\Enum\AlertState;
@ -10,20 +12,53 @@ use LibreNMS\Interfaces\Alert\Transport as TransportInterface;
abstract class Transport implements TransportInterface
{
protected $config;
/**
* @var string
*/
protected $name;
public static function make(string $type): TransportInterface
{
$class = self::getClass($type);
return new $class();
}
/**
* Transport constructor.
*
* @param null $transport_id
* @param null $transport
*/
public function __construct($transport_id = null)
public function __construct($transport = null)
{
if (! empty($transport_id)) {
$sql = 'SELECT `transport_config` FROM `alert_transports` WHERE `transport_id`=?';
$this->config = json_decode(dbFetchCell($sql, [$transport_id]), true);
if (! empty($transport)) {
if ($transport instanceof AlertTransport) {
$this->config = $transport->transport_config;
} else {
try {
$model = \App\Models\AlertTransport::findOrFail($transport); /** @var AlertTransport $model */
$this->config = $model->transport_config;
} catch (ModelNotFoundException $e) {
$this->config = [];
}
}
}
}
/**
* @return string The display name of this transport
*/
public function name(): string
{
if ($this->name !== null) {
return $this->name;
}
$path = explode('\\', get_called_class());
return array_pop($path);
}
/**
* Helper function to parse free form text box defined in ini style to key value pairs
*
@ -61,4 +96,50 @@ abstract class Transport implements TransportInterface
return isset($colors[$state]) ? $colors[$state] : '#337AB7';
}
/**
* Display the configuration details of this alert transport
*
* @return string
*/
public function displayDetails(): string
{
$output = '';
// Iterate through transport config template to display config details
$config = static::configTemplate();
foreach ($config['config'] as $item) {
if ($item['type'] == 'oauth') {
continue;
}
$val = $this->config[$item['name']];
if ($item['type'] == 'password') {
$val = '<b>&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;</b>';
} elseif ($item['type'] == 'select') {
// Match value to key name for select inputs
$val = array_search($val, $item['options']);
}
$output .= $item['title'] . ': ' . $val . PHP_EOL;
}
return $output;
}
/**
* Get the alert transport class from transport type.
*
* @param string $type
* @return string
*/
public static function getClass(string $type): string
{
return 'LibreNMS\\Alert\\Transport\\' . ucfirst($type);
}
protected function isHtmlContent($content): bool
{
return $content !== strip_tags($content);
}
}

View File

@ -18,6 +18,7 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Config;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Alerta extends Transport
{
@ -63,7 +64,7 @@ class Alerta extends Transport
'type' => $obj['title'],
];
$alert_message = json_encode($data);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
$headers = ['Content-Type: application/json', 'Authorization: Key ' . $opts['apikey']];
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_URL, $host);

View File

@ -25,9 +25,12 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Config;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Alertmanager extends Transport
{
protected $name = 'Alert Manager';
public function deliverAlert($obj, $opts)
{
$alertmanager_opts = $this->parseUserOptions($this->config['alertmanager-options']);
@ -72,7 +75,7 @@ class Alertmanager extends Transport
public static function postAlerts($url, $data)
{
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_TIMEOUT, 5);

View File

@ -25,9 +25,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Api extends Transport
{
protected $name = 'API';
public function deliverAlert($obj, $opts)
{
$url = $this->config['api-url'];
@ -70,7 +73,7 @@ class Api extends Transport
}
$client = new \GuzzleHttp\Client();
$request_opts['proxy'] = get_guzzle_proxy();
$request_opts['proxy'] = Proxy::forGuzzle();
if (isset($auth) && ! empty($auth[0])) {
$request_opts['auth'] = $auth;
}

View File

@ -40,6 +40,7 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Config;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Boxcar extends Transport
{
@ -101,7 +102,7 @@ class Boxcar extends Transport
}
$data['notification[long_message]'] = $message_text;
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, 'https://new.boxcar.io/api/notifications');
curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);

View File

@ -0,0 +1,89 @@
<?php
/*
* BrowserPush.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2021 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Alert\Transport;
use App\Models\User;
use App\Notifications\AlertNotification;
use LibreNMS\Alert\Transport;
use Notification;
class Browserpush extends Transport
{
protected $name = 'Browser Push';
public function deliverAlert($alert_data, $opts)
{
$users = User::when($this->config['user'] ?? 0, function ($query, $user_id) {
return $query->where('user_id', $user_id);
})->get();
Notification::send($users, new AlertNotification(
$alert_data['alert_id'],
$alert_data['title'],
$alert_data['msg'],
));
return true;
}
public static function configTemplate()
{
$users = [__('All Users') => 0];
foreach (User::get(['user_id', 'username', 'realname']) as $user) {
$users[$user->realname ?: $user->username] = $user->user_id;
}
return [
'config' => [
[
'title' => 'User',
'name' => 'user',
'descr' => 'LibreNMS User',
'type' => 'select',
'options' => $users,
],
],
'validation' => [
'user' => 'required|zero_or_exists:users,user_id',
],
];
}
public function displayDetails(): string
{
if ($this->config['user'] == 0) {
$count = \DB::table('push_subscriptions')->count();
return "All users: $count subscriptions";
} elseif ($user = User::find($this->config['user'])) {
$count = $user->pushSubscriptions()->count();
return "User: $user->username ($count subscriptions)";
}
return 'User not found';
}
}

View File

@ -13,9 +13,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Ciscospark extends Transport
{
protected $name = 'Cisco Spark';
public function deliverAlert($obj, $opts)
{
if (empty($this->config)) {
@ -57,7 +60,7 @@ class Ciscospark extends Transport
$data[$akey] = $text;
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, 'https://api.ciscospark.com/v1/messages');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, [

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Clickatell extends Transport
{
@ -40,7 +41,7 @@ class Clickatell extends Transport
$url = 'https://platform.clickatell.com/messages/http/send?apiKey=' . $opts['token'] . '&to=' . implode(',', $opts['to']) . '&content=' . urlencode($obj['title']);
$curl = curl_init($url);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -29,10 +29,11 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Discord extends Transport
{
const ALERT_FIELDS_TO_DISCORD_FIELDS = [
public const ALERT_FIELDS_TO_DISCORD_FIELDS = [
'timestamp' => 'Timestamp',
'severity' => 'Severity',
'hostname' => 'Hostname',
@ -80,7 +81,7 @@ class Discord extends Transport
$alert_message = json_encode($data);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $host);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, true);

View File

@ -19,6 +19,7 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Elasticsearch extends Transport
{
@ -41,7 +42,6 @@ class Elasticsearch extends Transport
$index = strftime('librenms-%Y.%m.%d');
$type = 'alert';
$severity = $obj['severity'];
$device = device_by_id_cache($obj['device_id']); // for event logging
if (! empty($opts['es_host'])) {
if (preg_match('/[a-zA-Z]/', $opts['es_host'])) {
@ -168,7 +168,7 @@ class Elasticsearch extends Transport
$alert_message = json_encode($data);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
if ($opts['es_proxy'] === true) {
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
}
curl_setopt($curl, CURLOPT_URL, $host);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
@ -186,7 +186,7 @@ class Elasticsearch extends Transport
$alert_message = json_encode($data);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
if ($opts['es_proxy'] === true) {
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
}
curl_setopt($curl, CURLOPT_URL, $host);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

View File

@ -25,6 +25,7 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Gitlab extends Transport
{
@ -43,8 +44,6 @@ class Gitlab extends Transport
{
// Don't create tickets for resolutions
if ($obj['state'] != AlertState::CLEAR) {
$device = device_by_id_cache($obj['device_id']); // for event logging
$project_id = $opts['project-id'];
$project_key = $opts['key'];
$details = 'Librenms alert for: ' . $obj['hostname'];
@ -60,7 +59,7 @@ class Gitlab extends Transport
$postdata = ['fields' => $data];
$datastring = json_encode($postdata);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
$headers = ['Accept: application/json', 'Content-Type: application/json', 'PRIVATE-TOKEN: ' . $project_key];
@ -75,7 +74,7 @@ class Gitlab extends Transport
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($code == 200) {
$gitlabout = json_decode($ret, true);
d_echo('Created GitLab issue ' . $gitlabout['key'] . ' for ' . $device);
d_echo('Created GitLab issue ' . $gitlabout['key'] . ' for ' . $obj['hostname']);
return true;
} else {

View File

@ -24,10 +24,13 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
use Log;
class Googlechat extends Transport
{
protected $name = 'Google Chat';
public function deliverAlert($obj, $opts)
{
$googlechat_conf['webhookurl'] = $this->config['googlechat-webhook'];
@ -43,6 +46,7 @@ class Googlechat extends Transport
// Create a new cURL resource
$ch = curl_init($data['webhookurl']);
Proxy::applyToCurl($ch);
// Attach encoded JSON string to the POST fields
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

View File

@ -25,9 +25,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Hipchat extends Transport
{
protected $name = 'HipChat';
public function deliverAlert($obj, $opts)
{
$hipchat_opts = $this->parseUserOptions($this->config['hipchat-options']);
@ -85,7 +88,7 @@ class Hipchat extends Transport
$data[] = 'message_format=' . urlencode($option['message_format']);
$data = implode('&', $data);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, true);

View File

@ -26,6 +26,7 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
/**
* The Hue API currently is fairly limited for alerts.
@ -51,7 +52,6 @@ class Hue extends Transport
if ($obj['state'] == AlertState::RECOVERED) {
return true;
} else {
$device = device_by_id_cache($obj['device_id']); // for event logging
$hue_user = $opts['user'];
$url = $opts['bridge'] . "/api/$hue_user/groups/0/action";
$curl = curl_init();
@ -59,7 +59,7 @@ class Hue extends Transport
$data = ['alert' => $duration];
$datastring = json_encode($data);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
$headers = ['Accept: application/json', 'Content-Type: application/json'];
@ -73,7 +73,7 @@ class Hue extends Transport
$ret = curl_exec($curl);
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($code == 200) {
d_echo('Sent alert to Phillips Hue Bridge ' . $opts['host'] . ' for ' . $device);
d_echo('Sent alert to Phillips Hue Bridge ' . $opts['host'] . ' for ' . $obj['hostname']);
return true;
} else {

View File

@ -28,6 +28,8 @@ use LibreNMS\Config;
class Irc extends Transport
{
protected $name = 'IRC';
public function deliverAlert($obj, $opts)
{
return $this->contactIrc($obj, $opts);

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Jira extends Transport
{
@ -47,8 +48,6 @@ class Jira extends Transport
return true;
}
$device = device_by_id_cache($obj['device_id']); // for event logging
$username = $opts['username'];
$password = $opts['password'];
$prjkey = $opts['prjkey'];
@ -65,7 +64,7 @@ class Jira extends Transport
$postdata = ['fields' => $data];
$datastring = json_encode($postdata);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
$headers = ['Accept: application/json', 'Content-Type: application/json'];
@ -81,7 +80,7 @@ class Jira extends Transport
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($code == 200) {
$jiraout = json_decode($ret, true);
d_echo('Created jira issue ' . $jiraout['key'] . ' for ' . $device);
d_echo('Created jira issue ' . $jiraout['key'] . ' for ' . $obj['hostname']);
return true;
} else {

View File

@ -14,6 +14,7 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Config;
use LibreNMS\Util\Proxy;
class Kayako extends Transport
{
@ -60,6 +61,7 @@ class Kayako extends Transport
$post_data = http_build_query($protocol, '', '&');
$curl = curl_init();
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -6,9 +6,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Linenotify extends Transport
{
protected $name = 'LINE Notify';
public function deliverAlert($obj, $opts)
{
$opts['line-notify-access-token'] = $this->config['line-notify-access-token'];
@ -23,6 +26,7 @@ class Linenotify extends Transport
$lineFields = ['message' => $obj['msg']];
$curl = curl_init();
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $lineUrl);
curl_setopt($curl, CURLOPT_HTTPHEADER, $lineHead);
curl_setopt($curl, CURLOPT_NOBODY, false);

View File

@ -46,7 +46,7 @@ class Mail extends Transport
$msg = preg_replace("/(?<!\r)\n/", "\r\n", $obj['msg']);
}
return send_mail($email, $obj['title'], $msg, $html);
return \LibreNMS\Util\Mail::send($email, $obj['title'], $msg, $html);
}
public static function configTemplate()
@ -65,9 +65,4 @@ class Mail extends Transport
],
];
}
private function isHtmlContent($content): bool
{
return $content !== strip_tags($content);
}
}

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Matrix extends Transport
{
@ -57,7 +58,7 @@ class Matrix extends Transport
$body = ['body'=>$message, 'msgtype'=>'m.text'];
$client = new \GuzzleHttp\Client();
$request_opts['proxy'] = get_guzzle_proxy();
$request_opts['proxy'] = Proxy::forGuzzle();
$request_opts['headers'] = $request_heads;
$request_opts['body'] = json_encode($body);
$res = $client->request('PUT', $host, $request_opts);

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Mattermost extends Transport
{
@ -62,9 +63,7 @@ class Mattermost extends Transport
'icon_url' => $api['icon'],
];
$device = device_by_id_cache($obj['device_id']);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
$httpheaders = ['Accept: application/json', 'Content-Type: application/json'];
$alert_payload = json_encode($data);
@ -82,7 +81,7 @@ class Mattermost extends Transport
return 'HTTP Status code ' . $code;
} else {
d_echo('Mattermost message sent for ' . $device);
d_echo('Mattermost message sent for ' . $obj['hostname']);
return true;
}

View File

@ -13,9 +13,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Msteams extends Transport
{
protected $name = 'Microsoft Teams';
public function deliverAlert($obj, $opts)
{
if (! empty($this->config)) {
@ -34,7 +37,7 @@ class Msteams extends Transport
'text' => strip_tags($obj['msg'], '<strong><em><h1><h2><h3><strike><ul><ol><li><pre><blockquote><a><img><p>'),
];
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, [

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Opsgenie extends Transport
{
@ -42,7 +43,7 @@ class Opsgenie extends Transport
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

View File

@ -14,9 +14,12 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Config;
use LibreNMS\Util\Proxy;
class Osticket extends Transport
{
protected $name = 'osTicket';
public function deliverAlert($obj, $opts)
{
if (! empty($this->config)) {
@ -47,7 +50,7 @@ class Osticket extends Transport
'attachments' => [],
];
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, [

View File

@ -28,11 +28,13 @@ use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Http\Request;
use LibreNMS\Alert\Transport;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
use Log;
use Validator;
class Pagerduty extends Transport
{
protected $name = 'PagerDuty';
public static $integrationKey = '2fc7c9f3c8030e74aae6';
public function deliverAlert($obj, $opts)
@ -81,7 +83,7 @@ class Pagerduty extends Transport
$client = new Client();
$request_opts = ['json' => $data];
$request_opts['proxy'] = get_proxy();
$request_opts['proxy'] = Proxy::forGuzzle();
try {
$result = $client->request('POST', $url, $request_opts);

View File

@ -24,9 +24,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Playsms extends Transport
{
protected $name = 'playSMS';
public function deliverAlert($obj, $opts)
{
$playsms_opts['url'] = $this->config['playsms-url'];
@ -47,7 +50,7 @@ class Playsms extends Transport
$url = $opts['url'] . '&op=pv&' . http_build_query($data);
$curl = curl_init($url);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Pushbullet extends Transport
{
@ -44,7 +45,7 @@ class Pushbullet extends Transport
$data = json_encode($data);
$curl = curl_init('https://api.pushbullet.com/v2/pushes');
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -39,6 +39,7 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Pushover extends Transport
{
@ -83,7 +84,7 @@ class Pushover extends Transport
$data = array_merge($data, $api['options']);
}
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, 'https://api.pushover.net/1/messages.json');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true);

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Rocket extends Transport
{
@ -57,7 +58,7 @@ class Rocket extends Transport
];
$alert_message = json_encode($data);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $host);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, true);

View File

@ -28,14 +28,15 @@ use Illuminate\Support\Facades\Log;
use LibreNMS\Alert\Transport;
use LibreNMS\Config;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Sensu extends Transport
{
// Sensu alert coding
const OK = 0;
const WARNING = 1;
const CRITICAL = 2;
const UNKNOWN = 3;
public const OK = 0;
public const WARNING = 1;
public const CRITICAL = 2;
public const UNKNOWN = 3;
private static $status = [
'ok' => Sensu::OK,
@ -51,6 +52,9 @@ class Sensu extends Transport
'better' => AlertState::BETTER,
];
/**
* @var Client
*/
private static $client = null;
public function deliverAlert($obj, $opts)
@ -74,7 +78,8 @@ class Sensu extends Transport
{
// The Sensu agent should be running on the poller - events can be sent directly to the backend but this has not been tested, and likely needs mTLS.
// The agent API is documented at https://docs.sensu.io/sensu-go/latest/reference/agent/#create-monitoring-events-using-the-agent-api
if (Sensu::$client->request('GET', $opts['url'] . '/healthz')->getStatusCode() !== 200) {
$request_options = ['proxy' => Proxy::forGuzzle()];
if (Sensu::$client->request('GET', $opts['url'] . '/healthz', $request_options)->getStatusCode() !== 200) {
return 'Sensu API is not responding';
}
@ -83,7 +88,8 @@ class Sensu extends Transport
$data = Sensu::generateData($obj, $opts, Sensu::OK, round(Config::get('rrd.step', 300) / 2));
Log::debug('Sensu transport sent last good event to socket: ', $data);
$result = Sensu::$client->request('POST', $opts['url'] . '/events', ['json' => $data]);
$request_options['json'] = $data;
$result = Sensu::$client->request('POST', $opts['url'] . '/events', $request_options);
if ($result->getStatusCode() !== 202) {
return $result->getReasonPhrase();
}
@ -94,7 +100,8 @@ class Sensu extends Transport
$data = Sensu::generateData($obj, $opts, Sensu::calculateStatus($obj['state'], $obj['severity']));
Log::debug('Sensu transport sent event to socket: ', $data);
$result = Sensu::$client->request('POST', $opts['url'] . '/events', ['json' => $data]);
$request_options['json'] = $data;
$result = Sensu::$client->request('POST', $opts['url'] . '/events', $request_options);
if ($result->getStatusCode() === 202) {
return true;
}

View File

@ -18,9 +18,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Signalwire extends Transport
{
protected $name = 'SignalWire';
public function deliverAlert($obj, $opts)
{
$signalwire_opts['spaceUrl'] = $this->config['signalwire-spaceUrl'];
@ -54,7 +57,7 @@ class Signalwire extends Transport
$curl = curl_init($url);
// set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -24,6 +24,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Slack extends Transport
{
@ -58,7 +59,7 @@ class Slack extends Transport
];
$alert_message = json_encode($data);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $host);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, true);

View File

@ -25,9 +25,12 @@ namespace LibreNMS\Alert\Transport;
use Illuminate\Support\Str;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Smseagle extends Transport
{
protected $name = 'SMSEagle';
public function deliverAlert($obj, $opts)
{
$smseagle_opts['url'] = $this->config['smseagle-url'];
@ -50,7 +53,7 @@ class Smseagle extends Transport
$url .= $opts['url'] . '/index.php/http_api/send_sms?' . http_build_query($params);
$curl = curl_init($url);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -24,9 +24,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Smsfeedback extends Transport
{
protected $name = 'SMSfeedback';
public function deliverAlert($obj, $opts)
{
$smsfeedback_opts['user'] = $this->config['smsfeedback-user'];
@ -49,7 +52,7 @@ class Smsfeedback extends Transport
$url = 'http://' . $opts['user'] . ':' . $opts['token'] . '@' . 'api.smsfeedback.ru/messages/v2/send/?' . http_build_query($params);
$curl = curl_init($url);
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -26,6 +26,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Telegram extends Transport
{
@ -41,7 +42,7 @@ class Telegram extends Transport
public static function contactTelegram($obj, $data)
{
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
$text = urlencode($obj['msg']);
$format = '';
if ($data['format']) {

View File

@ -16,6 +16,7 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Twilio extends Transport
{
@ -50,7 +51,7 @@ class Twilio extends Transport
$curl = curl_init($url);
// set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

View File

@ -24,9 +24,12 @@
namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Util\Proxy;
class Ukfastpss extends Transport
{
protected $name = 'UKFast PSS';
public function deliverAlert($obj, $opts)
{
return $this->contactUkfastpss($obj, $opts);
@ -57,7 +60,7 @@ class Ukfastpss extends Transport
$request_headers['Accept'] = 'application/json';
$client = new \GuzzleHttp\Client();
$request_opts['proxy'] = get_guzzle_proxy();
$request_opts['proxy'] = Proxy::forGuzzle();
$request_opts['headers'] = $request_headers;
$request_opts['body'] = json_encode($body);

View File

@ -26,9 +26,12 @@ namespace LibreNMS\Alert\Transport;
use LibreNMS\Alert\Transport;
use LibreNMS\Enum\AlertState;
use LibreNMS\Util\Proxy;
class Victorops extends Transport
{
protected $name = 'VictorOps';
public function deliverAlert($obj, $opts)
{
if (! empty($this->config)) {
@ -62,7 +65,7 @@ class Victorops extends Transport
}
$curl = curl_init();
set_curl_proxy($curl);
Proxy::applyToCurl($curl);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-type' => 'application/json']);

View File

@ -28,6 +28,7 @@ namespace LibreNMS;
use Composer\Script\Event;
use LibreNMS\Exceptions\FileWriteFailedException;
use LibreNMS\Util\EnvHelper;
use Minishlink\WebPush\VAPID;
class ComposerHelper
{
@ -94,6 +95,8 @@ class ComposerHelper
try {
EnvHelper::init();
$vapid = VAPID::createVapidKeys();
EnvHelper::writeEnv([
'NODE_ID' => uniqid(),
'DB_HOST' => $config['db_host'],
@ -105,6 +108,8 @@ class ComposerHelper
'APP_URL' => $config['base_url'],
'LIBRENMS_USER' => $config['user'],
'LIBRENMS_GROUP' => $config['group'],
'VAPID_PUBLIC_KEY' => $vapid['publicKey'],
'VAPID_PRIVATE_KEY' => $vapid['privateKey'],
]);
} catch (FileWriteFailedException $exception) {
echo $exception->getMessage() . PHP_EOL;

View File

@ -27,6 +27,11 @@ namespace LibreNMS\Interfaces\Alert;
interface Transport
{
/**
* @return string The display name of this transport.
*/
public function name(): string;
/**
* Gets called when an alert is sent
*
@ -40,4 +45,11 @@ interface Transport
* @return array
*/
public static function configTemplate();
/**
* Display the configuration details of this alert transport
*
* @return string
*/
public function displayDetails(): string;
}

127
LibreNMS/Util/Mail.php Normal file
View File

@ -0,0 +1,127 @@
<?php
/*
* Mail.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2021 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Util;
use Exception;
use LibreNMS\Config;
use PHPMailer\PHPMailer\PHPMailer;
class Mail
{
/**
* Parse string with emails. Return array with email (as key) and name (as value)
*
* @param string $emails
* @return array|false
*/
public static function parseEmails($emails)
{
$result = [];
$regex = '/^[\"\']?([^\"\']+)[\"\']?\s{0,}<([^@]+@[^>]+)>$/';
if (is_string($emails)) {
$emails = preg_split('/[,;]\s{0,}/', $emails);
foreach ($emails as $email) {
if (preg_match($regex, $email, $out, PREG_OFFSET_CAPTURE)) {
$result[$out[2][0]] = $out[1][0];
} else {
if (strpos($email, '@')) {
$from_name = Config::get('email_user');
$result[$email] = $from_name;
}
}
}
return $result;
}
// Return FALSE if input not string
return false;
}
/**
* Send email with PHPMailer
*
* @param string $emails
* @param string $subject
* @param string $message
* @param bool $html
* @return bool|string
*/
public static function send($emails, $subject, $message, bool $html = false)
{
if (is_array($emails) || ($emails = self::parseEmails($emails))) {
d_echo("Attempting to email $subject to: " . implode('; ', array_keys($emails)) . PHP_EOL);
$mail = new PHPMailer(true);
try {
$mail->Hostname = php_uname('n');
foreach (self::parseEmails(Config::get('email_from')) as $from => $from_name) {
$mail->setFrom($from, $from_name);
}
foreach ($emails as $email => $email_name) {
$mail->addAddress($email, $email_name);
}
$mail->Subject = $subject;
$mail->XMailer = Config::get('project_name');
$mail->CharSet = 'utf-8';
$mail->WordWrap = 76;
$mail->Body = $message;
if ($html) {
$mail->isHTML(true);
}
switch (strtolower(trim(Config::get('email_backend')))) {
case 'sendmail':
$mail->Mailer = 'sendmail';
$mail->Sendmail = Config::get('email_sendmail_path');
break;
case 'smtp':
$mail->isSMTP();
$mail->Host = Config::get('email_smtp_host');
$mail->Timeout = Config::get('email_smtp_timeout');
$mail->SMTPAuth = Config::get('email_smtp_auth');
$mail->SMTPSecure = Config::get('email_smtp_secure');
$mail->Port = Config::get('email_smtp_port');
$mail->Username = Config::get('email_smtp_username');
$mail->Password = Config::get('email_smtp_password');
$mail->SMTPAutoTLS = Config::get('email_auto_tls');
$mail->SMTPDebug = 0;
break;
default:
$mail->Mailer = 'mail';
break;
}
return $mail->send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
return $e->errorMessage();
} catch (Exception $e) {
return $e->getMessage();
}
}
return 'No contacts found';
}
}

84
LibreNMS/Util/Proxy.php Normal file
View File

@ -0,0 +1,84 @@
<?php
/*
* Proxy.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2021 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Util;
use LibreNMS\Config;
class Proxy
{
/**
* Return the proxy url
*
* @return array|bool|false|string
*/
public static function get()
{
if (getenv('http_proxy')) {
return getenv('http_proxy');
} elseif (getenv('https_proxy')) {
return getenv('https_proxy');
} elseif ($callback_proxy = Config::get('callback_proxy')) {
return $callback_proxy;
} elseif ($http_proxy = Config::get('http_proxy')) {
return $http_proxy;
}
return false;
}
/**
* Return the proxy url in guzzle format "tcp://127.0.0.1:8888"
*/
public static function forGuzzle(): string
{
$proxy = self::forCurl();
return empty($proxy) ? '' : ('tcp://' . $proxy);
}
/**
* Get the ip and port of the proxy
*
* @return string
*/
public static function forCurl(): string
{
return str_replace(['http://', 'https://'], '', rtrim(self::get(), '/'));
}
/**
* Set the proxy on a curl handle
*
* @param resource $curl
*/
public static function applyToCurl($curl): void
{
$proxy = self::forCurl();
if (! empty($proxy)) {
curl_setopt($curl, CURLOPT_PROXY, $proxy);
}
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers;
use App\Models\Alert;
use Illuminate\Http\Request;
use LibreNMS\Config;
use Log;
class AlertController extends Controller
{
public function ack(Request $request, Alert $alert): \Illuminate\Http\JsonResponse
{
$this->validate($request, [
'state' => 'required|int',
'ack_msg' => 'nullable|string',
'ack_until_clear' => 'nullable|in:0,1,true,false',
]);
$state = $request->get('state');
$state_description = '';
if ($state == 2) {
$alert->state = 1;
$state_description = 'UnAck';
$alert->open = 1;
} elseif ($state >= 1) {
$alert->state = 2;
$state_description = 'Ack';
$alert->open = 1;
}
$info = $alert->info;
$info['until_clear'] = filter_var($request->get('ack_until_clear'), FILTER_VALIDATE_BOOLEAN);
$alert->info = $info;
$timestamp = date(Config::get('dateformat.long'));
$username = $request->user()->username;
$ack_msg = $request->get('ack_msg');
$alert->note = trim($alert->note . PHP_EOL . "$timestamp - $state_description ($username) " . $ack_msg);
if ($alert->save()) {
if (in_array($state, [2, 22])) {
$rule_name = $alert->rule->name;
Log::event("$username acknowledged alert $rule_name note: $ack_msg", $alert->device_id, 'alert', 2, $alert->id);
}
return response()->json([
'message' => "Alert {$state_description}nowledged.",
'status' => 'ok',
]);
}
return response()->json([
'message' => 'Alert has not been acknowledged.',
'status' => 'error',
]);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers;
use App\Models\AlertTransport;
use App\Models\Device;
use Illuminate\Http\Request;
use LibreNMS\Alert\AlertUtil;
use LibreNMS\Config;
class AlertTransportController extends Controller
{
public function test(Request $request, AlertTransport $transport): \Illuminate\Http\JsonResponse
{
$device = Device::with('location')->first();
$obj = [
'hostname' => $device->hostname,
'device_id' => $device->device_id,
'sysDescr' => $device->sysDescr,
'version' => $device->version,
'hardware' => $device->hardware,
'location' => $device->location,
'title' => 'Testing transport from ' . Config::get('project_name'),
'elapsed' => '11s',
'alert_id' => '000',
'id' => '000',
'faults' => false,
'uid' => '000',
'severity' => 'critical',
'rule' => 'macros.device = 1',
'name' => 'Test-Rule',
'string' => '#1: test => string;',
'timestamp' => date('Y-m-d H:i:s'),
'contacts' => AlertUtil::getContacts($device->toArray()),
'state' => '1',
'msg' => 'This is a test alert',
];
$opts = Config::get('alert.transports.' . $transport->transport_type);
try {
$result = $transport->instance()->deliverAlert($obj, $opts);
if ($result === true) {
return response()->json(['status' => 'ok']);
}
} catch (\Exception $e) {
$result = $e->getMessage();
}
return response()->json([
'status' => 'error',
'message' => $result,
]);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PushNotificationController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function token(): string
{
return csrf_token();
}
public function key(): string
{
return config('webpush.vapid.public_key');
}
public function register(Request $request): \Illuminate\Http\JsonResponse
{
$this->validate($request, [
'description' => 'string',
'subscription.endpoint' => 'required|url',
'subscription.keys.auth' => 'required|string',
'subscription.keys.p256dh' => 'required|string',
]);
$subscription = $request->user()
->updatePushSubscription(
$request->input('subscription.endpoint'),
$request->input('subscription.keys.p256dh'),
$request->input('subscription.keys.auth')
);
$subscription->description = $request->get('description');
$success = $subscription->save();
return response()->json(['success' => $success], 200);
}
public function unregister(Request $request): void
{
$this->validate($request, [
'endpoint' => 'required|url',
]);
$request->user()
->deletePushSubscription($request->input('endpoint'));
}
}

View File

@ -250,6 +250,8 @@ class MenuComposer
// Search bar
$vars['typeahead_limit'] = Config::get('webui.global_search_result_limit');
$vars['browser_push'] = $user->hasBrowserPushTransport();
$view->with($vars);
}
}

View File

@ -34,6 +34,9 @@ use LibreNMS\Enum\AlertState;
class Alert extends Model
{
public $timestamps = false;
public $casts = [
'info' => 'array',
];
// ---- Query scopes ----

View File

@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use LibreNMS\Alert\Transport;
/**
* \App\Models\AlertTransport
*
* @property int $transport_id
* @property string $transport_name
* @property string $transport_type
* @property bool $is_default
* @property array|null $transport_config
*
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport query()
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport whereIsDefault($value)
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport whereTransportConfig($value)
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport whereTransportId($value)
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport whereTransportName($value)
* @method static \Illuminate\Database\Eloquent\Builder|AlertTransport whereTransportType($value)
* @mixin \Eloquent
*/
class AlertTransport extends Model
{
use HasFactory;
protected $primaryKey = 'transport_id';
public $timestamps = false;
protected $casts = [
'is_default' => 'boolean',
'transport_config' => 'array',
];
public function instance(): Transport
{
$class = Transport::getClass($this->transport_type);
return new $class($this);
}
}

View File

@ -7,11 +7,11 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use LibreNMS\Authentication\LegacyAuth;
use NotificationChannels\WebPush\HasPushSubscriptions;
use Permissions;
/**
@ -19,7 +19,7 @@ use Permissions;
*/
class User extends Authenticatable
{
use Notifiable, HasFactory;
use Notifiable, HasFactory, HasPushSubscriptions;
protected $primaryKey = 'user_id';
protected $fillable = ['realname', 'username', 'email', 'level', 'descr', 'can_modify_passwd', 'auth_type', 'auth_id', 'enabled'];
@ -124,6 +124,21 @@ class User extends Authenticatable
return false;
}
/**
* Checks if this user has a browser push notification transport configured.
*
* @return bool
*/
public function hasBrowserPushTransport(): bool
{
$user_id = \Auth::id();
return AlertTransport::query()
->where('transport_type', 'browserpush')
->where('transport_config', 'regexp', "\"user\":\"(0|$user_id)\"")
->exists();
}
// ---- Query scopes ----
/**
@ -189,9 +204,9 @@ class User extends Authenticatable
// ---- Define Relationships ----
public function apiToken(): HasOne
public function apiTokens(): HasMany
{
return $this->hasOne(\App\Models\ApiToken::class, 'user_id', 'user_id');
return $this->hasMany(\App\Models\ApiToken::class, 'user_id', 'user_id');
}
public function devices()
@ -222,6 +237,11 @@ class User extends Authenticatable
return $this->hasMany(\App\Models\Dashboard::class, 'user_id');
}
public function notificationAttribs(): HasMany
{
return $this->hasMany(NotificationAttrib::class, 'user_id');
}
public function preferences(): HasMany
{
return $this->hasMany(\App\Models\UserPref::class, 'user_id');

View File

@ -0,0 +1,78 @@
<?php
/*
* AlertNotification.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2021 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use NotificationChannels\WebPush\WebPushChannel;
use NotificationChannels\WebPush\WebPushMessage;
class AlertNotification extends Notification
{
/**
* @var \NotificationChannels\WebPush\WebPushMessage
*/
public $message;
public function __construct(int $alert_id, string $title, string $body)
{
$this->message = (new WebPushMessage)
->title($title)
->icon(asset('/images/mstile-144x144.png'))
->body($body)
->action('Acknowledge', 'alert.acknowledge')
->action('View', 'alert.view')
->options(['TTL' => 2000])
->data(['id' => $alert_id])
// ->badge()
// ->dir()
// ->image()
// ->lang()
// ->renotify()
// ->requireInteraction()
// ->tag()
// ->vibrate()
;
}
/**
* @param mixed $notifiable
* @return string[]
*/
public function via($notifiable): array
{
return [WebPushChannel::class];
}
/**
* @param mixed $notifiable
* @param \Illuminate\Notifications\Notification $notification
* @return \NotificationChannels\WebPush\WebPushMessage
*/
public function toWebPush($notifiable, Notification $notification): WebPushMessage
{
return $this->message;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
/**
* Handle the user "deleted" event.
*
* @param \App\Models\User $user
* @return void
*/
public function deleted(User $user)
{
$user->apiTokens()->delete();
$user->notificationAttribs()->delete();
$user->preferences()->delete();
$user->pushSubscriptions()->delete();
}
}

View File

@ -123,6 +123,7 @@ class AppServiceProvider extends ServiceProvider
{
\App\Models\Device::observe(\App\Observers\DeviceObserver::class);
\App\Models\Service::observe(\App\Observers\ServiceObserver::class);
\App\Models\User::observe(\App\Observers\UserObserver::class);
}
private function bootCustomValidators()

View File

@ -0,0 +1,33 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class NotificationSubscriptionStatus extends Component
{
/**
* @var bool
*/
public $userHasTransport;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
$this->userHasTransport = \Auth::user()->hasBrowserPushTransport();
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.notification-subscription-status');
}
}

View File

@ -35,6 +35,7 @@
"guzzlehttp/guzzle": "^7.0.1",
"influxdb/influxdb-php": "^1.14",
"justinrainbow/json-schema": "^5.2",
"laravel-notification-channels/webpush": "^5.1",
"laravel/framework": "^8.12",
"laravel/tinker": "^2.5",
"laravel/ui": "^3.0",
@ -70,8 +71,9 @@
"staudenmeir/dusk-updater": "^1.1"
},
"suggest": {
"ext-gmp": "Used for browser push notifications",
"ext-ldap": "*",
"ext-memcached": "Required if you utilize distributed polling",
"ext-memcached": "Required if you utilize wrapper based distributed polling",
"ext-mysqlnd": "*",
"ext-posix": "Allows for additional validation tests"
},

633
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4756600b7d329eed706b8a224e47fb4a",
"content-hash": "7d318d5eaf5bfcc28029dfc6acd19ebc",
"packages": [
{
"name": "amenadiel/jpgraph",
@ -1061,6 +1061,81 @@
},
"time": "2020-06-29T00:56:53+00:00"
},
{
"name": "fgrosse/phpasn1",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/fgrosse/PHPASN1.git",
"reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/20299033c35f4300eb656e7e8e88cf52d1d6694e",
"reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e",
"shasum": ""
},
"require": {
"php": ">=7.0.0"
},
"require-dev": {
"phpunit/phpunit": "~6.3",
"satooshi/php-coveralls": "~2.0"
},
"suggest": {
"ext-bcmath": "BCmath is the fallback extension for big integer calculations",
"ext-curl": "For loading OID information from the web if they have not bee defined statically",
"ext-gmp": "GMP is the preferred extension for big integer calculations",
"phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"FG\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Friedrich Große",
"email": "friedrich.grosse@gmail.com",
"homepage": "https://github.com/FGrosse",
"role": "Author"
},
{
"name": "All contributors",
"homepage": "https://github.com/FGrosse/PHPASN1/contributors"
}
],
"description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
"homepage": "https://github.com/FGrosse/PHPASN1",
"keywords": [
"DER",
"asn.1",
"asn1",
"ber",
"binary",
"decoding",
"encoding",
"x.509",
"x.690",
"x509",
"x690"
],
"support": {
"issues": "https://github.com/fgrosse/PHPASN1/issues",
"source": "https://github.com/fgrosse/PHPASN1/tree/v2.3.0"
},
"time": "2021-04-24T19:01:55+00:00"
},
{
"name": "fico7489/laravel-pivot",
"version": "3.0.7",
@ -1741,6 +1816,64 @@
},
"time": "2021-07-22T09:24:00+00:00"
},
{
"name": "laravel-notification-channels/webpush",
"version": "5.1.1",
"source": {
"type": "git",
"url": "https://github.com/laravel-notification-channels/webpush.git",
"reference": "a7ace910dfe7f0ef1677a7fa51361ebce2456fdb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-notification-channels/webpush/zipball/a7ace910dfe7f0ef1677a7fa51361ebce2456fdb",
"reference": "a7ace910dfe7f0ef1677a7fa51361ebce2456fdb",
"shasum": ""
},
"require": {
"illuminate/notifications": "^5.3|^6.0|^7.0|^8.0",
"illuminate/support": "^5.1|^6.0|^7.0|^8.0",
"minishlink/web-push": "^6.0",
"php": "^7.2|^8.0"
},
"require-dev": {
"mockery/mockery": "~1.0",
"orchestra/testbench": "^4.0",
"phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"NotificationChannels\\WebPush\\WebPushServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"NotificationChannels\\WebPush\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Cretu Eusebiu",
"email": "me@cretueusebiu.com",
"homepage": "http://cretueusebiu.com",
"role": "Developer"
}
],
"description": "Web Push Notifications driver for Laravel.",
"homepage": "https://github.com/laravel-notification-channels/webpush",
"support": {
"issues": "https://github.com/laravel-notification-channels/webpush/issues",
"source": "https://github.com/laravel-notification-channels/webpush/tree/5.1.1"
},
"time": "2021-01-08T15:12:09+00:00"
},
{
"name": "laravel/framework",
"version": "v8.62.0",
@ -2412,6 +2545,69 @@
},
"time": "2020-09-25T08:51:26+00:00"
},
{
"name": "minishlink/web-push",
"version": "v6.0.5",
"source": {
"type": "git",
"url": "https://github.com/web-push-libs/web-push-php.git",
"reference": "d87e9e3034ca2b95b1822b1b335e7761c14b89f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/d87e9e3034ca2b95b1822b1b335e7761c14b89f6",
"reference": "d87e9e3034ca2b95b1822b1b335e7761c14b89f6",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"guzzlehttp/guzzle": "^7.0.1|^6.2",
"php": ">=7.2",
"web-token/jwt-key-mgmt": "^2.0",
"web-token/jwt-signature": "^2.0",
"web-token/jwt-signature-algorithm-ecdsa": "^2.0",
"web-token/jwt-util-ecc": "^2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"phpstan/phpstan": "^0.11|^0.12",
"phpunit/phpunit": "^8.0|^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Minishlink\\WebPush\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Louis Lagrange",
"email": "lagrange.louis@gmail.com",
"homepage": "https://github.com/Minishlink"
}
],
"description": "Web Push library for PHP",
"homepage": "https://github.com/web-push-libs/web-push-php",
"keywords": [
"Push API",
"WebPush",
"notifications",
"push",
"web"
],
"support": {
"issues": "https://github.com/web-push-libs/web-push-php/issues",
"source": "https://github.com/web-push-libs/web-push-php/tree/v6.0.5"
},
"time": "2021-04-08T15:22:50+00:00"
},
{
"name": "monolog/monolog",
"version": "2.3.5",
@ -4247,6 +4443,71 @@
},
"time": "2021-06-04T09:56:25+00:00"
},
{
"name": "spomky-labs/base64url",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/base64url.git",
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d",
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.11|^0.12",
"phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
"phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
"phpstan/phpstan-phpunit": "^0.11|^0.12",
"phpstan/phpstan-strict-rules": "^0.11|^0.12"
},
"type": "library",
"autoload": {
"psr-4": {
"Base64Url\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky-Labs/base64url/contributors"
}
],
"description": "Base 64 URL Safe Encoding/Decoding PHP Library",
"homepage": "https://github.com/Spomky-Labs/base64url",
"keywords": [
"base64",
"rfc4648",
"safe",
"url"
],
"support": {
"issues": "https://github.com/Spomky-Labs/base64url/issues",
"source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-11-03T09:10:25+00:00"
},
{
"name": "swiftmailer/swiftmailer",
"version": "v6.2.7",
@ -6988,6 +7249,376 @@
],
"time": "2020-11-12T00:07:28+00:00"
},
{
"name": "web-token/jwt-core",
"version": "v2.2.11",
"source": {
"type": "git",
"url": "https://github.com/web-token/jwt-core.git",
"reference": "53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-token/jwt-core/zipball/53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678",
"reference": "53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678",
"shasum": ""
},
"require": {
"brick/math": "^0.8.17|^0.9",
"ext-json": "*",
"ext-mbstring": "*",
"fgrosse/phpasn1": "^2.0",
"php": ">=7.2",
"spomky-labs/base64url": "^1.0|^2.0"
},
"conflict": {
"spomky-labs/jose": "*"
},
"type": "library",
"autoload": {
"psr-4": {
"Jose\\Component\\Core\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-token/jwt-framework/contributors"
}
],
"description": "Core component of the JWT Framework.",
"homepage": "https://github.com/web-token",
"keywords": [
"JOSE",
"JWE",
"JWK",
"JWKSet",
"JWS",
"Jot",
"RFC7515",
"RFC7516",
"RFC7517",
"RFC7518",
"RFC7519",
"RFC7520",
"bundle",
"jwa",
"jwt",
"symfony"
],
"support": {
"source": "https://github.com/web-token/jwt-core/tree/v2.2.11"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-03-17T14:55:52+00:00"
},
{
"name": "web-token/jwt-key-mgmt",
"version": "v2.2.11",
"source": {
"type": "git",
"url": "https://github.com/web-token/jwt-key-mgmt.git",
"reference": "0b116379515700d237b4e5de86879078ccb09d8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-token/jwt-key-mgmt/zipball/0b116379515700d237b4e5de86879078ccb09d8a",
"reference": "0b116379515700d237b4e5de86879078ccb09d8a",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"web-token/jwt-core": "^2.0"
},
"suggest": {
"ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
"php-http/httplug": "To enable JKU/X5U support.",
"php-http/message-factory": "To enable JKU/X5U support.",
"web-token/jwt-util-ecc": "To use EC key analyzers."
},
"type": "library",
"autoload": {
"psr-4": {
"Jose\\Component\\KeyManagement\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-token/jwt-key-mgmt/contributors"
}
],
"description": "Key Management component of the JWT Framework.",
"homepage": "https://github.com/web-token",
"keywords": [
"JOSE",
"JWE",
"JWK",
"JWKSet",
"JWS",
"Jot",
"RFC7515",
"RFC7516",
"RFC7517",
"RFC7518",
"RFC7519",
"RFC7520",
"bundle",
"jwa",
"jwt",
"symfony"
],
"support": {
"source": "https://github.com/web-token/jwt-key-mgmt/tree/v2.2.11"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-03-17T14:55:52+00:00"
},
{
"name": "web-token/jwt-signature",
"version": "v2.2.11",
"source": {
"type": "git",
"url": "https://github.com/web-token/jwt-signature.git",
"reference": "015b59aaf3b6e8fb9f5bd1338845b7464c7d8103"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-token/jwt-signature/zipball/015b59aaf3b6e8fb9f5bd1338845b7464c7d8103",
"reference": "015b59aaf3b6e8fb9f5bd1338845b7464c7d8103",
"shasum": ""
},
"require": {
"web-token/jwt-core": "^2.1"
},
"suggest": {
"web-token/jwt-signature-algorithm-ecdsa": "ECDSA Based Signature Algorithms",
"web-token/jwt-signature-algorithm-eddsa": "EdDSA Based Signature Algorithms",
"web-token/jwt-signature-algorithm-experimental": "Experimental Signature Algorithms",
"web-token/jwt-signature-algorithm-hmac": "HMAC Based Signature Algorithms",
"web-token/jwt-signature-algorithm-none": "None Signature Algorithm",
"web-token/jwt-signature-algorithm-rsa": "RSA Based Signature Algorithms"
},
"type": "library",
"autoload": {
"psr-4": {
"Jose\\Component\\Signature\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-token/jwt-signature/contributors"
}
],
"description": "Signature component of the JWT Framework.",
"homepage": "https://github.com/web-token",
"keywords": [
"JOSE",
"JWE",
"JWK",
"JWKSet",
"JWS",
"Jot",
"RFC7515",
"RFC7516",
"RFC7517",
"RFC7518",
"RFC7519",
"RFC7520",
"bundle",
"jwa",
"jwt",
"symfony"
],
"support": {
"source": "https://github.com/web-token/jwt-signature/tree/v2.2.11"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-03-01T19:55:28+00:00"
},
{
"name": "web-token/jwt-signature-algorithm-ecdsa",
"version": "v2.2.11",
"source": {
"type": "git",
"url": "https://github.com/web-token/jwt-signature-algorithm-ecdsa.git",
"reference": "44cbbb4374c51f1cf48b82ae761efbf24e1a8591"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-token/jwt-signature-algorithm-ecdsa/zipball/44cbbb4374c51f1cf48b82ae761efbf24e1a8591",
"reference": "44cbbb4374c51f1cf48b82ae761efbf24e1a8591",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"web-token/jwt-signature": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Jose\\Component\\Signature\\Algorithm\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-token/jwt-framework/contributors"
}
],
"description": "ECDSA Based Signature Algorithms the JWT Framework.",
"homepage": "https://github.com/web-token",
"keywords": [
"JOSE",
"JWE",
"JWK",
"JWKSet",
"JWS",
"Jot",
"RFC7515",
"RFC7516",
"RFC7517",
"RFC7518",
"RFC7519",
"RFC7520",
"bundle",
"jwa",
"jwt",
"symfony"
],
"support": {
"source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/v2.2.11"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-01-21T19:18:03+00:00"
},
{
"name": "web-token/jwt-util-ecc",
"version": "v2.2.11",
"source": {
"type": "git",
"url": "https://github.com/web-token/jwt-util-ecc.git",
"reference": "915f3fde86f5236c205620d61177b9ef43863deb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-token/jwt-util-ecc/zipball/915f3fde86f5236c205620d61177b9ef43863deb",
"reference": "915f3fde86f5236c205620d61177b9ef43863deb",
"shasum": ""
},
"require": {
"brick/math": "^0.8.17|^0.9"
},
"suggest": {
"ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance",
"ext-gmp": "GMP or BCMath is highly recommended to improve the library performance"
},
"type": "library",
"autoload": {
"psr-4": {
"Jose\\Component\\Core\\Util\\Ecc\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-token/jwt-framework/contributors"
}
],
"description": "ECC Tools for the JWT Framework.",
"homepage": "https://github.com/web-token",
"keywords": [
"JOSE",
"JWE",
"JWK",
"JWKSet",
"JWS",
"Jot",
"RFC7515",
"RFC7516",
"RFC7517",
"RFC7518",
"RFC7519",
"RFC7520",
"bundle",
"jwa",
"jwt",
"symfony"
],
"support": {
"source": "https://github.com/web-token/jwt-util-ecc/tree/v2.2.11"
},
"funding": [
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-03-24T13:35:17+00:00"
},
{
"name": "webmozart/assert",
"version": "1.10.0",

View File

@ -25,6 +25,7 @@ return [
'enabled' => env('DEBUGBAR_ENABLED', null),
'except' => [
'api*',
'push*',
],
/*

48
config/webpush.php Normal file
View File

@ -0,0 +1,48 @@
<?php
return [
/**
* These are the keys for authentication (VAPID).
* These keys must be safely stored and should not change.
*/
'vapid' => [
'subject' => env('VAPID_SUBJECT'),
'public_key' => env('VAPID_PUBLIC_KEY'),
'private_key' => env('VAPID_PRIVATE_KEY'),
'pem_file' => env('VAPID_PEM_FILE'),
],
/**
* This is model that will be used to for push subscriptions.
*/
'model' => \NotificationChannels\WebPush\PushSubscription::class,
/**
* This is the name of the table that will be created by the migration and
* used by the PushSubscription model shipped with this package.
*/
'table_name' => 'push_subscriptions',
/**
* This is the database connection that will be used by the migration and
* the PushSubscription model shipped with this package.
*/
'database_connection' => null,
/**
* The Guzzle client options used by Minishlink\WebPush.
*/
'client_options' => [],
/**
* Google Cloud Messaging.
*
* @deprecated
*/
'gcm' => [
'key' => env('GCM_KEY'),
'sender_id' => env('GCM_SENDER_ID'),
],
];

View File

@ -283,7 +283,7 @@ if ($options['f'] === 'purgeusers') {
if ($purge > 0) {
$users = \App\Models\AuthLog::where('datetime', '>=', \Carbon\Carbon::now()->subDays($purge))
->distinct()->pluck('user')
->merge(\App\Models\User::has('apiToken')->pluck('username')) // don't purge users with api tokens
->merge(\App\Models\User::has('apiTokens')->pluck('username')) // don't purge users with api tokens
->unique();
if (\App\Models\User::thisAuth()->whereNotIn('username', $users)->delete()) {

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePushSubscriptionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('push_subscriptions', function (Blueprint $table) {
$table->bigIncrements('id');
$table->morphs('subscribable');
$table->string('endpoint', 500)->unique();
$table->string('public_key')->nullable();
$table->string('auth_token')->nullable();
$table->string('content_encoding')->nullable();
$table->string('description')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('push_subscriptions');
}
}

View File

@ -174,6 +174,15 @@ website and setup the transport.
| ------ | ------- |
| Access Token | i23f23mr23rwerw |
## Browser Push
Browser push notifications can send a notification to the user's device even when the browser is not open.
This requires HTTPS, the PHP GMP extension, [Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) support,
and permissions on each device to send alerts.
Simply configure an alert transport and allow notification permission on the device(s) you
wish to receive alerts on. You may disable alerts on a browser on the user preferences page.
## Canopsis
Canopsis is a hypervision tool. LibreNMS can send alerts to Canopsis

View File

@ -26,6 +26,7 @@ Table of Content:
- [Transports](Transports.md)
- [E-Mail](Transports.md#e-mail)
- [API](Transports.md#api)
- [Browser Push](Transports.md#browser-push)
- [Nagios-Compatible](Transports.md#nagios-compatible)
- [IRC](Transports.md#irc)
- [Slack](Transports.md#slack)

View File

@ -26,7 +26,7 @@ Connect to the server command line and follow the instructions below.
apt install software-properties-common
add-apt-repository universe
apt update
apt install acl curl composer fping git graphviz imagemagick mariadb-client mariadb-server mtr-tiny nginx-full nmap php7.4-cli php7.4-curl php7.4-fpm php7.4-gd php7.4-json php7.4-mbstring php7.4-mysql php7.4-snmp php7.4-xml php7.4-zip rrdtool snmp snmpd whois unzip python3-pymysql python3-dotenv python3-redis python3-setuptools python3-systemd
apt install acl curl composer fping git graphviz imagemagick mariadb-client mariadb-server mtr-tiny nginx-full nmap php7.4-cli php7.4-curl php7.4-fpm php7.4-gd php7.4-gmp php7.4-json php7.4-mbstring php7.4-mysql php7.4-snmp php7.4-xml php7.4-zip rrdtool snmp snmpd whois unzip python3-pymysql python3-dotenv python3-redis python3-setuptools python3-systemd
```
=== "Apache"
@ -34,7 +34,7 @@ Connect to the server command line and follow the instructions below.
apt install software-properties-common
add-apt-repository universe
apt update
apt install acl curl apache2 composer fping git graphviz imagemagick libapache2-mod-fcgid mariadb-client mariadb-server mtr-tiny nmap php7.4-cli php7.4-curl php7.4-fpm php7.4-gd php7.4-json php7.4-mbstring php7.4-mysql php7.4-snmp php7.4-xml php7.4-zip rrdtool snmp snmpd whois python3-pymysql python3-dotenv python3-redis python3-setuptools python3-systemd
apt install acl curl apache2 composer fping git graphviz imagemagick libapache2-mod-fcgid mariadb-client mariadb-server mtr-tiny nmap php7.4-cli php7.4-curl php7.4-fpm php7.4-gd php7.4-gmp php7.4-json php7.4-mbstring php7.4-mysql php7.4-snmp php7.4-xml php7.4-zip rrdtool snmp snmpd whois python3-pymysql python3-dotenv python3-redis python3-setuptools python3-systemd
```
=== "CentOS 8"
@ -43,7 +43,7 @@ Connect to the server command line and follow the instructions below.
dnf -y install epel-release
dnf module reset php
dnf module enable php:7.3
dnf install bash-completion cronie fping git ImageMagick mariadb-server mtr net-snmp net-snmp-utils nginx nmap php-fpm php-cli php-common php-curl php-gd php-json php-mbstring php-process php-snmp php-xml php-zip php-mysqlnd python3 python3-PyMySQL python3-redis python3-memcached python3-pip python3-systemd rrdtool unzip
dnf install bash-completion cronie fping git ImageMagick mariadb-server mtr net-snmp net-snmp-utils nginx nmap php-fpm php-cli php-common php-curl php-gd php-gmp php-json php-mbstring php-process php-snmp php-xml php-zip php-mysqlnd python3 python3-PyMySQL python3-redis python3-memcached python3-pip python3-systemd rrdtool unzip
```
=== "Apache"
@ -51,13 +51,13 @@ Connect to the server command line and follow the instructions below.
dnf -y install epel-release
dnf module reset php
dnf module enable php:7.3
dnf install bash-completion cronie fping git httpd ImageMagick mariadb-server mtr net-snmp net-snmp-utils nmap php-fpm php-cli php-common php-curl php-gd php-json php-mbstring php-process php-snmp php-xml php-zip php-mysqlnd python3 python3-PyMySQL python3-redis python3-memcached python3-pip python3-systemd rrdtool unzip
dnf install bash-completion cronie fping git httpd ImageMagick mariadb-server mtr net-snmp net-snmp-utils nmap php-fpm php-cli php-common php-curl php-gd php-gmp php-json php-mbstring php-process php-snmp php-xml php-zip php-mysqlnd python3 python3-PyMySQL python3-redis python3-memcached python3-pip python3-systemd rrdtool unzip
```
=== "Debian 10"
=== "NGINX"
```
apt install acl curl composer fping git graphviz imagemagick mariadb-client mariadb-server mtr-tiny nginx-full nmap php7.3-cli php7.3-curl php7.3-fpm php7.3-gd php7.3-json php7.3-mbstring php7.3-mysql php7.3-snmp php7.3-xml php7.3-zip python3-dotenv python3-pymysql python3-redis python3-setuptools python3-systemd rrdtool snmp snmpd whois
apt install acl curl composer fping git graphviz imagemagick mariadb-client mariadb-server mtr-tiny nginx-full nmap php7.3-cli php7.3-curl php7.3-fpm php7.3-gd php7.3-gmp php7.3-json php7.3-mbstring php7.3-mysql php7.3-snmp php7.3-xml php7.3-zip python3-dotenv python3-pymysql python3-redis python3-setuptools python3-systemd rrdtool snmp snmpd whois
```
## Add librenms user

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,6 +1,13 @@
{
"name": "LibreNMS",
"short_name": "LibreNMS",
"name": "LibreNMS: Network Monitoring",
"description": "A fully featured network monitoring system that provides a wealth of features and device support",
"icons": [
{
"src": "/images/mstile-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/images/android-chrome-192x192.png",
"sizes": "192x192",
@ -14,5 +21,34 @@
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
"display_override": ["window-control-overlay", "minimal-ui"],
"display": "standalone",
"start_url": "/overview",
"scope": "/",
"shortcuts": [
{
"name": "Alerts",
"description": "View active alerts",
"url": "/alerts",
"icons": [{ "src": "/images/icons/bell.svg", "purpose": "any maskable", "type": "image/svg+xml", "sizes": "256x256" }]
},
{
"name": "Devices",
"description": "View device list",
"url": "/devices",
"icons": [{ "src": "/images/icons/server.svg", "purpose": "any maskable", "type": "image/svg+xml", "sizes": "256x256" }]
},
{
"name": "Status",
"description": "View overview of device availability",
"url": "/availability-map",
"icons": [{ "src": "/images/icons/arrow-circle-up.svg", "purpose": "any maskable", "type": "image/svg+xml", "sizes": "256x256" }]
},
{
"name": "Map",
"description": "View geographical map",
"url": "/fullscreenmap",
"icons": [{ "src": "/images/icons/map-marked.svg", "purpose": "any maskable", "type": "image/svg+xml", "sizes": "256x256" }]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 B

File diff suppressed because one or more lines are too long

View File

@ -469,3 +469,8 @@ function humanize_duration(seconds) {
return res;
}
function popUp(URL)
{
window.open(URL, '_blank', 'toolbar=0,scrollbars=1,location=0,statusbar=0,menubar=0,resizable=1,width=550,height=600');
}

View File

@ -0,0 +1,79 @@
if ('serviceWorker' in navigator) {
// Register a Service Worker.
navigator.serviceWorker.register('/service-worker.js');
navigator.serviceWorker.ready
.then(function (registration) {
return registration.pushManager.getSubscription()
.then(async function (subscription) {
if (subscription) {
return subscription;
}
if (Notification.permission === "granted") {
// Get the server's public key
const response = await fetch('./push/key');
const vapidPublicKey = await response.text();
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidKey
});
}
});
}).then(function (subscription) {
if (! subscription) {
console.log('no subscription');
return;
}
const token = document.querySelector('meta[name=csrf-token]').getAttribute('content');
if (localStorage.getItem('notifications') === 'disabled' || Notification.permission === "denied") {
// disabled by user remove the subscription
fetch('./push/unregister', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
body: JSON.stringify({
endpoint: subscription.endpoint
}),
});
} else {
// Send the subscription details to the server
fetch('./push/register', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
body: JSON.stringify({
description: navigator.userAgent,
subscription: subscription
}),
});
}
});
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}

View File

@ -1,5 +1,5 @@
{
"/js/app.js": "/js/app.js?id=cc34801c36c263f094da",
"/js/app.js": "/js/app.js?id=f86bb54abe9f728932a7",
"/js/manifest.js": "/js/manifest.js?id=8d61162bb0caf92f60ff",
"/css/vendor.css": "/css/vendor.css?id=8d7b2ecb46047fe813e4",
"/css/app.css": "/css/app.css?id=54a173acc7d587a9afa4",

70
html/service-worker.js Normal file
View File

@ -0,0 +1,70 @@
self.addEventListener('fetch', function (event) {
});
self.addEventListener('push', function (e) {
if (!(self.Notification && self.Notification.permission === 'granted') && localStorage.getItem('notifications') !== 'disabled') {
//notifications aren't supported or permission not granted!
return;
}
if (e.data) {
const msg = e.data.json();
// console.log(msg)
e.waitUntil(self.registration.showNotification(msg.title, {
body: msg.body,
icon: msg.icon,
actions: msg.actions,
data: msg.data
}));
}
});
self.addEventListener('notificationclick', function (event) {
event.notification.close();
if (event.action === 'alert.acknowledge') {
post(`./alert/${event.notification.data.id}/ack`, {state: 1})
} else if (event.action === 'alert.view') {
// navigate to alert
event.waitUntil(self.clients.claim().then(() => self.clients.matchAll({type: 'window'}))
.then(clients => {
return clients.map(client => {
let alert_url = '/alerts?alert_id=' + event.notification.data.id;
// Check to make sure WindowClient.navigate() is supported.
if ('navigate' in client) {
return client.navigate(alert_url);
}
return self.clients.openWindow(alert_url);
});
}));
}
}, false);
let csrf;
function post(url, data, retry = true) {
if (!self.csrf) {
return self.post(url, data, true);
}
return fetch(url, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': self.csrf
},
body: JSON.stringify(data)
}).then((response) => {
if (response.status === 419 && retry === true) {
self.csrf = null; // reset csrf and try again
return self.post(url, data, false);
}
}).catch(e => console.log(e));
}
const fetchCsrf = async () => {
const response = await fetch('/push/token')
self.csrf = await response.text();
}

View File

@ -21,7 +21,6 @@ use LibreNMS\Modules\Core;
use LibreNMS\Util\Debug;
use LibreNMS\Util\IPv4;
use LibreNMS\Util\IPv6;
use PHPMailer\PHPMailer\PHPMailer;
function array_sort_by_column($array, $on, $order = SORT_ASC)
{
@ -715,82 +714,12 @@ function log_event($text, $device = null, $type = null, $severity = 2, $referenc
// Parse string with emails. Return array with email (as key) and name (as value)
function parse_email($emails)
{
$result = [];
$regex = '/^[\"\']?([^\"\']+)[\"\']?\s{0,}<([^@]+@[^>]+)>$/';
if (is_string($emails)) {
$emails = preg_split('/[,;]\s{0,}/', $emails);
foreach ($emails as $email) {
if (preg_match($regex, $email, $out, PREG_OFFSET_CAPTURE)) {
$result[$out[2][0]] = $out[1][0];
} else {
if (strpos($email, '@')) {
$from_name = Config::get('email_user');
$result[$email] = $from_name;
}
}
}
} else {
// Return FALSE if input not string
return false;
}
return $result;
return \LibreNMS\Util\Mail::parseEmails($emails);
}
function send_mail($emails, $subject, $message, $html = false)
{
if (is_array($emails) || ($emails = parse_email($emails))) {
d_echo("Attempting to email $subject to: " . implode('; ', array_keys($emails)) . PHP_EOL);
$mail = new PHPMailer(true);
try {
$mail->Hostname = php_uname('n');
foreach (parse_email(Config::get('email_from')) as $from => $from_name) {
$mail->setFrom($from, $from_name);
}
foreach ($emails as $email => $email_name) {
$mail->addAddress($email, $email_name);
}
$mail->Subject = $subject;
$mail->XMailer = Config::get('project_name');
$mail->CharSet = 'utf-8';
$mail->WordWrap = 76;
$mail->Body = $message;
if ($html) {
$mail->isHTML(true);
}
switch (strtolower(trim(Config::get('email_backend')))) {
case 'sendmail':
$mail->Mailer = 'sendmail';
$mail->Sendmail = Config::get('email_sendmail_path');
break;
case 'smtp':
$mail->isSMTP();
$mail->Host = Config::get('email_smtp_host');
$mail->Timeout = Config::get('email_smtp_timeout');
$mail->SMTPAuth = Config::get('email_smtp_auth');
$mail->SMTPSecure = Config::get('email_smtp_secure');
$mail->Port = Config::get('email_smtp_port');
$mail->Username = Config::get('email_smtp_username');
$mail->Password = Config::get('email_smtp_password');
$mail->SMTPAutoTLS = Config::get('email_auto_tls');
$mail->SMTPDebug = false;
break;
default:
$mail->Mailer = 'mail';
break;
}
$mail->send();
return true;
} catch (\PHPMailer\PHPMailer\Exception $e) {
return $e->errorMessage();
} catch (Exception $e) {
return $e->getMessage();
}
}
return 'No contacts found';
return \LibreNMS\Util\Mail::send($emails, $subject, $message, $html);
}
function hex2str($hex)
@ -1058,13 +987,7 @@ function guidv4($data)
*/
function set_curl_proxy($curl)
{
$proxy = get_proxy();
$tmp = rtrim($proxy, '/');
$proxy = str_replace(['http://', 'https://'], '', $tmp);
if (! empty($proxy)) {
curl_setopt($curl, CURLOPT_PROXY, $proxy);
}
\LibreNMS\Util\Proxy::applyToCurl($curl);
}
/**
@ -1074,12 +997,7 @@ function set_curl_proxy($curl)
*/
function get_guzzle_proxy()
{
$proxy = get_proxy();
$tmp = rtrim($proxy, '/');
$proxy = str_replace(['http://', 'https://'], '', $tmp);
return empty($proxy) ? '' : ('tcp://' . $proxy);
return \LibreNMS\Util\Proxy::forGuzzle();
}
/**
@ -1089,17 +1007,7 @@ function get_guzzle_proxy()
*/
function get_proxy()
{
if (getenv('http_proxy')) {
return getenv('http_proxy');
} elseif (getenv('https_proxy')) {
return getenv('https_proxy');
} elseif ($callback_proxy = Config::get('callback_proxy')) {
return $callback_proxy;
} elseif ($http_proxy = Config::get('http_proxy')) {
return $http_proxy;
}
return false;
return \LibreNMS\Util\Proxy::get();
}
function target_to_id($target)

View File

@ -164,6 +164,7 @@ if (defined('SHOW_SETTINGS')) {
</form>
';
} else {
$alert_id = $vars['alert_id'] ?? 0;
$device_id = $device['device_id'];
$acknowledged = $widget_settings['acknowledged'];
$fired = $widget_settings['fired'];
@ -255,6 +256,9 @@ var alerts_grid = $("#alerts_' . $unique_id . '").bootgrid({
return {
id: "alerts",
';
if (is_numeric($alert_id)) {
$common_output[] = "alert_id: '$alert_id',\n";
}
if (is_numeric($acknowledged)) {
$common_output[] = "acknowledged: '$acknowledged',\n";

View File

@ -4,10 +4,10 @@ echo "<div style='margin:auto; text-align: center; margin-top: 50px; max-width:6
print_optionbar_start(100, 600);
echo "
<table height=100% width=100%><tr>
<td><img src='images/no-48.png' valign=absmiddle></td>
<td style='color: darkred'><i class='fa fa-3x fa-ban'></i></td>
<td width=10></td>
<td>
<span style='color: #990000; font-weight: bold;'>
<span style='color: darkred; font-weight: bold;'>
<span style='font-size: 16px; font-weight: bold;'>Error</span>
<br />
<span style='font-size: 12px;'>You have insufficient permissions to view this page.</span>

View File

@ -1,90 +0,0 @@
<?php
/**
* ack-alert.inc.php
*
* LibreNMS ack-alert.inc.php
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*
* @copyright 2018 Neil Lathwood
* @author Neil Lathwood <gh+n@laf.io>
*/
use LibreNMS\Config;
header('Content-type: application/json');
$alert_id = $vars['alert_id'];
$state = $vars['state'];
$ack_msg = $vars['ack_msg'];
$until_clear = $vars['ack_until_clear'];
$status = 'error';
if (! is_numeric($alert_id)) {
$message = 'No alert selected';
} elseif (! is_numeric($state)) {
$message = 'No state passed';
} else {
if ($state == 2) {
$state = 1;
$state_descr = 'UnAck';
$open = 1;
} elseif ($state >= 1) {
$state = 2;
$state_descr = 'Ack';
$open = 1;
}
if ($until_clear === 'true') {
$until_clear = true;
} else {
$until_clear = false;
}
$info = json_encode([
'until_clear' => $until_clear,
]);
$username = Auth::user()->username;
$data = [
'state' => $state,
'open' => $open,
'info' => $info,
];
$note = dbFetchCell('SELECT note FROM alerts WHERE id=?', [$alert_id]);
if (! empty($note)) {
$note .= PHP_EOL;
}
$data['note'] = $note . date(Config::get('dateformat.long')) . " - $state_descr ($username) $ack_msg";
if (dbUpdate($data, 'alerts', 'id=?', [$alert_id]) >= 0) {
if (in_array($state, [2, 22])) {
$alert_info = dbFetchRow('SELECT `alert_rules`.`name`,`alerts`.`device_id` FROM `alert_rules` LEFT JOIN `alerts` ON `alerts`.`rule_id` = `alert_rules`.`id` WHERE `alerts`.`id` = ?', [$alert_id]);
log_event("$username acknowledged alert {$alert_info['name']} note: $ack_msg", $alert_info['device_id'], 'alert', 2, $alert_id);
}
$message = 'Alert acknowledged status changed.';
$status = 'ok';
} else {
$message = 'Alert has not been acknowledged.';
}
}//end if
exit(json_encode([
'status' => $status,
'message' => $message,
]));

View File

@ -22,13 +22,6 @@
* @copyright 2018 Vivia Nguyen-Tran
* @author Vivia Nguyen-Tran <vivia@ualberta.ca>
*/
use Illuminate\Container\Container;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Translation\FileLoader;
use Illuminate\Translation\Translator;
use Illuminate\Validation\Factory;
header('Content-type: application/json');
if (! Auth::user()->hasGlobalAdmin()) {
@ -79,10 +72,7 @@ if (empty($name)) {
// Build config values
$result = call_user_func_array($class . '::configTemplate', []);
$loader = new FileLoader(new Filesystem, "$install_dir/resources/lang");
$translator = new Translator($loader, 'en');
$validation = new Factory($translator, new Container);
$validator = $validation->make($vars, $result['validation']);
$validator = Validator::make($vars, $result['validation']);
if ($validator->fails()) {
$errors = $validator->errors();
foreach ($errors->all() as $error) {

View File

@ -1,67 +0,0 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. Please see LICENSE.txt at the top level of
* the source code distribution for details.
*/
use LibreNMS\Alert\AlertUtil;
use LibreNMS\Config;
if (! Auth::user()->hasGlobalAdmin()) {
header('Content-type: text/plain');
exit('ERROR: You need to be admin');
}
$transport = $vars['transport'] ?: null;
$transport_id = $vars['transport_id'] ?: null;
$tmp = [dbFetchRow('select device_id,hostname,sysDescr,version,hardware,location_id from devices order by device_id asc limit 1')];
$tmp['contacts'] = AlertUtil::getContacts($tmp);
$obj = [
'hostname' => $tmp[0]['hostname'],
'device_id' => $tmp[0]['device_id'],
'sysDescr' => $tmp[0]['sysDescr'],
'version' => $tmp[0]['version'],
'hardware' => $tmp[0]['hardware'],
'location' => $tmp[0]['location'],
'title' => 'Testing transport from ' . Config::get('project_name'),
'elapsed' => '11s',
'id' => '000',
'faults' => false,
'uid' => '000',
'severity' => 'critical',
'rule' => 'macros.device = 1',
'name' => 'Test-Rule',
'string' => '#1: test => string;',
'timestamp' => date('Y-m-d H:i:s'),
'contacts' => $tmp['contacts'],
'state' => '1',
'msg' => 'This is a test alert',
];
$response = ['status' => 'error'];
if ($transport_id) {
$transport = dbFetchCell('SELECT `transport_type` FROM `alert_transports` WHERE `transport_id` = ?', [$transport_id]);
}
$class = 'LibreNMS\\Alert\\Transport\\' . ucfirst($transport);
if (class_exists($class)) {
$opts = Config::get("alert.transports.$transport");
$instance = new $class($transport_id);
$result = $instance->deliverAlert($obj, $opts);
if ($result === true) {
$response['status'] = 'ok';
} else {
$response['message'] = $result;
}
}
header('Content-type: application/json');
echo json_encode($response);

View File

@ -58,9 +58,9 @@ use LibreNMS\Config;
var ack_until_clear = $("#ack_until_clear").bootstrapSwitch('state');
$.ajax({
type: "POST",
url: "ajax_form.php",
url: '<?php echo route('alert.ack', ['alert' => ':alert_id']) ?>'.replace(':alert_id', ack_alert_id),
dataType: "json",
data: { type: "ack-alert", alert_id: ack_alert_id, state: ack_alert_state, ack_msg: ack_alert_note, ack_until_clear: ack_until_clear },
data: { state: ack_alert_state, ack_msg: ack_alert_note, ack_until_clear: ack_until_clear },
success: function (data) {
if (data.status === "ok") {
toastr.success(data.message);
@ -70,7 +70,7 @@ use LibreNMS\Config;
$table.bootgrid("sort", sortDictionary);
$("#alert_ack_modal").modal('hide');
} else {
toastr.error(data.message);
toastr.error(data.message || 'Failed to update acknowledgement');
}
},
error: function(){

View File

@ -37,8 +37,8 @@ if (Auth::user()->hasGlobalAdmin()) {
</div>
<div class="form-group" title="The type of transport.">
<label for='transport-choice' class='col-sm-3 col-md-2 control-label'>Transport type: </label>
<div class="col-sm-3">
<select name='transport-choice' id='transport-choice' class='form-control'>
<div class="col-sm-9 col-md-10">
<select name='transport-choice' id='transport-choice' class='form-control' style="width: auto">
<?php
// Create list of transport
@ -49,10 +49,12 @@ if (Auth::user()->hasGlobalAdmin()) {
if (empty($transport)) {
continue;
}
$transports_list[] = $transport;
$class = "\LibreNMS\Alert\Transport\\$transport";
$instance = new $class;
$transports_list[$transport] = $instance->name();
}
foreach ($transports_list as $transport) {
echo '<option value="' . strtolower($transport) . '-form">' . $transport . '</option>';
foreach ($transports_list as $transport => $name) {
echo '<option value="' . strtolower($transport) . '-form">' . $name . '</option>';
} ?>
</select>
</div>
@ -67,7 +69,7 @@ if (Auth::user()->hasGlobalAdmin()) {
<?php
$switches = []; // store names of bootstrap switches
foreach ($transports_list as $transport) {
foreach ($transports_list as $transport => $name) {
$class = 'LibreNMS\\Alert\\Transport\\' . $transport;
if (! method_exists($class, 'configTemplate')) {
@ -77,7 +79,7 @@ if (Auth::user()->hasGlobalAdmin()) {
echo '<form method="post" role="form" id="' . strtolower($transport) . '-form" class="form-horizontal transport">';
echo csrf_field();
echo '<input type="hidden" name="transport-type" id="transport-type" value="' . strtolower($transport) . '">';
echo '<input type="hidden" name="transport-type" value="' . strtolower($transport) . '">';
$tmp = call_user_func($class . '::configTemplate');

View File

@ -32,5 +32,3 @@ $page_title = 'Alerts';
unset($device['device_id']);
?>
</div>

View File

@ -467,26 +467,6 @@ if ($count < 1) {
$("[data-toggle='modal'], [data-toggle='popover']").popover({
trigger: 'hover'
});
$('#ack-alert').on("click", function(e) {
event.preventDefault();
var alert_id = $(this).data("alert_id");
$.ajax({
type: "POST",
url: "ajax_form.php",
data: { type: "ack-alert", alert_id: alert_id },
success: function(msg){
$("#message").html('<div class="alert alert-info">'+msg+'</div>');
if(msg.indexOf("ERROR:") <= -1) {
setTimeout(function() {
location.reload(1);
}, 1000);
}
},
error: function(){
$("#message").html('<div class="alert alert-info">An error occurred acking this alert.</div>');
}
});
});
$("[name='alert-rule']").bootstrapSwitch('offColor','danger');
$('input[name="alert-rule"]').on('switchChange.bootstrapSwitch', function(event, state) {

View File

@ -31,54 +31,21 @@ if (Auth::user()->hasGlobalAdmin()) {
<?php
// Iterate through each alert transport
$query = 'SELECT `transport_id` AS `id`, `transport_name` AS `name`, `transport_type` AS `type`, `is_default`, `transport_config` AS `config` FROM `alert_transports` order by `name`';
foreach (dbFetchRows($query) as $transport) {
echo "<tr id=\"alert-transport-{$transport['id']}\">";
echo '<td>' . $transport['name'] . '</td>';
echo '<td>' . $transport['type'] . '</td>';
if ($transport['is_default'] == true) {
echo '<td>Yes</td>';
} else {
echo '<td>No</td>';
}
foreach (\App\Models\AlertTransport::all() as $transport) {
$instance = $transport->instance();
echo "<tr id=\"alert-transport-{$transport->transport_id}\">";
echo '<td>' . $transport->transport_name . '</td>';
echo '<td>' . $instance->name() . '</td>';
echo $transport->is_default ? '<td>Yes</td>' : '<td>No</td>';
echo '<td class="col-sm-4"><i>' . nl2br($instance->displayDetails()) . '</i></td>';
echo "<td class='col-sm-4'>";
// Iterate through transport config template to display config details
$class = 'LibreNMS\\Alert\\Transport\\' . ucfirst($transport['type']);
if (! method_exists($class, 'configTemplate')) {
//skip
continue;
}
$tmp = call_user_func($class . '::configTemplate');
$transport_config = json_decode($transport['config'], true);
foreach ($tmp['config'] as $item) {
if ($item['type'] == 'oauth') {
continue;
}
$val = $transport_config[$item['name']];
if ($item['type'] == 'password') {
$val = '<b>&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;</b>';
}
// Match value to key name for select inputs
if ($item['type'] == 'select') {
$val = array_search($val, $item['options']);
}
echo '<i>' . $item['title'] . ': ' . $val . '<br/></i>';
}
echo '</td>';
echo '<td>';
// Add action buttons for admin users only
if (Auth::user()->hasGlobalAdmin()) {
echo "<div class='btn-group btn-group-sm' role='group'>";
echo "<button type='button' class='btn btn-primary btn-sm' data-toggle='modal' data-target='#edit-alert-transport' data-transport_id='" . $transport['id'] . "' name='edit-alert-rule' data-container='body' data-toggle='popover' data-content='Edit transport'><i class='fa fa-lg fa-pencil' aria-hidden='true'></i></button> ";
echo "<button type='button' class='btn btn-danger btn-sm' aria-label='Delete' data-toggle='modal' data-target='#delete-alert-transport' data-transport_id='" . $transport['id'] . "' name='delete-alert-transport' data-container='body' data-toggle='popover' data-content='Delete transport'><i class='fa fa-lg fa-trash' aria-hidden='true'></i></button>";
echo "<button type='button' class='btn btn-warning btn-sm' data-transport_id='" . $transport['id'] . "' data-transport='{$transport['type']}' name='test-transport' id='test-transport' data-toggle='popover' data-content='Test transport'><i class='fa fa-lg fa-check' aria-hidden='true'></i></button> ";
echo "<button type='button' class='btn btn-primary btn-sm' data-toggle='modal' data-target='#edit-alert-transport' data-transport_id='" . $transport->transport_id . "' name='edit-alert-rule' data-container='body' data-toggle='popover' data-content='Edit transport'><i class='fa fa-lg fa-pencil' aria-hidden='true'></i></button> ";
echo "<button type='button' class='btn btn-danger btn-sm' aria-label='Delete' data-toggle='modal' data-target='#delete-alert-transport' data-transport_id='" . $transport->transport_id . "' name='delete-alert-transport' data-container='body' data-toggle='popover' data-content='Delete transport'><i class='fa fa-lg fa-trash' aria-hidden='true'></i></button>";
echo "<button type='button' class='btn btn-warning btn-sm' data-transport_id='" . $transport->transport_id . "' data-transport='{$transport->transport_type}' name='test-transport' id='test-transport' data-toggle='popover' data-content='Test transport'><i class='fa fa-lg fa-check' aria-hidden='true'></i></button> ";
echo '</div>';
}
echo '</td>';
@ -140,7 +107,7 @@ foreach (dbFetchRows($query) as $group) {
var transport = $this.data("transport");
$.ajax({
type: 'POST',
url: 'ajax_form.php',
url: '<?php echo route('alert.transports.test', ['transport' => ':transport_id']) ?>'.replace(':transport_id', transport_id),
data: { type: "test-transport", transport_id: transport_id },
dataType: "json",
success: function(data){

View File

@ -26,6 +26,11 @@ $alert_states = [
$show_recovered = false;
if (is_numeric($vars['alert_id']) && $vars['alert_id'] > 0) {
$where .= ' AND `alerts`.`id` = ?';
$param[] = $vars['alert_id'];
}
if (is_numeric($vars['device_id']) && $vars['device_id'] > 0) {
$where .= ' AND `alerts`.`device_id`=' . $vars['device_id'];
}

View File

@ -1739,6 +1739,22 @@ pseudowires:
- { Field: pw_descr, Type: varchar(128), 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [pseudowire_id], Unique: true, Type: BTREE }
push_subscriptions:
Columns:
- { Field: id, Type: 'bigint unsigned', 'Null': false, Extra: auto_increment }
- { Field: subscribable_type, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: subscribable_id, Type: 'bigint unsigned', 'Null': false, Extra: '' }
- { Field: endpoint, Type: varchar(500), 'Null': false, Extra: '' }
- { Field: public_key, Type: varchar(255), 'Null': true, Extra: '' }
- { Field: auth_token, Type: varchar(255), 'Null': true, Extra: '' }
- { Field: content_encoding, Type: varchar(255), 'Null': true, Extra: '' }
- { Field: description, Type: varchar(255), 'Null': true, Extra: '' }
- { Field: created_at, Type: timestamp, 'Null': true, Extra: '' }
- { Field: updated_at, Type: timestamp, 'Null': true, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
push_subscriptions_endpoint_unique: { Name: push_subscriptions_endpoint_unique, Columns: [endpoint], Unique: true, Type: BTREE }
push_subscriptions_subscribable_type_subscribable_id_index: { Name: push_subscriptions_subscribable_type_subscribable_id_index, Columns: [subscribable_type, subscribable_id], Unique: false, Type: BTREE }
route:
Columns:
- { Field: route_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }

View File

@ -0,0 +1,12 @@
<?php
return [
'notification-subscription-status' => [
'no-support' => 'This browser does not support notifications',
'no-transport' => 'To enable browser notifications, there must be an alert transport referencing this user',
'enabled' => 'Notifications enabled for this browser',
'disabled' => 'Notifications disabled for this browser',
'enable' => 'Enable',
'disable' => 'Disable',
],
];

View File

@ -55,11 +55,9 @@
var ack_until_clear = $("#ack_until_clear").bootstrapSwitch('state');
$.ajax({
type: "POST",
url: "ajax_form.php",
url: '{{ route('alert.ack', ['alert' => ':alert_id']) }}'.replace(':alert_id', ack_alert_id),
dataType: "json",
data: {
type: "ack-alert",
alert_id: ack_alert_id,
state: ack_alert_state,
ack_msg: ack_alert_note,
ack_until_clear: ack_until_clear

View File

@ -0,0 +1,64 @@
<div<div x-data="notificationSubscriptionStatus()">
<div x-show="! supported">@lang('components.notification-subscription-status.no-support')</div>
@if($userHasTransport)
<div x-show="supported">
<div>
<span x-text="enabled ? '@lang('components.notification-subscription-status.enabled')' : '@lang('components.notification-subscription-status.disabled')'"></span>
<button x-on:click="toggle()" type="button" class="tw-float-right tw-border tw-border-gray-500 tw-text-gray-500 hover:tw-bg-gray-500 hover:tw-text-gray-100 tw-rounded tw-px-4 tw-py-2" x-text="enabled ? '@lang('components.notification-subscription-status.disable')' : '@lang('components.notification-subscription-status.enable')'"></button>
</div>
</div>
@else
<div x-show="supported">
@admin
<a href="{{ url('alert-transports') }}">
@lang('components.notification-subscription-status.no-transport')
</a>
@else
@lang('components.notification-subscription-status.no-transport')
@endadmin
</div>
@endif
<script>
function notificationSubscriptionStatus() {
return {
supported: 'Notification' in window,
enabled: 'Notification' in window && Notification.permission === 'granted' && localStorage.getItem('notifications') !== 'disabled',
toggle() {
if (this.enabled) {
localStorage.setItem('notifications', 'disabled');
this.enabled = false;
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
subscription.unsubscribe().then(function(success) {
if (success) {
fetch('./push/unregister', {
method: 'post',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': '{{ csrf_token() }}'
},
body: JSON.stringify({
endpoint: subscription.endpoint
}),
});
}
})
}
})
})
} else if (Notification.permission === 'granted') {
localStorage.setItem('notifications', 'enabled');
this.enabled = true;
} else {
Notification.requestPermission().then((permission) => {
localStorage.setItem('notifications', 'enabled');
this.enabled = permission === 'granted';
});
}
}
}
}
</script>
</di</div>

View File

@ -75,17 +75,7 @@
});
var ajax_url = "{{ url('/ajax') }}";
</script>
<script src="{{ asset('js/librenms.js?ver=05072021') }}"></script>
<script type="text/javascript">
<!-- Begin
function popUp(URL)
{
day = new Date();
id = day.getTime();
eval("page" + id + " = window.open(URL, '" + id + "', 'toolbar=0,scrollbars=1,location=0,statusbar=0,menubar=0,resizable=1,width=550,height=600');");
}
// End -->
</script>
<script src="{{ asset('js/librenms.js?ver=09072021') }}"></script>
<script type="text/javascript" src="{{ asset('js/overlib_mini.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/toastr.min.js?ver=05072021') }}"></script>
<script type="text/javascript" src="{{ asset('js/boot.js') }}"></script>
@ -97,6 +87,9 @@
document.documentElement.classList.remove('tw-dark')
}
</script>
@auth
<script src="{{ asset('js/register-service-worker.js') }}" defer></script>
@endauth
@yield('javascript')
</head>
<body>

View File

@ -748,4 +748,14 @@
}
});
}
@if($browser_push)
if (localStorage.getItem('notifications') !== 'disabled') {
Notification.requestPermission().then(function (permission) {
if (permission === "denied") {
localStorage.setItem('notifications', 'disabled');
}
});
}
@endif
</script>

View File

@ -18,6 +18,10 @@
</div>
@endif
<x-panel title="{{ __('Push Notifications') }}">
<x-notification-subscription-status></x-notification-subscription-status>
</x-panel>
@if($can_change_password)
<x-panel title="{{ __('Change Password') }}">
<form method="POST" action="{{ route('users.update', [$user->user_id]) }}" class="form-horizontal" role="form">

Some files were not shown because too many files have changed in this diff Show More