mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 02:18:39 +00:00
New plugin system based on Laravel Package Development (#12998)
* use Blade view and Eloquent models for plugins * move views * fix style * fix style * revert mistake * Update Plugin.php delete test property "name" * rename plugin function to settings * last but not least - rename in Test.php * Rename Test to Example * fix typo * fix style * fix style * fix style * fix style - I hate tabs... * Extract view calls * fix method calls and style * Move Models the the abstract class * fix style * Convert to traits * Change the Example description * Fix style * Fix style * Fix style * Convert plugin function to Model static methods and delete .inc.php * fix style * fix style * Use scope * final methods blows up legacy code * Config > \LibreNMS\Config * convert the static string to a static method * Correct placement in the page * fix tabs * fix style * Rename from tait to hook to make it easier to understand and be complient * rename file * Typo * Started to change the docu * change to a more usefully Device_Overview example * and activate of course * PluginManager * fix .gitignore * only php files in the root folder * corrected .gitignore with all files :) * Rename the Hooks and ExampleClass for better readability * Fix style * Fix style * Exception handling (especially if DB is not present) * Fix style and update schema * fix indentation * actually correct indent * fix migration collation check include utf8mb4_bin * stop phpstan whining * A view lines documentation * add typeHints * Allow return null on handle * lint * fix return types * fix logic of column collation check * Fix MenuEntryHook * switch to longtext instead of json type for now :D * try phpstan on PHP 7.3 * set phpstan target version to 7.3 * all the typehints * optional * more * Use namespace to prevent view collisions disambiguate plugin and hook no magic guessing of names in PluginManager, bad assumptions remove unused plugins from the DB * cleanup plugin menu * cleanup on shutdown and ignore but log query error on cleanup * instanceof must be called against an instance * Allow multiple hooks per plugin * Port plugin ui code to Laravel * page instead of settings for v1 plugins * actually working settings pages a little url cleanup plugin/admin -> plugin/settings * fix style * Add page hook * PHPstan * Try to fix Illuminate\Http\RedirectResponse * typehint * Rewrite the doc * Fix style Co-authored-by: PipoCanaja <38363551+PipoCanaja@users.noreply.github.com> Co-authored-by: Tony Murray <murraytony@gmail.com>
This commit is contained in:
parent
2d5d7e14ed
commit
98ed6bb9dc
2
.gitignore
vendored
2
.gitignore
vendored
@ -19,8 +19,6 @@ Thumbs.db
|
||||
config.php
|
||||
html/css/custom/*
|
||||
html/images/custom/*
|
||||
html/plugins/*
|
||||
!html/plugins/Test/
|
||||
includes/custom/*
|
||||
junk
|
||||
nbproject
|
||||
|
@ -214,4 +214,40 @@ class Plugins
|
||||
|
||||
return count(self::$plugins);
|
||||
}
|
||||
|
||||
public static function scanNew()
|
||||
{
|
||||
$countInstalled = 0;
|
||||
|
||||
if (file_exists(\LibreNMS\Config::get('plugin_dir'))) {
|
||||
$plugin_files = array_diff(scandir(\LibreNMS\Config::get('plugin_dir')), ['..', '.']);
|
||||
$plugin_files = array_diff($plugin_files, Plugin::versionOne()->pluck('plugin_name')->toArray());
|
||||
foreach ($plugin_files as $name) {
|
||||
if (is_dir(\LibreNMS\Config::get('plugin_dir') . '/' . $name)
|
||||
&& is_file(\LibreNMS\Config::get('plugin_dir') . '/' . $name . '/' . $name . '.php')) {
|
||||
Plugin::create(['plugin_name' => $name, 'plugin_active' => false, 'version' => 1]);
|
||||
$countInstalled++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $countInstalled;
|
||||
}
|
||||
|
||||
public static function scanRemoved()
|
||||
{
|
||||
$countRemoved = 0;
|
||||
|
||||
if (file_exists(\LibreNMS\Config::get('plugin_dir'))) {
|
||||
$plugin_files = array_diff(scandir(\LibreNMS\Config::get('plugin_dir')), ['.', '..', '.gitignore']);
|
||||
$plugins = Plugin::versionOne()->whereNotIn('plugin_name', $plugin_files)->select('plugin_id')->get();
|
||||
foreach ($plugins as $plugin) {
|
||||
if ($plugin->delete()) {
|
||||
$countRemoved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $countRemoved;
|
||||
}
|
||||
}
|
||||
|
36
app/Exceptions/PluginDoesNotImplementHookException.php
Normal file
36
app/Exceptions/PluginDoesNotImplementHookException.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/*
|
||||
* PluginDoesNotImplementHook.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class PluginDoesNotImplementHookException extends PluginException
|
||||
{
|
||||
public function __construct(string $plugin, int $code = 0, Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("Plugin ($plugin) does not implement hook.", $code, $previous);
|
||||
}
|
||||
}
|
30
app/Exceptions/PluginException.php
Normal file
30
app/Exceptions/PluginException.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/*
|
||||
* PluginException.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class PluginException extends \Exception
|
||||
{
|
||||
}
|
36
app/Facades/PluginManager.php
Normal file
36
app/Facades/PluginManager.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/*
|
||||
* PluginManager.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class PluginManager extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return \App\Plugins\PluginManager::class;
|
||||
}
|
||||
}
|
25
app/Http/Controllers/PluginAdminController.php
Normal file
25
app/Http/Controllers/PluginAdminController.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Plugins\PluginManager;
|
||||
|
||||
class PluginAdminController extends Controller
|
||||
{
|
||||
public function __invoke(PluginManager $manager): \Illuminate\Contracts\View\View
|
||||
{
|
||||
// legacy v1 plugins
|
||||
\LibreNMS\Plugins::scanNew();
|
||||
\LibreNMS\Plugins::scanRemoved();
|
||||
|
||||
// v2 cleanup
|
||||
$manager->cleanupPlugins();
|
||||
|
||||
$plugins = Plugin::get();
|
||||
|
||||
return view('plugins.admin', [
|
||||
'plugins' => $plugins,
|
||||
]);
|
||||
}
|
||||
}
|
46
app/Http/Controllers/PluginLegacyController.php
Normal file
46
app/Http/Controllers/PluginLegacyController.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PluginLegacyController extends Controller
|
||||
{
|
||||
public function redirect(Request $request, ?string $pluginName = null): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if ($request->get('view') == 'admin') {
|
||||
return redirect()->route('plugin.admin')->setStatusCode(301);
|
||||
}
|
||||
|
||||
if ($resolved_plugin_name = $request->get('p', $pluginName)) {
|
||||
return redirect()->route('plugin.legacy', ['plugin' => $resolved_plugin_name])->setStatusCode(301);
|
||||
}
|
||||
|
||||
return redirect()->route('plugin.admin');
|
||||
}
|
||||
|
||||
public function __invoke(?Plugin $plugin): \Illuminate\Contracts\View\View
|
||||
{
|
||||
if (! empty($plugin)) {
|
||||
$plugin_path = \LibreNMS\Config::get('plugin_dir') . '/' . $plugin->plugin_name . '/' . $plugin->plugin_name . '.inc.php';
|
||||
|
||||
if (is_file($plugin_path)) {
|
||||
$init_modules = ['web', 'auth'];
|
||||
require base_path('/includes/init.php');
|
||||
|
||||
chdir(base_path('html'));
|
||||
ob_start();
|
||||
include $plugin_path;
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
chdir(base_path());
|
||||
}
|
||||
}
|
||||
|
||||
return view('plugins.legacy', [
|
||||
'title' => $plugin->plugin_name ?? trans('plugins.errors.not_exist'),
|
||||
'content' => $output ?? 'This plugin is either disabled or not available.',
|
||||
]);
|
||||
}
|
||||
}
|
43
app/Http/Controllers/PluginPageController.php
Normal file
43
app/Http/Controllers/PluginPageController.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Plugins\Hooks\PageHook;
|
||||
use App\Plugins\PluginManager;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PluginPageController extends Controller
|
||||
{
|
||||
public function __invoke(PluginManager $manager, Plugin $plugin): \Illuminate\Contracts\View\View
|
||||
{
|
||||
if (! $manager->pluginEnabled($plugin->plugin_name)) {
|
||||
abort(404, trans('plugins.errors.disabled', ['plugin' => $plugin->plugin_name]));
|
||||
}
|
||||
|
||||
$data = array_merge([
|
||||
// fallbacks to prevent exceptions
|
||||
'title' => trans('plugins.settings_page', ['plugin' => $plugin->plugin_name]),
|
||||
'plugin_name' => $plugin->plugin_name,
|
||||
'plugin_id' => Plugin::where('plugin_name', $plugin->plugin_name)->value('plugin_id'),
|
||||
'settings_view' => 'plugins.missing',
|
||||
'settings' => [],
|
||||
],
|
||||
(array) $manager->call(PageHook::class, [], $plugin->plugin_name)->first()
|
||||
);
|
||||
|
||||
return view('plugins.settings', $data);
|
||||
}
|
||||
|
||||
public function update(Request $request, Plugin $plugin): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$validated = $this->validate($request, [
|
||||
'plugin_active' => 'in:0,1',
|
||||
'settings' => 'array',
|
||||
]);
|
||||
|
||||
$plugin->fill($validated)->save();
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
43
app/Http/Controllers/PluginSettingsController.php
Normal file
43
app/Http/Controllers/PluginSettingsController.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Plugins\Hooks\SettingsHook;
|
||||
use App\Plugins\PluginManager;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PluginSettingsController extends Controller
|
||||
{
|
||||
public function __invoke(PluginManager $manager, Plugin $plugin): \Illuminate\Contracts\View\View
|
||||
{
|
||||
if (! $manager->pluginEnabled($plugin->plugin_name)) {
|
||||
abort(404, trans('plugins.errors.disabled', ['plugin' => $plugin->plugin_name]));
|
||||
}
|
||||
|
||||
$data = array_merge([
|
||||
// fallbacks to prevent exceptions
|
||||
'title' => trans('plugins.settings_page', ['plugin' => $plugin->plugin_name]),
|
||||
'plugin_name' => $plugin->plugin_name,
|
||||
'plugin_id' => Plugin::where('plugin_name', $plugin->plugin_name)->value('plugin_id'),
|
||||
'settings_view' => 'plugins.missing',
|
||||
'settings' => [],
|
||||
],
|
||||
(array) $manager->call(SettingsHook::class, [], $plugin->plugin_name)->first()
|
||||
);
|
||||
|
||||
return view('plugins.settings', $data);
|
||||
}
|
||||
|
||||
public function update(Request $request, Plugin $plugin): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$validated = $this->validate($request, [
|
||||
'plugin_active' => 'in:0,1',
|
||||
'settings' => 'array',
|
||||
]);
|
||||
|
||||
$plugin->fill($validated)->save();
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
@ -37,10 +37,13 @@ use App\Models\User;
|
||||
use App\Models\UserPref;
|
||||
use App\Models\Vminfo;
|
||||
use App\Models\WirelessSensor;
|
||||
use App\Plugins\Hooks\MenuEntryHook;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Plugins;
|
||||
use LibreNMS\Util\ObjectCache;
|
||||
use PluginManager;
|
||||
|
||||
class MenuComposer
|
||||
{
|
||||
@ -250,6 +253,12 @@ class MenuComposer
|
||||
// Search bar
|
||||
$vars['typeahead_limit'] = Config::get('webui.global_search_result_limit');
|
||||
|
||||
// Plugins
|
||||
$vars['has_v1_plugins'] = Plugins::count() != 0;
|
||||
$vars['v1_plugin_menu'] = Plugins::call('menu');
|
||||
$vars['has_v2_plugins'] = PluginManager::hasHooks(MenuEntryHook::class);
|
||||
$vars['menu_hooks'] = PluginManager::call(MenuEntryHook::class);
|
||||
|
||||
$vars['browser_push'] = $user->hasBrowserPushTransport();
|
||||
|
||||
$view->with($vars);
|
||||
|
@ -31,6 +31,8 @@ class Plugin extends BaseModel
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $primaryKey = 'plugin_id';
|
||||
protected $fillable = ['plugin_name', 'plugin_active', 'version', 'settings'];
|
||||
protected $casts = ['plugin_active' => 'bool', 'settings' => 'array'];
|
||||
|
||||
// ---- Query scopes ----
|
||||
|
||||
@ -42,4 +44,14 @@ class Plugin extends BaseModel
|
||||
{
|
||||
return $query->where('plugin_active', 1);
|
||||
}
|
||||
|
||||
public function scopeVersionOne($query)
|
||||
{
|
||||
return $query->where('version', 1);
|
||||
}
|
||||
|
||||
public function scopeVersionTwo($query)
|
||||
{
|
||||
return $query->where('version', 2);
|
||||
}
|
||||
}
|
||||
|
9
app/Plugins/.gitignore
vendored
Normal file
9
app/Plugins/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except these
|
||||
!/.gitignore
|
||||
!/*.php
|
||||
!/Hooks/
|
||||
!/Hooks/**
|
||||
!/ExamplePlugin/
|
||||
!/ExamplePlugin/**
|
32
app/Plugins/ExamplePlugin/DeviceOverview.php
Normal file
32
app/Plugins/ExamplePlugin/DeviceOverview.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* ExampleSettingsPlugin.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\DeviceOverviewHook;
|
||||
|
||||
class DeviceOverview extends DeviceOverviewHook
|
||||
{
|
||||
}
|
9
app/Plugins/ExamplePlugin/Menu.php
Normal file
9
app/Plugins/ExamplePlugin/Menu.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Plugins\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\MenuEntryHook;
|
||||
|
||||
class Menu extends MenuEntryHook
|
||||
{
|
||||
}
|
32
app/Plugins/ExamplePlugin/Page.php
Normal file
32
app/Plugins/ExamplePlugin/Page.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* ExampleSettingsPlugin.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\PageHook;
|
||||
|
||||
class Page extends PageHook
|
||||
{
|
||||
}
|
9
app/Plugins/ExamplePlugin/PortTab.php
Normal file
9
app/Plugins/ExamplePlugin/PortTab.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Plugins\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\PortTabHook;
|
||||
|
||||
class PortTab extends PortTabHook
|
||||
{
|
||||
}
|
32
app/Plugins/ExamplePlugin/Settings.php
Normal file
32
app/Plugins/ExamplePlugin/Settings.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* ExampleSettingsPlugin.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\SettingsHook;
|
||||
|
||||
class Settings extends SettingsHook
|
||||
{
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default panel-condensed">
|
||||
<div class="panel-heading">
|
||||
<strong>{{ $title }}</strong> <a href="{{ url('device/' . $device->device_id . '/notes') }}">[EDIT]</a>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{!! Str::markdown($device->notes) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
1
app/Plugins/ExamplePlugin/resources/views/menu.blade.php
Normal file
1
app/Plugins/ExamplePlugin/resources/views/menu.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<a href="{{ url('plugin/ExamplePlugin') }}"><i class="fa fa-coffee fa-fw fa-lg" aria-hidden="true"></i> Example Menu</a>
|
8
app/Plugins/ExamplePlugin/resources/views/page.blade.php
Normal file
8
app/Plugins/ExamplePlugin/resources/views/page.blade.php
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body ">
|
||||
<div class="pull-left" style="margin-top: 5px;">
|
||||
<span style="font-size: 20px;">{{ $title }}</a></span><br>
|
||||
Description
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1 @@
|
||||
This is a port plugin tab plugin for port {{ $port->getLabel() }}
|
94
app/Plugins/ExamplePlugin/resources/views/settings.blade.php
Normal file
94
app/Plugins/ExamplePlugin/resources/views/settings.blade.php
Normal file
@ -0,0 +1,94 @@
|
||||
<div style="margin: 15px;">
|
||||
<h4>{{ $plugin_name }} Settings:</h4>
|
||||
|
||||
<!-- Example of free-form settings, real plugins should use specific fields -->
|
||||
<!-- All input fields should be in the settings array (settings[]) -->
|
||||
|
||||
<form method="post" style="margin: 15px">
|
||||
@csrf
|
||||
<table id="settings-table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
@forelse($settings as $name => $value)
|
||||
<tr id="settings-row-{{ $name }}">
|
||||
<td>
|
||||
{{ $name }}
|
||||
</td>
|
||||
<td>
|
||||
<input id="value-{{ $value }}" type="text" name="settings[{{ $name }}]" value="{{ $value }}">
|
||||
<button type="button" onclick="deleteSetting('{{ $name }}')" class="delete-button"><i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td>No settings yet</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</table>
|
||||
<div style="margin: 15px 0;">
|
||||
<input id="new-setting-name" style="display: inline-block;" type="text" placeholder="Name">
|
||||
<input id="new-setting-value" style="display: inline-block;" type="text" placeholder="Value">
|
||||
<button type="button" onclick="newSetting()">Add Setting</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function newSetting() {
|
||||
var name = document.getElementById('new-setting-name').value;
|
||||
var value = document.getElementById('new-setting-value').value;
|
||||
var existing = document.getElementById('value-' + name);
|
||||
|
||||
if (existing) {
|
||||
existing.value = value;
|
||||
} else {
|
||||
// insert setting
|
||||
var newValue = document.createElement('input');
|
||||
newValue.id = 'value-' + name;
|
||||
newValue.type = 'text';
|
||||
newValue.name = 'settings[' + name + ']';
|
||||
newValue.value = value;
|
||||
|
||||
var deleteButton = document.createElement('button');
|
||||
deleteButton.type = 'button';
|
||||
deleteButton.className = 'delete-button';
|
||||
deleteButton.onclick = () => deleteSetting(name);
|
||||
var deleteIcon = document.createElement('i');
|
||||
deleteIcon.className = 'fa fa-trash';
|
||||
deleteButton.appendChild(deleteIcon);
|
||||
|
||||
var row = document.createElement('tr');
|
||||
row.id = 'settings-row-' + name;
|
||||
var col1 = document.createElement('td');
|
||||
var col2 = document.createElement('td');
|
||||
col1.innerText = name;
|
||||
col2.appendChild(newValue);
|
||||
col2.appendChild(document.createTextNode(' '));
|
||||
col2.appendChild(deleteButton);
|
||||
row.appendChild(col1);
|
||||
row.appendChild(col2);
|
||||
document.getElementById('settings-table').appendChild(row);
|
||||
}
|
||||
|
||||
document.getElementById('new-setting-name').value = '';
|
||||
document.getElementById('new-setting-value').value = '';
|
||||
}
|
||||
|
||||
function deleteSetting(name) {
|
||||
document.getElementById('settings-row-' + name).remove();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#settings-table td, #settings-table th {
|
||||
padding: .2em;
|
||||
}
|
||||
.delete-button {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
</style>
|
16
app/Plugins/Hook.php
Normal file
16
app/Plugins/Hook.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Plugins;
|
||||
|
||||
interface Hook
|
||||
{
|
||||
/**
|
||||
* Will be called by the plugin manager to check if the user is authorized. Will be called with Dependency Injection.
|
||||
*/
|
||||
// public function authorize(): bool;
|
||||
|
||||
/**
|
||||
* Will be called by the plugin manager to execute this plugin at the correct time. Will be called with Dependency Injection.
|
||||
*/
|
||||
// public function handle();
|
||||
}
|
54
app/Plugins/Hooks/DeviceOverviewHook.php
Normal file
54
app/Plugins/Hooks/DeviceOverviewHook.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* DeviceHook.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\Hooks;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class DeviceOverviewHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.device-overview';
|
||||
|
||||
public function authorize(User $user, Device $device, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(Device $device): array
|
||||
{
|
||||
return [
|
||||
'title' => __CLASS__,
|
||||
'device' => $device,
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName, Device $device): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view(Str::start($this->view, "$pluginName::"), $this->data($device));
|
||||
}
|
||||
}
|
50
app/Plugins/Hooks/MenuEntryHook.php
Normal file
50
app/Plugins/Hooks/MenuEntryHook.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/*
|
||||
* PluginMenuEntry.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\Hooks;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class MenuEntryHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.menu';
|
||||
|
||||
public function authorize(User $user, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName): array
|
||||
{
|
||||
return [Str::start($this->view, "$pluginName::"), $this->data()];
|
||||
}
|
||||
}
|
53
app/Plugins/Hooks/PageHook.php
Normal file
53
app/Plugins/Hooks/PageHook.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* SettingsHook.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\Hooks;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class PageHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.page';
|
||||
|
||||
public function authorize(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName): array
|
||||
{
|
||||
return array_merge([
|
||||
'settings_view' => Str::start($this->view, "$pluginName::"),
|
||||
], $this->data());
|
||||
}
|
||||
}
|
55
app/Plugins/Hooks/PortTabHook.php
Normal file
55
app/Plugins/Hooks/PortTabHook.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/*
|
||||
* PortPluginTab.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\Hooks;
|
||||
|
||||
use App\Models\Port;
|
||||
use App\Models\User;
|
||||
use App\Plugins\Hook;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class PortTabHook implements Hook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.port-tab';
|
||||
|
||||
public function authorize(User $user, Port $port, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(Port $port): array
|
||||
{
|
||||
return [
|
||||
'title' => __CLASS__,
|
||||
'port' => $port,
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName, Port $port): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view(Str::start($this->view, "$pluginName::"), $this->data($port));
|
||||
}
|
||||
}
|
54
app/Plugins/Hooks/SettingsHook.php
Normal file
54
app/Plugins/Hooks/SettingsHook.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* SettingsHook.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins\Hooks;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class SettingsHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.settings';
|
||||
|
||||
public function authorize(User $user, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(array $settings): array
|
||||
{
|
||||
return [
|
||||
'settings' => $settings,
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName, array $settings): array
|
||||
{
|
||||
return array_merge([
|
||||
'settings_view' => Str::start($this->view, "$pluginName::"),
|
||||
], $this->data($settings));
|
||||
}
|
||||
}
|
256
app/Plugins/PluginManager.php
Normal file
256
app/Plugins/PluginManager.php
Normal file
@ -0,0 +1,256 @@
|
||||
<?php
|
||||
/*
|
||||
* PluginManager.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Plugins;
|
||||
|
||||
use App\Exceptions\PluginException;
|
||||
use App\Models\Plugin;
|
||||
use Exception;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
class PluginManager
|
||||
{
|
||||
/** @var Collection */
|
||||
private $hooks;
|
||||
/** @var Collection */
|
||||
private $plugins;
|
||||
|
||||
/** @var array */
|
||||
private $validPlugins = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->hooks = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish plugin hook, this is the main way to hook into different parts of LibreNMS.
|
||||
* plugin_name should be unique. For internal (user) plugins in the app/Plugins directory, the directory name will be used.
|
||||
* Hook type will be the full class name of the hook from app/Plugins/Hooks.
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @param string $hookType
|
||||
* @param string $implementationClass
|
||||
* @return bool
|
||||
*/
|
||||
public function publishHook(string $pluginName, string $hookType, string $implementationClass): bool
|
||||
{
|
||||
try {
|
||||
$instance = new $implementationClass;
|
||||
$this->validPlugins[$pluginName] = 1;
|
||||
|
||||
if ($instance instanceof $hookType && $this->pluginEnabled($pluginName)) {
|
||||
if (! $this->hooks->has($hookType)) {
|
||||
$this->hooks->put($hookType, new Collection);
|
||||
}
|
||||
|
||||
$this->hooks->get($hookType)->push([
|
||||
'plugin_name' => $pluginName,
|
||||
'instance' => $instance,
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Error when loading hook $implementationClass of type $hookType for $pluginName: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any valid hooks
|
||||
*
|
||||
* @param string $hookType
|
||||
* @param array $args
|
||||
* @param string|null $plugin only for this plugin if set
|
||||
* @return bool
|
||||
*/
|
||||
public function hasHooks(string $hookType, array $args = [], ?string $plugin = null): bool
|
||||
{
|
||||
return $this->hooksFor($hookType, $args, $plugin)->isNotEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Coll all hooks for the given hook type.
|
||||
* args will be available for injection into the handle method to pass data through
|
||||
* settings is automatically injected
|
||||
*
|
||||
* @param string $hookType
|
||||
* @param array $args
|
||||
* @param string|null $plugin only for this plugin if set
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function call(string $hookType, array $args = [], ?string $plugin = null): Collection
|
||||
{
|
||||
try {
|
||||
return $this->hooksFor($hookType, $args, $plugin)
|
||||
->map(function ($hook) use ($args) {
|
||||
return app()->call([$hook['instance'], 'handle'], $this->fillArgs($args, $hook['plugin_name']));
|
||||
});
|
||||
} catch (Exception $e) {
|
||||
Log::error("Error calling hook $hookType: " . $e->getMessage());
|
||||
|
||||
return new Collection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings stored in the database for a plugin.
|
||||
* One plugin shares the settings across all hooks
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @return array
|
||||
*/
|
||||
public function getSettings(string $pluginName): array
|
||||
{
|
||||
return (array) $this->getPlugin($pluginName)->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save settings array to the database for the given plugin
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @param array $settings
|
||||
* @return bool
|
||||
*/
|
||||
public function setSettings(string $pluginName, array $settings): bool
|
||||
{
|
||||
$plugin = $this->getPlugin($pluginName);
|
||||
$plugin->settings = $settings;
|
||||
|
||||
return $plugin->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if plugin exists.
|
||||
* Does not create a DB entry if it does not exist.
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @return bool
|
||||
*/
|
||||
public function pluginExists(string $pluginName): bool
|
||||
{
|
||||
return $this->getPlugins()->has($pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if plugin of the given name is enabled.
|
||||
* Creates DB entry if one does not exist yet.
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @return bool
|
||||
*/
|
||||
public function pluginEnabled(string $pluginName): bool
|
||||
{
|
||||
return $this->getPlugin($pluginName)->plugin_active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove plugins that do not have any registered hooks.
|
||||
*/
|
||||
public function cleanupPlugins(): void
|
||||
{
|
||||
try {
|
||||
$valid = array_keys($this->validPlugins);
|
||||
Plugin::versionTwo()->whereNotIn('plugin_name', $valid)->get()->each->delete();
|
||||
} catch (QueryException $qe) {
|
||||
Log::error('Failed to clean up plugins: ' . $qe->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getPlugin(string $name): ?Plugin
|
||||
{
|
||||
$plugin = $this->getPlugins()->get($name);
|
||||
|
||||
if (! $plugin) {
|
||||
try {
|
||||
$plugin = Plugin::create([
|
||||
'plugin_name' => $name,
|
||||
'plugin_active' => 1,
|
||||
'version' => 2,
|
||||
]);
|
||||
$this->getPlugins()->put($name, $plugin);
|
||||
} catch (QueryException $e) {
|
||||
// DB not migrated/connected
|
||||
}
|
||||
}
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
|
||||
protected function getPlugins(): Collection
|
||||
{
|
||||
if ($this->plugins === null) {
|
||||
try {
|
||||
$this->plugins = Plugin::versionTwo()->get()->keyBy('plugin_name');
|
||||
} catch (QueryException $e) {
|
||||
// DB not migrated/connected
|
||||
$this->plugins = new Collection;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hookType
|
||||
* @param array $args
|
||||
* @param string|null $onlyPlugin
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function hooksFor(string $hookType, array $args, ?string $onlyPlugin): Collection
|
||||
{
|
||||
if (! $this->hooks->has($hookType)) {
|
||||
return new Collection;
|
||||
}
|
||||
|
||||
return $this->hooks->get($hookType)
|
||||
->when($onlyPlugin, function (Collection $hooks, $only) {
|
||||
return $hooks->where('plugin_name', $only);
|
||||
})
|
||||
->filter(function ($hook) use ($args) {
|
||||
return app()->call([$hook['instance'], 'authorize'], $this->fillArgs($args, $hook['plugin_name']));
|
||||
});
|
||||
}
|
||||
|
||||
protected function fillArgs(array $args, string $pluginName): array
|
||||
{
|
||||
if (isset($args['settings'])) {
|
||||
throw new PluginException('You cannot inject "settings", this is a reserved name');
|
||||
}
|
||||
|
||||
if (isset($args['pluginName'])) {
|
||||
throw new PluginException('You cannot inject "pluginName", this is a reserved name');
|
||||
}
|
||||
|
||||
return array_merge($args, [
|
||||
'pluginName' => $pluginName,
|
||||
'settings' => $this->getSettings($pluginName),
|
||||
]);
|
||||
}
|
||||
}
|
99
app/Providers/PluginProvider.php
Normal file
99
app/Providers/PluginProvider.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/*
|
||||
* PluginProvider.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2021 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Exceptions\PluginDoesNotImplementHookException;
|
||||
use App\Plugins\PluginManager;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PluginProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(PluginManager::class, function ($app) {
|
||||
return new PluginManager;
|
||||
});
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadLocalPlugins($this->app->make(PluginManager::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load any local plugins these plugins must implement only one hook.
|
||||
*/
|
||||
protected function loadLocalPlugins(PluginManager $manager): void
|
||||
{
|
||||
$plugin_view_location_registered = [];
|
||||
|
||||
foreach (glob(base_path('app/Plugins/*/*.php')) as $file) {
|
||||
if (preg_match('#^(.*/([^/]+))/([^/.]+)\.php#', $file, $matches)) {
|
||||
$plugin_name = $matches[2]; // containing directory name
|
||||
if ($plugin_name == 'Hooks') {
|
||||
continue; // don't load the hooks :D
|
||||
}
|
||||
|
||||
$class = $this->className($plugin_name, $matches[3]);
|
||||
$hook_type = $this->hookType($class);
|
||||
|
||||
// publish hooks in class
|
||||
$hook_published = $manager->publishHook($plugin_name, $hook_type, $class);
|
||||
|
||||
// register view namespace
|
||||
if ($hook_published && ! in_array($plugin_name, $plugin_view_location_registered)) {
|
||||
$plugin_view_location_registered[] = $plugin_name; // don't register twice
|
||||
$this->loadViewsFrom($matches[1], $plugin_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a hook is extended by the given class.
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*
|
||||
* @throws \App\Exceptions\PluginDoesNotImplementHookException
|
||||
*/
|
||||
protected function hookType(string $class): string
|
||||
{
|
||||
foreach (class_parents($class) as $parent) {
|
||||
if (Str::startsWith($parent, 'App\Plugins\Hooks\\')) {
|
||||
return $parent;
|
||||
}
|
||||
}
|
||||
|
||||
throw new PluginDoesNotImplementHookException($class);
|
||||
}
|
||||
|
||||
protected function className(string $dir, string $name): string
|
||||
{
|
||||
return 'App\Plugins\\' . $dir . '\\' . $name;
|
||||
}
|
||||
}
|
@ -186,6 +186,7 @@ return [
|
||||
* LibreNMS Service Providers...
|
||||
*/
|
||||
App\Providers\SnmptrapProvider::class,
|
||||
App\Providers\PluginProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
@ -244,6 +245,7 @@ return [
|
||||
|
||||
// LibreNMS
|
||||
'Permissions' => \App\Facades\Permissions::class,
|
||||
'PluginManager' => \App\Facades\PluginManager::class,
|
||||
'DeviceCache' => \App\Facades\DeviceCache::class,
|
||||
'Rrd' => \App\Facades\Rrd::class,
|
||||
'SnmpQuery' => \App\Facades\FacadeAccessorSnmp::class,
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class PluginsAddVersionAndSettings extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('plugins', function (Blueprint $table) {
|
||||
$table->integer('version')->default(1);
|
||||
$table->longText('settings')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('plugins', function (Blueprint $table) {
|
||||
$table->dropColumn(['version', 'settings']);
|
||||
});
|
||||
}
|
||||
}
|
@ -3,13 +3,99 @@ path: blob/master/doc/
|
||||
|
||||
# Developing for the Plugin System
|
||||
|
||||
This will most likely be deprecated in favour of adding the possible
|
||||
extensions to the core code base.
|
||||
With plugins you can extend LibreNMS with special functions that are
|
||||
specific to your setup or are not relevant or interesting for all community members.
|
||||
|
||||
This documentation will hopefully give you a basis for how to write a
|
||||
plugin for LibreNMS. A test plugin is included in LibreNMS distribution.
|
||||
You are able to intervene in defined places in the behavior of
|
||||
the website, without it coming to problems with future updates.
|
||||
|
||||
# Generic structure
|
||||
This documentation will give you a basis for writing a plugin for
|
||||
LibreNMS. An example plugin is included in the LibreNMS distribution.
|
||||
|
||||
|
||||
# Version 2 Plugin System structure
|
||||
|
||||
Plugins in version 2 need to be installed into app/Plugins
|
||||
|
||||
The structure of a plugin is follows:
|
||||
|
||||
```
|
||||
app/Plugins
|
||||
/PluginName
|
||||
/DeviceOverview.php
|
||||
/Menu.php
|
||||
/Page.php
|
||||
/PortTab.php
|
||||
/Settings.php
|
||||
/resources/views
|
||||
/device-overview.blade.php
|
||||
/menu.blade.php
|
||||
/page.blade.php
|
||||
/port-tab.blade.php
|
||||
/settings.blade.php
|
||||
```
|
||||
|
||||
The above structure is checked before a plugin can be installed.
|
||||
|
||||
All file/folder names are case sensitive and must match the structure.
|
||||
|
||||
Only the blade files that are really needed need to be created. A plugin manager
|
||||
will then load a hook that has a basic functionality.
|
||||
|
||||
If you want to customize the basic behavior of the hooks, you can create a
|
||||
class in 'app/Plugins/PluginName' and overload the hook methods.
|
||||
|
||||
- device-overview.blade.php :: This is called in the Device
|
||||
Overview page. You receive the $device as a object per default, you can do your
|
||||
work here and display your results in a frame.
|
||||
|
||||
```
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default panel-condensed">
|
||||
<div class="panel-heading">
|
||||
<strong>{{ $title }}</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ $device->hostname }}
|
||||
<!-- Do you stuff here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- port-tab.blade.php :: This is called in the Port page,
|
||||
in the "Plugins" menu_option that will appear when your plugin gets
|
||||
enabled. In this blade, you can do your work and display your
|
||||
results in a frame.
|
||||
|
||||
- menu.blade.php :: For a menu entry
|
||||
|
||||
- page.blade.pho :: Here is a good place to add a own LibreNMS page without dependence with a device. A good place to create your own lists with special requirements and behavior.
|
||||
|
||||
- settings.blade.php :: If you need your own settings and variables, you can have a look in the ExamplePlugin.
|
||||
|
||||
|
||||
|
||||
If you want to change the behavior, you can customize the hooks methods. Just as an example, you could imagine that the device-overview.blade.php should only be displayed when the device is in maintanence mode. Of course the method is more for a permission concept but it gives you the idea.
|
||||
|
||||
```
|
||||
abstract class DeviceOverviewHook
|
||||
{
|
||||
...
|
||||
public function authorize(User $user, Device $device, array $settings): bool
|
||||
{
|
||||
return $device->isUnderMaintenance();
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
# Version 1 Plugin System structure (legacy verion)
|
||||
|
||||
Plugins need to be installed into html/plugins
|
||||
|
||||
|
4
html/plugins/.gitignore
vendored
Normal file
4
html/plugins/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
@ -1,3 +0,0 @@
|
||||
<?php
|
||||
|
||||
echo 'Well done, the plugin system is up and running';
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace LibreNMS\Plugins;
|
||||
|
||||
class Test
|
||||
{
|
||||
public static function menu()
|
||||
{
|
||||
echo '<li><a href="plugin/p=Test">Test</a></li>';
|
||||
}
|
||||
|
||||
//end menu()
|
||||
|
||||
public function device_overview_container($device)
|
||||
{
|
||||
echo '<div class="container-fluid"><div class="row"> <div class="col-md-12"> <div class="panel panel-default panel-condensed"> <div class="panel-heading"><strong>' . get_class() . ' Plugin </strong> </div>';
|
||||
echo ' Example plugin in "Device - Overview" tab <br>';
|
||||
echo '</div></div></div></div>';
|
||||
}
|
||||
|
||||
public function port_container($device, $port)
|
||||
{
|
||||
echo '<div class="container-fluid"><div class="row"> <div class="col-md-12"> <div class="panel panel-default panel-condensed"> <div class="panel-heading"><strong>' . get_class() . ' plugin in "Port" tab</strong> </div>';
|
||||
echo 'Example display in Port tab</br>';
|
||||
echo '</div></div></div></div>';
|
||||
}
|
||||
}
|
@ -884,51 +884,6 @@ function port_fill_missing(&$port, $device)
|
||||
}
|
||||
}
|
||||
|
||||
function scan_new_plugins()
|
||||
{
|
||||
$installed = 0; // Track how many plugins we install.
|
||||
|
||||
if (file_exists(Config::get('plugin_dir'))) {
|
||||
$plugin_files = scandir(Config::get('plugin_dir'));
|
||||
foreach ($plugin_files as $name) {
|
||||
if (is_dir(Config::get('plugin_dir') . '/' . $name)) {
|
||||
if ($name != '.' && $name != '..') {
|
||||
if (is_file(Config::get('plugin_dir') . '/' . $name . '/' . $name . '.php') && is_file(Config::get('plugin_dir') . '/' . $name . '/' . $name . '.inc.php')) {
|
||||
$plugin_id = dbFetchRow('SELECT `plugin_id` FROM `plugins` WHERE `plugin_name` = ?', [$name]);
|
||||
if (empty($plugin_id)) {
|
||||
if (dbInsert(['plugin_name' => $name, 'plugin_active' => '0'], 'plugins')) {
|
||||
$installed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
|
||||
function scan_removed_plugins()
|
||||
{
|
||||
$removed = 0; // Track how many plugins will be removed from database
|
||||
|
||||
if (file_exists(Config::get('plugin_dir'))) {
|
||||
$plugin_files = scandir(Config::get('plugin_dir'));
|
||||
$installed_plugins = dbFetchColumn('SELECT `plugin_name` FROM `plugins`');
|
||||
foreach ($installed_plugins as $name) {
|
||||
if (in_array($name, $plugin_files)) {
|
||||
continue;
|
||||
}
|
||||
if (dbDelete('plugins', '`plugin_name` = ?', $name)) {
|
||||
$removed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $removed;
|
||||
}
|
||||
|
||||
function validate_device_id($id)
|
||||
{
|
||||
if (empty($id) || ! is_numeric($id)) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Plugins\Hooks\DeviceOverviewHook;
|
||||
|
||||
$overview = 1;
|
||||
|
||||
echo '
|
||||
@ -18,6 +20,9 @@ require 'overview/puppet_agent.inc.php';
|
||||
require 'overview/tracepath.inc.php';
|
||||
|
||||
echo LibreNMS\Plugins::call('device_overview_container', [$device]);
|
||||
PluginManager::call(DeviceOverviewHook::class, ['device' => DeviceCache::getPrimary()])->each(function ($view) {
|
||||
echo $view;
|
||||
});
|
||||
|
||||
require 'overview/ports.inc.php';
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
use App\Plugins\Hooks\PortTabHook;
|
||||
use LibreNMS\Util\Rewrite;
|
||||
use LibreNMS\Util\Url;
|
||||
|
||||
$vars['view'] = basename($vars['view'] ?? 'graphs');
|
||||
|
||||
$port = dbFetchRow('SELECT * FROM `ports` WHERE `port_id` = ?', [$vars['port']]);
|
||||
@ -32,7 +36,7 @@ if ($port['ifAdminStatus'] == 'up' && $port['ifOperStatus'] == 'up') {
|
||||
}
|
||||
|
||||
$i = 1;
|
||||
$inf = \LibreNMS\Util\Rewrite::normalizeIfName($ifname);
|
||||
$inf = Rewrite::normalizeIfName($ifname);
|
||||
|
||||
$bg = '#ffffff';
|
||||
|
||||
@ -100,7 +104,9 @@ if (count($components) > 0) {
|
||||
$menu_options['cbqos'] = 'CBQoS';
|
||||
}
|
||||
|
||||
if (LibreNMS\Plugins::countHooks('port_container')) {
|
||||
$portModel = \App\Models\Port::find($port['port_id']);
|
||||
|
||||
if (LibreNMS\Plugins::countHooks('port_container') || \PluginManager::hasHooks(PortTabHook::class, ['port' => $portModel])) {
|
||||
// Checking if any plugin implements the port_container. If yes, allow to display the menu_option
|
||||
$menu_options['plugins'] = 'Plugins';
|
||||
}
|
||||
@ -198,7 +204,7 @@ if (dbFetchCell("SELECT COUNT(*) FROM juniAtmVp WHERE port_id = '" . $port['port
|
||||
echo "<span class='pagemenu-selected'>";
|
||||
}
|
||||
|
||||
echo "<a href='" . \LibreNMS\Util\Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/bits/'>Bits</a>";
|
||||
echo "<a href='" . Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/bits/'>Bits</a>";
|
||||
if ($vars['view'] == 'junose-atm-vp' && $vars['graph'] == 'bits') {
|
||||
echo '</span>';
|
||||
}
|
||||
@ -208,7 +214,7 @@ if (dbFetchCell("SELECT COUNT(*) FROM juniAtmVp WHERE port_id = '" . $port['port
|
||||
echo "<span class='pagemenu-selected'>";
|
||||
}
|
||||
|
||||
echo "<a href='" . \LibreNMS\Util\Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/packets/'>Packets</a>";
|
||||
echo "<a href='" . Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/packets/'>Packets</a>";
|
||||
if ($vars['view'] == 'junose-atm-vp' && $vars['graph'] == 'bits') {
|
||||
echo '</span>';
|
||||
}
|
||||
@ -218,7 +224,7 @@ if (dbFetchCell("SELECT COUNT(*) FROM juniAtmVp WHERE port_id = '" . $port['port
|
||||
echo "<span class='pagemenu-selected'>";
|
||||
}
|
||||
|
||||
echo "<a href='" . \LibreNMS\Util\Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/cells/'>Cells</a>";
|
||||
echo "<a href='" . Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/cells/'>Cells</a>";
|
||||
if ($vars['view'] == 'junose-atm-vp' && $vars['graph'] == 'bits') {
|
||||
echo '</span>';
|
||||
}
|
||||
@ -228,7 +234,7 @@ if (dbFetchCell("SELECT COUNT(*) FROM juniAtmVp WHERE port_id = '" . $port['port
|
||||
echo "<span class='pagemenu-selected'>";
|
||||
}
|
||||
|
||||
echo "<a href='" . \LibreNMS\Util\Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/errors/'>Errors</a>";
|
||||
echo "<a href='" . Url::generate(['page' => 'device', 'device' => $device['device_id'], 'tab' => 'port', 'port' => $port['port_id']]) . "/junose-atm-vp/errors/'>Errors</a>";
|
||||
if ($vars['view'] == 'junose-atm-vp' && $vars['graph'] == 'bits') {
|
||||
echo '</span>';
|
||||
}
|
||||
@ -237,11 +243,11 @@ if (dbFetchCell("SELECT COUNT(*) FROM juniAtmVp WHERE port_id = '" . $port['port
|
||||
if (Auth::user()->hasGlobalAdmin() && \LibreNMS\Config::get('enable_billing') == 1) {
|
||||
$bills = dbFetchRows('SELECT `bill_id` FROM `bill_ports` WHERE `port_id`=?', [$port['port_id']]);
|
||||
if (count($bills) === 1) {
|
||||
echo "<span style='float: right;'><a href='" . \LibreNMS\Util\Url::generate(['page' => 'bill', 'bill_id' => $bills[0]['bill_id']]) . "'><i class='fa fa-money fa-lg icon-theme' aria-hidden='true'></i> View Bill</a></span>";
|
||||
echo "<span style='float: right;'><a href='" . Url::generate(['page' => 'bill', 'bill_id' => $bills[0]['bill_id']]) . "'><i class='fa fa-money fa-lg icon-theme' aria-hidden='true'></i> View Bill</a></span>";
|
||||
} elseif (count($bills) > 1) {
|
||||
echo "<span style='float: right;'><a href='" . \LibreNMS\Util\Url::generate(['page' => 'bills']) . "'><i class='fa fa-money fa-lg icon-theme' aria-hidden='true'></i> View Bills</a></span>";
|
||||
echo "<span style='float: right;'><a href='" . Url::generate(['page' => 'bills']) . "'><i class='fa fa-money fa-lg icon-theme' aria-hidden='true'></i> View Bills</a></span>";
|
||||
} else {
|
||||
echo "<span style='float: right;'><a href='" . \LibreNMS\Util\Url::generate(['page' => 'bills', 'view' => 'add', 'port' => $port['port_id']]) . "'><i class='fa fa-money fa-lg icon-theme' aria-hidden='true'></i> Create Bill</a></span>";
|
||||
echo "<span style='float: right;'><a href='" . Url::generate(['page' => 'bills', 'view' => 'add', 'port' => $port['port_id']]) . "'><i class='fa fa-money fa-lg icon-theme' aria-hidden='true'></i> Create Bill</a></span>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
* @author PipoCanaja <pipocanaja@gmail.com>
|
||||
*/
|
||||
|
||||
use App\Plugins\Hooks\PortTabHook;
|
||||
|
||||
$pagetitle[] = 'Plugins';
|
||||
$no_refresh = true;
|
||||
?>
|
||||
@ -21,3 +23,6 @@ $no_refresh = true;
|
||||
<hr>
|
||||
<?php
|
||||
echo \LibreNMS\Plugins::call('port_container', [$device, $port]);
|
||||
PluginManager::call(PortTabHook::class, ['port' => $portModel])->each(function ($view) {
|
||||
echo $view;
|
||||
});
|
||||
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
use LibreNMS\Config;
|
||||
|
||||
$link_array = ['page' => 'plugin'];
|
||||
|
||||
if ($vars['view'] == 'admin') {
|
||||
include_once Config::get('install_dir') . '/includes/html/pages/plugin/admin.inc.php';
|
||||
$pagetitle[] = 'Plugins';
|
||||
} else {
|
||||
$pagetitle[] = $vars['p'];
|
||||
$plugin = dbFetchRow("SELECT `plugin_name` FROM `plugins` WHERE `plugin_name` = ? AND `plugin_active`='1'", [$vars['p']]);
|
||||
if (! empty($plugin)) {
|
||||
$plugin_path = Config::get('plugin_dir') . '/' . $plugin['plugin_name'] . '/' . $plugin['plugin_name'] . '.inc.php';
|
||||
if (is_file($plugin_path)) {
|
||||
chdir(Config::get('install_dir') . '/html');
|
||||
include $plugin_path;
|
||||
chdir(Config::get('install_dir'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
print_error('This plugin is either disabled or not available.');
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (Auth::user()->hasGlobalAdmin()) {
|
||||
// Scan for new plugins and add to the database
|
||||
$new_plugins = scan_new_plugins();
|
||||
$removed_plugins = scan_removed_plugins();
|
||||
|
||||
// Check if we have to toggle enabled / disable a particular module
|
||||
$plugin_id = $_POST['plugin_id'];
|
||||
$plugin_active = $_POST['plugin_active'];
|
||||
if (is_numeric($plugin_id) && is_numeric($plugin_active)) {
|
||||
if ($plugin_active == '0') {
|
||||
$plugin_active = 1;
|
||||
} elseif ($plugin_active == '1') {
|
||||
$plugin_active = 0;
|
||||
} else {
|
||||
$plugin_active = 0;
|
||||
}
|
||||
|
||||
if (dbUpdate(['plugin_active' => $plugin_active], 'plugins', '`plugin_id` = ?', [$plugin_id])) {
|
||||
echo '
|
||||
<script type="text/javascript">
|
||||
$.ajax({
|
||||
url: "",
|
||||
context: document.body,
|
||||
success: function(s,x){
|
||||
$(this).html(s);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
';
|
||||
}
|
||||
}//end if?>
|
||||
|
||||
<div class="panel panel-default panel-condensed">
|
||||
<div class="panel-heading">
|
||||
<strong>System plugins</strong>
|
||||
</div>
|
||||
<?php
|
||||
if ($new_plugins > 0) {
|
||||
echo '<div class="panel-body">
|
||||
<div class="alert alert-warning">
|
||||
We have found ' . $new_plugins . ' new plugins that need to be configured and enabled
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
if ($removed_plugins > 0) {
|
||||
echo '<div class="panel-body">
|
||||
<div class="alert alert-warning">
|
||||
We have found ' . $removed_plugins . ' removed plugins
|
||||
</div>
|
||||
</div>';
|
||||
} ?>
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
|
||||
<?php
|
||||
foreach (dbFetchRows('SELECT * FROM plugins') as $plugins) {
|
||||
if ($plugins['plugin_active'] == 1) {
|
||||
$plugin_colour = 'bg-success';
|
||||
$plugin_button = 'danger';
|
||||
$plugin_label = 'Disable';
|
||||
} else {
|
||||
$plugin_colour = 'bg-danger';
|
||||
$plugin_button = 'success';
|
||||
$plugin_label = 'Enable';
|
||||
}
|
||||
|
||||
echo '<tr class="' . $plugin_colour . '">
|
||||
<td>
|
||||
' . $plugins['plugin_name'] . '
|
||||
</td>
|
||||
<td>
|
||||
<form class="form-inline" role="form" action="" method="post" id="' . $plugins['plugin_id'] . '" name=="' . $plugins['plugin_id'] . '">
|
||||
' . csrf_field() . '
|
||||
<input type="hidden" name="plugin_id" value="' . $plugins['plugin_id'] . '">
|
||||
<input type="hidden" name="plugin_active" value="' . $plugins['plugin_active'] . '">
|
||||
<button type="submit" class="btn btn-sm btn-' . $plugin_button . '">' . $plugin_label . '</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>';
|
||||
}//end foreach
|
||||
?>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
} else {
|
||||
include 'includes/html/error-no-perm.inc.php';
|
||||
}//end if
|
@ -1349,6 +1349,8 @@ plugins:
|
||||
- { Field: plugin_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
- { Field: plugin_name, Type: varchar(60), 'Null': false, Extra: '' }
|
||||
- { Field: plugin_active, Type: int, 'Null': false, Extra: '' }
|
||||
- { Field: version, Type: int, 'Null': false, Extra: '', Default: '1' }
|
||||
- { Field: settings, Type: longtext, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [plugin_id], Unique: true, Type: BTREE }
|
||||
pollers:
|
||||
|
12
resources/lang/en/plugins.php
Normal file
12
resources/lang/en/plugins.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'settings_page' => ':plugin: Settings',
|
||||
'admin_page' => 'Plugin Admin',
|
||||
'admin_title' => 'System Plugins',
|
||||
'errors' => [
|
||||
'not_exist' => 'Plugin :plugin does not exist.',
|
||||
'disabled' => 'Plugin :plugin is disabled.',
|
||||
'view_missing' => 'Missing view.',
|
||||
],
|
||||
];
|
@ -81,17 +81,21 @@
|
||||
</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@if(auth()->user()->isAdmin() || \LibreNMS\Plugins::count())
|
||||
@if(auth()->user()->isAdmin() || $has_v1_plugins || $has_v2_plugins)
|
||||
<li class="dropdown-submenu">
|
||||
<a><i class="fa fa-plug fa-fw fa-lg" aria-hidden="true"></i> @lang('Plugins')</a>
|
||||
<ul class="dropdown-menu">
|
||||
{!! \LibreNMS\Plugins::call('menu') !!}
|
||||
{!! $v1_plugin_menu !!}
|
||||
@foreach($menu_hooks as [$view, $data])
|
||||
<li>@include($view, $data)</li>
|
||||
@endforeach
|
||||
@admin
|
||||
@if(\LibreNMS\Plugins::count())
|
||||
<li role="presentation" class="divider"></li>
|
||||
@endif
|
||||
<li><a href="{{ url('plugin/view=admin') }}"> <i class="fa fa-lock fa-fw fa-lg"
|
||||
aria-hidden="true"></i>@lang('Plugin Admin')
|
||||
@if($has_v1_plugins || $has_v2_plugins)
|
||||
<li role="presentation" class="divider"></li>
|
||||
@endif
|
||||
<li>
|
||||
<a href="{{ route('plugin.admin') }}">
|
||||
<i class="fa fa-lock fa-fw fa-lg" aria-hidden="true"></i>@lang('Plugin Admin')
|
||||
</a>
|
||||
</li>
|
||||
@endadmin
|
||||
|
42
resources/views/plugins/admin.blade.php
Normal file
42
resources/views/plugins/admin.blade.php
Normal file
@ -0,0 +1,42 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', trans('plugins.admin_page'))
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="panel panel-default panel-condensed col-md-6 col-md-offset-3 col-xs-12 col-sm-8 col-sm-offset-2" style="padding: 0">
|
||||
<div class="panel-heading">
|
||||
<strong>@lang('plugins.admin_title')</strong>
|
||||
</div>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
@foreach($plugins as $plugin)
|
||||
<tr class="{{ $plugin->plugin_active ? 'bg-success' : 'bg-danger' }}">
|
||||
<td>{{ $plugin->plugin_name }}</td>
|
||||
<td>
|
||||
<form class="form-inline" role="form" action="{{ route('plugin.update', ['plugin' => $plugin->plugin_name]) }}" method="post" id="{{ $plugin->plugin_id }}" name="{{ $plugin->plugin_id }}">
|
||||
@csrf
|
||||
@if($plugin->plugin_active)
|
||||
<input type="hidden" name="plugin_active" value="0">
|
||||
<button type="submit" class="btn btn-sm btn-danger" style="min-width: 66px">@lang('Disable')</button>
|
||||
@else
|
||||
<input type="hidden" name="plugin_active" value="1">
|
||||
<button type="submit" class="btn btn-sm btn-success" style="min-width: 66px">@lang('Enable')</button>
|
||||
@endif
|
||||
@if($plugin->version == 1)
|
||||
<a href="{{ route('plugin.legacy', $plugin->plugin_name) }}" class="btn btn-sm btn-primary" style="min-width: 72px">@lang('Page')</a>
|
||||
@else
|
||||
<a href="{{ route('plugin.settings', $plugin->plugin_name) }}" class="btn btn-sm btn-primary" style="min-width: 72px">@lang('Settings')</a>
|
||||
@endif
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
7
resources/views/plugins/legacy.blade.php
Normal file
7
resources/views/plugins/legacy.blade.php
Normal file
@ -0,0 +1,7 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', $title)
|
||||
|
||||
@section('content')
|
||||
{!! $content !!}
|
||||
@endsection
|
1
resources/views/plugins/missing.blade.php
Normal file
1
resources/views/plugins/missing.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<h2>@lang('plugins.errors.view_missing')</h2>
|
5
resources/views/plugins/settings.blade.php
Normal file
5
resources/views/plugins/settings.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', $title)
|
||||
|
||||
@include($settings_view, $settings)
|
@ -77,6 +77,15 @@ Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
|
||||
Route::post('alert/transports/{transport}/test', [\App\Http\Controllers\AlertTransportController::class, 'test'])->name('alert.transports.test');
|
||||
});
|
||||
|
||||
Route::get('plugin/settings', 'PluginAdminController')->name('plugin.admin');
|
||||
Route::get('plugin/settings/{plugin:plugin_name}', 'PluginSettingsController')->name('plugin.settings');
|
||||
Route::post('plugin/settings/{plugin:plugin_name}', 'PluginSettingsController@update')->name('plugin.update');
|
||||
Route::get('plugin', 'PluginLegacyController@redirect');
|
||||
Route::redirect('plugin/view=admin', '/plugin/admin');
|
||||
Route::get('plugin/p={pluginName}', 'PluginLegacyController@redirect');
|
||||
Route::get('plugin/v1/{plugin:plugin_name}', 'PluginLegacyController')->name('plugin.legacy');
|
||||
Route::get('plugin/{plugin:plugin_name}', 'PluginPageController')->name('plugin.page');
|
||||
|
||||
// old route redirects
|
||||
Route::permanentRedirect('poll-log', 'poller/log');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user