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:
Tony Murray 2021-05-11 08:08:59 -05:00 committed by GitHub
parent 3d62be5003
commit 2d752925b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 14051 additions and 112 deletions

View File

@ -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)

View File

@ -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'));
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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",

View File

@ -88,6 +88,7 @@
"snmp3auth",
"ldap-groups",
"ad-groups",
"oxidized-maps",
"executable",
"directory"
]

13586
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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

View File

@ -31,6 +31,7 @@
disabled: Boolean,
required: Boolean,
pattern: String,
"update-status": String,
options: {}
}
}

View File

@ -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>

View 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 }} &lt; {{ 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">&times;</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>

View File

@ -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)',

View File

@ -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.',

View File

@ -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