Refactored and update Location Geocoding (#9359)

- Fix location so it is a regular database relation (this allows multiple devices to be accurately linked to one location and saves api calls)
- Parse coordinates from the location more consistently
- Add settings to webui
- ~~Used [PHP Geocoder](http://geocoder-php.org/), which has lots of backends and is well tested. (also includes reverse and geoip)~~
- Google Maps, Bing, Mapquest, and OpenStreetMap supported initially.
- Default to OpenStreetMap, which doesn't require a key.  They will liberally hand out bans if you exceed 1 query per second though.
- All other Geocoding APIs require an API key. (Google requires a credit card on file, but seems to be the most accurate)
- Update all (I think) sql queries to handle the new structure
- Remove final vestiges of override_sysLocation as a device attribute
- Update existing device groups and rules in DB
- Tested all APIs with good/bad location, no/bad/good key, and no connection.
- Cannot fix advanced queries that use location

This blocks #8868

DO NOT DELETE THIS TEXT

#### Please note

> Please read this information carefully. You can run `./scripts/pre-commit.php` to check your code before submitting.

- [x] Have you followed our [code guidelines?](http://docs.librenms.org/Developing/Code-Guidelines/)

#### Testers

If you would like to test this pull request then please run: `./scripts/github-apply <pr_id>`, i.e `./scripts/github-apply 5926`
After you are done testing, you can remove the changes with `./scripts/github-remove`.  If there are schema changes, you can ask on discord how to revert.
This commit is contained in:
Tony Murray
2018-11-28 16:49:18 -06:00
committed by Neil Lathwood
parent eadf27cbcc
commit 3e35ee0e7d
49 changed files with 996 additions and 313 deletions

View File

@@ -161,6 +161,12 @@ class Schema
} }
foreach ($tables as $table) { foreach ($tables as $table) {
// check for direct relationships
if (in_array($table, $relationships[$target])) {
d_echo("Direct relationship found $target -> $table\n");
return [$table, $target];
}
$table_relations = $relationships[$table]; $table_relations = $relationships[$table];
d_echo("Searching $table: " . json_encode($table_relations) . PHP_EOL); d_echo("Searching $table: " . json_encode($table_relations) . PHP_EOL);

View File

@@ -0,0 +1,38 @@
<?php
/**
* Geocoder.php
*
* Interface to resolve an address or location string to latitude and longitude coordinates.
*
* 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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Interfaces;
interface Geocoder
{
/**
* Try to get the coordinates of a given address.
* If unsuccessful, the returned array will be empty
*
* @param string $address
* @return array ['lat' => 0, 'lng' => 0]
*/
public function getCoordinates($address);
}

View File

@@ -26,6 +26,7 @@
namespace LibreNMS\Util; namespace LibreNMS\Util;
use LibreNMS\Config;
use LibreNMS\Exceptions\LockException; use LibreNMS\Exceptions\LockException;
use LibreNMS\Interfaces\Lock; use LibreNMS\Interfaces\Lock;
@@ -42,10 +43,10 @@ class FileLock implements Lock
private function __construct($lock_name) private function __construct($lock_name)
{ {
global $config; $install_dir = Config::get('install_dir');
$this->name = $lock_name; $this->name = $lock_name;
$this->file = "$config[install_dir]/.$lock_name.lock"; $this->file = "$install_dir/.$lock_name.lock";
$this->handle = fopen($this->file, "w+"); $this->handle = fopen($this->file, "w+");
} }

View File

@@ -0,0 +1,46 @@
<?php
/**
* BaseApi.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\ApiClients;
use GuzzleHttp\Client;
class BaseApi
{
protected $base_uri;
private $client;
protected function getClient()
{
if (is_null($this->client)) {
$this->client = new Client([
'base_uri' => $this->base_uri,
'tiemout' => 2,
]);
}
return $this->client;
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* BingApi.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\ApiClients;
use Exception;
use LibreNMS\Config;
use LibreNMS\Interfaces\Geocoder;
class BingApi extends BaseApi implements Geocoder
{
use GeocodingHelper;
protected $base_uri = 'http://dev.virtualearth.net';
protected $geocoding_uri = '/REST/v1/Locations';
/**
* Get latitude and longitude from geocode response
*
* @param array $data
* @return array
*/
protected function parseLatLng($data)
{
return [
'lat' => isset($data['resourceSets'][0]['resources'][0]['point']['coordinates'][0]) ? $data['resourceSets'][0]['resources'][0]['point']['coordinates'][0] : 0,
'lng' => isset($data['resourceSets'][0]['resources'][0]['point']['coordinates'][1]) ? $data['resourceSets'][0]['resources'][0]['point']['coordinates'][1] : 0,
];
}
/**
* Build Guzzle request option array
*
* @param string $address
* @return array
* @throws \Exception you may throw an Exception if validation fails
*/
protected function buildGeocodingOptions($address)
{
$api_key = Config::get('geoloc.api_key');
if (!$api_key) {
throw new Exception("Bing API key missing, set geoloc.api_key");
}
return [
'query' => [
'key' => $api_key,
'addressLine' => $address,
]
];
}
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*/
protected function checkResponse($response, $data)
{
return $response->getStatusCode() == 200 && !empty($data['resourceSets'][0]['resources']);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* GeocodingHelper.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\ApiClients;
use Exception;
use LibreNMS\Config;
use Log;
trait GeocodingHelper
{
/**
* From BaseApi...
*
* @return \GuzzleHttp\Client
*/
abstract protected function getClient();
/**
* Try to get the coordinates of a given address.
* If unsuccessful, the returned array will be empty
*
* @param string $address
* @return array ['lat' => 0, 'lng' => 0]
*/
public function getCoordinates($address)
{
if (!Config::get('geoloc.latlng', true)) {
Log::debug('Geocoding disabled');
return [];
}
try {
$options = $this->buildGeocodingOptions($address);
$response = $this->getClient()->get($this->geocoding_uri, $options);
$response_data = json_decode($response->getBody(), true);
if ($this->checkResponse($response, $response_data)) {
return $this->parseLatLng($response_data);
} else {
Log::error("Geocoding failed.", ['response' => $response_data]);
}
} catch (Exception $e) {
Log::error("Geocoding failed: " . $e->getMessage());
}
return [];
}
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*/
protected function checkResponse($response, $data)
{
return $response->getStatusCode() == 200;
}
/**
* Get latitude and longitude from geocode response
*
* @param array $data
* @return array
*/
abstract protected function parseLatLng($data);
/**
* Build Guzzle request option array
*
* @param string $address
* @return array
* @throws \Exception you may throw an Exception if validation fails
*/
abstract protected function buildGeocodingOptions($address);
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* GoogleGeocodeApi.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\ApiClients;
use Exception;
use LibreNMS\Config;
use LibreNMS\Interfaces\Geocoder;
class GoogleMapsApi extends BaseApi implements Geocoder
{
use GeocodingHelper;
protected $base_uri = 'https://maps.googleapis.com';
protected $geocoding_uri = '/maps/api/geocode/json';
/**
* Get latitude and longitude from geocode response
*
* @param array $data
* @return array
*/
private function parseLatLng($data)
{
return [
'lat' => isset($data['results'][0]['geometry']['location']['lat']) ? $data['results'][0]['geometry']['location']['lat'] : 0,
'lng' => isset($data['results'][0]['geometry']['location']['lng']) ? $data['results'][0]['geometry']['location']['lng'] : 0,
];
}
/**
* Get messages from response.
*
* @param array $data
* @return array
*/
protected function parseMessages($data)
{
return [
'error' => isset($data['error_message']) ? $data['error_message'] : '',
'response' => $data,
];
}
/**
* Build Guzzle request option array
*
* @param string $address
* @return array
* @throws \Exception you may throw an Exception if validation fails
*/
protected function buildGeocodingOptions($address)
{
$api_key = Config::get('geoloc.api_key');
if (!$api_key) {
throw new Exception('Google Maps API key missing, set geoloc.api_key');
}
return [
'query' => [
'key' => $api_key,
'address' => $address,
]
];
}
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*/
protected function checkResponse($response, $data)
{
return $response->getStatusCode() == 200 && $data['status'] == 'OK';
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* MapquestGeocodeApi.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\ApiClients;
use Exception;
use LibreNMS\Config;
use LibreNMS\Interfaces\Geocoder;
class MapquestApi extends BaseApi implements Geocoder
{
use GeocodingHelper;
protected $base_uri = 'https://open.mapquestapi.com';
protected $geocoding_uri = '/geocoding/v1/address';
/**
* Get latitude and longitude from geocode response
*
* @param array $data
* @return array
*/
protected function parseLatLng($data)
{
return [
'lat' => isset($data['results'][0]['locations'][0]['latLng']['lat']) ? $data['results'][0]['locations'][0]['latLng']['lat'] : 0,
'lng' => isset($data['results'][0]['locations'][0]['latLng']['lng']) ? $data['results'][0]['locations'][0]['latLng']['lng'] : 0,
];
}
/**
* Build Guzzle request option array
*
* @param string $address
* @return array
* @throws \Exception you may throw an Exception if validation fails
*/
protected function buildGeocodingOptions($address)
{
$api_key = Config::get('geoloc.api_key');
if (!$api_key) {
throw new Exception("MapQuest API key missing, set geoloc.api_key");
}
return [
'query' => [
'key' => $api_key,
'location' => $address,
'thumbMaps' => 'false',
]
];
}
/**
* Checks if the request was a success
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param array $data decoded response data
* @return bool
*/
protected function checkResponse($response, $data)
{
return $response->getStatusCode() == 200 && $data['info']['statuscode'] == 0;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* NominatimApi.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\ApiClients;
use LibreNMS\Interfaces\Geocoder;
class NominatimApi extends BaseApi implements Geocoder
{
use GeocodingHelper;
protected $base_uri = 'http://nominatim.openstreetmap.org';
protected $geocoding_uri = '/search';
/**
* Get latitude and longitude from geocode response
*
* @param array $data
* @return array
*/
protected function parseLatLng($data)
{
return [
'lat' => isset($data[0]['lat']) ? $data[0]['lat'] : 0,
'lng' => isset($data[0]['lon']) ? $data[0]['lon'] : 0,
];
}
/**
* Build Guzzle request option array
*
* @param string $address
* @return array
* @throws \Exception you may throw an Exception if validation fails
*/
protected function buildGeocodingOptions($address)
{
return [
'query' => [
'q' => $address,
'format' => 'json',
'limit' => 1,
],
'headers' => [
'User-Agent' => 'LibreNMS',
'Accept' => 'application/json',
]
];
}
}

View File

@@ -32,6 +32,7 @@ use App\Models\CefSwitching;
use App\Models\Component; use App\Models\Component;
use App\Models\Device; use App\Models\Device;
use App\Models\DeviceGroup; use App\Models\DeviceGroup;
use App\Models\Location;
use App\Models\Notification; use App\Models\Notification;
use App\Models\OspfInstance; use App\Models\OspfInstance;
use App\Models\Package; use App\Models\Package;
@@ -73,7 +74,7 @@ class MenuComposer
$vars['device_types'] = Device::hasAccess($user)->select('type')->distinct()->get()->pluck('type')->filter(); $vars['device_types'] = Device::hasAccess($user)->select('type')->distinct()->get()->pluck('type')->filter();
if (Config::get('show_locations') && Config::get('show_locations_dropdown')) { if (Config::get('show_locations') && Config::get('show_locations_dropdown')) {
$vars['locations'] = Device::hasAccess($user)->select('location')->distinct()->get()->pluck('location')->filter(); $vars['locations'] = Location::hasAccess($user)->select('location')->get()->map->display()->filter();
} else { } else {
$vars['locations'] = []; $vars['locations'] = [];
} }

View File

@@ -455,6 +455,11 @@ class Device extends BaseModel
return $this->belongsToMany('App\Models\DeviceGroup', 'device_group_device', 'device_id', 'device_group_id'); return $this->belongsToMany('App\Models\DeviceGroup', 'device_group_device', 'device_id', 'device_group_id');
} }
public function location()
{
return $this->belongsTo('App\Models\Location', 'location_id', 'id');
}
public function ospfInstances() public function ospfInstances()
{ {
return $this->hasMany('App\Models\OspfInstance', 'device_id'); return $this->hasMany('App\Models\OspfInstance', 'device_id');

150
app/Models/Location.php Normal file
View File

@@ -0,0 +1,150 @@
<?php
/**
* Location.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace App\Models;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
public $fillable = ['location', 'lat', 'lng'];
const CREATED_AT = null;
const UPDATED_AT = 'timestamp';
private $location_regex = '/\[\s*(?<lat>[-+]?(?:[1-8]?\d(?:\.\d+)?|90(?:\.0+)?))\s*,\s*(?<lng>[-+]?(?:180(?:\.0+)?|(?:(?:1[0-7]\d)|(?:[1-9]?\d))(?:\.\d+)?))\s*\]/';
/**
* Set up listeners for this Model
*/
public static function boot()
{
parent::boot();
static::creating(function (Location $location) {
// parse coordinates for new locations
$location->lookupCoordinates();
});
}
// ---- Helper Functions ----
/**
* Checks if this location has resolved latitude and longitude.
*
* @return bool
*/
public function hasCoordinates()
{
return !(is_null($this->lat) || is_null($this->lng));
}
/**
* Check if the coordinates are valid
* Even though 0,0 is a valid coordinate, we consider it invalid for ease
*/
public function coordinatesValid()
{
return $this->lat && $this->lng &&
abs($this->lat) <= 90 && abs($this->lng) <= 180;
}
/**
* Try to parse coordinates then
* call geocoding API to resolve latitude and longitude.
*/
public function lookupCoordinates()
{
if (!$this->hasCoordinates() && $this->location) {
$this->parseCoordinates();
if (!$this->hasCoordinates() &&
\LibreNMS\Config::get('geoloc.latlng', true) &&
$this->timestamp && $this->timestamp->diffInDays() > 2
) {
$this->fetchCoordinates();
$this->updateTimestamps();
}
}
}
/**
* Remove encoded GPS for nicer display
*
* @return string
*/
public function display()
{
return trim(preg_replace($this->location_regex, '', $this->location)) ?: $this->location;
}
protected function parseCoordinates()
{
if (preg_match($this->location_regex, $this->location, $parsed)) {
$this->fill($parsed);
}
}
protected function fetchCoordinates()
{
try {
/** @var \LibreNMS\Interfaces\Geocoder $api */
$api = app(\LibreNMS\Interfaces\Geocoder::class);
$this->fill($api->getCoordinates($this->location));
} catch (BindingResolutionException $e) {
// could not resolve geocoder, Laravel isn't booted. Fail silently.
}
}
// ---- Query scopes ----
/**
* @param Builder $query
* @param User $user
* @return Builder
*/
public function scopeHasAccess($query, $user)
{
if ($user->hasGlobalRead()) {
return $query;
}
$ids = Device::hasAccess($user)
->distinct()
->whereNotNull('location_id')
->pluck('location_id');
return $query->whereIn('id', $ids);
}
// ---- Define Relationships ----
public function devices()
{
return $this->hasMany('App\Models\Device', 'location_id');
}
}

View File

@@ -73,7 +73,7 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function register() public function register()
{ {
// $this->registerGeocoder();
} }
private function configureMorphAliases() private function configureMorphAliases()
@@ -83,4 +83,28 @@ class AppServiceProvider extends ServiceProvider
'sensor' => \App\Models\Sensor::class, 'sensor' => \App\Models\Sensor::class,
]); ]);
} }
private function registerGeocoder()
{
$this->app->alias(\LibreNMS\Interfaces\Geocoder::class, 'geocoder');
$this->app->bind(\LibreNMS\Interfaces\Geocoder::class, function ($app) {
$engine = Config::get('geoloc.engine');
switch ($engine) {
case 'mapquest':
Log::debug('MapQuest geocode engine');
return $app->make(\App\ApiClients\MapquestApi::class);
case 'bing':
Log::debug('Bing geocode engine');
return $app->make(\App\ApiClients\BingApi::class);
case 'openstreetmap':
Log::debug('OpenStreetMap geocode engine');
return $app->make(\App\ApiClients\NominatimApi::class);
default:
case 'google':
Log::debug('Google Maps geocode engine');
return $app->make(\App\ApiClients\GoogleMapsApi::class);
}
});
}
} }

