Skip to content

Commit 98c2587

Browse files
authored
Merge branch 'master' into feat-add-alert-template-api
2 parents f9fc289 + 43587a4 commit 98c2587

2,415 files changed

Lines changed: 749338 additions & 434711 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ jobs:
1919
fail-fast: false
2020
matrix:
2121
php-version:
22-
- 8.2
2322
- 8.4
2423
arguments:
2524
- --os a
@@ -31,10 +30,6 @@ jobs:
3130
- --os t
3231
- --os u,v,w,x,y,z,0,1,2,3,4,5,6,7,8,9
3332
include:
34-
-
35-
name: PHP 8.2
36-
php-version: 8.2
37-
database: mysql:8.0
3833
-
3934
name: PHP 8.4
4035
php-version: 8.4
@@ -44,6 +39,11 @@ jobs:
4439
php-version: 8.4
4540
database: mariadb:11.7
4641
arguments: --full --exclude-phpunit-group=browser,mibs,external-dependencies,os
42+
-
43+
name: Other
44+
php-version: 8.2
45+
database: mysql:8.0
46+
arguments: --full --exclude-phpunit-group=browser,mibs,external-dependencies,os
4747

4848
services:
4949
database:

LibreNMS/Alert/AlertUtil.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
use App\Facades\LibrenmsConfig;
3030
use App\Models\Device;
31+
use App\Models\DeviceGroup;
3132
use App\Models\User;
3233
use DeviceCache;
3334
use Illuminate\Database\Eloquent\Builder;
@@ -173,6 +174,8 @@ public static function findContactsOwners(array $results): array
173174
return User::whereNot('email', '')->where(function (Builder $query) use ($results): void {
174175
if ($device_ids = array_filter(Arr::pluck($results, 'device_id'))) {
175176
$query->orWhereHas('devicesOwned', fn ($q) => $q->whereIn('devices_perms.device_id', $device_ids));
177+
// Find all device groups that users have been granted access to where the device group also contains at least one device that we are looking for
178+
$query->orWhereHas('deviceGroups', fn ($q) => $q->whereIn('device_groups.id', DeviceGroup::WhereHas('devices', fn ($dq) => $dq->whereIn('devices.device_id', $device_ids))->pluck('device_groups.id')));
176179
}
177180
if ($port_ids = array_filter(Arr::pluck($results, 'port_id'))) {
178181
$query->orWhereHas('portsOwned', fn ($q) => $q->whereIn('ports_perms.port_id', $port_ids));

LibreNMS/Alert/RunAlerts.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@
3434
use App\Facades\DeviceCache;
3535
use App\Facades\LibrenmsConfig;
3636
use App\Facades\Rrd;
37+
use App\Models\Alert;
3738
use App\Models\AlertTransport;
3839
use App\Models\ApplicationMetric;
3940
use App\Models\Eventlog;
41+
use Illuminate\Support\Facades\DB;
42+
use Illuminate\Support\Facades\Log;
4043
use LibreNMS\Alerting\QueryBuilderParser;
4144
use LibreNMS\Enum\AlertState;
4245
use LibreNMS\Enum\MaintenanceStatus;
@@ -509,9 +512,18 @@ public function runAlerts()
509512
$alert['details']['count'] = 0;
510513
}
511514

512-
$chk = dbFetchRow('SELECT alerts.alerted,devices.ignore,devices.disabled FROM alerts,devices WHERE alerts.device_id = ? && devices.device_id = alerts.device_id && alerts.rule_id = ?', [$alert['device_id'], $alert['rule_id']]);
515+
$status_check = DB::table('devices')
516+
->where('device_id', $alert['device_id'])
517+
->first(['ignore', 'disabled']);
513518

514-
if ($chk['alerted'] == $alert['state']) {
519+
if ($status_check === null) {
520+
Log::warning("Alert #{$alert['id']} references non-existent device {$alert['device_id']}, cleaning up");
521+
Alert::query()->where('id', $alert['id'])->delete();
522+
523+
continue;
524+
}
525+
526+
if ($alert['alerted'] == $alert['state']) {
515527
$noiss = true;
516528
}
517529

@@ -561,7 +573,7 @@ public function runAlerts()
561573
$noiss = false;
562574
}
563575
}
564-
if ($chk['ignore'] == 1 || $chk['disabled'] == 1) {
576+
if ($status_check->ignore || $status_check->disabled) {
565577
$noiss = true;
566578
$updet = false;
567579
$noacc = false;

LibreNMS/Alert/Transport/Alertmanager.php

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
namespace LibreNMS\Alert\Transport;
2525

26+
use Illuminate\Http\Client\ConnectionException;
27+
use Illuminate\Http\Client\Pool;
2628
use LibreNMS\Alert\Transport;
2729
use LibreNMS\Enum\AlertState;
2830
use LibreNMS\Exceptions\AlertTransportDeliveryException;
@@ -59,36 +61,59 @@ public function deliverAlert(array $alert_data): bool
5961
$alertmanager_opts = $this->parseUserOptions($this->config['alertmanager-options']);
6062
foreach ($alertmanager_opts as $label => $value) {
6163
if (str_starts_with((string) $label, 'stc_')) {
62-
$data[0]['labels'][$label] = strip_tags((string) $value);
64+
// Static label: strip the stc_ prefix and use the value as-is
65+
$cleanLabel = substr((string) $label, 4);
66+
$data[0]['labels'][$cleanLabel] = strip_tags((string) $value);
6367
} else {
64-
$data[0]['labels'][$label] = strip_tags(
65-
(string) ($alert_data[$value] ?? current(array_filter(
66-
array_column($alert_data['faults'] ?? [], $value),
67-
fn ($v) => ! empty($v)
68-
)) ?? $value)
69-
);
68+
// Dynamic label: try to resolve value from alert data, faults, or fall back to literal
69+
$resolved = $alert_data[$value] ?? current(array_filter(
70+
array_column($alert_data['faults'] ?? [], $value),
71+
fn ($v) => ! empty($v)
72+
)) ?: $value;
73+
74+
$data[0]['labels'][$label] = strip_tags((string) $resolved);
75+
7076
if (str_starts_with((string) $label, 'dyn_') && $data[0]['labels'][$label] == $value) {
7177
unset($data[0]['labels'][$label]);
7278
}
7379
}
7480
}
7581

76-
$client = Http::client()->timeout(5);
82+
$urls = array_values(array_filter(array_map(trim(...), explode(',', (string) $url))));
7783

78-
if ($username != '' && $password != '') {
79-
$client->withBasicAuth($username, $password);
80-
}
84+
$client = Http::client()->timeout(2);
8185

82-
foreach (explode(',', (string) $url) as $am) {
83-
$post_url = ($am . '/api/v2/alerts');
84-
$res = $client->post($post_url, $data);
86+
$responses = $client->pool(fn (Pool $pool) => array_map(function (string $baseUrl) use ($pool, $username, $password, $data) {
87+
$req = $pool;
88+
if ($username !== '' && $password !== '') {
89+
$req = $req->withBasicAuth($username, $password);
90+
}
8591

86-
if ($res->successful()) {
87-
return true;
92+
return $req->post(rtrim($baseUrl, '/') . '/api/v2/alerts', $data);
93+
}, $urls));
94+
95+
foreach ($responses as $res) {
96+
if ($res instanceof ConnectionException) {
97+
throw new AlertTransportDeliveryException(
98+
$alert_data,
99+
0,
100+
$res->getMessage(),
101+
$alertmanager_msg,
102+
$data
103+
);
104+
}
105+
if (! $res->successful()) {
106+
throw new AlertTransportDeliveryException(
107+
$alert_data,
108+
$res->status(),
109+
$res->body(),
110+
$alertmanager_msg,
111+
$data
112+
);
88113
}
89114
}
90115

91-
throw new AlertTransportDeliveryException($alert_data, $res->status(), $res->body(), $alertmanager_msg, $data);
116+
return true;
92117
}
93118

94119
public static function configTemplate(): array

LibreNMS/Alert/Transport/Mail.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424

2525
namespace LibreNMS\Alert\Transport;
2626

27+
use App\Facades\DeviceCache;
2728
use App\Facades\LibrenmsConfig;
29+
use App\Models\Eventlog;
2830
use Exception;
2931
use Illuminate\Support\Str;
3032
use LibreNMS\Alert\AlertUtil;
3133
use LibreNMS\Alert\Transport;
34+
use LibreNMS\Enum\Severity;
3235
use LibreNMS\Exceptions\AlertTransportDeliveryException;
3336
use Spatie\Permission\Models\Role;
3437

@@ -43,6 +46,13 @@ public function deliverAlert(array $alert_data): bool
4346
default => $this->config['email'] ?? $alert_data['contacts'] ?? [], // contacts is only used by legacy synthetic transport
4447
};
4548

49+
if (is_array($emails) && count($emails) == 0) {
50+
$device = DeviceCache::get($alert_data['device_id']);
51+
Eventlog::log('No e-mail recipients found for transport ' . $alert_data['transport_name'], $device, 'alert', Severity::Notice);
52+
53+
return true;
54+
}
55+
4656
$html = LibrenmsConfig::get('email_html');
4757

4858
if ($html && ! $this->isHtmlContent($alert_data['msg'])) {

LibreNMS/Alert/Transport/Pagerduty.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function deliverAlert(array $alert_data): bool
4949
} else {
5050
// Legacy behaviour: strip tags and split into non-empty lines
5151
$safe_message = strip_tags($msg_raw) ?: 'Test';
52-
$message = array_filter(explode("\n", $safe_message), fn ($value): bool => strlen($value) > 0);
52+
$message = array_filter(explode("\n", $safe_message), fn ($value): bool => strlen((string) $value) > 0);
5353
$custom_details = ['message' => $message];
5454
}
5555
$data = [

LibreNMS/Authentication/ActiveDirectoryAuthorizer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ protected function userInGroup($username, $groupname)
7676
$search_filter,
7777
['cn']
7878
);
79+
80+
if ($search === false) {
81+
throw new AuthenticationException('LDAP search failed: ' . ldap_error($connection));
82+
}
83+
7984
$result = ldap_get_entries($connection, $search);
8085

8186
if ($result == false || $result['count'] !== 1) {

LibreNMS/Billing.php

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -127,47 +127,26 @@ public static function getLastMeasurement($bill_id): array
127127

128128
private static function get95thagg($bill_id, $datefrom, $dateto): float
129129
{
130-
$mq_sql = 'SELECT count(delta) FROM bill_data WHERE bill_id = ?';
131-
$mq_sql .= ' AND timestamp > ? AND timestamp <= ?';
132-
$measurements = dbFetchCell($mq_sql, [$bill_id, $datefrom, $dateto]);
133-
$measurement_95th = (round($measurements / 100 * 95) - 1);
130+
$sum_data = dbFetchRows('SELECT (SUM(delta) / SUM(period) * 8) as rate, FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`timestamp`) / 300) * 300) AS bucket_start, DATE_ADD(FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`timestamp`) / 300) * 300), INTERVAL 5 MINUTE) AS bucket_end,SUM(delta) as delta_sum FROM bill_data WHERE bill_id = ? AND timestamp > ? AND timestamp <= ? GROUP BY bill_id, bucket_start ORDER BY rate ASC', [$bill_id, $datefrom, $dateto]);
131+
$measurement_95th = (round(count($sum_data) / 100 * 95) - 2);
134132

135-
$q_95_sql = 'SELECT (delta / period * 8) AS rate FROM bill_data WHERE bill_id = ?';
136-
$q_95_sql .= ' AND timestamp > ? AND timestamp <= ? ORDER BY rate ASC';
137-
$a_95th = dbFetchColumn($q_95_sql, [$bill_id, $datefrom, $dateto]);
138-
$m_95th = $a_95th[$measurement_95th];
139-
140-
return round($m_95th, 2);
133+
return round($sum_data[$measurement_95th]['rate'], 2);
141134
}
142135

143136
private static function get95thIn($bill_id, $datefrom, $dateto): float
144137
{
145-
$mq_sql = 'SELECT count(delta) FROM bill_data WHERE bill_id = ?';
146-
$mq_sql .= ' AND timestamp > ? AND timestamp <= ?';
147-
$measurements = dbFetchCell($mq_sql, [$bill_id, $datefrom, $dateto]);
148-
$measurement_95th = (round($measurements / 100 * 95) - 1);
149-
150-
$q_95_sql = 'SELECT (in_delta / period * 8) AS rate FROM bill_data WHERE bill_id = ?';
151-
$q_95_sql .= ' AND timestamp > ? AND timestamp <= ? ORDER BY rate ASC';
152-
$a_95th = dbFetchColumn($q_95_sql, [$bill_id, $datefrom, $dateto]);
153-
$m_95th = $a_95th[$measurement_95th] ?? 0;
138+
$sum_data = dbFetchRows('SELECT (SUM(in_delta) / SUM(period) * 8) as rate, FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`timestamp`) / 300) * 300) AS bucket_start, DATE_ADD(FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`timestamp`) / 300) * 300), INTERVAL 5 MINUTE) AS bucket_end,SUM(in_delta) as delta_sum FROM bill_data WHERE bill_id = ? AND timestamp > ? AND timestamp <= ? GROUP BY bill_id, bucket_start ORDER BY rate ASC', [$bill_id, $datefrom, $dateto]);
139+
$measurement_95th = (round(count($sum_data) / 100 * 95) - 2);
154140

155-
return round($m_95th, 2);
141+
return round($sum_data[$measurement_95th]['rate'], 2);
156142
}
157143

158144
private static function get95thout($bill_id, $datefrom, $dateto): float
159145
{
160-
$mq_sql = 'SELECT count(delta) FROM bill_data WHERE bill_id = ?';
161-
$mq_sql .= ' AND timestamp > ? AND timestamp <= ?';
162-
$measurements = dbFetchCell($mq_sql, [$bill_id, $datefrom, $dateto]);
163-
$measurement_95th = (round($measurements / 100 * 95) - 1);
164-
165-
$q_95_sql = 'SELECT (out_delta / period * 8) AS rate FROM bill_data WHERE bill_id = ?';
166-
$q_95_sql .= ' AND timestamp > ? AND timestamp <= ? ORDER BY rate ASC';
167-
$a_95th = dbFetchColumn($q_95_sql, [$bill_id, $datefrom, $dateto]);
168-
$m_95th = $a_95th[$measurement_95th] ?? 0;
146+
$sum_data = dbFetchRows('SELECT (SUM(out_delta) / SUM(period) * 8) as rate, FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`timestamp`) / 300) * 300) AS bucket_start, DATE_ADD(FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`timestamp`) / 300) * 300), INTERVAL 5 MINUTE) AS bucket_end,SUM(out_delta) as delta_sum FROM bill_data WHERE bill_id = ? AND timestamp > ? AND timestamp <= ? GROUP BY bill_id, bucket_start ORDER BY rate ASC', [$bill_id, $datefrom, $dateto]);
147+
$measurement_95th = (round(count($sum_data) / 100 * 95) - 2);
169148

170-
return round($m_95th, 2);
149+
return round($sum_data[$measurement_95th]['rate'], 2);
171150
}
172151

173152
public static function getRates($bill_id, $datefrom, $dateto, $dir_95th): array

LibreNMS/ComposerHelper.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ private static function populateEnv()
134134
];
135135

136136
if (file_exists('config.php')) {
137-
/** @phpstan-ignore include.fileNotFound */
138137
@include 'config.php';
139138
}
140139

LibreNMS/DB/Eloquent.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
namespace LibreNMS\DB;
2828

2929
use Illuminate\Database\Connection;
30+
use Illuminate\Support\Facades\Config;
3031
use Illuminate\Support\Facades\DB;
3132
use LibreNMS\Util\Laravel;
3233
use PDOException;
@@ -102,7 +103,7 @@ public static function getDriver(): ?string
102103
*/
103104
public static function setConnection($name, $db_host = null, $db_user = '', $db_pass = '', $db_name = '', $db_port = null, $db_socket = null): void
104105
{
105-
\Config::set("database.connections.$name", [
106+
Config::set("database.connections.$name", [
106107
'driver' => 'mysql',
107108
'host' => $db_host,
108109
'port' => $db_port,
@@ -116,6 +117,6 @@ public static function setConnection($name, $db_host = null, $db_user = '', $db_
116117
'strict' => true,
117118
'engine' => null,
118119
]);
119-
\Config::set('database.default', $name);
120+
Config::set('database.default', $name);
120121
}
121122
}

0 commit comments

Comments
 (0)