1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Component.php |
4
|
|
|
* |
5
|
|
|
* LibreNMS module to Interface with the Component System |
6
|
|
|
* |
7
|
|
|
* This program is free software: you can redistribute it and/or modify |
8
|
|
|
* it under the terms of the GNU General Public License as published by |
9
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
10
|
|
|
* (at your option) any later version. |
11
|
|
|
* |
12
|
|
|
* This program is distributed in the hope that it will be useful, |
13
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the |
15
|
|
|
* GNU General Public License for more details. |
16
|
|
|
* |
17
|
|
|
* You should have received a copy of the GNU General Public License |
18
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
19
|
|
|
* |
20
|
|
|
* @link https://www.librenms.org |
21
|
|
|
* |
22
|
|
|
* @copyright 2015 Aaron Daniels <[email protected]> |
23
|
|
|
* @author Aaron Daniels <[email protected]> |
24
|
|
|
*/ |
25
|
|
|
|
26
|
|
|
namespace LibreNMS; |
27
|
|
|
|
28
|
|
|
use App\Models\ComponentPref; |
29
|
|
|
use App\Models\ComponentStatusLog; |
30
|
|
|
use Illuminate\Support\Arr; |
31
|
|
|
use Log; |
32
|
|
|
|
33
|
|
|
class Component |
34
|
|
|
{ |
35
|
|
|
/* |
36
|
|
|
* These fields are used in the component table. They are returned in the array |
37
|
|
|
* so that they can be modified but they can not be set as user attributes. We |
38
|
|
|
* also set their default values. |
39
|
|
|
*/ |
40
|
|
|
private $reserved = [ |
41
|
|
|
'type' => '', |
42
|
|
|
'label' => '', |
43
|
|
|
'status' => 0, |
44
|
|
|
'ignore' => 0, |
45
|
|
|
'disabled' => 0, |
46
|
|
|
'error' => '', |
47
|
|
|
]; |
48
|
|
|
|
49
|
|
|
public function getComponentCount($device_id = null) |
50
|
|
|
{ |
51
|
|
|
$counts = \App\Models\Component::query()->when($device_id, function ($query, $device_id) { |
52
|
|
|
return $query->where('device_id', $device_id); |
53
|
|
|
})->selectRaw('type, count(*) as count')->groupBy('type')->pluck('count', 'type'); |
54
|
|
|
|
55
|
|
|
return $counts->isEmpty() ? false : $counts->all(); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
public function getComponentType($TYPE = null) |
59
|
|
|
{ |
60
|
|
|
if (is_null($TYPE)) { |
61
|
|
|
$SQL = 'SELECT DISTINCT `type` as `name` FROM `component` ORDER BY `name`'; |
62
|
|
|
$row = dbFetchRow($SQL, []); |
63
|
|
|
} else { |
64
|
|
|
$SQL = 'SELECT DISTINCT `type` as `name` FROM `component` WHERE `type` = ? ORDER BY `name`'; |
65
|
|
|
$row = dbFetchRow($SQL, [$TYPE]); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
if (! isset($row)) { |
69
|
|
|
// We didn't find any component types |
70
|
|
|
return false; |
71
|
|
|
} else { |
72
|
|
|
// We found some.. |
73
|
|
|
return $row; |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
public function getComponents($device_id = null, $options = []) |
78
|
|
|
{ |
79
|
|
|
$query = \App\Models\Component::query() |
80
|
|
|
->with('prefs'); |
81
|
|
|
|
82
|
|
|
// Device_id is shorthand for filter C.device_id = $device_id. |
83
|
|
|
if (! is_null($device_id)) { |
84
|
|
|
$options['filter']['device_id'] = ['=', $device_id]; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// Type is shorthand for filter type = $type. |
88
|
|
|
if (isset($options['type'])) { |
89
|
|
|
$options['filter']['type'] = ['=', $options['type']]; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
$validFields = ['device_id', 'type', 'id', 'label', 'status', 'disabled', 'ignore', 'error']; |
93
|
|
|
|
94
|
|
|
// filter field => array(operator,value) |
95
|
|
|
// Filters results based on the field, operator and value |
96
|
|
|
foreach (array_intersect_key($options['filter'], array_flip($validFields)) as $field => $filter) { |
97
|
|
|
$op = $filter[0]; |
98
|
|
|
$value = $op == 'LIKE' ? "%{$filter[1]}%" : $filter[1]; |
99
|
|
|
$query->where($field, $op, $value); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// sort column direction |
103
|
|
|
// Add SQL sorting to the results |
104
|
|
|
if (isset($options['sort'])) { |
105
|
|
|
[$column, $direction] = explode(' ', $options['sort']); |
106
|
|
|
$query->orderBy($column, $direction); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
// limit array(start,count) |
110
|
|
|
if (isset($options['limit'])) { |
111
|
|
|
$query->offset($options['limit'][0])->limit($options['limit'][1]); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
// get and format results as expected by receivers |
115
|
|
|
return $query->get()->groupBy('device_id')->map(function ($group) { |
116
|
|
|
return $group->keyBy('id')->map(function ($component) { |
117
|
|
|
return $component->prefs->pluck('value', 'attribute') |
118
|
|
|
->merge($component->only(array_keys($this->reserved))); |
119
|
|
|
}); |
120
|
|
|
})->toArray(); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
public function getComponentStatus($device = null) |
124
|
|
|
{ |
125
|
|
|
$sql_query = 'SELECT status, count(status) as count FROM component WHERE'; |
126
|
|
|
$sql_param = []; |
127
|
|
|
$add = 0; |
128
|
|
|
|
129
|
|
|
if (! is_null($device)) { |
130
|
|
|
// Add a device filter to the SQL query. |
131
|
|
|
$sql_query .= ' `device_id` = ?'; |
132
|
|
|
$sql_param[] = $device; |
133
|
|
|
$add++; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
if ($add == 0) { |
137
|
|
|
// No filters, remove " WHERE" -6 |
138
|
|
|
$sql_query = substr($sql_query, 0, strlen($sql_query) - 6); |
139
|
|
|
} |
140
|
|
|
$sql_query .= ' GROUP BY status'; |
141
|
|
|
d_echo('SQL Query: ' . $sql_query); |
142
|
|
|
|
143
|
|
|
// $service is not null, get only what we want. |
144
|
|
|
$result = dbFetchRows($sql_query, $sql_param); |
145
|
|
|
|
146
|
|
|
// Set our defaults to 0 |
147
|
|
|
$count = [0 => 0, 1 => 0, 2 => 0]; |
148
|
|
|
// Rebuild the array in a more convenient method |
149
|
|
|
foreach ($result as $v) { |
150
|
|
|
$count[$v['status']] = $v['count']; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
d_echo('Component Count by Status: ' . print_r($count, true) . "\n"); |
|
|
|
|
154
|
|
|
|
155
|
|
|
return $count; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
public function getComponentStatusLog($component_id, $start, $end) |
159
|
|
|
{ |
160
|
|
|
if (($component_id == null) || ($start == null) || ($end == null)) { |
161
|
|
|
// Error... |
162
|
|
|
d_echo('Required arguments are missing. Component ID: ' . $component_id . ', Start: ' . $start . ', End: ' . $end . "\n"); |
163
|
|
|
|
164
|
|
|
return false; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
// Create our return array. |
168
|
|
|
$return = []; |
169
|
|
|
|
170
|
|
|
// 1. find the previous value, this is the value when $start occurred. |
171
|
|
|
$sql_query = 'SELECT status FROM `component_statuslog` WHERE `component_id` = ? AND `timestamp` < ? ORDER BY `id` desc LIMIT 1'; |
172
|
|
|
$sql_param = [$component_id, $start]; |
173
|
|
|
$result = dbFetchRow($sql_query, $sql_param); |
174
|
|
|
if ($result == false) { |
175
|
|
|
$return['initial'] = false; |
176
|
|
|
} else { |
177
|
|
|
$return['initial'] = $result['status']; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// 2. Then we just need a list of all the entries for the time period. |
181
|
|
|
$sql_query = 'SELECT status, `timestamp`, message FROM `component_statuslog` WHERE `component_id` = ? AND `timestamp` >= ? AND `timestamp` < ? ORDER BY `timestamp`'; |
182
|
|
|
$sql_param = [$component_id, $start, $end]; |
183
|
|
|
$return['data'] = dbFetchRows($sql_query, $sql_param); |
184
|
|
|
|
185
|
|
|
d_echo('Status Log Data: ' . print_r($return, true) . "\n"); |
|
|
|
|
186
|
|
|
|
187
|
|
|
return $return; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
public function createComponent($device_id, $type) |
191
|
|
|
{ |
192
|
|
|
$component = \App\Models\Component::create(['device_id' => $device_id, 'type' => $type]); |
193
|
|
|
|
194
|
|
|
// Add a default status log entry - we always start ok. |
195
|
|
|
$this->createStatusLogEntry($component->id, 0, 'Component Created'); |
196
|
|
|
|
197
|
|
|
// Create a default component array based on what was inserted. |
198
|
|
|
return [$component->id => $component->only(array_keys($this->reserved))]; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
public function createStatusLogEntry($component_id, $status, $message) |
202
|
|
|
{ |
203
|
|
|
try { |
204
|
|
|
return ComponentStatusLog::create(['component_id' => $component_id, 'status' => $status, 'message' => $message])->id; |
205
|
|
|
} catch (\Exception $e) { |
206
|
|
|
Log::debug('Failed to create component status log'); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return 0; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
public function deleteComponent($id) |
213
|
|
|
{ |
214
|
|
|
// Delete a component from the database. |
215
|
|
|
return \App\Models\Component::destroy($id); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
public function setComponentPrefs($device_id, $updated) |
219
|
|
|
{ |
220
|
|
|
$updated = Arr::wrap($updated); |
221
|
|
|
\App\Models\Component::whereIn('id', array_keys($updated)) |
222
|
|
|
->with('prefs') |
223
|
|
|
->get() |
224
|
|
|
->each(function (\App\Models\Component $component) use ($device_id, $updated) { |
225
|
|
|
$update = $updated[$component->id]; |
226
|
|
|
unset($update['type']); // can't change type |
227
|
|
|
|
228
|
|
|
// update component attributes |
229
|
|
|
$component->fill($update); |
230
|
|
|
if ($component->isDirty()) { |
231
|
|
|
// Log the update to the Eventlog. |
232
|
|
|
$message = "Component $component->id has been modified: "; |
233
|
|
|
$message .= collect($component->getDirty())->map(function ($value, $key) { |
234
|
|
|
return "$key => $value"; |
235
|
|
|
})->implode(','); |
236
|
|
|
|
237
|
|
|
// If the Status has changed we need to add a log entry |
238
|
|
|
if ($component->isDirty('status')) { |
239
|
|
|
Log::debug('Status Changed - Old: ' . $component->getOriginal('status') . ", New: $component->status\n"); |
|
|
|
|
240
|
|
|
$this->createStatusLogEntry($component->id, $component->status, $component->error); |
241
|
|
|
} |
242
|
|
|
$component->save(); |
243
|
|
|
|
244
|
|
|
Log::event($message, $component->device_id, 'component', 3, $component->id); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
// update preferences |
248
|
|
|
$prefs = collect($updated[$component->id])->filter(function ($value, $attr) { |
249
|
|
|
return ! array_key_exists($attr, $this->reserved); |
250
|
|
|
}); |
251
|
|
|
|
252
|
|
|
$invalid = $component->prefs->keyBy('id'); |
253
|
|
|
|
254
|
|
|
foreach ($prefs as $attribute => $value) { |
255
|
|
|
$existing = $component->prefs->firstWhere('attribute', $attribute); |
256
|
|
|
if ($existing) { |
257
|
|
|
$invalid->forget($existing->id); |
258
|
|
|
$existing->fill(['value' => $value]); |
259
|
|
|
if ($existing->isDirty()) { |
260
|
|
|
Log::event("Component: $component->type($component->id). Attribute: $attribute, was modified from: " . $existing->getOriginal('value') . ", to: $value", $device_id, 'component', 3, $component->id); |
261
|
|
|
$existing->save(); |
262
|
|
|
} |
263
|
|
|
} else { |
264
|
|
|
$component->prefs()->save(new ComponentPref(['attribute' => $attribute, 'value' => $value])); |
265
|
|
|
Log::event("Component: $component->type($component->id). Attribute: $attribute, was added with value: $value", $component->device_id, 'component', 3, $component->id); |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
foreach ($invalid as $pref) { |
270
|
|
|
$pref->delete(); |
271
|
|
|
Log::event("Component: $component->type($component->id). Attribute: $pref->attribute, was deleted.", $component->device_id, 'component', 4); |
272
|
|
|
} |
273
|
|
|
}); |
274
|
|
|
|
275
|
|
|
return true; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Get the component id for the first component in the array |
280
|
|
|
* Only set $device_id if using the array from getCompenents(), which is keyed by device_id |
281
|
|
|
* |
282
|
|
|
* @param array $component_array |
283
|
|
|
* @param int $device_id |
284
|
|
|
* @return int the component id |
285
|
|
|
*/ |
286
|
|
|
public function getFirstComponentID($component_array, $device_id = null) |
287
|
|
|
{ |
288
|
|
|
if (! is_null($device_id) && isset($component_array[$device_id])) { |
289
|
|
|
$component_array = $component_array[$device_id]; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
foreach ($component_array as $id => $array) { |
293
|
|
|
return $id; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
return -1; |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|