View File

@@ -313,14 +313,14 @@ function list_devices()
} }
$select = " d.*, GROUP_CONCAT(dd.device_id) AS dependency_parent_id, GROUP_CONCAT(dd.hostname) AS dependency_parent_hostname, `lat`, `lng` "; $select = " d.*, GROUP_CONCAT(dd.device_id) AS dependency_parent_id, GROUP_CONCAT(dd.hostname) AS dependency_parent_hostname, `lat`, `lng` ";
$join = " LEFT JOIN `device_relationships` AS dr ON dr.`child_device_id` = d.`device_id` LEFT JOIN `devices` AS dd ON dr.`parent_device_id` = dd.`device_id` LEFT JOIN `locations` ON `locations`.`location` = `d`.`location`"; $join = " LEFT JOIN `device_relationships` AS dr ON dr.`child_device_id` = d.`device_id` LEFT JOIN `devices` AS dd ON dr.`parent_device_id` = dd.`device_id` LEFT JOIN `locations` ON `locations`.`id` = `d`.`location_id`";
if ($type == 'all' || empty($type)) { if ($type == 'all' || empty($type)) {
$sql = '1'; $sql = '1';
} elseif ($type == 'active') { } elseif ($type == 'active') {
$sql = "`d`.`ignore`='0' AND `d`.`disabled`='0'"; $sql = "`d`.`ignore`='0' AND `d`.`disabled`='0'";
} elseif ($type == 'location') { } elseif ($type == 'location') {
$sql = "`d`.`location` LIKE '%".$query."%'"; $sql = "`locations`.`location` LIKE '%".$query."%'";
} elseif ($type == 'ignored') { } elseif ($type == 'ignored') {
$sql = "`d`.`ignore`='1' AND `d`.`disabled`='0'"; $sql = "`d`.`ignore`='1' AND `d`.`disabled`='0'";
} elseif ($type == 'up') { } elseif ($type == 'up') {
@@ -1296,7 +1296,7 @@ function list_oxidized()
$params = array($hostname); $params = array($hostname);
} }
foreach (dbFetchRows("SELECT hostname,sysname,sysDescr,hardware,os,location,ip AS ip FROM `devices` LEFT JOIN devices_attribs AS `DA` ON devices.device_id = DA.device_id AND `DA`.attrib_type='override_Oxidized_disable' WHERE `disabled`='0' AND `ignore` = 0 AND (DA.attrib_value = 'false' OR DA.attrib_value IS NULL) AND (`type` NOT IN ($device_types) AND `os` NOT IN ($device_os)) $sql", $params) as $device) { foreach (dbFetchRows("SELECT hostname,sysname,sysDescr,hardware,os,locations.location,ip AS ip FROM `devices` LEFT JOIN devices_attribs AS `DA` ON devices.device_id = DA.device_id LEFT JOIN locations ON devices.location_id = locations.id AND `DA`.attrib_type='override_Oxidized_disable' WHERE `disabled`='0' AND `ignore` = 0 AND (DA.attrib_value = 'false' OR DA.attrib_value IS NULL) AND (`type` NOT IN ($device_types) AND `os` NOT IN ($device_os)) $sql", $params) as $device) {
// Convert from packed value to human value // Convert from packed value to human value
$device['ip'] = inet6_ntop($device['ip']); $device['ip'] = inet6_ntop($device['ip']);

View File

@@ -38,14 +38,15 @@ $temp_output .= "
"; ";
$locations = array(); $locations = array();
foreach (getlocations() as $location) { foreach (getlocations() as $location_row) {
$location = mres($location); $location = $location_row['location'];
$location_id = $location_row['id'];
$devices = array(); $devices = array();
$devices_down = array(); $devices_down = array();
$devices_up = array(); $devices_up = array();
$count = 0; $count = 0;
$down = 0; $down = 0;
foreach (dbFetchRows("SELECT devices.device_id,devices.hostname,devices.status FROM devices LEFT JOIN devices_attribs ON devices.device_id = devices_attribs.device_id WHERE ( devices.location = ? || ( devices_attribs.attrib_type = 'override_sysLocation_string' && devices_attribs.attrib_value = ? ) ) && devices.disabled = 0 && devices.ignore = 0 GROUP BY devices.hostname", array($location,$location)) as $device) { foreach (dbFetchRows("SELECT `device_id`, `hostname`, `status` FROM `devices` WHERE `location_id` = ? && `disabled` = 0 && `ignore` = 0 GROUP BY `hostname`", [$location_id]) as $device) {
if ($config['frontpage_globe']['markers'] == 'devices' || empty($config['frontpage_globe']['markers'])) { if ($config['frontpage_globe']['markers'] == 'devices' || empty($config['frontpage_globe']['markers'])) {
$devices[] = $device['hostname']; $devices[] = $device['hostname'];
$count++; $count++;
@@ -67,7 +68,7 @@ foreach (getlocations() as $location) {
} }
} }
} }
$pdown = ($down / $count)*100; $pdown = $count ? ($down / $count)*100 : 0;
if ($config['frontpage_globe']['markers'] == 'devices' || empty($config['frontpage_globe']['markers'])) { if ($config['frontpage_globe']['markers'] == 'devices' || empty($config['frontpage_globe']['markers'])) {
$devices_down = array_merge(array(count($devices_up). " Devices OK"), $devices_down); $devices_down = array_merge(array(count($devices_up). " Devices OK"), $devices_down);
} elseif ($config['frontpage_globe']['markers'] == 'ports') { } elseif ($config['frontpage_globe']['markers'] == 'ports') {

View File

@@ -173,18 +173,18 @@ var greenMarker = L.AwesomeMarkers.icon({
// Checking user permissions // Checking user permissions
if (LegacyAuth::user()->hasGlobalRead()) { if (LegacyAuth::user()->hasGlobalRead()) {
// Admin or global read-only - show all devices // Admin or global read-only - show all devices
$sql = "SELECT DISTINCT(`device_id`),`devices`.`location`,`sysName`,`hostname`,`os`,`status`,`lat`,`lng` FROM `devices` $sql = "SELECT DISTINCT(`device_id`),`location`,`sysName`,`hostname`,`os`,`status`,`lat`,`lng` FROM `devices`
LEFT JOIN `locations` ON `devices`.`location`=`locations`.`location` LEFT JOIN `locations` ON `devices`.`location_id`=`locations`.`id`
WHERE `disabled`=0 AND `ignore`=0 AND ((`lat` != '' AND `lng` != '') OR (`devices`.`location` REGEXP '\[[0-9\.\, ]+\]')) WHERE `disabled`=0 AND `ignore`=0 AND ((`lat` != '' AND `lng` != '') OR (`location` REGEXP '\[[0-9\.\, ]+\]'))
AND `status` IN " . dbGenPlaceholders(count($status_select)) . AND `status` IN " . dbGenPlaceholders(count($status_select)) .
" ORDER BY `status` ASC, `hostname`"; " ORDER BY `status` ASC, `hostname`";
$param = $status_select; $param = $status_select;
} else { } else {
// Normal user - grab devices that user has permissions to // Normal user - grab devices that user has permissions to
$sql = "SELECT DISTINCT(`devices`.`device_id`) as `device_id`,`devices`.`location`,`sysName`,`hostname`,`os`,`status`,`lat`,`lng` $sql = "SELECT DISTINCT(`devices`.`device_id`) as `device_id`,`location`,`sysName`,`hostname`,`os`,`status`,`lat`,`lng`
FROM `devices_perms`, `devices` FROM `devices_perms`, `devices`
LEFT JOIN `locations` ON `devices`.`location`=`locations`.`location` LEFT JOIN `locations` ON `devices`.location_id=`locations`.`id`
WHERE `disabled`=0 AND `ignore`=0 AND ((`lat` != '' AND `lng` != '') OR (`devices`.`location` REGEXP '\[[0-9\.\, ]+\]')) WHERE `disabled`=0 AND `ignore`=0 AND ((`lat` != '' AND `lng` != '') OR (`location` REGEXP '\[[0-9\.\, ]+\]'))
AND `devices`.`device_id` = `devices_perms`.`device_id` AND `devices`.`device_id` = `devices_perms`.`device_id`
AND `devices_perms`.`user_id` = ? AND `status` IN " . dbGenPlaceholders(count($status_select)) . AND `devices_perms`.`user_id` = ? AND `status` IN " . dbGenPlaceholders(count($status_select)) .
" ORDER BY `status` ASC, `hostname`"; " ORDER BY `status` ASC, `hostname`";

View File

@@ -100,28 +100,19 @@ if ($device['sysContact']) {
</tr>'; </tr>';
} }
if ($device['location']) { if ($device['location_id']) {
echo '<tr> $location = \App\Models\Location::find($device['location_id']);
<td>Location</td>
<td>'.$device['location'].'</td>
</tr>';
if (get_dev_attrib($device, 'override_sysLocation_bool') && !empty($device['real_location'])) {
echo '<tr> echo '<tr>
<td>SNMP Location</td> <td>Location</td>
<td>'.$device['real_location'].'</td> <td>' . $location->location . '</td>
</tr>'; </tr>';
}
}
$loc = parse_location($device['location']); if ($location->coordinatesValid()) {
if (!is_array($loc)) { echo '<tr>
$loc = dbFetchRow("SELECT `lat`,`lng` FROM `locations` WHERE `location`=? LIMIT 1", array($device['location']));
}
if (is_array($loc)) {
echo '<tr>
<td>Lat / Lng</td> <td>Lat / Lng</td>
<td>['.$loc['lat'].','.$loc['lng'].'] <div class="pull-right"><a href="https://maps.google.com/?q='.$loc['lat'].'+'.$loc['lng'].'" target="_blank" class="btn btn-success btn-xs" role="button"><i class="fa fa-map-marker" style="color:white" aria-hidden="true"></i> Map</button></div></a></td> <td>[' . $location->lat . ',' . $location->lng . '] <div class="pull-right"><a href="https://maps.google.com/?q=' . $location->lat . '+' . $location->lng . '" target="_blank" class="btn btn-success btn-xs" role="button"><i class="fa fa-map-marker" style="color:white" aria-hidden="true"></i> Map</button></div></a></td>
</tr>'; </tr>';
}
} }
if ($uptime) { if ($uptime) {

View File

@@ -24,7 +24,7 @@ $transport = $vars['transport'] ?: null;
$transport_id = $vars['transport_id'] ?: null; $transport_id = $vars['transport_id'] ?: null;
require_once $config['install_dir'].'/includes/alerts.inc.php'; require_once $config['install_dir'].'/includes/alerts.inc.php';
$tmp = array(dbFetchRow('select device_id,hostname,sysDescr,version,hardware,location from devices order by device_id asc limit 1')); $tmp = array(dbFetchRow('select device_id,hostname,sysDescr,version,hardware,location_id from devices order by device_id asc limit 1'));
$tmp['contacts'] = GetContacts($tmp); $tmp['contacts'] = GetContacts($tmp);
$obj = array( $obj = array(
"hostname" => $tmp[0]['hostname'], "hostname" => $tmp[0]['hostname'],

View File

@@ -880,27 +880,12 @@ function devclass($device)
function getlocations() function getlocations()
{ {
$locations = array();
// Fetch regular locations
if (LegacyAuth::user()->hasGlobalRead()) { if (LegacyAuth::user()->hasGlobalRead()) {
$rows = dbFetchRows('SELECT location FROM devices AS D GROUP BY location ORDER BY location'); return dbFetchRows('SELECT id, location FROM locations ORDER BY location');
} else {
$rows = dbFetchRows('SELECT location FROM devices AS D, devices_perms AS P WHERE D.device_id = P.device_id AND P.user_id = ? GROUP BY location ORDER BY location', array(LegacyAuth::id()));
} }
foreach ($rows as $row) { return dbFetchRows('SELECT id, L.location FROM devices AS D, locations AS L, devices_perms AS P WHERE D.device_id = P.device_id AND P.user_id = ? AND D.location_id = L.id ORDER BY location', [LegacyAuth::id()]);
// Only add it as a location if it wasn't overridden (and not already there) }
if ($row['location'] != '') {
if (!in_array($row['location'], $locations)) {
$locations[] = $row['location'];
}
}
}
sort($locations);
return $locations;
}//end getlocations()
/** /**

View File

@@ -1,9 +1,9 @@
<?php <?php
foreach (dbFetchRows('SELECT * FROM `devices` WHERE `location` = ?', array($vars['id'])) as $device) { foreach (dbFetchRows('SELECT * FROM `devices`,`locations` WHERE location_id = ? && devices.location_id = locations.id', [$vars['id']]) as $device) {
if ($auth || device_permitted($device_id)) { if ($auth || device_permitted($device_id)) {
$devices[] = $device; $devices[] = $device;
$title = $vars['id']; $title = $device['location'];
$auth = true; $auth = true;
} }
} }

View File

@@ -189,9 +189,10 @@ if (Auth::user()->hasGlobalAdmin()) {
<li class="dropdown-submenu"> <li class="dropdown-submenu">
<a href="#"><i class="fa fa-map-marker fa-fw fa-lg" aria-hidden="true"></i> Geo Locations</a> <a href="#"><i class="fa fa-map-marker fa-fw fa-lg" aria-hidden="true"></i> Geo Locations</a>
<ul class="dropdown-menu scrollable-menu"> <ul class="dropdown-menu scrollable-menu">
<li><a href="locations"><i class="fa fa-map-marker fa-fw fa-lg" aria-hidden="true"></i> All Locations</a></li>
'); ');
foreach ($locations as $location) { foreach ($locations as $location) {
echo(' <li><a href="devices/location=' . urlencode($location) . '/"><i class="fa fa-building fa-fw fa-lg" aria-hidden="true"></i> ' . $location . ' </a></li>'); echo(' <li><a href="devices/location=' . $location['id'] . '/"><i class="fa fa-building fa-fw fa-lg" aria-hidden="true"></i> ' . htmlentities($location['location']) . ' </a></li>');
} }
echo(' echo('
</ul> </ul>

View File

@@ -117,7 +117,7 @@ if ($rowCount != -1) {
$sql .= " LIMIT $limit_low,$limit_high"; $sql .= " LIMIT $limit_low,$limit_high";
} }
$sql = "SELECT `alerts`.*, `devices`.`hostname`, `devices`.`sysName`, `devices`.`hardware`, `devices`.`location`, `alert_rules`.`rule`, `alert_rules`.`name`, `alert_rules`.`severity` $sql"; $sql = "SELECT `alerts`.*, `devices`.`hostname`, `devices`.`sysName`, `devices`.`hardware`, `alert_rules`.`rule`, `alert_rules`.`name`, `alert_rules`.`severity` $sql";
$rulei = 0; $rulei = 0;
$format = $vars['format']; $format = $vars['format'];

View File

@@ -18,7 +18,7 @@ use LibreNMS\Authentication\LegacyAuth;
$where = 1; $where = 1;
$param = array(); $param = array();
$sql = ' FROM `devices`'; $sql = ' FROM `devices` LEFT JOIN locations ON devices.location_id = locations.id';
if (!LegacyAuth::user()->hasGlobalRead()) { if (!LegacyAuth::user()->hasGlobalRead()) {
$sql .= ' LEFT JOIN `devices_perms` AS `DP` ON `devices`.`device_id` = `DP`.`device_id`'; $sql .= ' LEFT JOIN `devices_perms` AS `DP` ON `devices`.`device_id` = `DP`.`device_id`';
@@ -26,10 +26,6 @@ if (!LegacyAuth::user()->hasGlobalRead()) {
$param[] = LegacyAuth::id(); $param[] = LegacyAuth::id();
} }
if (!empty($vars['location'])) {
$sql .= " LEFT JOIN `devices_attribs` AS `DB` ON `DB`.`device_id`=`devices`.`device_id` AND `DB`.`attrib_type`='override_sysLocation_bool' AND `DB`.`attrib_value`='1' LEFT JOIN `devices_attribs` AS `DA` ON `devices`.`device_id`=`DA`.`device_id`";
}
if (!empty($vars['group']) && is_numeric($vars['group'])) { if (!empty($vars['group']) && is_numeric($vars['group'])) {
$sql .= " LEFT JOIN `device_group_device` AS `DG` ON `DG`.`device_id`=`devices`.`device_id`"; $sql .= " LEFT JOIN `device_group_device` AS `DG` ON `DG`.`device_id`=`devices`.`device_id`";
$where .= " AND `DG`.`device_group_id`=?"; $where .= " AND `DG`.`device_group_id`=?";
@@ -97,7 +93,8 @@ if (!empty($vars['location']) && $vars['location'] == 'Unset') {
} }
if (!empty($vars['location'])) { if (!empty($vars['location'])) {
$sql .= " AND `location` = ?"; $sql .= " AND (`location` = ? OR `location_id` = ?)";
$param[] = $vars['location'];
$param[] = $vars['location']; $param[] = $vars['location'];
} }
@@ -120,7 +117,7 @@ if ($rowCount != -1) {
$sql .= " LIMIT $limit_low,$limit_high"; $sql .= " LIMIT $limit_low,$limit_high";
} }
$sql = "SELECT DISTINCT(`devices`.`device_id`),`devices`.* $sql"; $sql = "SELECT DISTINCT(`devices`.`device_id`),`devices`.*,locations.location $sql";
if (!isset($vars['format'])) { if (!isset($vars['format'])) {
$vars['format'] = 'list_detail'; $vars['format'] = 'list_detail';

View File

@@ -46,7 +46,7 @@ if (!empty($vars['hostname'])) {
} }
if (!empty($vars['location'])) { if (!empty($vars['location'])) {
$where .= " AND `D`.`location` = ?"; $where .= " AND `D`.`location_id` = ?";
$param[] = $vars['location']; $param[] = $vars['location'];
} }

View File

@@ -36,10 +36,11 @@ if (isset($config['branding']) && is_array($config['branding'])) {
} }
} }
$where = '';
$param = [];
if (is_numeric($_GET['device']) && isset($_GET['device'])) { if (is_numeric($_GET['device']) && isset($_GET['device'])) {
$where = 'WHERE device_id = '.mres($_GET['device']); $where = '&& device_id = ?';
} else { $param[] = $_GET['device'];
$where = '';
} }
// FIXME this shit probably needs tidied up. // FIXME this shit probably needs tidied up.
@@ -57,7 +58,7 @@ if (isset($_GET['format']) && preg_match("/^[a-z]*$/", $_GET['format'])) {
} else { } else {
$loc_count = 1; $loc_count = 1;
foreach (dbFetch("SELECT * from devices ".$where) as $device) { foreach (dbFetch("SELECT *, locations.location from devices,locations WHERE devices.location_id = locations.id ".$where, $param) as $device) {
if ($device) { if ($device) {
$links = dbFetch("SELECT * from ports AS I, links AS L WHERE I.device_id = ? AND L.local_port_id = I.port_id ORDER BY L.remote_hostname", array($device['device_id'])); $links = dbFetch("SELECT * from ports AS I, links AS L WHERE I.device_id = ? AND L.local_port_id = I.port_id ORDER BY L.remote_hostname", array($device['device_id']));
if (count($links)) { if (count($links)) {

View File

@@ -1,70 +1,57 @@
<?php <?php
use App\Models\Device; use App\Models\Device;
use App\Models\Location;
use LibreNMS\Authentication\LegacyAuth; use LibreNMS\Authentication\LegacyAuth;
$device_model = Device::find($device['device_id']);
if ($_POST['editing']) { if ($_POST['editing']) {
if (LegacyAuth::user()->hasGlobalAdmin()) { if (LegacyAuth::user()->hasGlobalAdmin()) {
$updated = 0;
if (isset($_POST['parent_id'])) { if (isset($_POST['parent_id'])) {
$parents = array_diff((array)$_POST['parent_id'], ['0']); $parents = array_diff((array)$_POST['parent_id'], ['0']);
// TODO avoid loops! // TODO avoid loops!
Device::find($device['device_id'])->parents()->sync($parents); $device_model->parents()->sync($parents);
} }
$override_sysLocation_bool = mres($_POST['override_sysLocation']); $override_sysLocation = (int)isset($_POST['override_sysLocation']);
if (isset($_POST['sysLocation'])) { $override_sysLocation_string = isset($_POST['sysLocation']) ? $_POST['sysLocation'] : null;
$override_sysLocation_string = $_POST['sysLocation'];
}
if ($device['override_sysLocation'] != $override_sysLocation_bool || $device['location'] != $override_sysLocation_string) { if ($override_sysLocation) {
$updated = 1; if ($override_sysLocation_string) {
} $location = Location::firstOrCreate(['location' => $override_sysLocation_string]);
$device_model->location()->associate($location);
if ($override_sysLocation_bool) { } else {
$override_sysLocation = 1; $device_model->location()->dissociate();
} else {
$override_sysLocation = 0;
}
dbUpdate(array('override_sysLocation'=>$override_sysLocation), 'devices', '`device_id`=?', array($device['device_id']));
if (isset($override_sysLocation_string)) {
dbUpdate(array('location'=>$override_sysLocation_string), 'devices', '`device_id`=?', array($device['device_id']));
}
if ($device['type'] != $vars['type']) {
$param['type'] = $vars['type'];
$update_type = true;
}
#FIXME needs more sanity checking! and better feedback
$param['purpose'] = $vars['descr'];
$param['ignore'] = set_numeric($vars['ignore']);
$param['disabled'] = set_numeric($vars['disabled']);
$rows_updated = dbUpdate($param, 'devices', '`device_id` = ?', array($device['device_id']));
if ($rows_updated > 0 || $updated) {
if ($update_type === true) {
set_dev_attrib($device, 'override_device_type', true);
} }
$update_message = "Device record updated."; } elseif ($device_model->override_sysLocation) {
$updated = 1; // no longer overridden, clear location
$device = dbFetchRow("SELECT * FROM `devices` WHERE `device_id` = ?", array($device['device_id'])); $device_model->location()->dissociate();
} elseif ($rows_updated == 0) {
$update_message = "Device record unchanged. No update necessary.";
$updated = -1;
} else {
$update_message = "Device record update error.";
} }
$device_model->override_sysLocation = $override_sysLocation;
$device_model->purpose = $_POST['descr'];
$device_model->ignore = (int)isset($_POST['ignore']);
$device_model->disabled = (int)isset($_POST['disabled']);
$device_model->type = $_POST['type'];
if ($device_model->isDirty('type')) {
set_dev_attrib($device, 'override_device_type', true);
}
if ($device_model->isDirty()) {
if ($device_model->save()) {
Toastr::success(__('Device record updated'));
} else {
Toastr::error(__('Device record update error'));
}
}
if (isset($_POST['hostname']) && $_POST['hostname'] !== '' && $_POST['hostname'] !== $device['hostname']) { if (isset($_POST['hostname']) && $_POST['hostname'] !== '' && $_POST['hostname'] !== $device['hostname']) {
if (LegacyAuth::user()->hasGlobalAdmin()) { if (LegacyAuth::user()->hasGlobalAdmin()) {
$result = renamehost($device['device_id'], $_POST['hostname'], 'webui'); $result = renamehost($device['device_id'], $_POST['hostname'], 'webui');
if ($result == "") { if ($result == "") {
print_message("Hostname updated from {$device['hostname']} to {$_POST['hostname']}"); Toastr::success("Hostname updated from {$device['hostname']} to {$_POST['hostname']}");
echo ' echo '
<script> <script>
var loc = window.location; var loc = window.location;
@@ -72,10 +59,10 @@ if ($_POST['editing']) {
</script> </script>
'; ';
} else { } else {
print_error($result . ". Does your web server have permission to modify the rrd files?"); Toastr::error($result . ". Does your web server have permission to modify the rrd files?");
} }
} else { } else {
print_error('Only administrative users may update the device hostname'); Toastr::error('Only administrative users may update the device hostname');
} }
} }
} else { } else {
@@ -83,16 +70,6 @@ if ($_POST['editing']) {
} }
} }
$descr = $device['purpose'];
$override_sysLocation = $device['override_sysLocation'];
$override_sysLocation_string = $device['location'];
if ($updated && $update_message) {
print_message($update_message);
} elseif ($update_message) {
print_error($update_message);
}
?> ?>
<h3> Device Settings </h3> <h3> Device Settings </h3>
<div class="row"> <div class="row">
@@ -127,7 +104,7 @@ if ($updated && $update_message) {
<div class="form-group"> <div class="form-group">
<label for="descr" class="col-sm-2 control-label">Description:</label> <label for="descr" class="col-sm-2 control-label">Description:</label>
<div class="col-sm-6"> <div class="col-sm-6">
<textarea id="descr" name="descr" class="form-control"><?php echo(display($device['purpose'])); ?></textarea> <textarea id="descr" name="descr" class="form-control"><?php echo(display($device_model->purpose)); ?></textarea>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -139,7 +116,7 @@ if ($updated && $update_message) {
foreach ($config['device_types'] as $type) { foreach ($config['device_types'] as $type) {
echo(' <option value="'.$type['type'].'"'); echo(' <option value="'.$type['type'].'"');
if ($device['type'] == $type['type']) { if ($device_model->type == $type['type']) {
echo(' selected="1"'); echo(' selected="1"');
$unknown = 0; $unknown = 0;
} }
@@ -157,21 +134,21 @@ if ($updated && $update_message) {
<div class="col-sm-6"> <div class="col-sm-6">
<input onclick="edit.sysLocation.disabled=!edit.override_sysLocation.checked" type="checkbox" name="override_sysLocation" <input onclick="edit.sysLocation.disabled=!edit.override_sysLocation.checked" type="checkbox" name="override_sysLocation"
<?php <?php
if ($override_sysLocation) { if ($device_model->override_sysLocation) {
echo(' checked="1"'); echo(' checked="1"');
} }
?> /> ?> />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group" title="To set coordinates, include [latitude,longitude]">
<div class="col-sm-2"></div> <div class="col-sm-2"></div>
<div class="col-sm-6"> <div class="col-sm-6">
<input id="sysLocation" name="sysLocation" class="form-control" <input id="sysLocation" name="sysLocation" class="form-control"
<?php <?php
if (!$override_sysLocation) { if (!$device_model->override_sysLocation) {
echo(' disabled="1"'); echo(' disabled="1"');
} }
?> value="<?php echo($override_sysLocation_string); ?>" /> ?> value="<?php echo display($device_model->location->location); ?>" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -206,7 +183,7 @@ if ($updated && $update_message) {
<div class="col-sm-6"> <div class="col-sm-6">
<input name="disabled" type="checkbox" id="disabled" value="1" <input name="disabled" type="checkbox" id="disabled" value="1"
<?php <?php
if ($device["disabled"]) { if ($device_model->disabled) {
echo("checked=checked"); echo("checked=checked");
} }
?> /> ?> />
@@ -217,7 +194,7 @@ if ($updated && $update_message) {
<div class="col-sm-6"> <div class="col-sm-6">
<input name="ignore" type="checkbox" id="ignore" value="1" <input name="ignore" type="checkbox" id="ignore" value="1"
<?php <?php
if ($device['ignore']) { if ($device_model->ignore) {
echo("checked=checked"); echo("checked=checked");
} }
?> /> ?> />

View File

@@ -188,7 +188,7 @@ if ($format == "graph") {
$where .= " )"; $where .= " )";
} }
$query = "SELECT * FROM `devices` WHERE 1 "; $query = "SELECT * FROM `devices`,locations WHERE devices.location_id = locations.id ";
if (isset($where)) { if (isset($where)) {
$query .= $where; $query .= $where;
@@ -325,18 +325,17 @@ if ($format == "graph") {
$locations_options = "<select name='location' id='location' class='form-control input-sm'>"; $locations_options = "<select name='location' id='location' class='form-control input-sm'>";
$locations_options .= "<option value=''>All Locations</option>"; $locations_options .= "<option value=''>All Locations</option>";
foreach (getlocations() as $location) { foreach (getlocations() as $location_row) {
if ($location) { $location = clean_bootgrid($location_row['location']);
$location = clean_bootgrid($location); $location_id = $location_row['id'];
if ($location == $vars['location']) { if ($location == $vars['location'] || $location_id == $vars['location']) {
$location_selected = 'selected'; $location_selected = 'selected';
} else { } else {
$location_selected = ''; $location_selected = '';
}
$ui_location = strlen($location) > 15 ? substr($location, 0, 15) . "..." : $location;
$locations_options .= "<option value='" . $location . "' " . $location_selected . ">" . $ui_location . "</option>";
} }
$ui_location = strlen($location) > 15 ? substr($location, 0, 15) . "..." : $location;
$locations_options .= "<option value='" . $location_id . "' " . $location_selected . ">" . $ui_location . "</option>";
} }
$locations_options .= "</select>"; $locations_options .= "</select>";

View File

@@ -68,7 +68,7 @@ if ($config['map']['engine'] == 'leaflet') {
<script src='js/jquery.mousewheel.min.js'></script> <script src='js/jquery.mousewheel.min.js'></script>
<?php <?php
$x=0; $x=0;
foreach (dbFetchRows("SELECT `hostname`,`devices`.`location`,`status`, COUNT(`status`) AS `total`,`lat`,`lng` FROM `devices` LEFT JOIN `locations` ON `devices`.`location`=`locations`.`location` WHERE `disabled`=0 AND `ignore`=0 AND `lat` != '' AND `lng` != '' GROUP BY `status`,`lat`,`lng` ORDER BY `status` ASC, `hostname`") as $map_devices) { foreach (dbFetchRows("SELECT `hostname`,`location`,`status`, COUNT(`status`) AS `total`,`lat`,`lng` FROM `devices` LEFT JOIN `locations` ON `devices`.location_id=`locations`.`id` WHERE `disabled`=0 AND `ignore`=0 AND `lat` != '' AND `lng` != '' GROUP BY `status`,`lat`,`lng` ORDER BY `status` ASC, `hostname`") as $map_devices) {
$color = "#29FF3B"; $color = "#29FF3B";
$size = 15; $size = 15;
$status = 'Up'; $status = 'Up';

View File

@@ -72,7 +72,7 @@ if ($config['map']['engine'] == 'leaflet') {
<script src='js/jquery.mousewheel.min.js'></script> <script src='js/jquery.mousewheel.min.js'></script>
<?php <?php
$x=0; $x=0;
foreach (dbFetchRows("SELECT `hostname`,`devices`.`location`,`status`, COUNT(`status`) AS `total`,`lat`,`lng` FROM `devices` LEFT JOIN `locations` ON `devices`.`location`=`locations`.`location` WHERE `disabled`=0 AND `ignore`=0 AND `lat` != '' AND `lng` != '' GROUP BY `status`,`lat`,`lng` ORDER BY `status` ASC, `hostname`") as $map_devices) { foreach (dbFetchRows("SELECT `hostname`,`location`,`status`, COUNT(`status`) AS `total`,`lat`,`lng` FROM `devices` LEFT JOIN `locations` ON `devices`.`location_id`=`locations`.`id` WHERE `disabled`=0 AND `ignore`=0 AND `lat` != '' AND `lng` != '' GROUP BY `status`,`lat`,`lng` ORDER BY `status` ASC, `hostname`") as $map_devices) {
$color = "#29FF3B"; $color = "#29FF3B";
$size = 15; $size = 15;
$status = 'Up'; $status = 'Up';

View File

@@ -1,6 +1,6 @@
<?php <?php
use LibreNMS\Authentication\LegacyAuth; use App\Models\Location;
$pagetitle[] = 'Locations'; $pagetitle[] = 'Locations';
@@ -38,20 +38,13 @@ print_optionbar_end();
echo '<table cellpadding="7" cellspacing="0" class="devicetable" width="100%">'; echo '<table cellpadding="7" cellspacing="0" class="devicetable" width="100%">';
foreach (getlocations() as $location) { foreach (Location::hasAccess(Auth::user())->get() as $location) {
if (LegacyAuth::user()->hasGlobalAdmin()) { /** @var Location $location */
$num = dbFetchCell('SELECT COUNT(device_id) FROM devices WHERE location = ?', array($location)); $num = $location->devices()->count();
$net = dbFetchCell("SELECT COUNT(device_id) FROM devices WHERE location = ? AND type = 'network'", array($location)); $net = $location->devices()->where('type', 'network')->count();
$srv = dbFetchCell("SELECT COUNT(device_id) FROM devices WHERE location = ? AND type = 'server'", array($location)); $srv = $location->devices()->where('type', 'server')->count();
$fwl = dbFetchCell("SELECT COUNT(device_id) FROM devices WHERE location = ? AND type = 'firewall'", array($location)); $fwl = $location->devices()->where('type', 'firewall')->count();
$hostalerts = dbFetchCell("SELECT COUNT(device_id) FROM devices WHERE location = ? AND status = '0'", array($location)); $hostalerts = $location->devices()->isDown()->count();
} else {
$num = dbFetchCell('SELECT COUNT(D.device_id) FROM devices AS D, devices_perms AS P WHERE D.device_id = P.device_id AND P.user_id = ? AND location = ?', array(LegacyAuth::id(), $location));
$net = dbFetchCell("SELECT COUNT(D.device_id) FROM devices AS D, devices_perms AS P WHERE D.device_id = P.device_id AND P.user_id = ? AND location = ? AND D.type = 'network'", array(LegacyAuth::id(), $location));
$srv = dbFetchCell("SELECT COUNT(D.device_id) FROM devices AS D, devices_perms AS P WHERE D.device_id = P.device_id AND P.user_id = ? AND location = ? AND type = 'server'", array(LegacyAuth::id(), $location));
$fwl = dbFetchCell("SELECT COUNT(D.device_id) FROM devices AS D, devices_perms AS P WHERE D.device_id = P.device_id AND P.user_id = ? AND location = ? AND type = 'firewall'", array(LegacyAuth::id(), $location));
$hostalerts = dbFetchCell("SELECT COUNT(device_id) FROM devices AS D, devices_perms AS P WHERE D.device_id = P.device_id AND P.user_id = ? AND location = ? AND status = '0'", array(LegacyAuth::id(), $location));
}
if ($hostalerts) { if ($hostalerts) {
$alert = '<i class="fa fa-flag" style="color:red" aria-hidden="true"></i>'; $alert = '<i class="fa fa-flag" style="color:red" aria-hidden="true"></i>';
@@ -61,7 +54,7 @@ foreach (getlocations() as $location) {
if ($location != '') { if ($location != '') {
echo ' <tr class="locations"> echo ' <tr class="locations">
<td class="interface" width="300"><a class="list-bold" href="devices/location='.urlencode($location).'/">'.$location.'</a></td> <td class="interface" width="300"><a class="list-bold" href="devices/location='.$location->id.'/">'.display($location->location).'</a></td>
<td width="100">'.$alert.'</td> <td width="100">'.$alert.'</td>
<td width="100">'.$num.' devices</td> <td width="100">'.$num.' devices</td>
<td width="100">'.$net.' network</td> <td width="100">'.$net.' network</td>
@@ -78,7 +71,7 @@ foreach (getlocations() as $location) {
$graph_array['width'] = '220'; $graph_array['width'] = '220';
$graph_array['to'] = $config['time']['now']; $graph_array['to'] = $config['time']['now'];
$graph_array['legend'] = 'no'; $graph_array['legend'] = 'no';
$graph_array['id'] = $location; $graph_array['id'] = $location->id;
include 'includes/print-graphrow.inc.php'; include 'includes/print-graphrow.inc.php';

View File

@@ -246,15 +246,17 @@ if ((isset($vars['searchbar']) && $vars['searchbar'] != "hide") || !isset($vars[
$output .= "<select title='Location' name='location' id='location' class='form-control input-sm'>&nbsp;"; $output .= "<select title='Location' name='location' id='location' class='form-control input-sm'>&nbsp;";
$output .= "<option value=''>All Locations</option>"; $output .= "<option value=''>All Locations</option>";
foreach (getlocations() as $location) { foreach (getlocations() as $location_row) {
$location = $location_row['location'];
$location_id = $location_row['id'];
if ($location) { if ($location) {
if ($location == $vars['location']) { if ($location_id == $vars['location']) {
$locationselected = "selected"; $locationselected = "selected";
} else { } else {
$locationselected = ""; $locationselected = "";
} }
$ui_location = strlen($location) > 15 ? substr($location, 0, 15) . "..." : $location; $ui_location = strlen($location) > 15 ? substr($location, 0, 15) . "..." : $location;
$output .= "<option value='" . clean_bootgrid($location) . "' " . $locationselected . ">" . clean_bootgrid($ui_location) . "</option>"; $output .= "<option value='$location_id' $locationselected>" . clean_bootgrid($ui_location) . "</option>";
} }
} }
@@ -318,7 +320,7 @@ foreach ($vars as $var => $value) {
$param[] = "%" . $value . "%"; $param[] = "%" . $value . "%";
break; break;
case 'location': case 'location':
$where .= " AND D.location LIKE ?"; $where .= " AND L.location LIKE ?";
$param[] = "%" . $value . "%"; $param[] = "%" . $value . "%";
break; break;
case 'device_id': case 'device_id':
@@ -402,7 +404,7 @@ if ($ignore_filter == 0 && $disabled_filter == 0) {
$where .= " AND `I`.`ignore` = 0 AND `I`.`disabled` = 0 AND `I`.`deleted` = 0"; $where .= " AND `I`.`ignore` = 0 AND `I`.`disabled` = 0 AND `I`.`deleted` = 0";
} }
$query = "SELECT * FROM `ports` AS I, `devices` AS D WHERE I.device_id = D.device_id " . $where . " " . $query_sort; $query = "SELECT * FROM `ports` AS I, `devices` AS D, `locations` AS L WHERE I.device_id = D.device_id D.location_id = L.id" . $where . " " . $query_sort;
$row = 1; $row = 1;

View File

@@ -4,6 +4,26 @@ $no_refresh = true;
$config_groups = get_config_by_group('external'); $config_groups = get_config_by_group('external');
$location_conf = [
[
'name' => 'geoloc.engine',
'descr' => 'Geocoding Engine',
'type' => 'select',
'options' => [
['value' => 'google', 'description' => 'Google Maps'],
['value' => 'openstreetmap', 'description' => 'OpenStreetMap'],
['value' => 'mapquest', 'description' => 'MapQuest'],
['value' => 'bing', 'description' => 'Bing Maps'],
]
],
[
'name' => 'geoloc.api_key',
'descr' => 'Geocoding API Key',
'type' => 'text',
'class' => 'geoloc_api_key'
],
];
$oxidized_conf = array( $oxidized_conf = array(
array('name' => 'oxidized.enabled', array('name' => 'oxidized.enabled',
'descr' => 'Enable Oxidized support', 'descr' => 'Enable Oxidized support',
@@ -84,12 +104,23 @@ echo '
<form class="form-horizontal" role="form" action="" method="post"> <form class="form-horizontal" role="form" action="" method="post">
'; ';
echo generate_dynamic_config_panel('Location Geocoding', $config_groups, $location_conf);
echo generate_dynamic_config_panel('Oxidized integration', $config_groups, $oxidized_conf); echo generate_dynamic_config_panel('Oxidized integration', $config_groups, $oxidized_conf);
echo generate_dynamic_config_panel('Unix-agent integration', $config_groups, $unixagent_conf); echo generate_dynamic_config_panel('Unix-agent integration', $config_groups, $unixagent_conf);
echo generate_dynamic_config_panel('RRDTool Setup', $config_groups, $rrdtool_conf); echo generate_dynamic_config_panel('RRDTool Setup', $config_groups, $rrdtool_conf);
echo generate_dynamic_config_panel('PeeringDB Integration', $config_groups, $peeringdb_conf); echo generate_dynamic_config_panel('PeeringDB Integration', $config_groups, $peeringdb_conf);
echo ' ?>
</form> </form>
</div> </div>
'; <script>
$('#geoloc\\.engine').change(function () {
var engine = this.value;
if (engine === 'openstreetmap') {
$('.geoloc_api_key').hide();
} else {
$('.geoloc_api_key').show();
}
}).change(); // trigger initially
</script>

View File

@@ -415,7 +415,7 @@ function DescribeAlert($alert)
{ {
$obj = array(); $obj = array();
$i = 0; $i = 0;
$device = dbFetchRow('SELECT hostname, sysName, sysDescr, sysContact, os, type, ip, hardware, version, location, purpose, notes, uptime, status, status_reason FROM devices WHERE device_id = ?', array($alert['device_id'])); $device = dbFetchRow('SELECT hostname, sysName, sysDescr, sysContact, os, type, ip, hardware, version, purpose, notes, uptime, status, status_reason, locations.location FROM devices, locations WHERE locations.id = location_id && device_id = ?', array($alert['device_id']));
$attribs = get_dev_attribs($alert['device_id']); $attribs = get_dev_attribs($alert['device_id']);
$obj['hostname'] = $device['hostname']; $obj['hostname'] = $device['hostname'];

View File

@@ -366,7 +366,7 @@ function device_by_id_cache($device_id, $refresh = false)
if (!$refresh && isset($cache['devices']['id'][$device_id]) && is_array($cache['devices']['id'][$device_id])) { if (!$refresh && isset($cache['devices']['id'][$device_id]) && is_array($cache['devices']['id'][$device_id])) {
$device = $cache['devices']['id'][$device_id]; $device = $cache['devices']['id'][$device_id];
} else { } else {
$device = dbFetchRow("SELECT `devices`.*, `lat`, `lng` FROM `devices` LEFT JOIN locations ON `devices`.`location`=`locations`.`location` WHERE `device_id` = ?", array($device_id)); $device = dbFetchRow("SELECT `devices`.*, `location`, `lat`, `lng` FROM `devices` LEFT JOIN locations ON `devices`.location_id=`locations`.`id` WHERE `device_id` = ?", [$device_id]);
$device['attribs'] = get_dev_attribs($device['device_id']); $device['attribs'] = get_dev_attribs($device['device_id']);
load_os($device); load_os($device);
@@ -1122,14 +1122,16 @@ function ceph_rrd($gtype)
/** /**
* Parse location field for coordinates * Parse location field for coordinates
* @param string location The location field to look for coords in. * @param string location The location field to look for coords in.
* @return array Containing the lat and lng coords * @return array|bool Containing the lat and lng coords
**/ **/
function parse_location($location) function parse_location($location)
{ {
preg_match('/(\[)(-?[0-9\. ]+),[ ]*(-?[0-9\. ]+)(\])/', $location, $tmp_loc); preg_match('/\[(-?[0-9. ]+), *(-?[0-9. ]+)\]/', $location, $tmp_loc);
if (is_numeric($tmp_loc[2]) && is_numeric($tmp_loc[3])) { if (is_numeric($tmp_loc[1]) && is_numeric($tmp_loc[2])) {
return array('lat' => $tmp_loc[2], 'lng' => $tmp_loc[3]); return ['lat' => $tmp_loc[1], 'lng' => $tmp_loc[2]];
} }
return false;
}//end parse_location() }//end parse_location()
/** /**
@@ -1354,11 +1356,16 @@ function ResolveGlues($tables, $target, $x = 0, $hist = array(), $last = array()
'sensors_to_state_indexes.sensor_id', 'sensors_to_state_indexes.sensor_id',
"sensors.$target", "sensors.$target",
)); ));
} elseif ($table == 'application_metrics' && $target = 'device_id') { } elseif ($table == 'application_metrics' && $target == 'device_id') {
return array_merge($last, array( return array_merge($last, array(
'application_metrics.app_id', 'application_metrics.app_id',
"applications.$target", "applications.$target",
)); ));
} elseif ($table == 'locations' && $target == 'device_id') {
return array_merge($last, [
'locations.id',
'devices.device_id.location_id'
]);
} }
$glues = dbFetchRows('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME LIKE "%\_id"', array($table)); $glues = dbFetchRows('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME LIKE "%\_id"', array($table));

View File

@@ -916,8 +916,8 @@ $config['unix-agent-read-time-out'] = 10;
// seconds // seconds
// Lat / Lon support for maps // Lat / Lon support for maps
$config['geoloc']['latlng'] = true; // True to enable translation of location to latlng co-ordinates #$config['geoloc']['latlng'] = true; // True to enable translation of location to latlng co-ordinates
$config['geoloc']['engine'] = 'google'; #$config['geoloc']['engine'] = 'google';
$config['map']['engine'] = 'leaflet'; $config['map']['engine'] = 'leaflet';
$config['mapael']['default_map'] = 'maps/world_countries.js'; $config['mapael']['default_map'] = 'maps/world_countries.js';
$config['leaflet']['default_lat'] = '51.4800'; $config['leaflet']['default_lat'] = '51.4800';

View File

@@ -127,7 +127,12 @@ function GenGroupSQL($pattern, $search = '', $extra = 0)
list($tmp,$last) = explode('.', $glue); list($tmp,$last) = explode('.', $glue);
$qry .= $glue.' = '; $qry .= $glue.' = ';
} else { } else {
list($tmp,$new) = explode('.', $glue); $parts = explode('.', $glue);
if (count($parts) == 3) {
list($tmp, $new, $last) = $parts;
} else {
list($tmp,$new) = $parts;
}
$qry .= $tmp.'.'.$last.' && '.$tmp.'.'.$new.' = '; $qry .= $tmp.'.'.$last.' && '.$tmp.'.'.$new.' = ';
$last = $new; $last = $new;
} }

View File

@@ -2466,13 +2466,17 @@ function get_schema_list()
function get_db_schema() function get_db_schema()
{ {
try { try {
return \LibreNMS\DB\Eloquent::DB() $db = \LibreNMS\DB\Eloquent::DB();
->table('dbSchema') if ($db) {
->orderBy('version', 'DESC') return $db->table('dbSchema')
->value('version'); ->orderBy('version', 'DESC')
->value('version');
}
} catch (PDOException $e) { } catch (PDOException $e) {
return 0; // return default
} }
return 0;
} }
/** /**

View File

@@ -11,6 +11,8 @@
* See COPYING for more details. * See COPYING for more details.
*/ */
use App\Models\Location;
use LibreNMS\Config;
use LibreNMS\RRD\RrdDefinition; use LibreNMS\RRD\RrdDefinition;
$snmpdata = snmp_get_multi_oid($device, 'sysUpTime.0 sysLocation.0 sysContact.0 sysName.0 sysObjectID.0 sysDescr.0', '-OQnUt', 'SNMPv2-MIB'); $snmpdata = snmp_get_multi_oid($device, 'sysUpTime.0 sysLocation.0 sysContact.0 sysName.0 sysObjectID.0 sysDescr.0', '-OQnUt', 'SNMPv2-MIB');
@@ -79,14 +81,23 @@ foreach (array('sysContact', 'sysObjectID', 'sysName', 'sysDescr') as $elem) {
} }
} }
if ($poll_device['sysLocation'] && $device['location'] != $poll_device['sysLocation'] && $device['override_sysLocation'] == 0) { if ($device['override_sysLocation'] == 0 && $poll_device['sysLocation']) {
$update_array['location'] = $poll_device['sysLocation']; /** @var Location $location */
$device['location'] = $poll_device['sysLocation']; $location = Location::firstOrCreate(['location' => $poll_device['sysLocation']]);
log_event('Location -> ' . $poll_device['sysLocation'], $device, 'system', 3);
if ($device['location_id'] != $location->id) {
$device['location_id'] = $location->id;
$update_array['location_id'] = $location->id;
log_event('Location -> ' . $location->location, $device, 'system', 3);
}
} }
if ($config['geoloc']['latlng'] === true) { // make sure the location has coordinates
location_to_latlng($device); if (Config::get('geoloc.latlng', true) && ($location || $location = Location::find($device['location_id']))) {
if (!$location->hasCoordinates()) {
$location->lookupCoordinates();
$location->save();
}
} }
unset($snmpdata, $uptime_data, $uptime, $tags, $poll_device); unset($snmpdata, $uptime_data, $uptime, $tags, $poll_device);

View File

@@ -1,5 +1,6 @@
<?php <?php
use LibreNMS\Config;
use LibreNMS\RRD\RrdDefinition; use LibreNMS\RRD\RrdDefinition;
use LibreNMS\Exceptions\JsonAppException; use LibreNMS\Exceptions\JsonAppException;
use LibreNMS\Exceptions\JsonAppPollingFailedException; use LibreNMS\Exceptions\JsonAppPollingFailedException;
@@ -524,108 +525,6 @@ function get_main_serial($device)
} }
}//end get_main_serial() }//end get_main_serial()
function location_to_latlng($device)
{
global $config;
if (function_check('curl_version') !== true) {
d_echo("Curl support for PHP not enabled\n");
return false;
}
$bad_loc = false;
$device_location = $device['location'];
if (!empty($device_location)) {
$new_device_location = preg_replace("/ /", "+", $device_location);
$new_device_location = preg_replace('/[^A-Za-z0-9\-\+]/', '', $new_device_location); // Removes special chars.
// We have a location string for the device.
$loc = parse_location($device_location);
if (!is_array($loc)) {
$loc = dbFetchRow("SELECT `lat`,`lng` FROM `locations` WHERE `location`=? LIMIT 1", array($device_location));
}
if (is_array($loc) === false) {
// Grab data from which ever Geocode service we use.
switch ($config['geoloc']['engine']) {
case "google":
default:
d_echo("Google geocode engine being used\n");
$api_key = ($config['geoloc']['api_key']);
if (!empty($api_key)) {
$api_url = "https://maps.googleapis.com/maps/api/geocode/json?address=$new_device_location&key=$api_key";
} else {
d_echo("No geocode API key set\n");
}
break;
case "mapquest":
d_echo("Mapquest geocode engine being used\n");
$api_key = ($config['geoloc']['api_key']);
if (!empty($api_key)) {
$api_url = "http://open.mapquestapi.com/geocoding/v1/address?key=$api_key&location=$new_device_location&thumbMaps=false";
} else {
d_echo("No geocode API key set\n");
}
break;
case "bing":
d_echo("Bing geocode engine being used\n");
$api_key = ($config['geoloc']['api_key']);
if (!empty($api_key)) {
$api_url = "http://dev.virtualearth.net/REST/v1/Locations?addressLine=$new_device_location&key=$api_key";
} else {
d_echo("No geocode API key set\n");
}
break;
}
$curl_init = curl_init($api_url);
set_curl_proxy($curl_init);
curl_setopt($curl_init, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl_init, CURLOPT_TIMEOUT, 2);
curl_setopt($curl_init, CURLOPT_TIMEOUT_MS, 2000);
curl_setopt($curl_init, CURLOPT_CONNECTTIMEOUT, 5);
$data = json_decode(curl_exec($curl_init), true);
// Parse the data from the specific Geocode services.
d_echo(json_encode($data));
switch ($config['geoloc']['engine']) {
case "google":
default:
if ($data['status'] == 'OK') {
$loc = $data['results'][0]['geometry']['location'];
} else {
$bad_loc = true;
}
break;
case "mapquest":
if ($data['info']['statuscode'] == 0) {
$loc['lat'] = $data['results'][0]['locations'][0]['latLng']['lat'];
$loc['lng'] = $data['results'][0]['locations'][0]['latLng']['lng'];
} else {
$bad_loc = true;
}
break;
case "bing":
if ($data['statusDescription'] == 'OK') {
$loc['lat'] = $data['resourceSets'][0]["resources"][0]["point"]["coordinates"][0];
$loc['lng'] = $data['resourceSets'][0]["resources"][0]["point"]["coordinates"][1];
} else {
$bad_loc = true;
}
break;
}
if ($bad_loc === true) {
d_echo("Bad lat / lng received\n");
} else {
$loc['timestamp'] = array('NOW()');
$loc['location'] = $device_location;
if (dbInsert($loc, 'locations')) {
d_echo("Device lat/lng created\n");
} else {
d_echo("Device lat/lng could not be created\n");
}
}
} else {
d_echo("Using cached lat/lng from other device\n");
}
}
}// end location_to_latlng()
/** /**
* Update the application status and output in the database. * Update the application status and output in the database.
* *

View File

@@ -451,7 +451,7 @@ devices:
- { Field: version, Type: text, 'Null': true, Extra: '' } - { Field: version, Type: text, 'Null': true, Extra: '' }
- { Field: hardware, Type: text, 'Null': true, Extra: '' } - { Field: hardware, Type: text, 'Null': true, Extra: '' }
- { Field: features, Type: text, 'Null': true, Extra: '' } - { Field: features, Type: text, 'Null': true, Extra: '' }
- { Field: location, Type: text, 'Null': true, Extra: '' } - { Field: location_id, Type: int(11), 'Null': true, Extra: '' }
- { Field: os, Type: varchar(32), 'Null': true, Extra: '' } - { Field: os, Type: varchar(32), 'Null': true, Extra: '' }
- { Field: status, Type: tinyint(1), 'Null': false, Extra: '', Default: '0' } - { Field: status, Type: tinyint(1), 'Null': false, Extra: '', Default: '0' }
- { Field: status_reason, Type: varchar(50), 'Null': false, Extra: '' } - { Field: status_reason, Type: varchar(50), 'Null': false, Extra: '' }
@@ -772,12 +772,13 @@ loadbalancer_vservers:
locations: locations:
Columns: Columns:
- { Field: id, Type: int(11), 'Null': false, Extra: auto_increment } - { Field: id, Type: int(11), 'Null': false, Extra: auto_increment }
- { Field: location, Type: text, 'Null': false, Extra: '' } - { Field: location, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: lat, Type: 'double(10,6)', 'Null': false, Extra: '' } - { Field: lat, Type: 'double(10,6)', 'Null': true, Extra: '' }
- { Field: lng, Type: 'double(10,6)', 'Null': false, Extra: '' } - { Field: lng, Type: 'double(10,6)', 'Null': true, Extra: '' }
- { Field: timestamp, Type: datetime, 'Null': false, Extra: '' } - { Field: timestamp, Type: datetime, 'Null': false, Extra: '' }
Indexes: Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE } PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
locations_location_uindex: { Name: locations_location_uindex, Columns: [location], Unique: true, Type: BTREE }
mac_accounting: mac_accounting:
Columns: Columns:
- { Field: ma_id, Type: int(11), 'Null': false, Extra: auto_increment } - { Field: ma_id, Type: int(11), 'Null': false, Extra: auto_increment }

View File

@@ -13,7 +13,7 @@
use LibreNMS\Config; use LibreNMS\Config;
$init_modules = array('polling', 'alerts'); $init_modules = ['polling', 'alerts', 'laravel'];
require __DIR__ . '/includes/init.php'; require __DIR__ . '/includes/init.php';
$poller_start = microtime(true); $poller_start = microtime(true);

View File

@@ -118,8 +118,9 @@
@if($locations) @if($locations)
<li role="presentation" class="divider"></li> <li role="presentation" class="divider"></li>
<li class="dropdown-submenu"> <li class="dropdown-submenu">
<a href="#"><i class="fa fa-map-marker fa-fw fa-lg" aria-hidden="true"></i> Geo Locations</a> <a href="#"><i class="fa fa-map-marker fa-fw fa-lg" aria-hidden="true"></i> @lang('Geo Locations')</a>
<ul class="dropdown-menu scrollable-menu"> <ul class="dropdown-menu scrollable-menu">
<li><a href="{{ url('locations') }}"><i class="fa fa-map-marker fa-fw fa-lg" aria-hidden="true"></i> @lang('All Locations')</a></li>
@foreach($locations as $location) @foreach($locations as $location)
<li><a href="{{ url("devices/location=" . urlencode($location)) }}"><i class="fa fa-building fa-fw fa-lg" aria-hidden="true"></i> {{ $location }}</a></li> <li><a href="{{ url("devices/location=" . urlencode($location)) }}"><i class="fa fa-building fa-fw fa-lg" aria-hidden="true"></i> {{ $location }}</a></li>
@endforeach @endforeach

17
sql-schema/272.sql Normal file
View File

@@ -0,0 +1,17 @@
ALTER TABLE devices ADD location_id int NULL AFTER location;
ALTER TABLE locations MODIFY lat double(10,6);
ALTER TABLE locations MODIFY lng double(10,6);
ALTER TABLE locations MODIFY location varchar(255) NOT NULL;
INSERT INTO locations (location, timestamp) SELECT devices.location, NOW() FROM devices WHERE devices.location IS NOT NULL AND NOT EXISTS (SELECT location FROM locations WHERE location = devices.location);
DELETE t1 FROM locations t1 INNER JOIN locations t2 WHERE t1.id < t2.id AND t1.location = t2.location;
CREATE UNIQUE INDEX locations_location_uindex ON locations (location);
UPDATE devices INNER JOIN locations ON devices.location = locations.location SET devices.location_id = locations.id;
ALTER TABLE devices DROP location;
UPDATE alert_rules SET builder=REPLACE(builder, 'devices.location', 'locations.location');
UPDATE device_groups SET pattern=REPLACE(pattern, 'devices.location', 'locations.location');
INSERT INTO config (config_name,config_value,config_default,config_descr,config_group,config_group_order,config_sub_group,config_sub_group_order,config_hidden,config_disabled) values ('geoloc.engine','','','Geocoding Engine','external',0,'location',0,'0','0');
INSERT INTO config (config_name,config_value,config_default,config_descr,config_group,config_group_order,config_sub_group,config_sub_group_order,config_hidden,config_disabled) values ('geoloc.api_key','','','Geocoding API Key (Required to function)','external',0,'location',0,'0','0');

View File

@@ -7,4 +7,5 @@ use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class LaravelTestCase extends BaseTestCase abstract class LaravelTestCase extends BaseTestCase
{ {
use CreatesApplication; use CreatesApplication;
use SnmpsimHelpers;
} }

View File

@@ -58,7 +58,7 @@ class OSModulesTest extends DBTestCase
*/ */
public function testOS($os, $variant, $modules) public function testOS($os, $variant, $modules)
{ {
$this->requreSnmpsim(); // require snmpsim for tests $this->requireSnmpsim(); // require snmpsim for tests
global $snmpsim; global $snmpsim;
try { try {

View File

@@ -45,11 +45,20 @@ class SchemaTest extends TestCase
"devices" => [ "devices" => [
"Columns" => [ "Columns" => [
["Field" => "device_id", "Type" => "int(11) unsigned", "Null" => false, "Extra" => "auto_increment"], ["Field" => "device_id", "Type" => "int(11) unsigned", "Null" => false, "Extra" => "auto_increment"],
["Field" => "location_id", "Type" => "int(11)", "Null" => true, "Extra" => ""],
], ],
"Indexes" => [ "Indexes" => [
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["device_id"], "Unique" => true, "Type" => "BTREE"], "PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["device_id"], "Unique" => true, "Type" => "BTREE"],
] ]
], ],
"locations" => [
"Columns" => [
["Field" => "id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
],
"Indexes" => [
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["id"], "Unique" => true, "Type" => "BTREE"],
]
],
"ports" => [ "ports" => [
"Columns" => [ "Columns" => [
["Field" => "port_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"], ["Field" => "port_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
@@ -123,7 +132,8 @@ class SchemaTest extends TestCase
$expected = [ $expected = [
'bills' => [], 'bills' => [],
'bill_ports' => ['bills', 'ports'], 'bill_ports' => ['bills', 'ports'],
'devices' => [], 'devices' => ['locations'],
'locations' => [],
'ports' => ['devices'], 'ports' => ['devices'],
'sensors' => ['devices'], 'sensors' => ['devices'],
'sensors_to_state_indexes' => ['sensors', 'state_indexes'], 'sensors_to_state_indexes' => ['sensors', 'state_indexes'],
@@ -139,6 +149,7 @@ class SchemaTest extends TestCase
$schema = $this->getSchemaMock(); $schema = $this->getSchemaMock();
$this->assertEquals(['devices'], $schema->findRelationshipPath('devices')); $this->assertEquals(['devices'], $schema->findRelationshipPath('devices'));
$this->assertEquals(['locations', 'devices'], $schema->findRelationshipPath('locations'));
$this->assertEquals(['devices', 'ports'], $schema->findRelationshipPath('ports')); $this->assertEquals(['devices', 'ports'], $schema->findRelationshipPath('ports'));
$this->assertEquals(['devices', 'ports', 'bill_ports'], $schema->findRelationshipPath('bill_ports')); $this->assertEquals(['devices', 'ports', 'bill_ports'], $schema->findRelationshipPath('bill_ports'));
$this->assertEquals(['devices', 'ports', 'bill_ports', 'bills'], $schema->findRelationshipPath('bills')); $this->assertEquals(['devices', 'ports', 'bill_ports', 'bills'], $schema->findRelationshipPath('bills'));

36
tests/SnmpsimHelpers.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
/**
* ChecksSnmpsim.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 2018 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Tests;
trait SnmpsimHelpers
{
public function requireSnmpsim()
{
if (!getenv('SNMPSIM')) {
$this->markTestSkipped('Snmpsim required for this test. Set SNMPSIM=1 to enable.');
}
}
}

View File

@@ -29,6 +29,8 @@ use LibreNMS\Util\Snmpsim;
abstract class TestCase extends \PHPUnit_Framework_TestCase abstract class TestCase extends \PHPUnit_Framework_TestCase
{ {
use SnmpsimHelpers;
/** @var Snmpsim snmpsim instance */ /** @var Snmpsim snmpsim instance */
protected $snmpsim = null; protected $snmpsim = null;
@@ -57,11 +59,4 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
\LibreNMS\DB\Eloquent::DB()->rollBack(); \LibreNMS\DB\Eloquent::DB()->rollBack();
} }
} }
public function requreSnmpsim()
{
if (!getenv('SNMPSIM')) {
$this->markTestSkipped('Snmpsim required for this test. Set SNMPSIM=1 to enable.');
}
}
} }

View File

@@ -37,6 +37,8 @@ ports:
os: os:
devices: devices:
included_fields: [sysName, sysObjectID, sysDescr, sysContact, version, hardware, features, location, os, type, serial, icon] included_fields: [sysName, sysObjectID, sysDescr, sysContact, version, hardware, features, location, os, type, serial, icon]
joins:
- { left: devices.location_id, right: locations.id, select: [location] }
processors: processors:
processors: processors:
excluded_fields: [device_id, processor_id] excluded_fields: [device_id, processor_id]