mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Dynamic Select setting (#13179)
* Dynamic Select setting embeds select2 and uses ajax to call to backend for options poller-group included * fix validation a bit * fix typehint * move minProperties into the select schema * Change dashboard-select to select-dynamic Love deleting code * Change dashboard-select to select-dynamic Love deleting code wire up a few select2 options * fix whitespace * Not a model, just an object * Suggestion from @SourceDoctor autocomplete values of select and select-dynamic Got a little creative with InternalHttpRequest...
This commit is contained in:
@@ -60,7 +60,7 @@ class BashCompletionCommand extends Command
|
||||
if (method_exists($command, 'completeArgument')) {
|
||||
foreach ($input->getArguments() as $name => $value) {
|
||||
if ($current == $value) {
|
||||
$values = $command->completeArgument($name, $value);
|
||||
$values = $command->completeArgument($name, $value, $previous);
|
||||
if (! empty($values)) {
|
||||
echo implode(PHP_EOL, $values);
|
||||
|
||||
|
45
app/Console/Commands/InternalHttpRequest.php
Normal file
45
app/Console/Commands/InternalHttpRequest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/*
|
||||
* InternalHttpRequest.php
|
||||
*
|
||||
* Access to the internal http request code used in tests.
|
||||
*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication;
|
||||
use Illuminate\Foundation\Testing\Concerns\MakesHttpRequests;
|
||||
|
||||
class InternalHttpRequest
|
||||
{
|
||||
use MakesHttpRequests;
|
||||
use InteractsWithAuthentication;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Foundation\Application|mixed
|
||||
*/
|
||||
private $app;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->app = app();
|
||||
}
|
||||
}
|
@@ -24,21 +24,70 @@
|
||||
|
||||
namespace App\Console\Commands\Traits;
|
||||
|
||||
use App\Console\Commands\InternalHttpRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
use LibreNMS\Util\DynamicConfig;
|
||||
use LibreNMS\Util\DynamicConfigItem;
|
||||
|
||||
trait CompletesConfigArgument
|
||||
{
|
||||
public function completeArgument($name, $value)
|
||||
public function completeArgument($name, $value, $previous)
|
||||
{
|
||||
if ($name == 'setting') {
|
||||
$config = new DynamicConfig();
|
||||
|
||||
return $config->all()->keys()->filter(function ($setting) use ($value) {
|
||||
return (new DynamicConfig())->all()->keys()->filter(function ($setting) use ($value) {
|
||||
return Str::startsWith($setting, $value);
|
||||
})->toArray();
|
||||
} elseif ($name == 'value') {
|
||||
$config = (new DynamicConfig())->get($previous);
|
||||
|
||||
switch ($config->getType()) {
|
||||
case 'select-dynamic':
|
||||
return $this->suggestionsForSelectDynamic($config, $value);
|
||||
case 'select':
|
||||
return $this->suggestionsForSelect($config, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function suggestionsForSelect(DynamicConfigItem $config, ?string $value): array
|
||||
{
|
||||
$options = collect($config['options']);
|
||||
$keyStartsWith = $options->filter(function ($description, $key) use ($value) {
|
||||
return Str::startsWith($key, $value);
|
||||
});
|
||||
|
||||
// try to see if it matches a value (aka key)
|
||||
if ($keyStartsWith->isNotEmpty()) {
|
||||
return $keyStartsWith->keys()->all();
|
||||
}
|
||||
|
||||
// last chance to try to find by the description
|
||||
return $options->filter(function ($description, $key) use ($value) {
|
||||
return Str::contains($description, $value);
|
||||
})->keys()->all();
|
||||
}
|
||||
|
||||
protected function suggestionsForSelectDynamic(DynamicConfigItem $config, ?string $value): array
|
||||
{
|
||||
// need auth to make http request
|
||||
if ($admin = User::adminOnly()->first()) {
|
||||
$target = $config['options']['target'];
|
||||
$data = ['limit' => 10];
|
||||
if ($value) {
|
||||
$data['term'] = $value; // filter in sql
|
||||
}
|
||||
|
||||
// make "http" request
|
||||
$results = (new InternalHttpRequest())
|
||||
->actingAs($admin)
|
||||
->json('GET', route("ajax.select.$target"), $data)->json('results');
|
||||
|
||||
return array_column($results, 'id');
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@@ -30,36 +30,53 @@ class DashboardController extends SelectController
|
||||
{
|
||||
protected function searchFields($request)
|
||||
{
|
||||
return ['dashboard_name'];
|
||||
return ['dashboard_name', 'username'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the base query for this resource
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
|
||||
*/
|
||||
protected function baseQuery($request)
|
||||
{
|
||||
return Dashboard::query()
|
||||
->where('access', '>', 0)
|
||||
->with('user')
|
||||
->orderBy('user_id')
|
||||
->orderBy('dashboard_name');
|
||||
->leftJoin('users', 'dashboards.user_id', 'users.user_id') // left join so we can search username
|
||||
->orderBy('dashboards.user_id')
|
||||
->orderBy('dashboard_name')
|
||||
->select(['dashboard_id', 'username', 'dashboard_name']);
|
||||
}
|
||||
|
||||
public function formatItem($dashboard)
|
||||
/**
|
||||
* @param object $dashboard
|
||||
* @return array
|
||||
*/
|
||||
public function formatItem($dashboard): array
|
||||
{
|
||||
/** @var Dashboard $dashboard */
|
||||
return [
|
||||
'id' => $dashboard->dashboard_id,
|
||||
'text' => $this->describe($dashboard),
|
||||
];
|
||||
}
|
||||
|
||||
private function describe($dashboard)
|
||||
public function formatResponse($paginator)
|
||||
{
|
||||
return "{$dashboard->user->username}: {$dashboard->dashboard_name} ("
|
||||
if (! request()->has('term')) {
|
||||
$paginator->prepend((object) ['dashboard_id' => 0]);
|
||||
}
|
||||
|
||||
return parent::formatResponse($paginator);
|
||||
}
|
||||
|
||||
private function describe($dashboard): string
|
||||
{
|
||||
if ($dashboard->dashboard_id == 0) {
|
||||
return 'No Default Dashboard';
|
||||
}
|
||||
|
||||
return "{$dashboard->username}: {$dashboard->dashboard_name} ("
|
||||
. ($dashboard->access == 1 ? __('read-only') : __('read-write')) . ')';
|
||||
}
|
||||
}
|
||||
|
66
app/Http/Controllers/Select/PollerGroupController.php
Normal file
66
app/Http/Controllers/Select/PollerGroupController.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/*
|
||||
* PollerGroupController.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 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Select;
|
||||
|
||||
use App\Models\PollerGroup;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PollerGroupController extends SelectController
|
||||
{
|
||||
protected function searchFields($request)
|
||||
{
|
||||
return ['group_name', 'descr'];
|
||||
}
|
||||
|
||||
protected function baseQuery($request)
|
||||
{
|
||||
return PollerGroup::query()->select(['id', 'group_name']);
|
||||
}
|
||||
|
||||
protected function formatResponse($paginator)
|
||||
{
|
||||
// prepend the default group, unless filtered out
|
||||
if ($this->includeGeneral()) {
|
||||
$general = new PollerGroup;
|
||||
$general->id = 0;
|
||||
$general->group_name = 'General';
|
||||
$paginator->prepend($general);
|
||||
}
|
||||
|
||||
return parent::formatResponse($paginator);
|
||||
}
|
||||
|
||||
private function includeGeneral(): bool
|
||||
{
|
||||
if (request()->has('id') && request('id') !== 0) {
|
||||
return false;
|
||||
} elseif (request()->has('term') && ! Str::contains('general', strtolower(request('term')))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -51,7 +51,10 @@ abstract class SelectController extends PaginatedAjaxController
|
||||
$this->validate($request, $this->rules());
|
||||
$limit = $request->get('limit', 50);
|
||||
|
||||
$query = $this->search($request->get('term'), $this->baseQuery($request), $this->searchFields($request));
|
||||
$query = $this->baseQuery($request)->when($request->has('id'), function ($query) {
|
||||
return $query->whereKey(request('id'));
|
||||
});
|
||||
$query = $this->search($request->get('term'), $query, $this->searchFields($request));
|
||||
$this->sort($request, $query);
|
||||
$paginator = $query->simplePaginate($limit);
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=a6bdef835406a9a4e138",
|
||||
"/js/app.js": "/js/app.js?id=a3b972f53d547c6c17ba",
|
||||
"/js/manifest.js": "/js/manifest.js?id=1514baaea419f38abb7d",
|
||||
"/css/app.css": "/css/app.css?id=996b9e3da0c3ab98067e",
|
||||
"/js/vendor.js": "/js/vendor.js?id=ccca9695062f1f68aaa8",
|
||||
|
@@ -1309,10 +1309,16 @@
|
||||
},
|
||||
"default_poller_group": {
|
||||
"default": 0,
|
||||
"type": "integer",
|
||||
"type": "select-dynamic",
|
||||
"group": "poller",
|
||||
"section": "distributed",
|
||||
"order": 3
|
||||
"order": 3,
|
||||
"options": {
|
||||
"target": "poller-group"
|
||||
},
|
||||
"validate": {
|
||||
"value": "integer|zero_or_exists:poller_groups,id"
|
||||
}
|
||||
},
|
||||
"distributed_poller_memcached_host": {
|
||||
"default": "example.net",
|
||||
@@ -5439,9 +5445,12 @@
|
||||
"group": "webui",
|
||||
"section": "dashboard",
|
||||
"order": 0,
|
||||
"type": "dashboard-select",
|
||||
"type": "select-dynamic",
|
||||
"options": {
|
||||
"target": "dashboard"
|
||||
},
|
||||
"validate": {
|
||||
"value": "zero_or_exists:dashboards,dashboard_id"
|
||||
"value": "integer|zero_or_exists:dashboards,dashboard_id"
|
||||
}
|
||||
},
|
||||
"webui.dynamic_graphs": {
|
||||
|
@@ -27,8 +27,7 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"minProperties": 2
|
||||
"type": "object"
|
||||
},
|
||||
"units": {
|
||||
"type": "string"
|
||||
@@ -67,7 +66,31 @@
|
||||
"additionalProperties": false,
|
||||
"anyOf": [
|
||||
{
|
||||
"properties": { "type": {"const": "select"} },
|
||||
"properties": {
|
||||
"type": {"const": "select"},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"minProperties": 2
|
||||
}
|
||||
},
|
||||
"required": ["options"]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {"const": "select-dynamic"},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allowClear": {"type": "boolean"},
|
||||
"callback": {"type": "string"},
|
||||
"placeholder": {"type": "string"},
|
||||
"target": {"type": "string"}
|
||||
},
|
||||
"minProperties": 1,
|
||||
"required": ["target"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["options"]
|
||||
},
|
||||
{
|
||||
@@ -84,7 +107,6 @@
|
||||
"color",
|
||||
"float",
|
||||
"graph",
|
||||
"dashboard-select",
|
||||
"snmp3auth",
|
||||
"ldap-groups",
|
||||
"ad-groups",
|
||||
|
@@ -1,67 +0,0 @@
|
||||
<!--
|
||||
- SettingDashboardSelect.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 <https://www.gnu.org/licenses/>.
|
||||
-
|
||||
- @package LibreNMS
|
||||
- @link https://www.librenms.org
|
||||
- @copyright 2019 Tony Murray
|
||||
- @author Tony Murray <murraytony@gmail.com>
|
||||
-->
|
||||
|
||||
<template>
|
||||
<v-select
|
||||
:options="localOptions"
|
||||
label="text"
|
||||
:clearable="false"
|
||||
:value="selected"
|
||||
@input="$emit('input', $event.id)"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</v-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import BaseSetting from "./BaseSetting";
|
||||
|
||||
export default {
|
||||
name: "SettingDashboardSelect",
|
||||
mixins: [BaseSetting],
|
||||
data() {
|
||||
return {
|
||||
ajaxData: {results: []},
|
||||
default: {id: 0, text: this.$t('No Default Dashboard')}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
axios.get(route('ajax.select.dashboard')).then((response) => this.ajaxData = response.data);
|
||||
},
|
||||
computed: {
|
||||
localOptions() {
|
||||
return [this.default].concat(this.ajaxData.results)
|
||||
},
|
||||
selected() {
|
||||
return this.value === 0 ? this.default : this.ajaxData.results.find(dash => dash.id === this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
92
resources/js/components/SettingSelectDynamic.vue
Normal file
92
resources/js/components/SettingSelectDynamic.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<!--
|
||||
- SettingSelect2.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>
|
||||
<select class="form-control"
|
||||
:name="name"
|
||||
:value="value"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseSetting from "./BaseSetting";
|
||||
|
||||
export default {
|
||||
name: "SettingSelectDynamic",
|
||||
mixins: [BaseSetting],
|
||||
data() {
|
||||
return {
|
||||
select2: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(value) {
|
||||
this.select2.val(value).trigger('change');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return {
|
||||
theme: "bootstrap",
|
||||
dropdownAutoWidth : true,
|
||||
width: "auto",
|
||||
allowClear: Boolean(this.options.allowClear),
|
||||
placeholder: this.options.placeholder,
|
||||
ajax: {
|
||||
url: route('ajax.select.' + this.options.target).toString(),
|
||||
delay: 250,
|
||||
data: this.options.callback
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// load initial data
|
||||
axios.get(route('ajax.select.' + this.options.target), {params: {id: this.value}}).then((response) => {
|
||||
response.data.results.forEach((item) => {
|
||||
if (item.id == this.value) {
|
||||
this.select2.append(new Option(item.text, item.id, true, true))
|
||||
.trigger('change');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.select2 = $(this.$el)
|
||||
.find('select')
|
||||
.select2(this.settings);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.select2.select2('destroy');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -103,24 +103,25 @@ Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
|
||||
|
||||
// js select2 data controllers
|
||||
Route::group(['prefix' => 'select', 'namespace' => 'Select'], function () {
|
||||
Route::get('application', 'ApplicationController');
|
||||
Route::get('bill', 'BillController');
|
||||
Route::get('application', 'ApplicationController')->name('ajax.select.application');
|
||||
Route::get('bill', 'BillController')->name('ajax.select.bill');
|
||||
Route::get('dashboard', 'DashboardController')->name('ajax.select.dashboard');
|
||||
Route::get('device', 'DeviceController');
|
||||
Route::get('device-field', 'DeviceFieldController');
|
||||
Route::get('device-group', 'DeviceGroupController');
|
||||
Route::get('port-group', 'PortGroupController');
|
||||
Route::get('eventlog', 'EventlogController');
|
||||
Route::get('graph', 'GraphController');
|
||||
Route::get('graph-aggregate', 'GraphAggregateController');
|
||||
Route::get('graylog-streams', 'GraylogStreamsController');
|
||||
Route::get('syslog', 'SyslogController');
|
||||
Route::get('location', 'LocationController');
|
||||
Route::get('munin', 'MuninPluginController');
|
||||
Route::get('service', 'ServiceController');
|
||||
Route::get('template', 'ServiceTemplateController');
|
||||
Route::get('port', 'PortController');
|
||||
Route::get('port-field', 'PortFieldController');
|
||||
Route::get('device', 'DeviceController')->name('ajax.select.device');
|
||||
Route::get('device-field', 'DeviceFieldController')->name('ajax.select.device-field');
|
||||
Route::get('device-group', 'DeviceGroupController')->name('ajax.select.device-group');
|
||||
Route::get('port-group', 'PortGroupController')->name('ajax.select.port-group');
|
||||
Route::get('eventlog', 'EventlogController')->name('ajax.select.eventlog');
|
||||
Route::get('graph', 'GraphController')->name('ajax.select.graph');
|
||||
Route::get('graph-aggregate', 'GraphAggregateController')->name('ajax.select.graph-aggregate');
|
||||
Route::get('graylog-streams', 'GraylogStreamsController')->name('ajax.select.graylog-streams');
|
||||
Route::get('syslog', 'SyslogController')->name('ajax.select.syslog');
|
||||
Route::get('location', 'LocationController')->name('ajax.select.location');
|
||||
Route::get('munin', 'MuninPluginController')->name('ajax.select.munin');
|
||||
Route::get('service', 'ServiceController')->name('ajax.select.service');
|
||||
Route::get('template', 'ServiceTemplateController')->name('ajax.select.template');
|
||||
Route::get('poller-group', 'PollerGroupController')->name('ajax.select.poller-group');
|
||||
Route::get('port', 'PortController')->name('ajax.select.port');
|
||||
Route::get('port-field', 'PortFieldController')->name('ajax.select.port-field');
|
||||
});
|
||||
|
||||
// jquery bootgrid data controllers
|
||||
|
Reference in New Issue
Block a user