mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 10:28:13 +00:00
Oxidized improvements (#12773)
* Oxidized API cleanup Import more settings to the UI use lnms config:set * validate os and type exist * map settings WIP * oops * editing working. Needed to add new property to pass update state to child * implement deleting and handle text overflow a little better. * Update app/Models/Device.php Co-authored-by: Jellyfrog <Jellyfrog@users.noreply.github.com> * Update app/Models/Device.php Co-authored-by: Jellyfrog <Jellyfrog@users.noreply.github.com> * revert change * fix style * add return Co-authored-by: Jellyfrog <Jellyfrog@users.noreply.github.com>
This commit is contained in:
parent
3d62be5003
commit
2d752925b0
@ -464,6 +464,17 @@ class Device extends BaseModel
|
||||
]);
|
||||
}
|
||||
|
||||
public function scopeWhereAttributeDisabled(Builder $query, string $attribute): Builder
|
||||
{
|
||||
return $query->leftJoin('devices_attribs', function (JoinClause $query) use ($attribute) {
|
||||
$query->on('devices.device_id', 'devices_attribs.device_id')
|
||||
->where('devices_attribs.attrib_type', $attribute);
|
||||
})->where(function (Builder $query) {
|
||||
$query->whereNull('devices_attribs.attrib_value')
|
||||
->orWhere('devices_attribs.attrib_value', '!=', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeWhereUptime($query, $uptime, $modifier = '<')
|
||||
{
|
||||
return $query->where([
|
||||
@ -472,17 +483,9 @@ class Device extends BaseModel
|
||||
]);
|
||||
}
|
||||
|
||||
public function scopeCanPing(Builder $query)
|
||||
public function scopeCanPing(Builder $query): Builder
|
||||
{
|
||||
return $query->where('disabled', 0)
|
||||
->leftJoin('devices_attribs', function (JoinClause $query) {
|
||||
$query->on('devices.device_id', 'devices_attribs.device_id')
|
||||
->where('devices_attribs.attrib_type', 'override_icmp_disable');
|
||||
})
|
||||
->where(function (Builder $query) {
|
||||
$query->whereNull('devices_attribs.attrib_value')
|
||||
->orWhere('devices_attribs.attrib_value', '!=', 'true');
|
||||
});
|
||||
return $this->scopeWhereAttributeDisabled($query->where('disabled', 0), 'override_icmp_disable');
|
||||
}
|
||||
|
||||
public function scopeHasAccess($query, User $user)
|
||||
|
@ -143,11 +143,24 @@ class AppServiceProvider extends ServiceProvider
|
||||
$ip = substr($value, 0, strpos($value, '/') ?: strlen($value)); // allow prefixes too
|
||||
|
||||
return IP::isValid($ip) || Validate::hostname($value);
|
||||
}, __('The :attribute must a valid IP address/network or hostname.'));
|
||||
});
|
||||
|
||||
Validator::extend('is_regex', function ($attribute, $value) {
|
||||
return @preg_match($value, '') !== false;
|
||||
}, __(':attribute is not a valid regular expression'));
|
||||
});
|
||||
|
||||
Validator::extend('keys_in', function ($attribute, $value, $parameters, $validator) {
|
||||
$extra_keys = is_array($value) ? array_diff(array_keys($value), $parameters) : [];
|
||||
|
||||
$validator->addReplacer('keys_in', function ($message, $attribute, $rule, $parameters) use ($extra_keys) {
|
||||
return str_replace(
|
||||
[':extra', ':values'],
|
||||
[implode(',', $extra_keys), implode(',', $parameters)],
|
||||
$message);
|
||||
});
|
||||
|
||||
return is_array($value) && empty($extra_keys);
|
||||
});
|
||||
|
||||
Validator::extend('zero_or_exists', function ($attribute, $value, $parameters, $validator) {
|
||||
if ($value === 0) {
|
||||
@ -157,6 +170,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
$validator = Validator::make([$attribute => $value], [$attribute => 'exists:' . implode(',', $parameters)]);
|
||||
|
||||
return $validator->passes();
|
||||
}, __('validation.exists'));
|
||||
}, trans('validation.exists'));
|
||||
}
|
||||
}
|
||||
|
@ -33,16 +33,16 @@ working Oxidized setup which is already taking config snapshots for
|
||||
your devices. When you have that, you only need the following config
|
||||
to enable the display of device configs within the device page itself:
|
||||
|
||||
```php
|
||||
$config['oxidized']['enabled'] = TRUE;
|
||||
$config['oxidized']['url'] = 'http://127.0.0.1:8888';
|
||||
```bash
|
||||
lnms config:set oxidized.enabled true
|
||||
lnms config:set oxidized.url http://127.0.0.1:8888
|
||||
```
|
||||
|
||||
LibreNMS supports config versioning if Oxidized does. This is known
|
||||
to work with the git output module.
|
||||
|
||||
```php
|
||||
$config['oxidized']['features']['versioning'] = true;
|
||||
```bash
|
||||
lnms config:set oxidized.features.versioning true
|
||||
```
|
||||
|
||||
Oxidized supports various ways to utilise credentials to login to
|
||||
@ -52,14 +52,14 @@ supports sending groups back to Oxidized so that you can then define
|
||||
group credentials within Oxidized. To enable this support please
|
||||
switch on 'Enable the return of groups to Oxidized':
|
||||
|
||||
```php
|
||||
$config['oxidized']['group_support'] = true;
|
||||
```bash
|
||||
lnms config:set oxidized.group_support true
|
||||
```
|
||||
|
||||
You can set a default group that devices will fall back to with:
|
||||
|
||||
```php
|
||||
$config['oxidized']['default_group'] = 'default';
|
||||
```bash
|
||||
lnms config:set oxidized.default_group default
|
||||
```
|
||||
|
||||
# SELinux
|
||||
@ -101,11 +101,10 @@ time.
|
||||
LibreNMS is able to reload the Oxidized list of nodes, each time a
|
||||
device is added to LibreNMS. To do so, edit the option in Global
|
||||
Settings>External Settings>Oxidized Integration or add the following
|
||||
to your config.php.
|
||||
|
||||
```php
|
||||
$config['oxidized']['reload_nodes'] = true;
|
||||
to your config.
|
||||
|
||||
```bash
|
||||
lnms config:set oxidized.reload_nodes true
|
||||
```
|
||||
|
||||
# Creating overrides
|
||||
@ -121,38 +120,56 @@ Matching of hosts can be done using `hostname`, `sysname`, `os`,
|
||||
key and value, or a 'regex' key and value. The order of matching is:
|
||||
|
||||
- `hostname`
|
||||
- `sysname`
|
||||
- `sysName`
|
||||
- `sysDescr`
|
||||
- `hardware`
|
||||
- `os`
|
||||
- `location`
|
||||
- `ip`
|
||||
|
||||
To match on the device hostnames or sysnames that contain 'lon-sw' or
|
||||
if the location contains 'London' then you would place the following
|
||||
within config.php:
|
||||
To match on the device hostnames or sysNames that contain 'lon-sw' or
|
||||
if the location contains 'London' then you would set the following:
|
||||
|
||||
```php
|
||||
$config['oxidized']['maps']['group']['hostname'][] = array('regex' => '/^lon-sw/', 'group' => 'london-switches');
|
||||
$config['oxidized']['maps']['group']['sysname'][] = array('regex' => '/^lon-sw/', 'group' => 'london-switches');
|
||||
$config['oxidized']['maps']['group']['location'][] = array('regex' => '/london/', 'group' => 'london-switches');
|
||||
```bash
|
||||
lnms config:set oxidized.maps.group.hostname.+ '{"regex": "/^lon-sw/", "value": "london-switches"}'
|
||||
lnms config:set oxidized.maps.group.sysName.+ '{"regex": "/^lon-sw/", "value": "london-switches"}'
|
||||
lnms config:set oxidized.maps.group.location.+ '{"regex": "/london/", "value": "london-switches"}'
|
||||
```
|
||||
|
||||
To match on a device os of edgeos then please use the following:
|
||||
|
||||
```php
|
||||
$config['oxidized']['maps']['group']['os'][] = array('match' => 'edgeos', 'group' => 'wireless');
|
||||
```bash
|
||||
lnms config:set oxidized.maps.group.os.+ '{"match": "edgeos", "value": "wireless"}'
|
||||
```
|
||||
|
||||
Matching on OS requires system name of the OS. For example, 'match' =>
|
||||
'RouterOS' will not work, while 'match' => 'routeros' will.
|
||||
Matching on OS requires system name of the OS. For example, "match": "RouterOS"
|
||||
will not work, while "match": "routeros" will.
|
||||
|
||||
To override the IP Oxidized uses to poll the device, you can add the
|
||||
following within config.php:
|
||||
To edit an existing map, you must use the index to override it.
|
||||
|
||||
```php
|
||||
$config['oxidized']['maps']['ip']['sysname'][] = array('regex' => '/^my.node/', 'ip' => '192.168.1.10');
|
||||
$config['oxidized']['maps']['ip']['sysname'][] = array('match' => 'my-other.node', 'ip' => '192.168.1.20');
|
||||
```bash
|
||||
lnms config:get oxidized.maps.os.os
|
||||
array (
|
||||
0 =>
|
||||
array (
|
||||
'match' => 'airos-af-ltu',
|
||||
'value' => 'airfiber',
|
||||
),
|
||||
1 =>
|
||||
array (
|
||||
'match' => 'airos-af',
|
||||
'value' => 'airfiber',
|
||||
),
|
||||
)
|
||||
|
||||
lnms config:set oxidized.maps.os.os.1 '{"match": "airos-af", "value": "something-else"}'
|
||||
```
|
||||
|
||||
To override the IP Oxidized uses to poll the device, set the following:
|
||||
|
||||
```bash
|
||||
lnms config:set oxidized.maps.ip.sysName.+ '{"regex": "/^my.node/", "value": "192.168.1.10"}'
|
||||
lnms config:set oxidized.maps.ip.sysName.+ '{"match": "my-other.node", "value": "192.168.1.20"}'
|
||||
```
|
||||
|
||||
This allows extending the configuration further by providing a
|
||||
@ -160,16 +177,16 @@ completely flexible model for custom flags and settings, for example,
|
||||
below shows the ability to add an ssh_proxy host within Oxidized
|
||||
simply by adding the below to your configuration:
|
||||
|
||||
```php
|
||||
$config['oxidized']['maps']['ssh_proxy']['sysname'][] = array('regex' => '/^my.node/', 'ssh_proxy' => 'my-ssh-gateway.node');
|
||||
```bash
|
||||
lnms config:set oxidized.maps.ssh_proxy.sysName.+ '{"regex": "/^my.node/", "value": "my-ssh-gateway.node"}'
|
||||
```
|
||||
|
||||
Or of course, any custom value that could be needed or wanted can be
|
||||
applied, for example, setting a "myAttribute" to "Super cool value"
|
||||
for any configured and enabled "routeros" device.
|
||||
|
||||
```php
|
||||
$config['oxidized']['maps']['myAttribute']['os'][] = array('match' => 'routeros', 'myAttribute' => 'Super cool value');
|
||||
```bash
|
||||
lnms config:set oxidized.maps.myAttribute.os.+ '{"match": "routeros", "value": "Super cool value"}'
|
||||
```
|
||||
|
||||
Verify the return of groups by querying the API:
|
||||
@ -195,11 +212,11 @@ you can edit those devices in Device -> Edit -> Misc and enable
|
||||
"Exclude from Oxidized?"
|
||||
|
||||
It's also possible to exclude certain device types and OS' from being
|
||||
output via the API. This is currently only possible via config.php:
|
||||
output via the API.
|
||||
|
||||
```php
|
||||
$config['oxidized']['ignore_types'] = array('server','power');
|
||||
$config['oxidized']['ignore_os'] = array('linux','windows');
|
||||
```bash
|
||||
lnms config:set oxidized.ignore_types '["server", "power"]'
|
||||
lnms config:set oxidized.ignore_os '["linux", "windows"]'
|
||||
```
|
||||
|
||||
# Trigger configuration backups
|
||||
|
@ -1,6 +1,3 @@
|
||||
body {
|
||||
background: #333;
|
||||
}
|
||||
.fa-nav-icons {
|
||||
color: #bfc0c0;
|
||||
}
|
||||
@ -186,7 +183,7 @@
|
||||
}
|
||||
small {
|
||||
font-size: 80%;
|
||||
color: #FFF;
|
||||
color: #FFF;
|
||||
}
|
||||
sub,
|
||||
sup {
|
||||
@ -7212,3 +7209,7 @@
|
||||
.vis-legend-text {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.expandable:hover span {
|
||||
background-color: #2e3338;
|
||||
}
|
||||
|
@ -2388,3 +2388,18 @@ label {
|
||||
.logon-message {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.expandable {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.expandable:hover {
|
||||
overflow: visible;
|
||||
}
|
||||
.expandable:hover span {
|
||||
position: relative;
|
||||
background-color: white;
|
||||
padding: 2px 3px 2px 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,13 +1,21 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=50f7cb634569cc8bce2c",
|
||||
"/js/app.js": "/js/app.js?id=7326d976853ee518f0dc",
|
||||
"/js/manifest.js": "/js/manifest.js?id=411da0f32dfa6d682e04",
|
||||
"/css/app.css": "/css/app.css?id=996b9e3da0c3ab98067e",
|
||||
"/js/vendor.js": "/js/vendor.js?id=54e44dd06cb8f6a3e6fe",
|
||||
"/js/vendor.js": "/js/vendor.js?id=00bcb49d05c1545efab0",
|
||||
"/js/lang/ar.js": "/js/lang/ar.js?id=867496e0e07ff6097886",
|
||||
"/js/lang/de.js": "/js/lang/de.js?id=db973f6aaff0cda764c6",
|
||||
"/js/lang/en.js": "/js/lang/en.js?id=fd41f6b9991c32a36645",
|
||||
"/js/lang/fr.js": "/js/lang/fr.js?id=096c9e010fc548e53b0a",
|
||||
"/js/lang/it.js": "/js/lang/it.js?id=b28a63928155eeb4e2a1",
|
||||
"/js/lang/nl.js": "/js/lang/nl.js?id=052c04cc5d1f408fcf55",
|
||||
"/js/lang/pl.js": "/js/lang/pl.js?id=0cca1dde319e96cac73d",
|
||||
"/js/lang/pt-br.js": "/js/lang/pt-br.js?id=cf9e6e0244e1a17fddf1",
|
||||
"/js/lang/pt.js": "/js/lang/pt.js?id=38b658d49f91c95b2566",
|
||||
"/js/lang/ro.js": "/js/lang/ro.js?id=55032462a25b893fa186",
|
||||
"/js/lang/ru.js": "/js/lang/ru.js?id=f6b7c078755312a0907c",
|
||||
"/js/lang/th.js": "/js/lang/th.js?id=3d5d6b9874b3ea673b3f",
|
||||
"/js/lang/tr.js": "/js/lang/tr.js?id=d964e6a3e0bc51bbd7a1",
|
||||
"/js/lang/uk.js": "/js/lang/uk.js?id=c19a5dcee4724579cb41",
|
||||
"/js/lang/zh-CN.js": "/js/lang/zh-CN.js?id=12f95651fb6629cbf3f3",
|
||||
"/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=87ab9d2f187593100bc3"
|
||||
|
@ -1343,26 +1343,31 @@ function get_oxidized_config(Illuminate\Http\Request $request)
|
||||
|
||||
function list_oxidized(Illuminate\Http\Request $request)
|
||||
{
|
||||
$hostname = $request->route('hostname');
|
||||
$devices = [];
|
||||
$device_types = "'" . implode("','", Config::get('oxidized.ignore_types', [])) . "'";
|
||||
$device_os = "'" . implode("','", Config::get('oxidized.ignore_os', [])) . "'";
|
||||
$return = [];
|
||||
$devices = Device::query()
|
||||
->where('disabled', 0)
|
||||
->when($request->route('hostname'), function ($query, $hostname) {
|
||||
return $query->where('hostname', $hostname);
|
||||
})
|
||||
->whereNotIn('type', Config::get('oxidized.ignore_types', []))
|
||||
->whereNotIn('os', Config::get('oxidized.ignore_os', []))
|
||||
->whereAttributeDisabled('override_Oxidized_disable')
|
||||
->select(['hostname', 'sysName', 'sysDescr', 'hardware', 'os', 'ip', 'location_id'])
|
||||
->get();
|
||||
|
||||
$sql = '';
|
||||
$params = [];
|
||||
if ($hostname) {
|
||||
$sql = ' AND hostname = ?';
|
||||
$params = [$hostname];
|
||||
}
|
||||
|
||||
foreach (dbFetchRows("SELECT hostname,sysname,sysDescr,hardware,os,locations.location,ip AS ip FROM `devices` LEFT JOIN locations ON devices.location_id = locations.id 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) {
|
||||
// Convert from packed value to human value
|
||||
$device['ip'] = inet6_ntop($device['ip']);
|
||||
/** @var Device $device */
|
||||
foreach ($devices as $device) {
|
||||
$output = [
|
||||
'hostname' => $device->hostname,
|
||||
'os' => $device->os,
|
||||
'ip' => $device->ip,
|
||||
];
|
||||
|
||||
// Pre-populate the group with the default
|
||||
if (Config::get('oxidized.group_support') === true && ! empty(Config::get('oxidized.default_group'))) {
|
||||
$device['group'] = Config::get('oxidized.default_group');
|
||||
$output['group'] = Config::get('oxidized.default_group');
|
||||
}
|
||||
|
||||
foreach (Config::get('oxidized.maps') as $maps_column => $maps) {
|
||||
// Based on Oxidized group support we can apply groups by setting group_support to true
|
||||
if ($maps_column == 'group' && Config::get('oxidized.group_support', true) !== true) {
|
||||
@ -1370,39 +1375,30 @@ function list_oxidized(Illuminate\Http\Request $request)
|
||||
}
|
||||
|
||||
foreach ($maps as $field_type => $fields) {
|
||||
if ($field_type == 'sysname') {
|
||||
$value = $device->sysName; // fix typo in previous code forcing users to use sysname instead of sysName
|
||||
} elseif ($field_type == 'location') {
|
||||
$value = $device->location->location;
|
||||
} else {
|
||||
$value = $device->$field_type;
|
||||
}
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if (isset($field['regex']) && preg_match($field['regex'] . 'i', $device[$field_type])) {
|
||||
$device[$maps_column] = $field[$maps_column];
|
||||
if (isset($field['regex']) && preg_match($field['regex'] . 'i', $value)) {
|
||||
$output[$maps_column] = $field['value'] ?? $field[$maps_column]; // compatibility with old format
|
||||
break;
|
||||
} elseif (isset($field['match']) && $field['match'] == $device[$field_type]) {
|
||||
$device[$maps_column] = $field[$maps_column];
|
||||
} elseif (isset($field['match']) && $field['match'] == $value) {
|
||||
$output[$maps_column] = $field['value'] ?? $field[$maps_column]; // compatibility with old format
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We remap certain device OS' that have different names with Oxidized models
|
||||
$models = [
|
||||
'airos-af-ltu' => 'airfiber',
|
||||
'airos-af' => 'airfiber',
|
||||
'arista_eos' => 'eos',
|
||||
'vyos' => 'vyatta',
|
||||
'slms' => 'zhoneolt',
|
||||
'fireware' => 'firewareos',
|
||||
'fortigate' => 'fortios',
|
||||
];
|
||||
|
||||
$device['os'] = str_replace(array_keys($models), array_values($models), $device['os']);
|
||||
|
||||
unset($device['location']);
|
||||
unset($device['sysname']);
|
||||
unset($device['sysDescr']);
|
||||
unset($device['hardware']);
|
||||
$devices[] = $device;
|
||||
$return[] = $output;
|
||||
}
|
||||
|
||||
return response()->json($devices, 200, [], JSON_PRETTY_PRINT);
|
||||
return response()->json($return, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
function list_bills(Illuminate\Http\Request $request)
|
||||
|
@ -4092,6 +4092,56 @@
|
||||
"order": 3,
|
||||
"type": "boolean"
|
||||
},
|
||||
"oxidized.ignore_os": {
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"group": "external",
|
||||
"section": "oxidized",
|
||||
"order": 7,
|
||||
"validate": {
|
||||
"value.*": "exists:devices,os"
|
||||
}
|
||||
},
|
||||
"oxidized.ignore_types": {
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"group": "external",
|
||||
"section": "oxidized",
|
||||
"order": 8,
|
||||
"validate": {
|
||||
"value.*": "exists:devices,type"
|
||||
}
|
||||
},
|
||||
"oxidized.maps": {
|
||||
"default": {
|
||||
"os": {
|
||||
"os": [
|
||||
{"match": "airos-af-ltu", "value": "airfiber"},
|
||||
{"match": "airos-af", "value": "airfiber"},
|
||||
{"match": "arista_eos", "value": "eos"},
|
||||
{"match": "ciscosb", "value": "ciscosmb"},
|
||||
{"match": "f5", "value": "tmos"},
|
||||
{"match": "fireware", "value": "firewareos"},
|
||||
{"match": "fortigate", "value": "fortios"},
|
||||
{"match": "vyos", "value": "vyatta"},
|
||||
{"match": "slms", "value": "zhoneolt"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "oxidized-maps",
|
||||
"validate": {
|
||||
"value": "array",
|
||||
"value.*": "array|keys_in:hostname,sysName,sysDescr,hardware,os,location,ip",
|
||||
"value.*.*": "array|min:1",
|
||||
"value.*.*.*": "array|size:2|keys_in:match,regex,value",
|
||||
"value.*.*.*.value": "required|string",
|
||||
"value.*.*.*.regex": "is_regex",
|
||||
"value.*.*.*.match": "string"
|
||||
},
|
||||
"group": "external",
|
||||
"section": "oxidized",
|
||||
"order": 6
|
||||
},
|
||||
"oxidized.reload_nodes": {
|
||||
"default": false,
|
||||
"group": "external",
|
||||
|
@ -88,6 +88,7 @@
|
||||
"snmp3auth",
|
||||
"ldap-groups",
|
||||
"ad-groups",
|
||||
"oxidized-maps",
|
||||
"executable",
|
||||
"directory"
|
||||
]
|
||||
|
13586
package-lock.json
generated
13586
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -32,6 +32,7 @@
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"v-tooltip": "^2.0.3",
|
||||
"vue-i18n": "^8.22.2",
|
||||
"vue-js-modal": "*",
|
||||
"vue-js-toggle-button": "^1.3.3",
|
||||
"vue-multiselect": "^2.1.6",
|
||||
"vue-nav-tabs": "^0.5.7",
|
||||
|
@ -36,6 +36,9 @@ Vue.component('multiselect', Multiselect)
|
||||
import VueTabs from 'vue-nav-tabs'
|
||||
Vue.use(VueTabs)
|
||||
|
||||
import VModal from 'vue-js-modal'
|
||||
Vue.use(VModal)
|
||||
|
||||
// Vue.mixin({
|
||||
// methods: {
|
||||
// route: route
|
||||
|
@ -31,6 +31,7 @@
|
||||
disabled: Boolean,
|
||||
required: Boolean,
|
||||
pattern: String,
|
||||
"update-status": String,
|
||||
options: {}
|
||||
}
|
||||
}
|
||||
|
@ -36,14 +36,15 @@
|
||||
:disabled="setting.overridden"
|
||||
:required="setting.required"
|
||||
:options="setting.options"
|
||||
:update-status="updateStatus"
|
||||
@input="changeValue($event)"
|
||||
@change="changeValue($event)"
|
||||
></component>
|
||||
<span class="form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button :style="{'opacity': showResetToDefault()?1:0}" @click="resetToDefault" class="btn btn-default" type="button" v-tooltip="{ content: $t('Reset to default') }"><i class="fa fa-refresh"></i></button>
|
||||
<button :style="{'opacity': showUndo()?1:0}" @click="resetToInitial" class="btn btn-primary" type="button" v-tooltip="{ content: $t('Undo') }"><i class="fa fa-undo"></i></button>
|
||||
<button :style="{'opacity': showResetToDefault()?1:0}" @click="resetToDefault" class="btn btn-default" :class="{'disable-events': ! showResetToDefault()}" type="button" v-tooltip="{ content: $t('Reset to default') }"><i class="fa fa-refresh"></i></button>
|
||||
<button :style="{'opacity': showUndo()?1:0}" @click="resetToInitial" class="btn btn-primary" :class="{'disable-events': ! showUndo()}" type="button" v-tooltip="{ content: $t('Undo') }"><i class="fa fa-undo"></i></button>
|
||||
<div v-if="hasHelp()" v-tooltip="{content: getHelp(), trigger: 'hover click'}" class="fa fa-fw fa-lg fa-question-circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,20 +61,24 @@
|
||||
data() {
|
||||
return {
|
||||
value: this.setting.value,
|
||||
updateStatus: 'none',
|
||||
feedback: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
persistValue(value) {
|
||||
this.updateStatus = 'pending';
|
||||
axios.put(route(this.prefix + '.update', this.getRouteParams()), {value: value})
|
||||
.then((response) => {
|
||||
this.value = response.data.value;
|
||||
this.$emit('setting-updated', {name: this.setting.name, value: this.value});
|
||||
this.updateStatus = 'success';
|
||||
this.feedback = 'has-success';
|
||||
setTimeout(() => this.feedback = '', 3000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.feedback = 'has-error';
|
||||
this.updateStatus = 'error';
|
||||
toastr.error(error.response.data.message);
|
||||
|
||||
// don't reset certain types back to actual value on error
|
||||
@ -163,5 +168,7 @@
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.disable-events {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
242
resources/js/components/SettingOxidizedMaps.vue
Normal file
242
resources/js/components/SettingOxidizedMaps.vue
Normal file
@ -0,0 +1,242 @@
|
||||
<!--
|
||||
- SettingOxidizedMaps.vue
|
||||
-
|
||||
- Description-
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU General Public License as published by
|
||||
- the Free Software Foundation, either version 3 of the License, or
|
||||
- (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
- GNU General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
- @package LibreNMS
|
||||
- @link http://librenms.org
|
||||
- @copyright 2021 Tony Murray
|
||||
- @author Tony Murray <murraytony@gmail.com>
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="new-btn-div" v-show="! disabled">
|
||||
<button type="button" class="btn btn-primary" @click="showModal(null)"><i class="fa fa-plus"></i> {{ $t('New Map Rule') }}</button>
|
||||
</div>
|
||||
<div class="panel panel-default" v-for="(map, index) in maps">
|
||||
<div class="panel-body">
|
||||
<div class="col-md-5 expandable"><span>{{ map.source }} {{ map.matchType === 'regex' ? '~' : '=' }} {{ map.matchValue }}</span></div>
|
||||
<div class="col-md-4 expandable"><span>{{ map.target }} < {{ map.replacement }}</span></div>
|
||||
<div class="col-md-3 buttons">
|
||||
<div class="btn-group" v-tooltip="disabled ? $t('settings.readonly') : false">
|
||||
<button type="button" class="btn btn-sm btn-info" v-tooltip="$t('Edit')" :disabled="disabled" @click="showModal(index)"><i class="fa fa-lg fa-edit"></i></button>
|
||||
<button type="button" class="btn btn-sm btn-danger" v-tooltip="$t('Delete')" :disabled="disabled" @click="deleteItem(index)"><i class="fa fa-lg fa-remove"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<modal name="maps" height="auto">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" @click="$modal.hide('maps')">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">{{ mapModalIndex ? $t('Edit Map Rule') : $t('New Map Rule') }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="source" class="col-sm-4 control-label">Source</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-control" id="source" v-model="mapModalSource">
|
||||
<option value="hostname">hostname</option>
|
||||
<option value="os">os</option>
|
||||
<option value="type">type</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="match_value" class="col-sm-4">
|
||||
<select class="form-control" id="match_type" v-model="mapModalMatchType">
|
||||
<option value="match">Match (=)</option>
|
||||
<option value="regex">Regex (~)</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" class="form-control" id="match_value" placeholder="" v-model="mapModalMatchValue">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-horizontal" role="form">
|
||||
<div class="form-group">
|
||||
<label for="target" class="col-sm-4 control-label">Target</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-control" id="target" v-model="mapModalTarget">
|
||||
<option value="os">os</option>
|
||||
<option value="group">group</option>
|
||||
<option value="ip">ip</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="value" class="col-sm-4 control-label">Replacement</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" class="form-control" id="value" placeholder="" v-model="mapModalReplacement">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-8 col-sm-offset-4">
|
||||
<button type="button" class="btn btn-primary" @click="submitModal">{{ $t('Submit') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseSetting from "./BaseSetting";
|
||||
|
||||
export default {
|
||||
name: "SettingOxidizedMaps",
|
||||
mixins: [BaseSetting],
|
||||
data() {
|
||||
return {
|
||||
mapModalIndex: null,
|
||||
mapModalSource: null,
|
||||
mapModalMatchType: null,
|
||||
mapModalMatchValue: null,
|
||||
mapModalTarget: null,
|
||||
mapModalReplacement: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showModal(index) {
|
||||
this.fillForm(index);
|
||||
this.$modal.show('maps')
|
||||
},
|
||||
submitModal() {
|
||||
let newMaps = this.maps;
|
||||
let newMap = {
|
||||
target: this.mapModalTarget,
|
||||
source: this.mapModalSource,
|
||||
matchType: this.mapModalMatchType,
|
||||
matchValue: this.mapModalMatchValue,
|
||||
replacement: this.mapModalReplacement
|
||||
};
|
||||
|
||||
if (this.mapModalIndex) {
|
||||
newMaps[this.mapModalIndex] = newMap;
|
||||
} else {
|
||||
newMaps.push(newMap)
|
||||
}
|
||||
console.log(newMaps, newMap);
|
||||
|
||||
this.updateValue(newMaps);
|
||||
},
|
||||
fillForm(index) {
|
||||
let exists = this.maps.hasOwnProperty(index);
|
||||
this.mapModalIndex = index;
|
||||
this.mapModalSource = exists ? this.maps[index].source : null;
|
||||
this.mapModalMatchType = exists ? this.maps[index].matchType : null;
|
||||
this.mapModalMatchValue = exists ? this.maps[index].matchValue : null;
|
||||
this.mapModalTarget = exists ? this.maps[index].target : null;
|
||||
this.mapModalReplacement = exists ? this.maps[index].replacement : null;
|
||||
},
|
||||
deleteItem(index) {
|
||||
let newMap = this.maps;
|
||||
newMap.splice(index, 1);
|
||||
this.updateValue(newMap);
|
||||
},
|
||||
updateValue(newMaps) {
|
||||
let newValue = {};
|
||||
newMaps.forEach((map) => {
|
||||
if (newValue[map.target] === undefined) {
|
||||
newValue[map.target] = {};
|
||||
}
|
||||
if (newValue[map.target][map.source] === undefined) {
|
||||
newValue[map.target][map.source] = []
|
||||
}
|
||||
let newMap = {};
|
||||
newMap[map.matchType] = map.matchValue;
|
||||
newMap.value = map.replacement;
|
||||
newValue[map.target][map.source].push(newMap);
|
||||
});
|
||||
this.$emit('input', newValue);
|
||||
},
|
||||
formatSource(source, matches) {
|
||||
if (matches.hasOwnProperty('regex')) {
|
||||
return source + ' ~ ' + matches.regex;
|
||||
}
|
||||
|
||||
if (matches.hasOwnProperty('match')) {
|
||||
return source + ' = ' + matches.match;
|
||||
}
|
||||
|
||||
return 'invalid';
|
||||
},
|
||||
formatTarget(target, matches) {
|
||||
let value = matches.hasOwnProperty('value') ? matches.value : matches[target];
|
||||
return target + ' > ' + value;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
updateStatus() {
|
||||
if (this.updateStatus === 'success') {
|
||||
this.$modal.hide('maps')
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
maps() {
|
||||
let flatMaps = [];
|
||||
Object.keys(this.value).forEach((target) => {
|
||||
Object.keys(this.value[target]).forEach((source) => {
|
||||
this.value[target][source].forEach((match) => {
|
||||
let type = match.hasOwnProperty('regex') ? 'regex' : 'match';
|
||||
flatMaps.push({
|
||||
target: target,
|
||||
source: source,
|
||||
matchType: type,
|
||||
matchValue: match[type],
|
||||
replacement: match.hasOwnProperty('value') ? match.value : match[target],
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
return flatMaps;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.expandable {
|
||||
padding: 5px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
white-space: nowrap;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.new-btn-div {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
@ -949,9 +949,21 @@ return [
|
||||
'group_support' => [
|
||||
'description' => 'Enable the return of groups to Oxidized',
|
||||
],
|
||||
'ignore_os' => [
|
||||
'description' => 'Do not backup these OS',
|
||||
'help' => 'Do not backup the listed OS with Oxidized. The OS must match the LibreNMS OS name (these are all lowercase with no spaces). Only allows existing OS.',
|
||||
],
|
||||
'ignore_types' => [
|
||||
'description' => 'Do not backup these device types',
|
||||
'help' => 'Do not backup the listed device types with Oxidized. Only allows existing types.',
|
||||
],
|
||||
'reload_nodes' => [
|
||||
'description' => 'Reload Oxidized nodes list, each time a device is added',
|
||||
],
|
||||
'maps' => [
|
||||
'description' => 'Variable Mapping',
|
||||
'help' => 'Used to set group or other variables or map OS names that differ.',
|
||||
],
|
||||
'url' => [
|
||||
'description' => 'URL to your Oxidized API',
|
||||
'help' => 'Oxidized API url (For example: http://127.0.0.1:8888)',
|
||||
|
@ -62,9 +62,12 @@ return [
|
||||
'in_array' => 'The :attribute field does not exist in :other.',
|
||||
'integer' => 'The :attribute must be an integer.',
|
||||
'ip' => 'The :attribute must be a valid IP address.',
|
||||
'ip_or_hostname' => 'The :attribute must a valid IP address/network or hostname.',
|
||||
'ipv4' => 'The :attribute must be a valid IPv4 address.',
|
||||
'ipv6' => 'The :attribute must be a valid IPv6 address.',
|
||||
'is_regex' => 'The :attribute is not a valid regular expression',
|
||||
'json' => 'The :attribute must be a valid JSON string.',
|
||||
'keys_in' => 'The :attribute contains invalid keys: :extra. Valid keys: :values',
|
||||
'lt' => [
|
||||
'numeric' => 'The :attribute must be less than :value.',
|
||||
'file' => 'The :attribute must be less than :value kilobytes.',
|
||||
|
@ -40,8 +40,8 @@
|
||||
<link href="{{ asset('css/select2.min.css') }}" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset('css/select2-bootstrap.min.css') }}" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset('css/query-builder.default.min.css') }}" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=20191124" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset('css/' . LibreNMS\Config::get('applied_site_style', 'light') . '.css?ver=632417642') }}" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset(LibreNMS\Config::get('stylesheet', 'css/styles.css')) }}?ver=20210421" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset('css/' . LibreNMS\Config::get('applied_site_style', 'light') . '.css?ver=632417643') }}" rel="stylesheet" type="text/css" />
|
||||
@foreach(LibreNMS\Config::get('webui.custom_css', []) as $custom_css)
|
||||
<link href="{{ $custom_css }}" rel="stylesheet" type="text/css" />
|
||||
@endforeach
|
||||
|
Loading…
Reference in New Issue
Block a user