Add Service Templates (#12107)

* service templates - move to blade - jellyfrog fixes - revert get to find and fix discovery lookup

* service templates - move to blade - wip

* service templates - move to blade - wip

* service templates - move to blade - deprecate delete_service_template

* service templates - move to blade - deprecate delete_service_template

* service templates - move to blade - deprecate remove_service_template

* service templates - move to blade - deprecate remove_service_template

* service templates - move to blade - fix permisson lookup

* service templates - move to blade - fix remove route

* service templates - move to blade - fix remove route

* service templates - move to blade - fix permisson lookup

* service templates - move to blade - fix permisson lookup

* service templates - move to blade - fix permisson lookup

* service templates - move to blade - fix permisson lookup

* service templates - move to blade - fixs

* service templates - move to blade - fixes

* service templates - move to blade - fixes

* service templates - move to blade - fixes

* service templates - move to blade - nullable input?

* service templates - move to blade - nullable input?

* service templates - move to blade - nullable input?

* service templates - move to blade - add selected for device group id and type

* service templates - move to blade - add selected for device group id and type

* service templates - move to blade - add selected for device group id and type

* service templates - move to blade - fix discovery response

* service templates - move to blade - fix device group index display

* service templates - move to blade - add delete to discover_service_template

* service templates - move to blade - add discover all button - function foobar

* service templates - move to blade - add discover all button - function foobar

* service templates - move to blade - add discover all button - function foobar

* service templates - move to blade - fixes

* service templates - move to blade - fixes

* service templates - move to blade - fixes

* service templates - move to blade - button fixes

* service templates - move to blade - button fixes

* service templates - move to blade - button fixes

* service templates - move to blade - attempt route fix

* service templates - move to blade - attempt route fix

* service templates - move to blade - attempt route fix

* service templates - move to blade - attempt route fix

* service templates - move to blade - fix discovery function

* service templates - move to blade - fix discovery function

* service templates - move to blade - fix return values - thanks SourceDoctor

* service templates - move to blade - attempt route fix

* service templates - move to blade - button variable fix

* service templates - move to blade - button variable fix

* service templates - move to blade - button variable fix

* service templates - move to blade - function fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - move to blade - permissions fix

* service templates - add initial api - and model permissions fix

* service templates - add initial api - discovery too

* service templates - add initial api - discovery too

* service templates - merge fix

* service templates - api fixes

* service templates - api fixes

* service templates - remove bad fixe

* service templates - discover null? :)

* service templates - Policy requires Facade?

* service templates - Policy requires Facade?

* service templates - REVERT Policy requires Facade?

* service templates - REVERT Policy requires Facade?

* Revert "service templates - REVERT Policy requires Facade?"

This reverts commit 7752b3e4e5.

* service templates - clean up discover api call

* service templates - clean up discover api call

* service templates - clean up discover api call

* service templates - clean up discover api call

* service templates - clean up discover api call

* service templates - attempt route fix

* Revert "service templates - attempt route fix"

This reverts commit b48970351e.

* service templates - jellyfrog fixes - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template

* service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template

* Revert "service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template"

This reverts commit ef93315db5.

* Revert "service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template"

This reverts commit 73af819a60.

* Revert "service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template"

This reverts commit dd019c3105.

* Revert "service templates - jellyfrog fixes - dbchanged - DeviceGroup relates to Service Template"

This reverts commit 94aaaf7976.

* service templates - jellyfrog fixes - DeviceGroup relates to Service Template

* service templates - fix permissions

* service templates - db service_templates_perms

* service templates - fix permissions

* service templates - fix permissions

* service templates - fix permissions

* service templates - fix permissions

* service templates - fix permissions

* service templates - fix edit.blade missing parameters - still 403 :(

* service templates - jellyfrog -fix route and construct

* service templates - jellyfrog -fix route and construct

* service templates - jellyfrog -fix route and construct

* Revert "service templates - jellyfrog -fix route and construct"

This reverts commit ec1eea7a99.

* service templates - jellyfrog -fix routes

* service templates - jellyfrog -fix services lookup

* service templates - edit blade fixes

* service templates - edit blade fixes - service or services or template or serviceTemplate or service_template good luck

* service templates - edit blade fixes

* service templates - index blade fixes

* service templates - discover return fix

* service templates - remove return fix

* service templates - fix discovery api returns

* service templates - fix discovery api returns

* service templates - fix discovery api returns

* service templates - fix discovery - test

* service templates - fix discovery

* service templates - fix checkbox integer

* service templates - fix checkbox integer

* service templates - fix checkbox integer

* service templates - fix checkbox integer

* service templates - fix checkbox integer

* service templates - fix discover all

* service templates - fix discover all

* service templates - fix discover all

* service templates - fix discover all

* service templates - fix discover all

* service templates - fix discover all

* service templates - fix discover all

* service templates - fix discover

* service templates - fix discover

* service templates - fix discover

* service templates - fix discover

* service templates - fix discover

* service templates - move discover/apply to blade - last of the old functions - wip

* service templates - move discover/apply to blade - last of the old functions - wip

* service templates - move discover/apply to blade - last of the old functions - wip

* service templates - change all relevant to template for sanity

* service templates - inspection fixes

* service templates - inspection fixes

* service templates - inspection fixes

* service templates - murrant fixes - use createOrUpdate - add obervers - fix function call

* service templates - murrant fixes - add ServiceController store - not used

* service templates - murrant fixes - add Observers file)

* service templates - murrant fixes - correct ServiceController

* service templates - murrant fixes - fix observers load

* service templates - observer fix

* service templates - schedule discovery function call fix, cheeky, is this allowed?

* service templates - attempt route fix.

* service templates - attempt route fix 2

* service templates - blade route applyall missing url - ie the route lol

* service templates - blade route applyall missing url - ie the route lol

* service templates - fix routes - bingo!

* service templates - fix routes - bingo!

* service templates - fix blade missing quote

* service templates - jellyfrog fix - remove pluck

* service templates - missing template id on service update

* service templates - jellyfrog fix - add get

* service templates - fix service db defaults / null

* service templates - fix service update

* service templates - fix service update

* service templates - fix service update

* service templates - fix service devicegroup change - obsoletes service_template_change in servicesdb

* service templates - fix service devicegroup change - obsoletes service_template_change in servicesdb

* Add service templates - phpcs fixes - and attemp whereNotIn scope

* Add service templates - styleci

* Add service templates - styleci

* Add service templates - fix notindevicegroup scope

* Add service templates - removed service service_template_changed - not required for delete or update

* Add service templates - removed service service_template_changed - not required for delete or update

* Add service templates - murrant fixes - add protected casts - fix checkbox

* Add service templates - fix checkbox - attempt2

* Add service templates - fix checkbox - attempt3

* Add service templates - fix checkbox - attempt3

* Add service templates - fix checkbox - attempt4

* Add service templates - fix delete row selection

* Revert "Add service templates - fix delete row selection"

This reverts commit 4d9e4990f2.

* Add service templates - fix delete row selection

* Add service templates - fix automatic discovery

* Add service templates - fix automatic discovery

* Add service templates - checkbox - add hidden input

* Add service templates - checkbox - add old value back?

* Add service templates - checkbox - fix disabled

* Add service templates - checkbox - fix disabled

* Add service templates - service fixes - styleci

* Add service templates - make devicegroup required

* Add service templates - let Disable text wrap

* Add service templates - dont allow device group to be deleted when service templates exist

* Add service templates - dont allow device group to be deleted when service templates exist

* Add service templates - enable multiple device groups - wip

* Add service templates - enable multiple device groups - wip

* Add service templates - enable multiple device groups - wip

* Add service templates - enable multiple device groups - dbschema - wip

* Add service templates - enable multiple device groups - db migration - wip

* Add service templates - enable multiple device groups - db migration - wip

* Add service templates - enable multiple device groups - wip

* Add service templates - enable multiple device groups - wip

* Add service templates - enable service template Groups - relationships to device AND device groups

* Add service templates - styleci fixes

* Add service templates - styleci fixes

* Add service templates - fixes

* Add service templates - fixes

* Add service templates - fixes

* Add service templates - fixes

* Add service templates - fixes

* Add service templates - fixes

* Add service templates - fixes

* Add service templates - fixes

* Add service templates - db - primarykey issues

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fix migrations

* Add service templates - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - blade view - fixes

* Add service templates - misc update for travis build trigger

* Add service templates - restrict view to only show device/groups that have templates applied

* Add service templates - fix devicegroup update

* remove permissions table stuff

* Fix schema rollback

* fix style

* Update ServiceTemplateController.php

Co-authored-by: Tony Murray <murraytony@gmail.com>
This commit is contained in:
Anthony F. McInerney 2021-02-02 06:40:11 +00:00 committed by GitHub
parent 89cbf152f6
commit 5388a20c09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2215 additions and 95 deletions

48
LibreNMS/Services.php Normal file
View File

@ -0,0 +1,48 @@
<?php
/*
* Services.php
*
* Nagios services helper
*
* 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 2020 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS;
class Services
{
/**
* List all available services from nagios plugins directory
*
* @return array
*/
public static function list()
{
$services = [];
if (is_dir(Config::get('nagios_plugins'))) {
foreach (scandir(Config::get('nagios_plugins')) as $file) {
if (substr($file, 0, 6) === 'check_') {
$services[] = substr($file, 6);
}
}
}
return $services;
}
}

View File

