1 | <?php |
||
2 | /** |
||
3 | * DeviceController.php |
||
4 | * |
||
5 | * -Description- |
||
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 2019 Tony Murray |
||
23 | * @author Tony Murray <[email protected]> |
||
24 | */ |
||
25 | |||
26 | namespace App\Http\Controllers\Table; |
||
27 | |||
28 | use App\Models\Device; |
||
29 | use App\Models\Location; |
||
30 | use Illuminate\Database\Eloquent\Builder; |
||
31 | use Illuminate\Support\Arr; |
||
32 | use Illuminate\Support\Facades\Auth; |
||
33 | use LibreNMS\Config; |
||
34 | use LibreNMS\Util\Rewrite; |
||
35 | use LibreNMS\Util\Time; |
||
36 | use LibreNMS\Util\Url; |
||
37 | |||
38 | class DeviceController extends TableController |
||
39 | { |
||
40 | private $detailed; // display format is detailed |
||
41 | |||
42 | protected function rules() |
||
43 | { |
||
44 | return [ |
||
45 | 'format' => 'nullable|in:list_basic,list_detail', |
||
46 | 'os' => 'nullable|string', |
||
47 | 'version' => 'nullable|string', |
||
48 | 'hardware' => 'nullable|string', |
||
49 | 'features' => 'nullable|string', |
||
50 | 'location' => 'nullable|string', |
||
51 | 'type' => 'nullable|string', |
||
52 | 'state' => 'nullable|in:0,1,up,down', |
||
53 | 'disabled' => 'nullable|in:0,1', |
||
54 | 'ignore' => 'nullable|in:0,1', |
||
55 | 'disable_notify' => 'nullable|in:0,1', |
||
56 | 'group' => 'nullable|int', |
||
57 | 'poller_group' => 'nullable|int', |
||
58 | 'device_id' => 'nullable|int', |
||
59 | ]; |
||
60 | } |
||
61 | |||
62 | protected function filterFields($request) |
||
63 | { |
||
64 | return ['os', 'version', 'hardware', 'features', 'type', 'status' => 'state', 'disabled', 'disable_notify', 'ignore', 'location_id' => 'location', 'device_id' => 'device_id']; |
||
65 | } |
||
66 | |||
67 | protected function searchFields($request) |
||
68 | { |
||
69 | return ['sysName', 'hostname', 'hardware', 'os', 'locations.location']; |
||
70 | } |
||
71 | |||
72 | protected function sortFields($request) |
||
73 | { |
||
74 | return [ |
||
75 | 'status' => 'status', |
||
76 | 'icon' => 'icon', |
||
77 | 'hostname' => 'hostname', |
||
78 | 'hardware' => 'hardware', |
||
79 | 'os' => 'os', |
||
80 | 'uptime' => \DB::raw('IF(`status` = 1, `uptime`, `last_polled` - NOW())'), |
||
81 | 'location' => 'location', |
||
82 | 'device_id' => 'device_id', |
||
83 | ]; |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Defines the base query for this resource |
||
88 | * |
||
89 | * @param \Illuminate\Http\Request $request |
||
90 | * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder |
||
91 | */ |
||
92 | protected function baseQuery($request) |
||
93 | { |
||
94 | /** @var Builder $query */ |
||
95 | $query = Device::hasAccess($request->user())->with('location')->withCount(['ports', 'sensors', 'wirelessSensors']); |
||
96 | |||
97 | // if searching or sorting the location field, join the locations table |
||
98 | if ($request->get('searchPhrase') || in_array('location', array_keys($request->get('sort', [])))) { |
||
99 | $query->leftJoin('locations', 'locations.id', 'devices.location_id'); |
||
100 | } |
||
101 | |||
102 | // filter device group, not sure this is the most efficient query |
||
103 | if ($group = $request->get('group')) { |
||
104 | $query->whereHas('groups', function ($query) use ($group) { |
||
105 | $query->where('id', $group); |
||
106 | }); |
||
107 | } |
||
108 | |||
109 | if ($request->get('poller_group') !== null) { |
||
110 | $query->where('poller_group', $request->get('poller_group')); |
||
111 | } |
||
112 | |||
113 | return $query; |
||
114 | } |
||
115 | |||
116 | protected function adjustFilterValue($field, $value) |
||
117 | { |
||
118 | if ($field == 'location' && ! is_numeric($value)) { |
||
119 | return Location::query()->where('location', $value)->value('id'); |
||
120 | } |
||
121 | |||
122 | if ($field == 'state' && ! is_numeric($value)) { |
||
123 | return str_replace(['up', 'down'], [1, 0], $value); |
||
124 | } |
||
125 | |||
126 | return $value; |
||
127 | } |
||
128 | |||
129 | private function isDetailed() |
||
130 | { |
||
131 | if (is_null($this->detailed)) { |
||
132 | $this->detailed = \Request::get('format', 'list_detail') == 'list_detail'; |
||
133 | } |
||
134 | |||
135 | return $this->detailed; |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * @param Device $device |
||
140 | * @return array|\Illuminate\Database\Eloquent\Model|\Illuminate\Support\Collection |
||
141 | */ |
||
142 | public function formatItem($device) |
||
143 | { |
||
144 | return [ |
||
145 | 'extra' => $this->getLabel($device), |
||
146 | 'status' => $this->getStatus($device), |
||
147 | 'maintenance' => $device->isUnderMaintenance(), |
||
148 | 'icon' => '<img src="' . asset($device->icon) . '" title="' . pathinfo($device->icon, PATHINFO_FILENAME) . '">', |
||
149 | 'hostname' => $this->getHostname($device), |
||
150 | 'metrics' => $this->getMetrics($device), |
||
151 | 'hardware' => Rewrite::ciscoHardware($device), |
||
152 | 'os' => $this->getOsText($device), |
||
153 | 'uptime' => (! $device->status && ! $device->last_polled) ? __('Never polled') : Time::formatInterval($device->status ? $device->uptime : $device->last_polled->diffInSeconds(), 'short'), |
||
154 | 'location' => $this->getLocation($device), |
||
155 | 'actions' => view('device.actions', ['actions' => $this->getActions($device)])->__toString(), |
||
156 | 'device_id' => $device->device_id, |
||
157 | ]; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Get the device up/down status |
||
162 | * |
||
163 | * @param Device $device |
||
164 | * @return string |
||
165 | */ |
||
166 | private function getStatus($device) |
||
167 | { |
||
168 | if ($device->disabled == 1) { |
||
169 | return 'disabled'; |
||
170 | } elseif ($device->status == 0) { |
||
171 | return 'down'; |
||
172 | } |
||
173 | |||
174 | return 'up'; |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Get the status label class |
||
179 | * |
||
180 | * @param Device $device |
||
181 | * @return string |
||
182 | */ |
||
183 | private function getLabel($device) |
||
184 | { |
||
185 | if ($device->disabled == 1) { |
||
186 | return 'blackbg'; |
||
187 | } elseif ($device->disable_notify == 1) { |
||
188 | return 'blackbg'; |
||
189 | } elseif ($device->ignore == 1) { |
||
190 | return 'label-default'; |
||
191 | } elseif ($device->status == 0) { |
||
192 | return 'label-danger'; |
||
193 | } else { |
||
194 | $warning_time = \LibreNMS\Config::get('uptime_warning', 84600); |
||
195 | if ($device->uptime < $warning_time && $device->uptime != 0) { |
||
196 | return 'label-warning'; |
||
197 | } |
||
198 | |||
199 | return 'label-success'; |
||
200 | } |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * @param Device $device |
||
205 | * @return string |
||
206 | */ |
||
207 | private function getHostname($device) |
||
208 | { |
||
209 | return (string) view('device.list.hostname', [ |
||
210 | 'device' => $device, |
||
211 | 'detailed' => $this->isDetailed(), |
||
212 | ]); |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * @param Device $device |
||
217 | * @return string |
||
218 | */ |
||
219 | private function getOsText($device) |
||
220 | { |
||
221 | $os_text = Config::getOsSetting($device->os, 'text'); |
||
222 | |||
223 | if ($this->isDetailed()) { |
||
224 | $os_text .= '<br />' . $device->version . ($device->features ? " ($device->features)" : ''); |
||
225 | } |
||
226 | |||
227 | return $os_text; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * @param Device $device |
||
232 | * @return string |
||
233 | */ |
||
234 | private function getMetrics($device) |
||
235 | { |
||
236 | $port_count = $device->ports_count; |
||
237 | $sensor_count = $device->sensors_count; |
||
238 | $wireless_count = $device->wirelessSensors_count; |
||
239 | |||
240 | $metrics = []; |
||
241 | if ($port_count) { |
||
242 | $metrics[] = $this->formatMetric($device, $port_count, 'ports', 'fa-link'); |
||
243 | } |
||
244 | |||
245 | if ($sensor_count) { |
||
246 | $metrics[] = $this->formatMetric($device, $sensor_count, 'health', 'fa-dashboard'); |
||
247 | } |
||
248 | |||
249 | if ($wireless_count) { |
||
250 | $metrics[] = $this->formatMetric($device, $wireless_count, 'wireless', 'fa-wifi'); |
||
251 | } |
||
252 | |||
253 | $glue = $this->isDetailed() ? '<br />' : ' '; |
||
254 | $metrics_content = implode(count($metrics) == 2 ? $glue : '', $metrics); |
||
255 | |||
256 | return '<div class="device-table-metrics">' . $metrics_content . '</div>'; |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * @param int|Device $device |
||
261 | * @param mixed $count |
||
262 | * @param mixed $tab |
||
263 | * @param mixed $icon |
||
264 | * @return string |
||
265 | */ |
||
266 | private function formatMetric($device, $count, $tab, $icon) |
||
267 | { |
||
268 | $html = '<a href="' . Url::deviceUrl($device, ['tab' => $tab]) . '">'; |
||
269 | $html .= '<span><i title="' . $tab . '" class="fa ' . $icon . ' fa-lg icon-theme"></i> ' . $count; |
||
270 | $html .= '</span></a> '; |
||
271 | |||
272 | return $html; |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * @param Device $device |
||
277 | * @return string |
||
278 | */ |
||
279 | private function getLocation($device) |
||
280 | { |
||
281 | return extension_loaded('mbstring') |
||
282 | ? mb_substr($device->location, 0, 32, 'utf8') |
||
283 | : substr($device->location, 0, 32); |
||
284 | } |
||
285 | |||
286 | private function getActions(Device $device): array |
||
287 | { |
||
288 | $actions = [ |
||
289 | [ |
||
290 | [ |
||
291 | 'title' => 'View Device', |
||
292 | 'href' => Url::deviceUrl($device), |
||
293 | 'icon' => 'fa-id-card', |
||
294 | 'external' => false, |
||
295 | ], |
||
296 | [ |
||
297 | 'title' => 'View alerts', |
||
298 | 'href' => Url::deviceUrl($device, ['tab' => 'alerts']), |
||
299 | 'icon' => 'fa-exclamation-circle', |
||
300 | 'external' => false, |
||
301 | ], |
||
302 | ], |
||
303 | ]; |
||
304 | |||
305 | if (\Auth::user()->hasGlobalAdmin()) { |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
306 | $actions[0][] = [ |
||
307 | 'title' => 'Edit device', |
||
308 | 'href' => Url::deviceUrl($device, ['tab' => 'edit']), |
||
309 | 'icon' => 'fa-gear', |
||
310 | 'external' => false, |
||
311 | ]; |
||
312 | } |
||
313 | $row = $this->isDetailed() ? 1 : 0; |
||
314 | |||
315 | $actions[$row][] = [ |
||
316 | 'title' => 'Telnet to ' . $device->hostname, |
||
317 | 'href' => 'telnet://' . $device->hostname, |
||
318 | 'icon' => 'fa-terminal', |
||
319 | ]; |
||
320 | |||
321 | $ssh_href = 'ssh://' . $device->hostname; |
||
322 | if ($server = Config::get('gateone.server')) { |
||
323 | $ssh_href = Config::get('gateone.use_librenms_user') |
||
324 | ? $server . '?ssh=ssh://' . Auth::user()->username . '@' . $device->hostname . '&location=' . $device->hostname |
||
325 | : $server . '?ssh=ssh://' . $device->hostname . '&location=' . $device->hostname; |
||
326 | } |
||
327 | |||
328 | $actions[$row][] = [ |
||
329 | 'title' => 'SSH to ' . $device->hostname, |
||
330 | 'href' => $ssh_href, |
||
331 | 'icon' => 'fa-lock', |
||
332 | ]; |
||
333 | |||
334 | $actions[$row][] = [ |
||
335 | 'title' => 'Launch browser to ' . $device->hostname, |
||
336 | 'href' => 'https://' . $device->hostname, |
||
337 | 'onclick' => 'http_fallback(this); return false;', |
||
338 | 'icon' => 'fa-globe', |
||
339 | ]; |
||
340 | |||
341 | foreach (array_values(Arr::wrap(Config::get('html.device.links'))) as $index => $custom) { |
||
342 | if ($custom['action'] ?? false) { |
||
343 | $row = $this->isDetailed() ? $index % 2 : 0; |
||
344 | $custom['href'] = view(['template' => $custom['url']], ['device' => $device])->__toString(); // @phpstan-ignore-line |
||
345 | $actions[$row][] = $custom; |
||
346 | } |
||
347 | } |
||
348 | |||
349 | return $actions; |
||
350 | } |
||
351 | } |
||
352 |