@ -175,6 +175,11 @@ class DeviceGroupController extends Controller
*/
public function destroy(DeviceGroup $deviceGroup)
{
if ($deviceGroup->serviceTemplates()->exists()) {
$msg = __('Device Group :name still has Service Templates associated with it. Please remove or update the Service Template accordingly', ['name' => $deviceGroup->name]);
return response($msg, 200);
}
$deviceGroup->delete();
$msg = __('Device Group :name deleted', ['name' => $deviceGroup->name]);

View File

@ -0,0 +1,48 @@
<?php
/**
* ServiceTemplateController.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/>.
*
* @link http://librenms.org
* @copyright 2020 Anthony F McInerney <bofh80>
* @author Anthony F McInerney <afm404@gmail.com>
*/
namespace App\Http\Controllers\Select;
use App\Models\ServiceTemplate;
class ServiceTemplateController extends SelectController
{
protected function searchFields($request)
{
return ['name'];
}
protected function baseQuery($request)
{
return ServiceTemplate::hasAccess($request->user())->select('id', 'name');
}
public function formatItem($template)
{
return [
'id' => $template->id,
'text' => $template->name,
];
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers;
use App\Models\Service;
use Illuminate\Http\Request;
use Toastr;
class ServiceController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function store(Request $request)
{
$request = [
'service_name' => 'required|string|unique:service',
'device_id' => 'integer',
'service_type' => 'string',
'service_param' => 'nullable|string',
'service_ip' => 'nullable|string',
'service_desc' => 'nullable|string',
'service_changed' => 'integer',
'service_disabled' => 'integer',
'service_ignore' => 'integer',
];
$service = Service::make(
$request->only(
[
'service_name',
'device_id',
'service_type',
'service_param',
'service_ip',
'service_desc',
'service_changed',
'service_disabled',
'service_ignore',
]
)
);
$service->save();
Toastr::success(__('Service :name created', ['name' => $service->service_name]));
return redirect()->route('services.templates.index');
}
}

View File

@ -0,0 +1,378 @@
<?php
namespace App\Http\Controllers;
use App\Models\Device;
use App\Models\DeviceGroup;
use App\Models\Service;
use App\Models\ServiceTemplate;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use LibreNMS\Alerting\QueryBuilderFilter;
use LibreNMS\Services;
use Toastr;
class ServiceTemplateController extends Controller
{
public function __construct()
{
$this->authorizeResource(ServiceTemplate::class, 'template');
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response|\Illuminate\View\View
*/
public function index()
{
//$this->authorize('manage', ServiceTemplate::class);
return view(
'service-template.index', [
'service_templates' => ServiceTemplate::orderBy('name')->withCount('devices')->withCount('groups')->get(),
'groups' => DeviceGroup::orderBy('name')->has('serviceTemplates')->get(),
'devices' => Device::orderBy('hostname')->has('serviceTemplates')->get(),
]
);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response|\Illuminate\View\View
*/
public function create()
{
return view(
'service-template.create', [
'template' => new ServiceTemplate(),
'service_templates' => ServiceTemplate::orderBy('name')->get(),
'services' => Services::list(),
'filters' => json_encode(new QueryBuilderFilter('group')),
]
);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function store(Request $request)
{
$this->validate(
$request, [
'name' => 'required|string|unique:service_templates',
'groups' => 'array',
'groups.*' => 'integer',
'devices' => 'array',
'devices.*' => 'integer',
'type' => 'string',
'dtype' => 'required|in:dynamic,static',
'dgtype' => 'required|in:dynamic,static',
'drules' => 'json|required_if:dtype,dynamic',
'dgrules' => 'json|required_if:dgtype,dynamic',
'param' => 'nullable|string',
'ip' => 'nullable|string',
'desc' => 'nullable|string',
'changed' => 'integer',
'disabled' => 'integer',
'ignore' => 'integer',
]
);
$template = ServiceTemplate::make(
$request->only(
[
'name',
'type',
'dtype',
'dgtype',
'drules',
'dgrules',
'param',
'ip',
'desc',
'changed',
'disabled',
'ignore',
]
)
);
$template->drules = json_decode($request->drules);
$template->dgrules = json_decode($request->dgrules);
$template->save();
if ($request->dtype == 'static') {
$template->devices()->sync($request->devices);
}
if ($request->dgtype == 'static') {
$template->groups()->sync($request->groups);
}
Toastr::success(__('Service Template :name created', ['name' => $template->name]));
return redirect()->route('services.templates.index');
}
/**
* Display the specified resource.
*
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function show(ServiceTemplate $template)
{
return redirect(url('/services/templates/' . $template->id));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\Response|\Illuminate\View\View
*/
public function edit(ServiceTemplate $template)
{
return view(
'service-template.edit', [
'template' => $template,
'filters' => json_encode(new QueryBuilderFilter('group')),
'services' => Services::list(),
]
//
);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function update(Request $request, ServiceTemplate $template)
{
$this->validate(
$request, [
'name' => [
'required',
'string',
Rule::unique('service_templates')->where(
function ($query) use ($template) {
$query->where('id', '!=', $template->id);
}
),
],
'dtype' => 'required|in:dynamic,static',
'drules' => 'json|required_if:dtype,dynamic',
'devices' => 'array',
'devices.*' => 'integer',
'dgtype' => 'required|in:dynamic,static',
'dgrules' => 'json|required_if:dgtype,dynamic',
'groups' => 'array',
'groups.*' => 'integer',
'type' => 'string',
'param' => 'nullable|string',
'ip' => 'nullable|string',
'desc' => 'nullable|string',
'changed' => 'integer',
'disabled' => 'integer',
'ignore' => 'integer',
]
);
$template->fill(
$request->only(
[
'name',
'type',
'dtype',
'dgtype',
'drules',
'dgrules',
'param',
'ip',
'desc',
'changed',
'ignore',
'disabled',
]
)
);
$devices_updated = false;
if ($template->dtype == 'static') {
// sync device_ids from input
$updated = $template->devices()->sync($request->get('devices', []));
// check for attached/detached/updated
$devices_updated = array_sum(array_map(function ($device_ids) {
return count($device_ids);
}, $updated)) > 0;
} else {
$template->drules = json_decode($request->drules);
}
$device_groups_updated = false;
if ($template->dgtype == 'static') {
// sync device_group_ids from input
$updated = $template->groups()->sync($request->get('groups', []));
// check for attached/detached/updated
$device_groups_updated = array_sum(array_map(function ($device_group_ids) {
return count($device_group_ids);
}, $updated)) > 0;
} else {
$template->dgrules = json_decode($request->dgrules);
}
if ($template->isDirty() || $devices_updated || $device_groups_updated) {
try {
if ($template->save() || $devices_updated || $device_groups_updated) {
Toastr::success(__('Service Template :name updated', ['name' => $template->name]));
} else {
Toastr::error(__('Failed to save'));
return redirect()->back()->withInput();
}
} catch (\Illuminate\Database\QueryException $e) {
return redirect()->back()->withInput()->withErrors([
'drules' => __('Rules resulted in invalid query: ') . $e->getMessage(),
'dgrules' => __('Rules resulted in invalid query: ') . $e->getMessage(),
]);
}
} else {
Toastr::info(__('No changes made'));
}
return redirect()->route('services.templates.index');
}
/**
* Apply specified Service Template to Device Groups.
*
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function applyDeviceGroups(ServiceTemplate $template)
{
foreach (DeviceGroup::inServiceTemplate($template->id)->get() as $device_group) {
foreach (Device::inDeviceGroup($device_group->id)->get() as $device) {
$device->services()->updateOrCreate(
[
'service_template_id' => $template->id,
],
[
'service_name' => $template->name,
'service_type' => $template->type,
'service_template_id' => $template->id,
'service_param' => $template->param,
'service_ip' => $template->ip,
'service_desc' => $template->desc,
'service_disabled' => $template->disabled,
'service_ignore' => $template->ignore,
]
);
}
}
$msg = __('Services for Template :name have been updated', ['name' => $template->name]);
return response($msg, 200);
}
/**
* Apply specified Service Template to Devices.
*
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function applyDevices(ServiceTemplate $template)
{
foreach (Device::inServiceTemplate($template->id)->get() as $device) {
$device->services()->updateOrCreate(
[
'service_template_id' => $template->id,
],
[
'service_name' => $template->name,
'service_type' => $template->type,
'service_template_id' => $template->id,
'service_param' => $template->param,
'service_ip' => $template->ip,
'service_desc' => $template->desc,
'service_disabled' => $template->disabled,
'service_ignore' => $template->ignore,
]
);
}
$msg = __('Services for Template :name have been updated', ['name' => $template->name]);
return response($msg, 200);
}
/**
* Apply all Service Templates.
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function applyAll()
{
foreach (ServiceTemplate::all() as $template) {
ServiceTemplateController::apply($template);
}
$msg = __('All Service Templates have been applied');
return response($msg, 200);
}
/**
* Apply specified Service Template.
*
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function apply(ServiceTemplate $template)
{
ServiceTemplateController::applyDevices($template);
ServiceTemplateController::applyDeviceGroups($template);
// remove any remaining services no longer in the correct device group
foreach (Device::notInServiceTemplate($template->id)->notInDeviceGroup($template->groups)->get() as $device) {
Service::where('device_id', $device->device_id)->where('service_template_id', $template->id)->delete();
}
$msg = __('All Service Templates have been applied');
return response($msg, 200);
}
/**
* Remove specified Service Template.
*
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function remove(ServiceTemplate $template)
{
Service::where('service_template_id', $template->id)->delete();
$msg = __('All Service Templates have been applied');
return response($msg, 200);
}
/**
* Destroy the specified resource from storage.
*
* @param \App\Models\ServiceTemplate $template
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\View\View
*/
public function destroy(ServiceTemplate $template)
{
Service::where('service_template_id', $template->id)->delete();
$template->delete();
$msg = __('Service Template :name deleted, Services removed', ['name' => $template->name]);
return response($msg, 200);
}
}

View File

@ -456,11 +456,46 @@ class Device extends BaseModel
public function scopeInDeviceGroup($query, $deviceGroup)
{
return $query->whereIn($query->qualifyColumn('device_id'), function ($query) use ($deviceGroup) {
$query->select('device_id')
->from('device_group_device')
->where('device_group_id', $deviceGroup);
});
return $query->whereIn(
$query->qualifyColumn('device_id'), function ($query) use ($deviceGroup) {
$query->select('device_id')
->from('device_group_device')
->where('device_group_id', $deviceGroup);
}
);
}
public function scopeNotInDeviceGroup($query, $deviceGroup)
{
return $query->whereNotIn(
$query->qualifyColumn('device_id'), function ($query) use ($deviceGroup) {
$query->select('device_id')
->from('device_group_device')
->where('device_group_id', $deviceGroup);
}
);
}
public function scopeInServiceTemplate($query, $serviceTemplate)
{
return $query->whereIn(
$query->qualifyColumn('device_id'), function ($query) use ($serviceTemplate) {
$query->select('device_id')
->from('service_templates_device')
->where('service_template_id', $serviceTemplate);
}
);
}
public function scopeNotInServiceTemplate($query, $serviceTemplate)
{
return $query->whereNotIn(
$query->qualifyColumn('device_id'), function ($query) use ($serviceTemplate) {
$query->select('device_id')
->from('service_templates_device')
->where('service_template_id', $serviceTemplate);
}
);
}
// ---- Define Relationships ----
@ -635,6 +670,11 @@ class Device extends BaseModel
return $this->hasMany(\App\Models\Sensor::class, 'device_id');
}
public function serviceTemplates()
{
return $this->belongsToMany(\App\Models\ServiceTemplate::class, 'service_templates_device', 'device_id', 'service_template_id');
}
public function services()
{
return $this->hasMany(\App\Models\Service::class, 'device_id');

View File

@ -138,6 +138,28 @@ class DeviceGroup extends BaseModel
return $query->whereIn('id', Permissions::deviceGroupsForUser($user));
}
public function scopeInServiceTemplate($query, $serviceTemplate)
{
return $query->whereIn(
$query->qualifyColumn('id'), function ($query) use ($serviceTemplate) {
$query->select('device_group_id')
->from('service_templates_device_group')
->where('service_template_id', $serviceTemplate);
}
);
}
public function scopeNotInServiceTemplate($query, $serviceTemplate)
{
return $query->whereNotIn(
$query->qualifyColumn('id'), function ($query) use ($serviceTemplate) {
$query->select('device_group_id')
->from('service_templates_device_group')
->where('service_template_id', $serviceTemplate);
}
);
}
// ---- Define Relationships ----
public function devices()
@ -154,4 +176,9 @@ class DeviceGroup extends BaseModel
{
return $this->belongsToMany(\App\Models\User::class, 'devices_group_perms', 'device_group_id', 'user_id');
}
public function serviceTemplates()
{
return $this->belongsToMany(\App\Models\ServiceTemplate::class, 'service_templates_device_group', 'device_group_id', 'service_template_id');
}
}

View File

@ -8,6 +8,22 @@ class Service extends DeviceRelatedModel
{
public $timestamps = false;
protected $primaryKey = 'service_id';
protected $fillable = [
'service_id',
'device_id',
'service_ip',
'service_type',
'service_desc',
'service_param',
'service_ignore',
'service_status',
'service_changed',
'service_message',
'service_disabled',
'service_ds',
'service_template_id',
'service_name',
];
// ---- Query Scopes ----

View File

@ -0,0 +1,258 @@
<?php
/**
* ServiceTemplate.php
*
* Service Templates with Dynamic groups of devices
*
* 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/>.
*
* @link http://librenms.org
* @copyright 2020 Anthony F McInerney <bofh80>
* @author Anthony F McInerney <afm404@gmail.com>
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use LibreNMS\Alerting\QueryBuilderFluentParser;
use Log;
class ServiceTemplate extends BaseModel
{
public $timestamps = false;
protected $primaryKey = 'id';
protected $fillable = [
'id',
'ip',
'type',
'dtype',
'dgtype',
'drules',
'dgrules',
'desc',
'param',
'ignore',
'status',
'changed',
'disabled',
'name',
];
protected $attributes = [ // default values
'ignore' => '0',
'disabled' => '0',
];
protected $casts = [
'ignore' => 'integer',
'disabled' => 'integer',
'drules' => 'array',
'dgrules' => 'array',
];
public static function boot()
{
parent::boot();
static::deleting(function (ServiceTemplate $template) {
$template->devices()->detach();
$template->groups()->detach();
});
static::saving(function (ServiceTemplate $template) {
if ($template->isDirty('drules')) {
$template->drules = $template->getDeviceParser()->generateJoins()->toArray();
}
if ($template->isDirty('dgrules')) {
$template->dgrules = $template->getDeviceGroupParser()->generateJoins()->toArray();
}
});
static::saved(function (ServiceTemplate $template) {
if ($template->isDirty('drules')) {
$template->updateDevices();
}
if ($template->isDirty('dgrules')) {
$template->updateGroups();
}
});
}
// ---- Helper Functions ----
/**
* Update devices included in this template (dynamic only)
*/
public function updateDevices()
{
if ($this->dtype == 'dynamic') {
$this->devices()->sync(QueryBuilderFluentParser::fromJSON($this->rules)->toQuery()
->distinct()->pluck('devices.device_id'));
}
}
/**
* Update device groups included in this template (dynamic only)
*/
public function updateGroups()
{
if ($this->dgtype == 'dynamic') {
$this->groups()->sync(QueryBuilderFluentParser::fromJSON($this->rules)->toQuery()
->distinct()->pluck('device_groups.id'));
}
}
/**
* Update the device template groups for the given device or device_id
*
* @param Device|int $device
* @return array
*/
public static function updateServiceTemplatesForDevice($device)
{
$device = ($device instanceof Device ? $device : Device::find($device));
if (! $device instanceof Device) {
// could not load device
return [
'attached' => [],
'detached' => [],
'updated' => [],
];
}
$template_ids = static::query()
->with(['devices' => function ($query) {
$query->select('devices.device_id');
}])
->get()
->filter(function ($template) use ($device) {
/** @var ServiceTemplate $template */
if ($template->dtype == 'dynamic') {
try {
return $template->getDeviceParser()
->toQuery()
->where('devices.device_id', $device->device_id)
->exists();
} catch (\Illuminate\Database\QueryException $e) {
Log::error("Service Template '$template->name' generates invalid query: " . $e->getMessage());
return false;
}
}
// for static, if this device is include, keep it.
return $template->devices
->where('device_id', $device->device_id)
->isNotEmpty();
})->pluck('id');
return $device->serviceTemplates()->sync($template_ids);
}
/**
* Update the device template groups for the given device group or device_group_id
*
* @param DeviceGroup|int $deviceGroup
* @return array
*/
public static function updateServiceTemplatesForDeviceGroup($deviceGroup)
{
$deviceGroup = ($deviceGroup instanceof DeviceGroup ? $deviceGroup : DeviceGroup::find($deviceGroup));
if (! $deviceGroup instanceof DeviceGroup) {
// could not load device
return [
'attached' => [],
'detached' => [],
'updated' => [],
];
}
$template_ids = static::query()
->with(['device_groups' => function ($query) {
$query->select('device_groups.id');
}])
->get()
->filter(function ($template) use ($deviceGroup) {
/** @var ServiceTemplate $template */
if ($template->dgtype == 'dynamic') {
try {
return $template->getDeviceGroupParser()
->toQuery()
->where('device_groups.id', $deviceGroup->id)
->exists();
} catch (\Illuminate\Database\QueryException $e) {
Log::error("Service Template '$template->name' generates invalid query: " . $e->getMessage());
return false;
}
}
// for static, if this device group is include, keep it.
return $template->groups
->where('device_group_id', $deviceGroup->id)
->isNotEmpty();
})->pluck('id');
return $deviceGroup->serviceTemplates()->sync($template_ids);
}
/**
* Get a query builder parser instance from this Service Template device rule
*
* @return QueryBuilderFluentParser
*/
public function getDeviceParser()
{
return QueryBuilderFluentParser::fromJson($this->drules);
}
/**
* Get a query builder parser instance from this Service Template device group rule
*
* @return QueryBuilderFluentParser
*/
public function getDeviceGroupParser()
{
return QueryBuilderFluentParser::fromJson($this->dgrules);
}
// ---- Query Scopes ----
/**
* @param Builder $query
* @return Builder
*/
public function scopeIsDisabled($query)
{
return $query->where('disabled', 1);
}
// ---- Define Relationships ----
public function devices()
{
return $this->belongsToMany(\App\Models\Device::class, 'service_templates_device', 'service_template_id', 'device_id');
}
public function services()
{
return $this->belongsToMany(\App\Models\Service::class, 'service_templates_device', 'service_template_id', 'device_id');
}
public function groups()
{
return $this->belongsToMany(\App\Models\DeviceGroup::class, 'service_templates_device_group', 'service_template_id', 'device_group_id');
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Observers;
use App\Models\Service;
class ServiceObserver
{
/**
* Handle the service "created" event.
*
* @param \App\Models\Service $service
* @return void
*/
public function created(Service $service)
{
//
}
/**
* Handle the service "updated" event.
*
* @param \App\Models\Service $service
* @return void
*/
public function updated(Service $service)
{
//
}
/**
* Handle the service "deleted" event.
*
* @param \App\Models\Service $service
* @return void
*/
public function deleted(Service $service)
{
//
}
/**
* Handle the service "restored" event.
*
* @param \App\Models\Service $service
* @return void
*/
public function restored(Service $service)
{
//
}
/**
* Handle the service "force deleted" event.
*
* @param \App\Models\Service $service
* @return void
*/
public function forceDeleted(Service $service)
{
//
}
}

View File

@ -0,0 +1,119 @@
<?php
namespace App\Policies;
use App\Models\ServiceTemplate;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ServiceTemplatePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any service templates.
*
* @param \App\Models\User $user
* @return mixed
*/
public function viewAny(User $user)
{
return $user->hasGlobalRead();
}
/**
* Determine whether the user can view the service template.
*
* @param \App\Models\User $user
* @param \App\Models\ServiceTemplate $template
* @return mixed
*/
public function view(User $user, ServiceTemplate $template)
{
return $this->viewAny($user);
}
/**
* Determine whether the user can create service templates.
*
* @param \App\Models\User $user
* @return mixed
*/
public function create(User $user)
{
return $user->hasGlobalAdmin();
}
/**
* Determine whether the user can update the service template.
*
* @param \App\Models\User $user
* @param \App\Models\ServiceTemplate $template
* @return mixed
*/
public function update(User $user, ServiceTemplate $template)
{
return $user->isAdmin();
}
/**
* Determine whether the user can delete the service template.
*
* @param \App\Models\User $user
* @param \App\Models\ServiceTemplate $template
* @return mixed
*/
public function delete(User $user, ServiceTemplate $template)
{
return $user->isAdmin();
}
/**
* Determine whether the user can restore the service template.
*
* @param \App\Models\User $user
* @param \App\Models\ServiceTemplate $template
* @return mixed
*/
public function restore(User $user, ServiceTemplate $template)
{
return $user->hasGlobalAdmin();
}
/**
* Determine whether the user can permanently delete the service template.
*
* @param \App\Models\User $user
* @param \App\Models\ServiceTemplate $template
* @return mixed
*/
public function forceDelete(User $user, ServiceTemplate $template)
{
return $user->isAdmin();
}
/**
* Determine whether the user can view the stored configuration of the service template
* from Oxidized or Rancid
*
* @param \App\Models\User $user
* @param \App\Models\ServiceTemplate $template
* @return mixed
*/
public function showConfig(User $user, ServiceTemplate $template)
{
return $user->isAdmin();
}
/**
* Determine whether the user can update service template notes.
*
* @param \App\Models\User $user
* @param \App\Models\ServiceTemplate $template
* @return mixed
*/
public function updateNotes(User $user, ServiceTemplate $template)
{
return $user->isAdmin();
}
}

View File

@ -130,6 +130,7 @@ class AppServiceProvider extends ServiceProvider
private function bootObservers()
{
\App\Models\Device::observe(\App\Observers\DeviceObserver::class);
\App\Models\Service::observe(\App\Observers\ServiceObserver::class);
}
private function bootCustomValidators()

View File

@ -20,6 +20,7 @@ class AuthServiceProvider extends ServiceProvider
\App\Models\DeviceGroup::class => \App\Policies\DeviceGroupPolicy::class,
\App\Models\PollerCluster::class => \App\Policies\PollerClusterPolicy::class,
\App\Models\Port::class => \App\Policies\PortPolicy::class,
\App\Models\ServiceTemplate::class => \App\Policies\ServiceTemplatePolicy::class,
];
/**

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateServiceTemplatesDeviceGroupTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('service_templates_device_group', function (Blueprint $table) {
$table->unsignedInteger('service_template_id')->unsigned()->index();
$table->unsignedInteger('device_group_id')->unsigned()->index();
$table->primary(['service_template_id', 'device_group_id'], 'service_templates_device_group_relationship_primary');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('service_templates_device_group');
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateServiceTemplatesDeviceTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('service_templates_device', function (Blueprint $table) {
$table->unsignedInteger('service_template_id')->unsigned()->index();
$table->unsignedInteger('device_id')->unsigned()->index();
$table->primary(['service_template_id', 'device_id'], 'service_templates_device_relationship_primary');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('service_templates_device');
}
}

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateServiceTemplatesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('service_templates', function (Blueprint $table) {
$table->increments('id');
$table->text('ip')->nullable()->default(null);
$table->string('type');
$table->string('dtype', 16)->default('static');
$table->string('dgtype', 16)->default('static');
$table->text('drules')->nullable();
$table->text('dgrules')->nullable();
$table->text('desc')->nullable()->default(null);
$table->text('param')->nullable()->default(null);
$table->boolean('ignore')->default(0);
if (\LibreNMS\DB\Eloquent::getDriver() == 'mysql') {
$table->timestamp('changed')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
} else {
$table->timestamp('changed')->useCurrent();
}
$table->boolean('disabled')->default(0);
$table->string('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('service_templates');
}
}

View File

@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class ExtendServicesTableForServiceTemplatesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('services', function (Blueprint $table) {
$table->unsignedInteger('service_template_id')->default(0);
$table->string('service_name')->nullable()->default(null);
$table->text('service_desc')->nullable()->default(null)->change();
$table->text('service_param')->nullable()->default(null)->change();
$table->boolean('service_ignore')->default(0)->change();
$table->text('service_ip')->nullable()->default(null)->change();
$table->text('service_ds')->nullable()->default(null)->change();
$table->text('service_message')->nullable()->default(null)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('services', function (Blueprint $table) {
$table->dropColumn([
'service_template_id',
'service_name',
]);
$table->text('service_desc')->change();
$table->text('service_param')->change();
$table->boolean('service_ignore')->change();
$table->text('service_ip')->change();
$table->text('service_ds')->change();
$table->text('service_message')->change();
});
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddForeignKeysToServiceTemplatesDeviceGroupTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('service_templates_device_group', function (Blueprint $table) {
$table->foreign('service_template_id')->references('id')->on('service_templates')->onUpdate('RESTRICT')->onDelete('CASCADE');
$table->foreign('device_group_id')->references('id')->on('device_groups')->onUpdate('RESTRICT')->onDelete('CASCADE');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (\LibreNMS\DB\Eloquent::getDriver() !== 'sqlite') {
Schema::table('service_templates_device_group', function (Blueprint $table) {
$table->dropForeign('service_templates_device_group_service_template_id_foreign');
$table->dropForeign('service_templates_device_group_device_group_id_foreign');
});
}
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddForeignKeysToServiceTemplatesDeviceTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('service_templates_device', function (Blueprint $table) {
$table->foreign('service_template_id')->references('id')->on('service_templates')->onUpdate('RESTRICT')->onDelete('CASCADE');
$table->foreign('device_id')->references('device_id')->on('devices')->onUpdate('RESTRICT')->onDelete('CASCADE');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (\LibreNMS\DB\Eloquent::getDriver() !== 'sqlite') {
Schema::table('service_templates_device', function (Blueprint $table) {
$table->dropForeign('service_templates_device_service_template_id_foreign');
$table->dropForeign('service_templates_device_device_id_foreign');
});
}
}
}

View File

@ -23,6 +23,54 @@ Note: Plugins will only load if they are prefixed with `check_`.
The `check_` prefix is stripped out when displaying in the "Add Service"
GUI "Type" dropdown list.
## Service Templates
Service Templates within LibreNMS provides the same ability as Nagios
does with Host Groups. Known as Device Groups in LibreNMS.
They are applied devices that belong to the specified Device Group.
Use the Apply buttons to manually create or update Services for the Service
Template.
Use the Remove buttons to manually remove Services for the Service Template.
After you Edit a Service Template, and then use Apply, all relevant changes are
pushed to existing Services previously created.
You can also enable Service Templates Auto Discovery to have Services
added / removed / updated on regular discover intervals.
When a Device is a member of multiple Device Groups, templates from
all of those Device Groups are applied.
If a Device is added or removed from a Device Group, when the Apply button
is used or Auto Discovery runs Services will be added / removed as
appropriate.
**Service Templates are tied into Device Groups, you need at least
one Device Group to be able to add Service Templates - You can define a
dummy one. The Device Group does not need members to add Service Templates.**
## Service Auto Discovery
To automatically create services for devices with available checks.
You need to enable the discover services within config.php with the following:
```php
$config['discover_services'] = true;
```
## Service Templates Auto Discovery
To automatically create services for devices with configured
Service Templates.
You need to enable the discover services within config.php with the following:
```php
$config['discover_services_templates'] = true;
```
## Setup
Service checks are now distributable if you run a distributed
@ -157,6 +205,42 @@ then you can run the following command to help troubleshoot services.
./check-services.php -d
```
## Related Polling / Discovery Options
These settings are related and should be investigated and set accordingly.
The below values are not defaults or recommended.
```php
$config['service_poller_enabled'] = true;
```
```php
$config['service_poller_workers'] = 16;
```
```php
$config['service_poller_frequency'] = 300;
```
```php
$config['service_poller_down_retry'] = 5;
```
```php
$config['service_discovery_enabled'] = true;
```
```php
$config['service_discovery_workers'] = 16;
```
```php
$config['service_discovery_frequency'] = 3600;
```
```php
$config['service_services_enabled'] = true;
```
```php
$config['service_services_workers'] = 16;
```
```php
$config['service_services_frequency'] = 60;
```
## Service checks polling logic
Service check is skipped when the associated device is not pingable,

View File

@ -1,7 +1,11 @@
<?php
use App\Http\Controllers\ServiceTemplateController;
use LibreNMS\Config;
if (Config::get('discover_services_templates')) {
ServiceTemplateController::applyAll();
}
if (Config::get('discover_services')) {
// FIXME: use /etc/services?
$known_services = [

View File

@ -18,6 +18,7 @@ use App\Models\DeviceGroup;
use App\Models\DeviceOutage;
use App\Models\PortsFdb;
use App\Models\Sensor;
use App\Models\ServiceTemplate;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Routing\Router;
use Illuminate\Support\Arr;
@ -2328,6 +2329,57 @@ function missing_fields($required_fields, $data)
return false;
}
function add_service_template_for_device_group(Illuminate\Http\Request $request)
{
$data = json_decode($request->getContent(), true);
if (json_last_error() || ! is_array($data)) {
return api_error(400, "We couldn't parse the provided json. " . json_last_error_msg());
}
$rules = [
'name' => 'required|string|unique:service_templates',
'device_group_id' => 'integer',
'type' => 'string',
'param' => 'nullable|string',
'ip' => 'nullable|string',
'desc' => 'nullable|string',
'changed' => 'integer',
'disabled' => 'integer',
'ignore' => 'integer',
];
$v = Validator::make($data, $rules);
if ($v->fails()) {
return api_error(422, $v->messages());
}
// Only use the rules if they are able to be parsed by the QueryBuilder
$query = QueryBuilderParser::fromJson($data['rules'])->toSql();
if (empty($query)) {
return api_error(500, "We couldn't parse your rule");
}
$serviceTemplate = ServiceTemplate::make(['name' => $data['name'], 'device_group_id' => $data['device_group_id'], 'type' => $data['type'], 'param' => $data['param'], 'ip' => $data['ip'], 'desc' => $data['desc'], 'changed' => $data['changed'], 'disabled' => $data['disabled'], 'ignore' => $data['ignore']]);
$serviceTemplate->save();
return api_success($serviceTemplate->id, 'id', 'Service Template ' . $serviceTemplate->name . ' created', 201);
}
function get_service_templates(Illuminate\Http\Request $request)
{
if ($request->user()->cannot('viewAny', ServiceTemplate::class)) {
return api_error(403, 'Insufficient permissions to access service templates');
}
$templates = ServiceTemplate::query()->orderBy('name')->get();
if ($templates->isEmpty()) {
return api_error(404, 'No service templates found');
}
return api_success($templates->makeHidden('pivot')->toArray(), 'templates', 'Found ' . $templates->count() . ' service templates');
}
function add_service_for_host(Illuminate\Http\Request $request)
{
$hostname = $request->route('hostname');
@ -2344,7 +2396,9 @@ function add_service_for_host(Illuminate\Http\Request $request)
$service_desc = $data['desc'] ? $data['desc'] : '';
$service_param = $data['param'] ? $data['param'] : '';
$service_ignore = $data['ignore'] ? true : false; // Default false
$service_id = add_service($device_id, $service_type, $service_desc, $service_ip, $service_param, (int) $service_ignore);
$service_disable = $data['disable'] ? true : false; // Default false
$service_name = $data['name'];
$service_id = add_service($device_id, $service_type, $service_desc, $service_ip, $service_param, (int) $service_ignore, (int) $service_disable, 0, $service_name);
if ($service_id != false) {
return api_success_noresult(201, "Service $service_type has been added to device $hostname (#$service_id)");
}

View File

@ -24,10 +24,12 @@ $param = $vars['param'];
$ignore = isset($vars['ignore']) ? 1 : 0;
$disabled = isset($vars['disabled']) ? 1 : 0;
$device_id = $vars['device_id'];
$template_id = $vars['template_id'];
$name = $vars['name'];
if (is_numeric($service_id) && $service_id > 0) {
// Need to edit.
$update = ['service_desc' => $desc, 'service_ip' => $ip, 'service_param' => $param, 'service_ignore' => $ignore, 'service_disabled' => $disabled];
$update = ['service_desc' => $desc, 'service_ip' => $ip, 'service_param' => $param, 'service_ignore' => $ignore, 'service_disabled' => $disabled, 'service_template_id' => $template_id, 'service_name' => $name];
if (is_numeric(edit_service($update, $service_id))) {
$status = ['status' =>0, 'message' => 'Modified Service: <i>' . $service_id . ': ' . $type . '</i>'];
} else {
@ -35,7 +37,7 @@ if (is_numeric($service_id) && $service_id > 0) {
}
} else {
// Need to add.
$service_id = add_service($device_id, $type, $desc, $ip, $param, $ignore, $disabled);
$service_id = add_service($device_id, $type, $desc, $ip, $param, $ignore, $disabled, 0, $name);
if ($service_id == false) {
$status = ['status' =>1, 'message' => 'ERROR: Failed to add Service: <i>' . $type . '</i>'];
} else {

View File

@ -27,7 +27,9 @@ if (is_numeric($service_id) && $service_id > 0) {
'desc' => $service[0]['service_desc'],
'param' => $service[0]['service_param'],
'ignore' => $service[0]['service_ignore'],
'disabled' => $service[0]['service_disabled'],
'disabled' => $service[0]['service_disabled'],
'template_id' => $service[0]['service_template_id'],
'name' => $service[0]['service_name'],
];
header('Content-Type: application/json');

View File

@ -30,64 +30,88 @@ if (Auth::user()->hasGlobalAdmin()) {
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h5 class="modal-title" id="Create">Services</h5>
<h2 class="modal-title" id="Create">Add / Edit Service</h2>
</div>
<div class="modal-body">
<form method="post" role="form" id="service" class="form-horizontal service-form">
<?php echo csrf_field() ?>
<input type="hidden" name="service_id" id="service_id" value="">
<input type="hidden" name="device_id" id="device_id" value="<?php echo $device['device_id']?>">
<input type="hidden" name="type" id="type" value="create-service">
<div class="form-service">
<div class="col-sm-12">
<span id="ajax_response">&nbsp;</span>
<div class='alert alert-info'>Service will modified for the specified Device.</div>
<div class='well well-lg'>
<div class="modal-body">
<form method="post" role="form" id="service" class="form-horizontal service-form">
<?php echo csrf_field() ?>
<input type="hidden" name="service_id" id="service_id" value="">
<input type="hidden" name="service_template_id" id="service_template_id" value="">
<input type="hidden" name="device_id" id="device_id" value="<?php echo $device['device_id']?>">
<input type="hidden" name="type" id="type" value="create-service">
<div class="form-group">
<div class="col-sm-12">
<span id="ajax_response">&nbsp;</span>
</div>
</div>
</div>
<div class="form-service row">
<label for='stype' class='col-sm-3 control-label'>Type: </label>
<div class="col-sm-9">
<select id='stype' name='stype' placeholder='type' class='form-control has-feedback'>
<?php echo $stype?>
</select>
<div class="form-group row">
<label for='name' class='col-sm-3 control-label'>Name: </label>
<div class="col-sm-9">
<input type='text' id='name' name='name' class='form-control input-sm' placeholder=''/>
</div>
<div class='col-sm-9'>
</div>
</div>
</div>
<div class='form-service row'>
<label for='desc' class='col-sm-3 control-label'>Description: </label>
<div class='col-sm-9'>
<textarea id='desc' name='desc' class='form-control'></textarea>
<div class="form-group row">
<label for='stype' class='col-sm-3 control-label'>Check Type: </label>
<div class="col-sm-9">
<select id='stype' name='stype' placeholder='type' class='form-control has-feedback'>
<?php echo $stype?>
</select>
</div>
<div class='col-sm-9'>
</div>
</div>
</div>
<div class="form-service row">
<label for='ip' class='col-sm-3 control-label'>IP Address: </label>
<div class="col-sm-9">
<input type='text' id='ip' name='ip' class='form-control has-feedback' placeholder='<?php echo $device['hostname']?>'/>
<div class='form-group row'>
<label for='desc' class='col-sm-3 control-label'>Description: </label>
<div class='col-sm-9'>
<textarea id='desc' name='desc' class='form-control' rows='5'></textarea>
</div>
<div class='col-sm-9'>
</div>
</div>
</div>
<div class="form-service row">
<label for='param' class='col-sm-3 control-label'>Parameters: </label>
<div class="col-sm-9">
<input type='text' id='param' name='param' class='form-control has-feedback' placeholder=''/>
<div class="form-group row">
<label for='ip' class='col-sm-3 control-label'>Remote Host: </label>
<div class="col-sm-9">
<input type='text' id='ip' name='ip' class='form-control has-feedback' placeholder='IP Address or Hostname'/>
</div>
<div class='col-sm-9'>
</div>
</div>
</div>
<div class="form-service row">
<label for='ignore' class='col-sm-3 control-label'>Ignore alert tag: </label>
<div class="col-sm-9">
<input type='checkbox' id='ignore' name='ignore'>
<div class="form-group row">
<label for='param' class='col-sm-3 control-label'>Parameters: </label>
<div class="col-sm-9">
<input type='text' id='param' name='param' class='form-control has-feedback' placeholder=''/>
</div>
<div class='col-sm-9'>
</div>
</div>
</div>
<div class="form-service row">
<label for='disabled' class='col-sm-3 control-label'>Disable polling and alerting: </label>
<div class="col-sm-9">
<input type='checkbox' id='disabled' name='disabled'>
<div class="form-group row">
<div class="col-sm-12 alert alert-info">
<label class='control-label text-left input-sm'>Parameters may be required and will be different depending on the service check.</label>
</div>
</div>
</div>
<div class="form-service row">
<div class="col-sm-offset-3 col-sm-9">
<button class="btn btn-success btn-sm" type="submit" name="service-submit" id="service-submit" value="save">Save Service</button>
<div class="form-group row">
<label for='ignore' class='col-sm-3 control-label'>Ignore alert tag: </label>
<div class="col-sm-9">
<input type='checkbox' id='ignore' name='ignore'>
</div>
</div>
</div>
<div class="clearfix"></div>
</form>
<div class="form-group row">
<label for='disabled' class='col-sm-3 control-label'>Disable polling and alerting: </label>
<div class="col-sm-9">
<input type='checkbox' id='disabled' name='disabled'>
</div>
</div>
<hr>
<div class="form-group row">
<center><button class="btn btn-default btn-sm" type="submit" name="service-submit" id="service-submit" value="save">Save Service</button></center>
</div>
<div class="clearfix"></div>
</form>
</div>
</div>
</div>
</div>
@ -104,6 +128,9 @@ $('#create-service').on('hide.bs.modal', function (event) {
$('#param').val('');
$('#ignore').val('');
$('#disabled').val('');
$('#service_template_id').val('');
$('#name').val('');
$('#service_template_name').val('');
});
// on-load
@ -131,6 +158,8 @@ $('#create-service').on('show.bs.modal', function (e) {
if ($('#disabled').attr('value') == 1) {
$('#disabled').prop("checked", true);
}
$('#service_template_id').val(output['service_template_id']);
$('#name').val(output['name']);
}
});

View File

@ -9,7 +9,7 @@ if (! Auth::user()->hasGlobalAdmin()) {
if (Auth::user()->hasGlobalAdmin()) {
$updated = '1';
$service_id = add_service($vars['device'], $vars['type'], $vars['descr'], $vars['ip'], $vars['params'], 0);
$service_id = add_service($vars['device'], $vars['type'], $vars['descr'], $vars['ip'], $vars['params'], $vars['ignore'], $vars['disabled'], 0, $vars['name']);
if ($service_id) {
$message .= $message_break . 'Service added (' . $service_id . ')!';
$message_break .= '<br />';
@ -31,6 +31,8 @@ if (! Auth::user()->hasGlobalAdmin()) {
$pagetitle[] = 'Add service';
echo "<div class='row'>
<div class='col-sm-3'>
</div>
<div class='col-sm-6'>";
include_once 'includes/html/print-service-add.inc.php';

View File

@ -5,7 +5,7 @@ if (Auth::user()->hasGlobalRead()) {
if (Auth::user()->hasGlobalAdmin()) {
$updated = '1';
$service_id = add_service($vars['device'], $vars['type'], $vars['descr'], $vars['ip'], $vars['params'], 0);
$service_id = add_service($vars['device'], $vars['type'], $vars['descr'], $vars['ip'], $vars['params'], $vars['ignore'], $vars['disabled'], 0, $vars['name']);
if ($service_id) {
$message .= $message_break . 'Service added (' . $service_id . ')!';
$message_break .= '<br />';

View File

@ -173,10 +173,14 @@ require_once 'includes/html/modal/delete_service.inc.php';
echo '<table class="table table-hover table-condensed">';
echo '<thead>';
echo '<th style="width:1%;max-width:1%;"></th>';
echo '<th style="width:10%;max-width: 10%;">Service</th>';
echo '<th style="width:15%;max-width: 15%;">Last Changed</th>';
echo '<th style="width:15%;max-width: 15%;">Description</th>';
echo '<th style="width:10%;max-width: 10%;">Name</th>';
echo '<th style="width:10%;max-width: 10%;">Check Type</th>';
echo '<th style="width:10%;max-width: 15%;">Remote Host</th>';
echo '<th >Message</th>';
echo '<th style="width:15%;max-width: 15%;">Description</th>';
echo '<th style="width:15%;max-width: 15%;">Last Changed</th>';
echo '<th style="width:5%;max-width: 5%;">Ignored</th>';
echo '<th style="width:5%;max-width: 5%;">Disabled</th>';
echo '<th style="width:5%;max-width:5%;"></th>';
echo '</thead>';
}
@ -185,10 +189,14 @@ require_once 'includes/html/modal/delete_service.inc.php';
echo '<tr id="row_' . $service['service_id'] . '">';
echo '<td><span data-toggle="tooltip" title="' . $title . '" class="alert-status ' . $label . '"></span></td>';
echo '<td>' . nl2br(display($service['service_name'])) . '</td>';
echo '<td>' . nl2br(display($service['service_type'])) . '</td>';
echo '<td>' . formatUptime(time() - $service['service_changed']) . '</td>';
echo '<td>' . nl2br(display($service['service_desc'])) . '</td>';
echo '<td>' . nl2br(display($service['service_ip'])) . '</td>';
echo '<td>' . nl2br(display($service['service_message'])) . '</td>';
echo '<td>' . nl2br(display($service['service_desc'])) . '</td>';
echo '<td>' . formatUptime(time() - $service['service_changed']) . '</td>';
echo '<td>' . nl2br(display($service['service_ignore'])) . '</td>';
echo '<td>' . nl2br(display($service['service_disabled'])) . '</td>';
if (Auth::user()->hasGlobalAdmin()) {
echo "<td>

View File

@ -1,13 +1,22 @@
<?php
echo "
<h3><span class='label label-success threeqtr-width'>Add Service</span></h3>
<form id='addsrv' name='addsrv' method='post' action='' class='form-horizontal' role='form'>
" . csrf_field() . "
<div><h2>Add Service</h2></div>
<div class='alert alert-info'>Service will created for the specified Device.</div>
<div class='well well-lg'>
<div class='form-group'>
<label for='name' class='col-sm-2 control-label'>Name:</label>
<div class='col-sm-5'>
<input name='name' id='name' class='form-control input-sm'>
</div>
<div class='col-sm-5'>
</div>
</div>
<div class='form-group'>
<input type='hidden' name='addsrv' value='yes'>
<label for='device' class='col-sm-2 control-label'>Device</label>
<label for='device' class='col-sm-2 control-label'>Device:</label>
<div class='col-sm-5'>
<select name='device' class='form-control input-sm'>
$devicesform
@ -17,7 +26,7 @@ echo "
</div>
</div>
<div class='form-group'>
<label for='type' class='col-sm-2 control-label'>Type</label>
<label for='type' class='col-sm-2 control-label'>Check Type:</label>
<div class='col-sm-5'>
<select name='type' id='type' class='form-control input-sm'>
$servicesform
@ -27,7 +36,7 @@ echo "
</div>
</div>
<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-5'>
<textarea name='descr' id='descr' class='form-control input-sm' rows='5'></textarea>
</div>
@ -35,22 +44,37 @@ echo "
</div>
</div>
<div class='form-group'>
<label for='ip' class='col-sm-2 control-label'>IP Address</label>
<label for='ip' class='col-sm-2 control-label'>Remote Host:</label>
<div class='col-sm-5'>
<input name='ip' id='ip' class='form-control input-sm' placeholder='IP Address'>
<input name='ip' id='ip' class='form-control input-sm' placeholder='IP Address or Hostname'>
</div>
<div class='col-sm-5'>
</div>
</div>
<div class='form-group'>
<label for='params' class='col-sm-2 control-label'>Parameters</label>
<label for='params' class='col-sm-2 control-label'>Parameters:</label>
<div class='col-sm-5'>
<input name='params' id='params' class='form-control input-sm'>
</div>
<div class='col-sm-5'>
This may be required based on the service check.
</div>
<div class='form-group'>
<div class='col-sm-12 alert alert-info'>
<label class='control-label text-left input-sm'>Parameters may be required and will be different depending on the service check.</label>
</div>
</div>
<button type='submit' name='Submit' class='btn btn-success input-sm'>Add Service</button>
<div class='form-group'>
<label for='ignore' class='col-sm-2 control-label'>Ignore Alert Tag:</label>
<div class='col-sm-5'>
<input name='ignore' id='ignore' type='checkbox'>
</div>
</div>
<div class='form-group'>
<label for='disabled' class='col-sm-2 control-label'>Disable Polling and Alerting: </label>
<div class='col-sm-5'>
<input name='disabled' id='disabled' type='checkbox'>
</div>
</div>
<hr>
<center><button type='submit' name='Submit' class='btn btn-default input-sm'>Add Service</button></center>
</div>
</form>";

View File

@ -36,7 +36,7 @@ function get_service_status($device = null)
return $service_count;
}
function add_service($device, $type, $desc, $ip = '', $param = '', $ignore = 0, $disabled = 0)
function add_service($device, $type, $desc, $ip = '', $param = '', $ignore = 0, $disabled = 0, $template_id = '', $name)
{
if (! is_array($device)) {
$device = device_by_id_cache($device);
@ -46,14 +46,14 @@ function add_service($device, $type, $desc, $ip = '', $param = '', $ignore = 0,
$ip = Device::pollerTarget($device['hostname']);
}
$insert = ['device_id' => $device['device_id'], 'service_ip' => $ip, 'service_type' => $type, 'service_changed' => ['UNIX_TIMESTAMP(NOW())'], 'service_desc' => $desc, 'service_param' => $param, 'service_ignore' => $ignore, 'service_status' => 3, 'service_message' => 'Service not yet checked', 'service_ds' => '{}', 'service_disabled' => $disabled];
$insert = ['device_id' => $device['device_id'], 'service_ip' => $ip, 'service_type' => $type, 'service_changed' => ['UNIX_TIMESTAMP(NOW())'], 'service_desc' => $desc, 'service_param' => $param, 'service_ignore' => $ignore, 'service_status' => 3, 'service_message' => 'Service not yet checked', 'service_ds' => '{}', 'service_disabled' => $disabled, 'service_template_id' => $template_id, 'service_name' => $name];
return dbInsert($insert, 'services');
}
function service_get($device = null, $service = null)
{
$sql_query = 'SELECT `service_id`,`device_id`,`service_ip`,`service_type`,`service_desc`,`service_param`,`service_ignore`,`service_status`,`service_changed`,`service_message`,`service_disabled`,`service_ds` FROM `services` WHERE';
$sql_query = 'SELECT `service_id`,`device_id`,`service_ip`,`service_type`,`service_desc`,`service_param`,`service_ignore`,`service_status`,`service_changed`,`service_message`,`service_disabled`,`service_ds`,`service_template_id`,`service_name` FROM `services` WHERE';
$sql_param = [];
$add = 0;
@ -108,7 +108,7 @@ function delete_service($service = null)
function discover_service($device, $service)
{
if (! dbFetchCell('SELECT COUNT(service_id) FROM `services` WHERE `service_type`= ? AND `device_id` = ?', [$service, $device['device_id']])) {
add_service($device, $service, "(Auto discovered) $service");
add_service($device, $service, "$service Monitoring (Auto Discovered)", null, null, 0, 0, 0, "AUTO: $service");
log_event('Autodiscovered service: type ' . mres($service), $device, 'service', 2);
echo '+';
}
@ -326,12 +326,5 @@ function check_service($command)
*/
function list_available_services()
{
$services = [];
foreach (scandir(Config::get('nagios_plugins')) as $file) {
if (substr($file, 0, 6) === 'check_') {
$services[] = substr($file, 6);
}
}
return $services;
return \LibreNMS\Services::list();
}

View File

@ -947,6 +947,10 @@
"default": false,
"type": "boolean"
},
"discover_services_templates": {
"default": false,
"type": "boolean"
},
"discovery_by_ip": {
"default": false,
"type": "boolean"

View File

@ -1755,19 +1755,60 @@ services:
Columns:
- { Field: service_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
- { Field: device_id, Type: 'int unsigned', 'Null': false, Extra: '' }
- { Field: service_ip, Type: text, 'Null': false, Extra: '' }
- { Field: service_ip, Type: text, 'Null': true, Extra: '' }
- { Field: service_type, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: service_desc, Type: text, 'Null': false, Extra: '' }
- { Field: service_param, Type: text, 'Null': false, Extra: '' }
- { Field: service_ignore, Type: tinyint, 'Null': false, Extra: '' }
- { Field: service_desc, Type: text, 'Null': true, Extra: '' }
- { Field: service_param, Type: text, 'Null': true, Extra: '' }
- { Field: service_ignore, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
- { Field: service_status, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
- { Field: service_changed, Type: 'int unsigned', 'Null': false, Extra: '', Default: '0' }
- { Field: service_message, Type: text, 'Null': false, Extra: '' }
- { Field: service_message, Type: text, 'Null': true, Extra: '' }
- { Field: service_disabled, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
- { Field: service_ds, Type: text, 'Null': false, Extra: '' }
- { Field: service_ds, Type: text, 'Null': true, Extra: '' }
- { Field: service_template_id, Type: 'int unsigned', 'Null': false, Extra: '', Default: '0' }
- { Field: service_name, Type: varchar(255), 'Null': true, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [service_id], Unique: true, Type: BTREE }
services_device_id_index: { Name: services_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
service_templates:
Columns:
- { Field: id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
- { Field: ip, Type: text, 'Null': true, Extra: '' }
- { Field: type, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: dtype, Type: varchar(16), 'Null': false, Extra: '', Default: static }
- { Field: dgtype, Type: varchar(16), 'Null': false, Extra: '', Default: static }
- { Field: drules, Type: text, 'Null': true, Extra: '' }
- { Field: dgrules, Type: text, 'Null': true, Extra: '' }
- { Field: desc, Type: text, 'Null': true, Extra: '' }
- { Field: param, Type: text, 'Null': true, Extra: '' }
- { Field: ignore, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
- { Field: changed, Type: 'timestamp', 'Null': false, , Extra: 'on update CURRENT_TIMESTAMP', Default: CURRENT_TIMESTAMP }
- { Field: disabled, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
- { Field: name, Type: varchar(255), 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
service_templates_device:
Columns:
- { Field: service_template_id, Type: 'int unsigned', 'Null': false, Extra: '' }
- { Field: device_id, Type: 'int unsigned', 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [service_template_id, device_id], Unique: true, Type: BTREE }
service_templates_device_service_template_id_index: { Name: service_templates_device_service_template_id_index, Columns: [service_template_id], Unique: false, Type: BTREE }
service_templates_device_device_id_index: { Name: service_templates_device_device_id_index, Columns: [device_id], Unique: false, Type: BTREE }
Constraints:
service_templates_device_service_template_id_foreign: { name: service_templates_device_service_template_id_foreign, foreign_key: service_template_id, table: service_templates, key: id, extra: 'ON DELETE CASCADE' }
service_templates_device_device_id_foreign: { name: service_templates_device_device_id_foreign, foreign_key: device_id, table: devices, key: device_id, extra: 'ON DELETE CASCADE' }
service_templates_device_group:
Columns:
- { Field: service_template_id, Type: 'int unsigned', 'Null': false, Extra: '' }
- { Field: device_group_id, Type: 'int unsigned', 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [service_template_id, device_group_id], Unique: true, Type: BTREE }
service_templates_device_group_service_template_id_index: { Name: service_templates_device_group_service_template_id_index, Columns: [service_template_id], Unique: false, Type: BTREE }
service_templates_device_group_device_group_id_index: { Name: service_templates_device_group_device_group_id_index, Columns: [device_group_id], Unique: false, Type: BTREE }
Constraints:
service_templates_device_group_service_template_id_foreign: { name: service_templates_device_group_service_template_id_foreign, foreign_key: service_template_id, table: service_templates, key: id, extra: 'ON DELETE CASCADE' }
service_templates_device_group_device_group_id_foreign: { name: service_templates_device_group_device_group_id_foreign, foreign_key: device_group_id, table: device_groups, key: id, extra: 'ON DELETE CASCADE' }
session:
Columns:
- { Field: session_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }

View File

@ -234,8 +234,12 @@
aria-hidden="true"></i> <span
class="hidden-sm">@lang('Services')</span></a>
<ul class="dropdown-menu">
<li><a href="{{ url('services') }}"><i class="fa fa-cogs fa-fw fa-lg"
aria-hidden="true"></i> @lang('All Services')</a>
<li><a href="{{ url('services') }}"><i class="fa fa-cogs fa-fw fa-lg" aria-hidden="true"></i> @lang('All Services')</a>
</li>
<li><a href="{{ route('services.templates.index') }}"><span class="fa-stack" aria-hidden="true" style="font-size: 12px">
<i class="fa fa-square fa-stack-2x"></i>
<i class="fa fa-cogs fa-stack-1x fa-inverse"></i>
</span> @lang('Services Templates')</a>
</li>
@if($service_counts['warning'] || $service_counts['critical'])
<li role="presentation" class="divider"></li>

View File

@ -0,0 +1,31 @@
@extends('layouts.librenmsv1')
@section('title', __('Create Service Template'))
@section('content')
<div class="container">
<div class="row">
<form action="{{ route('services.templates.store') }}" method="POST" role="form"
class="form-horizontal services-templates-form col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 col-sm-12">
<legend><h2>@lang('Create Service Template')</h2></legend>
<div class='alert alert-info'>Service Template will created for the specified Device Group.</div>
@csrf
<div class='well well-lg'>
@include('service-template.form')
<div class="form-group">
<hr>
<center><button type="submit" class="btn btn-primary">@lang('Save')</button>
<a type="button" class="btn btn-danger"
href="{{ route('services.templates.index') }}">@lang('Cancel')</a>
</center>
</div>
</div>
</form>
</div>
</div>
@endsection
@section('javascript')
<script src="{{ asset('js/sql-parser.min.js') }}"></script>
<script src="{{ asset('js/query-builder.standalone.min.js') }}"></script>
@endsection

View File

@ -0,0 +1,32 @@
@extends('layouts.librenmsv1')
@section('title', __('Edit Service Template'))
@section('content')
<div class="container">
<div class="row">
<form action="{{ route('services.templates.update', $template->id) }}" method="POST" role="form"
class="form-horizontal services-templates-form col-md-10 col-md-offset-1 col-sm-12">
<legend><h2>@lang('Edit Service Template'): {{ $template->name }}</h2></legend>
<div class='alert alert-info'>Service Template will edited for the specified Device Group.</div>
{{ method_field('PUT') }}
@csrf
<div class='well well-lg'>
@include('service-template.form')
<div class="form-group">
<center><div class="col-sm-9 col-sm-offset-3 col-md-10 col-sm-offset-2">
<button type="submit" class="btn btn-primary">@lang('Save')</button>
<a type="button" class="btn btn-danger"
href="{{ route('services.templates.index') }}">@lang('Cancel')</a>
</div></center>
</div>
</div>
</form>
</div>
</div>
@endsection
@section('javascript')
<script src="{{ asset('js/sql-parser.min.js') }}"></script>
<script src="{{ asset('js/query-builder.standalone.min.js') }}"></script>
@endsection

View File

@ -0,0 +1,288 @@
<div class="form-group @if($errors->has('name')) has-error @endif">
<label for="name" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Name')</label>
<div class="col-sm-9 col-md-10">
<input type="text" class="form-control" id="name" name="name" value="{{ old('name', $template->name) }}">
<span class="help-block">{{ $errors->first('name') }}</span>
</div>
</div>
<div class="form-group @if($errors->has('dtype')) has-error @endif">
<label for="dtype" class="control-label col-sm-3 col-md-2">@lang('Device Type')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="dtype" name="dtype" onchange="change_st_dtype(this)">
<option value="static"
@if(old('dtype', $template->dtype) == 'static') selected @endif>@lang('Static')</option>
<option value="dynamic"
@if(old('dtype', $template->dtype) == 'dynamic') selected @endif>@lang('Dynamic')</option>
</select>
<span class="help-block">{{ $errors->first('dtype') }}</span>
</div>
</div>
<div id="dynamic-st-d-form" class="form-group @if($errors->has('drules')) has-error @endif">
<label for="drules" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Define Device Rules')</label>
<div class="col-sm-9 col-md-10">
<div id="builder"></div>
<span class="help-block">{{ $errors->first('drules') }}</span>
</div>
</div>
<div id="static-st-d-form" class="form-group @if($errors->has('devices')) has-error @endif" style="display: none">
<label for="devices" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Select Devices')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="devices" name="devices[]" multiple>
@foreach($template->devices as $device)
<option value="{{ $device->device_id }}" selected>{{ $device->displayName() }}</option>
@endforeach
</select>
<span class="help-block">{{ $errors->first('devices') }}</span>
</div>
</div>
<div class="form-group @if($errors->has('dgtype')) has-error @endif">
<label for="dgtype" class="control-label col-sm-3 col-md-2">@lang('Device Group Type')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="dgtype" name="dgtype" onchange="change_st_dgtype(this)">
<option value="static"
@if(old('dgtype', $template->dgtype) == 'static') selected @endif>@lang('Static')</option>
<option value="dynamic"
@if(old('dgtype', $template->dgtype) == 'dynamic') selected @endif>@lang('Dynamic')</option>
</select>
<span class="help-block">{{ $errors->first('dgtype') }}</span>
</div>
</div>
<div id="dynamic-st-dg-form" class="form-group @if($errors->has('dgrules')) has-error @endif">
<label for="dgrules" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Device Group Rules')</label>
<div class="col-sm-9 col-md-10">
<div id="builder2"></div>
<span class="help-block">{{ $errors->first('dgrules') }}</span>
</div>
</div>
<div id="static-st-dg-form" class="form-group @if($errors->has('groups')) has-error @endif" style="display: none">
<label for="groups" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Device Groups')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="groups" name="groups[]" multiple>
@foreach($template->groups as $group)
<option value="{{ $group->id }}" selected>{{ $group->name }}</option>
@endforeach
</select>
<span class="help-block">{{ $errors->first('groups') }}</span>
</div>
</div>
<div class="form-group @if($errors->has('type')) has-error @endif">
<label for="type" class="control-label col-sm-3 col-md-2">@lang('Check Type')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="type" name="type">
@foreach($services as $current_service)
<option value="{{ $current_service }}" @if($current_service == $template->type) selected @endif>{{ $current_service }}</option>
@endforeach
</select>
<span class="help-block">{{ $errors->first('type') }}</span>
</div>
</div>
<div class="form-group @if($errors->has('desc')) has-error @endif">
<label for="desc" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Description')</label>
<div class="col-sm-9 col-md-10">
<input type="text" class="form-control" id="desc" name="desc" value="{{ old('desc', $template->desc) }}">
<span class="help-block">{{ $errors->first('desc') }}</span>
</div>
</div>
<div class="form-group @if($errors->has('ip')) has-error @endif">
<label for="ip" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Remote Host')</label>
<div class="col-sm-9 col-md-10">
<input type="text" class="form-control" id="ip" name="ip" value="{{ old('ip', $template->ip) }}">
<span class="help-block">{{ $errors->first('ip') }}</span>
</div>
</div>
<div class="form-group @if($errors->has('param')) has-error @endif">
<label for="param" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Parameters')</label>
<div class="col-sm-9 col-md-10">
<input type="text" class="form-control" id="param" name="param" value="{{ old('param', $template->param) }}">
<span class="help-block">{{ $errors->first('param') }}</span>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12 alert alert-info">
<label class='control-label text-left input-sm'>Parameters may be required and will be different depending on the service check.</label>
</div>
</div>
<div class="form-group @if($errors->has('ignore')) has-error @endif">
<label for="ignore" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Ignore alert tag')</label>
<div class="col-sm-9 col-md-10">
<input type="hidden" value="0" name="ignore">
<input type="checkbox" class="form-control" id="ignore" name="ignore" data-size="small" value="{{ old('ignore', $template->ignore) }}"@if(old('ignore', $template->ignore) == 1) checked @endif>
<span class="help-block">{{ $errors->first('ignore') }}</span>
</div>
</div>
<div class="form-group @if($errors->has('disabled')) has-error @endif">
<label for="disabled" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Disable polling and alerting')</label>
<div class="col-sm-9 col-md-10">
<input type="hidden" value="0" name="disabled">
<input type="checkbox" class="form-control" id="disabled" name="disabled" data-size="small" value="{{ old('disabled', $template->disabled) }}"@if(old('disabled', $template->disabled) == 1) checked @endif>
<span class="help-block">{{ $errors->first('disabled') }}</span>
</div>
</div>
<script>
$("[type='checkbox']").bootstrapSwitch('offColor','danger');
$("#ignore").on( 'switchChange.bootstrapSwitch', function (e, state) {
var value = $(this).is(':checked') ? "1": "0";
$('#ignore').val(value);
});
$("#disabled").on( 'switchChange.bootstrapSwitch', function (e, state) {
var value = $(this).is(':checked') ? "1": "0";
$('#disabled').val(value);
});
function change_st_dtype(select) {
var dtype = select.options[select.selectedIndex].value;
document.getElementById("dynamic-st-d-form").style.display = (dtype === 'dynamic' ? 'block' : 'none');
document.getElementById("static-st-d-form").style.display = (dtype === 'dynamic' ? 'none' : 'block');
}
change_st_dtype(document.getElementById('dtype'));
function change_st_dgtype(select) {
var dgtype = select.options[select.selectedIndex].value;
document.getElementById("dynamic-st-dg-form").style.display = (dgtype === 'dynamic' ? 'block' : 'none');
document.getElementById("static-st-dg-form").style.display = (dgtype === 'dynamic' ? 'none' : 'block');
}
change_st_dgtype(document.getElementById('dgtype'));
init_select2('#devices', 'device', {multiple: true});
init_select2('#groups', 'device-group', {multiple: true});
var builder = $('#builder').on('afterApplyRuleFlags.queryBuilder afterCreateRuleFilters.queryBuilder', function () {
$("[name$='_filter']").each(function () {
$(this).select2({
dropdownAutoWidth: true,
width: 'auto'
});
});
}).on('ruleToSQL.queryBuilder.filter', function (e, rule) {
if (rule.operator === 'regexp') {
e.value += ' \'' + rule.value + '\'';
}
}).queryBuilder({
plugins: [
'bt-tooltip-errors'
// 'not-group'
],
filters: {!! $filters !!},
operators: [
'equal', 'not_equal', 'between', 'not_between', 'begins_with', 'not_begins_with', 'contains', 'not_contains', 'ends_with', 'not_ends_with', 'is_empty', 'is_not_empty', 'is_null', 'is_not_null', 'in', 'not_in',
{type: 'less', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'regex', nb_inputs: 1, multiple: false, apply_to: ['string', 'number']},
{type: 'not_regex', nb_inputs: 1, multiple: false, apply_to: ['string', 'number']}
],
lang: {
operators: {
regexp: 'regex',
not_regex: 'not regex'
}
},
sqlOperators: {
regexp: {op: 'REGEXP'},
not_regexp: {op: 'NOT REGEXP'}
},
sqlRuleOperator: {
'REGEXP': function (v) {
return {val: v, op: 'regexp'};
},
'NOT REGEXP': function (v) {
return {val: v, op: 'not_regexp'};
}
}
});
var builder2 = $('#builder2').on('afterApplyRuleFlags.queryBuilder afterCreateRuleFilters.queryBuilder', function () {
$("[name$='_filter']").each(function () {
$(this).select2({
dropdownAutoWidth: true,
width: 'auto'
});
});
}).on('ruleToSQL.queryBuilder.filter', function (e, rule) {
if (rule.operator === 'regexp') {
e.value += ' \'' + rule.value + '\'';
}
}).queryBuilder({
plugins: [
'bt-tooltip-errors'
// 'not-group'
],
filters: {!! $filters !!},
operators: [
'equal', 'not_equal', 'between', 'not_between', 'begins_with', 'not_begins_with', 'contains', 'not_contains', 'ends_with', 'not_ends_with', 'is_empty', 'is_not_empty', 'is_null', 'is_not_null', 'in', 'not_in',
{type: 'less', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'regex', nb_inputs: 1, multiple: false, apply_to: ['string', 'number']},
{type: 'not_regex', nb_inputs: 1, multiple: false, apply_to: ['string', 'number']}
],
lang: {
operators: {
regexp: 'regex',
not_regex: 'not regex'
}
},
sqlOperators: {
regexp: {op: 'REGEXP'},
not_regexp: {op: 'NOT REGEXP'}
},
sqlRuleOperator: {
'REGEXP': function (v) {
return {val: v, op: 'regexp'};
},
'NOT REGEXP': function (v) {
return {val: v, op: 'not_regexp'};
}
}
});
$('.service-template-form').submit(function (eventObj) {
if ($('#dtype').val() === 'static') {
return true;
} else {
$('<input type="hidden" name="drules" />')
.attr('value', JSON.stringify(builder.queryBuilder('getRules')))
.appendTo(this);
if (!builder.queryBuilder('validate')) {
return false;
}
}
if ($('#dgtype').val() === 'static') {
return true;
} else {
$('<input type="hidden" name="dgrules" />')
.attr('value', JSON.stringify(builder2.queryBuilder('getRules')))
.appendTo(this);
if (!builder2.queryBuilder('validate')) {
return false;
}
}
return true;
});
</script>
<script>
var drules = {!! json_encode(old('drules') ? json_decode(old('drules')) : $template->drules) !!};
if (drules) {
builder.queryBuilder('setRules', drules);
}
var dgrules = {!! json_encode(old('dgrules') ? json_decode(old('dgrules')) : $template->dgrules) !!};
if (dgrules) {
builder2.queryBuilder('setRules', dgrules);
}
</script>

View File

@ -0,0 +1,229 @@
@extends('layouts.librenmsv1')
@section('title', __('Services Templates'))
@section('content')
<div class="container-fluid">
<x-panel id="manage-services-templates-panel">
<x-slot name="title">
<span class="fa-stack" aria-hidden="true">
<i class="fa fa-square fa-stack-2x"></i>
<i class="fa fa-cogs fa-stack-1x fa-inverse"></i>
</span> @lang('Services Templates')
</x-slot>
<div class="row" style="padding-bottom: 16px;">
<div class="col-md-12">
<a type="button" class="btn btn-primary" href="{{ route('services.templates.create') }}">
<i class="fa fa-plus"></i> @lang('New Service Template')
</a>
<button type="button" title="@lang('Apply Service Templates')" class="btn btn-success" aria-label="@lang('Apply Service Templates')"
onclick="applyAll_st(this, '{{ route('services.templates.applyAll') }}')">
<i
class="fa fa-refresh" aria-hidden="true"></i> @lang('Apply Service Templates')</button>
</div>
</div>
<div class="table-responsive">
<table id="manage-services-templates-table" class="table table-condensed table-hover">
<thead>
<tr>
<th>@lang('Name')</th>
<th>@lang('Description')</th>
<th>@lang('Devices')</th>
<th>@lang('Device Groups')</th>
<th>@lang('Device Type')</th>
<th>@lang('Device Group Type')</th>
<th>@lang('Device Rules')</th>
<th>@lang('Device Group Rules')</th>
<th>@lang('Actions')</th>
</tr>
</thead>
<tbody>
@foreach($service_templates as $template)
<tr id="row_{{ $template->id }}">
<td>{{ $template->name }}</td>
<td>{{ $template->desc }}</td>
<td>
<a href="{{ url("/devices/serviceTemplates=$template->id") }}">{{ $template->devices_count }}</a>
</td>
<td>
<a href="{{ url("/device-groups/serviceTemplates=$template->id") }}">{{ $template->groups_count }}</a>
</td>
<td>{{ __(ucfirst($template->dtype)) }}</td>
<td>{{ __(ucfirst($template->dgtype)) }}</td>
<td>{{ $template->dtype == 'dynamic' ? $template->getDeviceParser()->toSql(false) : '' }}</td>
<td>{{ $template->dgtype == 'dynamic' ? $template->getDeviceGroupParser()->toSql(false) : '' }}</td>
<td>
<button type="button" title="@lang('Apply Services for this Service Template')" class="btn btn-success btn-sm" aria-label="@lang('Apply')"
onclick="apply_st(this, '{{ $template->name }}', '{{ $template->id }}', '{{ route('services.templates.apply', $template->id) }}')">
<i class="fa fa-refresh" aria-hidden="true"></i></button>
<button type="button" title="@lang('Remove Services for this Service Template')" class="btn btn-warning btn-sm" aria-label="@lang('Remove')"
onclick="remove_st(this, '{{ $template->name }}', '{{ $template->id }}', '{{ route('services.templates.remove', $template->id) }}')">
<i class="fa fa-ban" aria-hidden="true"></i></button>
<a type="button" title="@lang('Edit Service Template')" class="btn btn-primary btn-sm" aria-label="@lang('Edit')"
href="{{ route('services.templates.edit', $template->id) }}">
<i class="fa fa-pencil" aria-hidden="true"></i></a>
<button type="button" class="btn btn-danger btn-sm" title="@lang('Delete Service Template')" aria-label="@lang('Delete')"
onclick="delete_st(this, '{{ $template->name }}', '{{ $template->id }}', '{{ route('services.templates.destroy', $template->id) }}')">
<i class="fa fa-trash" aria-hidden="true"></i></button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@foreach($groups as $group)
<x-panel id="manage-services-templates-panel-dg">
<x-slot name="title">
<i class="fa fa-th fa-fw fa-lg" aria-hidden="true"></i> @lang($group->name)
</x-slot>
<div class="table-responsive">
<table id="manage-services-templates-table-dg-{{ $group->id }}" class="table table-condensed table-hover">
<thead>
<tr>
<th>@lang('Name')</th>
<th>@lang('Check Type')</th>
<th>@lang('Parameters')</th>
<th>@lang('Remote Host')</th>
<th>@lang('Description')</th>
<th>@lang('Modified')</th>
<th>@lang('Ignored')</th>
<th>@lang('Disabled')</th>
</tr>
</thead>
<tbody>
@foreach($group->serviceTemplates as $template)
<tr id="row_{{ $template->id }}">
<td>{{ $template->name }}</td>
<td>{{ $template->type }}</td>
<td>{{ $template->param }}</td>
<td>{{ $template->ip }}</td>
<td>{{ $template->desc }}</td>
<td>{{ $template->changed }}</td>
<td>{{ $template->ignore }}</td>
<td>{{ $template->disabled }}</td>
<td>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</x-panel>
@endforeach
@foreach($devices as $device)
<x-panel id="manage-services-templates-panel-d">
<x-slot name="title">
<i class="fa fa-server fa-fw fa-lg" aria-hidden="true"></i> @lang($device->hostname)
</x-slot>
<div class="table-responsive">
<table id="manage-services-templates-table-d-{{ $device->id }}" class="table table-condensed table-hover">
<thead>
<tr>
<th>@lang('Name')</th>
<th>@lang('Check Type')</th>
<th>@lang('Parameters')</th>
<th>@lang('Remote Host')</th>
<th>@lang('Description')</th>
<th>@lang('Modified')</th>
<th>@lang('Ignored')</th>
<th>@lang('Disabled')</th>
</tr>
</thead>
<tbody>
@foreach($device->serviceTemplates as $template)
<tr id="row_{{ $template->id }}">
<td>{{ $template->name }}</td>
<td>{{ $template->type }}</td>
<td>{{ $template->param }}</td>
<td>{{ $template->ip }}</td>
<td>{{ $template->desc }}</td>
<td>{{ $template->changed }}</td>
<td>{{ $template->ignore }}</td>
<td>{{ $template->disabled }}</td>
<td>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</x-panel>
@endforeach
</x-panel>
</div>
@endsection
@section('scripts')
<script>
function apply_st(button, name, id, url) {
if (confirm('@lang('Are you sure you want to create Services for ')' + name + '?')) {
$.ajax({
url: url,
type: 'POST',
success: function (msg) {
toastr.success(msg);
},
error:function(){
toastr.error('No Services were updated when Applying this Service Template');
}
});
}
}
function applyAll_st(button, url) {
if (confirm('@lang('Are you sure you want to Apply All Service Templates?')')) {
$.ajax({
url: url,
type: 'POST',
success: function (msg) {
toastr.success(msg);
},
error:function(){
toastr.error('No Services were updated');
}
});
}
}
function remove_st(button, name, id, url) {
if (confirm('@lang('Are you sure you want to remove all Services created by ')' + name + '?')) {
$.ajax({
url: url,
type: 'POST',
success: function (msg) {
toastr.success(msg);
},
error: function () {
toastr.error('@lang('No Services for this Service Template were removed')');
}
});
}
return false;
}
function delete_st(button, name, id, url) {
var index = button.parentNode.parentNode.rowIndex;
if (confirm('@lang('Are you sure you want to delete AND remove all Services created by ')' + name + '?')) {
$.ajax({
url: url,
type: 'DELETE',
success: function (msg) {
document.getElementById("manage-services-templates-table").deleteRow(index);
toastr.success(msg);
},
error: function () {
toastr.error('@lang('The Service Template could not be deleted')');
}
});
}
return false;
}
</script>
@endsection
@section('css')
<style>
.table-responsive {
padding-top: 16px
}
</style>
@endsection

View File

@ -27,6 +27,12 @@ Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
Route::get('performance', 'PollerController@performanceTab')->name('poller.performance');
Route::resource('{id}/settings', 'PollerSettingsController', ['as' => 'poller'])->only(['update', 'destroy']);
});
Route::prefix('services')->name('services.')->group(function () {
Route::resource('templates', 'ServiceTemplateController');
Route::post('templates/applyAll', 'ServiceTemplateController@applyAll')->name('templates.applyAll');
Route::post('templates/apply/{template}', 'ServiceTemplateController@apply')->name('templates.apply');
Route::post('templates/remove/{template}', 'ServiceTemplateController@remove')->name('templates.remove');
});
Route::get('locations', 'LocationController@index');
Route::resource('preferences', 'UserPreferencesController', ['only' => ['index', 'store']]);
Route::resource('users', 'UserController');
@ -109,6 +115,7 @@ Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
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');
});