mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-24 14:24:41 +02:00
Merge pull request #4272 from Icinga/feature/library-support-4271
Add library support
This commit is contained in:
commit
e8fc6f93ae
@ -90,17 +90,21 @@ class ModuleCommand extends Command
|
||||
/**
|
||||
* Enable a given module
|
||||
*
|
||||
* Usage: icingacli module enable <module-name>
|
||||
* Usage: icingacli module enable <module-name> [--force]
|
||||
*/
|
||||
public function enableAction()
|
||||
{
|
||||
if (! $module = $this->params->shift()) {
|
||||
$module = $this->params->shift('module');
|
||||
}
|
||||
|
||||
$force = $this->params->shift('force', false);
|
||||
|
||||
if (! $module || $this->hasRemainingParams()) {
|
||||
return $this->showUsage();
|
||||
}
|
||||
$this->modules->enableModule($module);
|
||||
|
||||
$this->modules->enableModule($module, $force);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,7 @@ class AboutController extends Controller
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->version = Version::get();
|
||||
$this->view->libraries = Icinga::app()->getLibraries();
|
||||
$this->view->modules = Icinga::app()->getModuleManager()->getLoadedModules();
|
||||
$this->view->title = $this->translate('About');
|
||||
$this->view->tabs = $this->getTabs()->add(
|
||||
|
@ -138,6 +138,8 @@ class ConfigController extends Controller
|
||||
}
|
||||
|
||||
$this->view->module = $module;
|
||||
$this->view->libraries = $app->getLibraries();
|
||||
$this->view->moduleManager = $manager;
|
||||
$this->view->toggleForm = $toggleForm;
|
||||
$this->view->title = $module->getName();
|
||||
$this->view->tabs = $module->getConfigTabs()->activate('info');
|
||||
@ -168,7 +170,7 @@ class ConfigController extends Controller
|
||||
$form->handleRequest();
|
||||
} catch (Exception $e) {
|
||||
$this->view->exceptionMessage = $e->getMessage();
|
||||
$this->view->moduleName = $form->getValue('name');
|
||||
$this->view->moduleName = $form->getValue('identifier');
|
||||
$this->view->action = 'enable';
|
||||
$this->render('module-configuration-error');
|
||||
}
|
||||
@ -194,7 +196,7 @@ class ConfigController extends Controller
|
||||
$form->handleRequest();
|
||||
} catch (Exception $e) {
|
||||
$this->view->exceptionMessage = $e->getMessage();
|
||||
$this->view->moduleName = $form->getValue('name');
|
||||
$this->view->moduleName = $form->getValue('identifier');
|
||||
$this->view->action = 'disable';
|
||||
$this->render('module-configuration-error');
|
||||
}
|
||||
|
@ -18,6 +18,11 @@ use Icinga\Web\Url;
|
||||
*/
|
||||
class ErrorController extends ActionController
|
||||
{
|
||||
/**
|
||||
* Regular expression to match exceptions resulting from missing functions/classes
|
||||
*/
|
||||
const MISSING_DEP_ERROR = "/Uncaught Error:.*(?:undefined function (\S+)|Class '([^']+)' not found).* in ([^:]+)/";
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -44,21 +49,23 @@ class ErrorController extends ActionController
|
||||
$this->innerLayout = 'guest-error';
|
||||
}
|
||||
|
||||
$modules = Icinga::app()->getModuleManager();
|
||||
$sourcePath = ltrim($this->_request->get('PATH_INFO'), '/');
|
||||
$pathParts = preg_split('~/~', $sourcePath);
|
||||
$moduleName = array_shift($pathParts);
|
||||
|
||||
$module = null;
|
||||
switch ($error->type) {
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
|
||||
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
|
||||
$modules = Icinga::app()->getModuleManager();
|
||||
$path = ltrim($this->_request->get('PATH_INFO'), '/');
|
||||
$path = preg_split('~/~', $path);
|
||||
$path = array_shift($path);
|
||||
$this->getResponse()->setHttpResponseCode(404);
|
||||
$this->view->messages = array($this->translate('Page not found.'));
|
||||
if ($isAuthenticated) {
|
||||
if ($modules->hasInstalled($path) && ! $modules->hasEnabled($path)) {
|
||||
if ($modules->hasInstalled($moduleName) && ! $modules->hasEnabled($moduleName)) {
|
||||
$this->view->messages[0] .= ' ' . sprintf(
|
||||
$this->translate('Enabling the "%s" module might help!'),
|
||||
$path
|
||||
$moduleName
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -84,10 +91,29 @@ class ErrorController extends ActionController
|
||||
break;
|
||||
default:
|
||||
$this->getResponse()->setHttpResponseCode(500);
|
||||
$module = $modules->hasLoaded($moduleName) ? $modules->getModule($moduleName) : null;
|
||||
Logger::error("%s\n%s", $exception, IcingaException::getConfidentialTraceAsString($exception));
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to narrow down why the request has failed
|
||||
if (preg_match(self::MISSING_DEP_ERROR, $exception->getMessage(), $match)) {
|
||||
$sourcePath = $match[3];
|
||||
foreach ($modules->listLoadedModules() as $name) {
|
||||
$candidate = $modules->getModule($name);
|
||||
$modulePath = $candidate->getBaseDir();
|
||||
if (substr($sourcePath, 0, strlen($modulePath)) === $modulePath) {
|
||||
$module = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('/^(?:Icinga\\\Module\\\(\w+)|(\w+)\\\)/', $match[1] ?: $match[2], $natch)) {
|
||||
$this->view->requiredModule = isset($natch[1]) ? strtolower($natch[1]) : null;
|
||||
$this->view->requiredLibrary = isset($natch[2]) ? $natch[2] : null;
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->messages = array();
|
||||
|
||||
if ($this->getInvokeArg('displayExceptions')) {
|
||||
@ -114,6 +140,7 @@ class ErrorController extends ActionController
|
||||
->sendResponse();
|
||||
}
|
||||
|
||||
$this->view->module = $module;
|
||||
$this->view->request = $error->request;
|
||||
if (! $isAuthenticated) {
|
||||
$this->view->hideControls = true;
|
||||
|
@ -94,7 +94,27 @@
|
||||
)
|
||||
) ?>
|
||||
</div>
|
||||
<h2><?= $this->translate('Loaded modules') ?></h2>
|
||||
<h2><?= $this->translate('Loaded Libraries') ?></h2>
|
||||
<table class="table-row-selectable common-table" data-base-target="_next">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $this->translate('Name') ?></th>
|
||||
<th><?= $this->translate('Version') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($libraries as $library): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $this->escape($library->getName()) ?>
|
||||
<td>
|
||||
<?= $this->escape($library->getVersion()) ?: '-' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2><?= $this->translate('Loaded Modules') ?></h2>
|
||||
<table class="table-row-selectable common-table" data-base-target="_next">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -6,9 +6,11 @@
|
||||
<?= $this->translate('There is no such module installed.') ?>
|
||||
<?php return; endif ?>
|
||||
<?php
|
||||
$dependencies = $module->getDependencies();
|
||||
$requiredMods = $module->getRequiredModules();
|
||||
$requiredLibs = $module->getRequiredLibraries();
|
||||
$restrictions = $module->getProvidedRestrictions();
|
||||
$permissions = $module->getProvidedPermissions();
|
||||
$unmetDependencies = $moduleManager->hasUnmetDependencies($module->getName());
|
||||
$state = $moduleData->enabled ? ($moduleData->loaded ? 'enabled' : 'failed') : 'disabled'
|
||||
?>
|
||||
<table class="name-value-table">
|
||||
@ -20,9 +22,13 @@
|
||||
<th><?= $this->translate('State') ?></th>
|
||||
<td>
|
||||
<?= $state ?>
|
||||
<?php if (isset($this->toggleForm)): ?>
|
||||
<?php if (isset($this->toggleForm)): ?>
|
||||
<?php if ($moduleData->enabled || ! $unmetDependencies): ?>
|
||||
<?= $this->toggleForm ?>
|
||||
<?php else: ?>
|
||||
<?= $this->icon('attention-alt', $this->translate('Module can\'t be enabled due to unmet dependencies')) ?>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<tr>
|
||||
<th><?= $this->escape($this->translate('Version')) ?></th>
|
||||
@ -43,13 +49,58 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= $this->escape($this->translate('Dependencies')) ?></th>
|
||||
<td>
|
||||
<?php
|
||||
if (empty($dependencies)):
|
||||
echo $this->translate('This module has no dependencies');
|
||||
else: foreach ($dependencies as $name => $versionString): ?>
|
||||
<strong><?= $this->escape($name) ?></strong><?php if ($versionString !== true): ?>: <?= $this->escape($versionString) ?><?php endif ?><br />
|
||||
<?php endforeach; endif ?>
|
||||
<td class="module-dependencies">
|
||||
<?php if (empty($requiredLibs) && empty($requiredMods)): ?>
|
||||
<?= $this->translate('This module has no dependencies') ?>
|
||||
<?php else: ?>
|
||||
<?php if ($unmetDependencies): ?>
|
||||
<strong class="unmet-dependencies">
|
||||
<?= $this->translate('Unmet dependencies found! Module can\'t be enabled unless all dependencies are met.') ?>
|
||||
</strong>
|
||||
<?php endif ?>
|
||||
<?php if (! empty($requiredLibs)): ?>
|
||||
<table class="name-value-table">
|
||||
<caption><?= $this->translate('Libraries') ?></caption>
|
||||
<?php foreach ($requiredLibs as $libraryName => $versionString): ?>
|
||||
<tr>
|
||||
<th><?= $this->escape($libraryName) ?></th>
|
||||
<td>
|
||||
<?php if ($libraries->has($libraryName, $versionString === true ? null : $versionString)): ?>
|
||||
<?= $versionString === true ? '*' : $this->escape($versionString) ?>
|
||||
<?php else: ?>
|
||||
<span class="missing"><?= $versionString === true ? '*' : $this->escape($versionString) ?></span>
|
||||
<?php if (($library = $libraries->get($libraryName)) !== null): ?>
|
||||
(<?= $library->getVersion() ?>)
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
<?php if (! empty($requiredMods)): ?>
|
||||
<table class="name-value-table">
|
||||
<caption><?= $this->translate('Modules') ?></caption>
|
||||
<?php foreach ($requiredMods as $moduleName => $versionString): ?>
|
||||
<tr>
|
||||
<th><?= $this->escape($moduleName) ?></th>
|
||||
<td>
|
||||
<?php if ($moduleManager->has($moduleName, $versionString === true ? null : $versionString)): ?>
|
||||
<?= $versionString === true ? '*' : $this->escape($versionString) ?>
|
||||
<?php else: ?>
|
||||
<span class="missing"><?= $versionString === true ? '*' : $this->escape($versionString) ?></span>
|
||||
<?php if (! $moduleManager->hasInstalled($moduleName)): ?>
|
||||
(<?= $this->translate('not installed') ?>)
|
||||
<?php else: ?>
|
||||
(<?= $moduleManager->getModule($moduleName, false)->getVersion() ?><?= $moduleManager->hasEnabled($moduleName) ? '' : ', ' . $this->translate('disabled') ?>)
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if (! empty($permissions)): ?>
|
||||
|
@ -17,4 +17,41 @@ if (isset($stackTraces)) {
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if (isset($module)): ?>
|
||||
<?php $manager = \Icinga\Application\Icinga::app()->getModuleManager(); ?>
|
||||
<?php if ($manager->hasUnmetDependencies($module->getName())): ?>
|
||||
<div class="error-reason">
|
||||
<p><?= sprintf($this->translate(
|
||||
'This error might have occurred because module "%s" has unmet dependencies.'
|
||||
. ' Please check it\'s installation instructions and install missing dependencies.'
|
||||
), $module->getName()) ?></p>
|
||||
<?php if (isset($requiredModule) && $requiredModule && isset($module->getRequiredModules()[$requiredModule])): ?>
|
||||
<?php if (! $manager->hasInstalled($requiredModule)): ?>
|
||||
<p><?= sprintf($this->translate(
|
||||
'Module "%s" is required and missing. Please install a version of it matching the required one: %s'
|
||||
), $requiredModule, $module->getRequiredModules()[$requiredModule]) ?></p>
|
||||
<?php elseif (! $manager->hasEnabled($requiredModule)): ?>
|
||||
<p><?= sprintf($this->translate(
|
||||
'Module "%s" is required and installed, but not enabled. Please enable module "%1$s".'
|
||||
), $requiredModule) ?></p>
|
||||
<?php elseif (! $manager->has($requiredModule, $module->getRequiredModules()[$requiredModule])): ?>
|
||||
<p><?= sprintf($this->translate(
|
||||
'Module "%s" is required and installed, but its version (%s) does not satisfy the required one: %s'
|
||||
), $requiredModule, $manager->getModule($requiredModule, false)->getVersion(), $module->getRequiredModules()[$requiredModule]) ?></p>
|
||||
<?php endif ?>
|
||||
<?php elseif (isset($requiredLibrary) && $requiredLibrary && isset($module->getRequiredLibraries()[$requiredLibrary])): ?>
|
||||
<?php $libraries = \Icinga\Application\Icinga::app()->getLibraries(); ?>
|
||||
<?php if (! $libraries->has($requiredLibrary)): ?>
|
||||
<p><?= sprintf($this->translate(
|
||||
'Library "%s" is required and missing. Please install a version of it matching the required one: %s'
|
||||
), $requiredLibrary, $module->getRequiredLibraries()[$requiredLibrary]) ?></p>
|
||||
<?php elseif (! $libraries->has($requiredLibrary, $module->getRequiredLibraries()[$requiredLibrary])): ?>
|
||||
<p><?= sprintf($this->translate(
|
||||
'Library "%s" is required and installed, but its version (%s) does not satisfy the required one: %s'
|
||||
), $requiredLibrary, $libraries->get($requiredLibrary)->getVersion(), $module->getRequiredLibraries()[$requiredLibrary]) ?></p>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
@ -16,6 +16,7 @@
|
||||
</rule>
|
||||
<rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
|
||||
<exclude-pattern>library/Icinga/Application/Cli.php</exclude-pattern>
|
||||
<exclude-pattern>library/Icinga/Application/StaticWeb.php</exclude-pattern>
|
||||
<exclude-pattern>library/Icinga/Application/EmbeddedWeb.php</exclude-pattern>
|
||||
<exclude-pattern>library/Icinga/Application/functions.php</exclude-pattern>
|
||||
<exclude-pattern>library/Icinga/Application/LegacyWeb.php</exclude-pattern>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
namespace Icinga\Application;
|
||||
|
||||
use DirectoryIterator;
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use LogicException;
|
||||
@ -62,7 +63,7 @@ abstract class ApplicationBootstrap
|
||||
protected $vendorDir;
|
||||
|
||||
/**
|
||||
* Library directory
|
||||
* Icinga library directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
@ -89,6 +90,20 @@ abstract class ApplicationBootstrap
|
||||
*/
|
||||
protected $storageDir;
|
||||
|
||||
/**
|
||||
* External library paths
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $libraryPaths;
|
||||
|
||||
/**
|
||||
* Loaded external libraries
|
||||
*
|
||||
* @var Libraries
|
||||
*/
|
||||
protected $libraries;
|
||||
|
||||
/**
|
||||
* Icinga class loader
|
||||
*
|
||||
@ -176,6 +191,20 @@ abstract class ApplicationBootstrap
|
||||
$canonical = realpath($storageDir);
|
||||
$this->storageDir = $canonical ? $canonical : $storageDir;
|
||||
|
||||
if ($this->libraryPaths === null) {
|
||||
$libraryPaths = getenv('ICINGAWEB_LIBDIR');
|
||||
if ($libraryPaths !== false) {
|
||||
$this->libraryPaths = array_filter(array_map(
|
||||
'realpath',
|
||||
explode(':', $libraryPaths)
|
||||
), 'is_dir');
|
||||
} else {
|
||||
$this->libraryPaths = is_dir('/usr/share/php-icinga')
|
||||
? ['/usr/share/php-icinga']
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
set_include_path(
|
||||
implode(
|
||||
PATH_SEPARATOR,
|
||||
@ -197,6 +226,16 @@ abstract class ApplicationBootstrap
|
||||
*/
|
||||
abstract protected function bootstrap();
|
||||
|
||||
/**
|
||||
* Get loaded external libraries
|
||||
*
|
||||
* @return Libraries
|
||||
*/
|
||||
public function getLibraries()
|
||||
{
|
||||
return $this->libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for module manager
|
||||
*
|
||||
@ -499,6 +538,26 @@ abstract class ApplicationBootstrap
|
||||
return @file_exists($this->config->resolvePath('setup.token'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load external libraries
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function loadLibraries()
|
||||
{
|
||||
$this->libraries = new Libraries();
|
||||
foreach ($this->libraryPaths as $libraryPath) {
|
||||
foreach (new DirectoryIterator($libraryPath) as $path) {
|
||||
if (! $path->isDot() && is_dir($path->getRealPath())) {
|
||||
$this->libraries->registerPath($path->getPathname())
|
||||
->registerAutoloader();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup default logging
|
||||
*
|
||||
|
@ -38,6 +38,7 @@ class Cli extends ApplicationBootstrap
|
||||
$this->assertRunningOnCli();
|
||||
$this->setupLogging()
|
||||
->setupErrorHandling()
|
||||
->loadLibraries()
|
||||
->loadConfig()
|
||||
->setupTimezone()
|
||||
->setupInternationalization()
|
||||
|
@ -65,6 +65,7 @@ class EmbeddedWeb extends ApplicationBootstrap
|
||||
return $this
|
||||
->setupZendAutoloader()
|
||||
->setupErrorHandling()
|
||||
->loadLibraries()
|
||||
->loadConfig()
|
||||
->setupLogging()
|
||||
->setupLogger()
|
||||
|
83
library/Icinga/Application/Libraries.php
Normal file
83
library/Icinga/Application/Libraries.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||
|
||||
namespace Icinga\Application;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use Icinga\Application\Libraries\Library;
|
||||
|
||||
class Libraries implements IteratorAggregate
|
||||
{
|
||||
/** @var Library[] */
|
||||
protected $libraries = [];
|
||||
|
||||
/**
|
||||
* Iterate over registered libraries
|
||||
*
|
||||
* @return ArrayIterator
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->libraries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a library from the given path
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return Library The registered library
|
||||
*/
|
||||
public function registerPath($path)
|
||||
{
|
||||
$library = new Library($path);
|
||||
$this->libraries[] = $library;
|
||||
|
||||
return $library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a library with the given name has been registered
|
||||
*
|
||||
* Passing a version constraint also verifies that the library's version matches.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name, $version = null)
|
||||
{
|
||||
$library = $this->get($name);
|
||||
if ($library === null) {
|
||||
return false;
|
||||
} elseif ($version === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$operator = '=';
|
||||
if (preg_match('/^([<>=]{1,2})\s*v?((?:[\d.]+)(?:\D+)?)$/', $version, $match)) {
|
||||
$operator = $match[1];
|
||||
$version = $match[2];
|
||||
}
|
||||
|
||||
return version_compare($library->getVersion(), $version, $operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a library by name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Library|null
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
foreach ($this->libraries as $library) {
|
||||
if ($library->getName() === $name) {
|
||||
return $library;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
224
library/Icinga/Application/Libraries/Library.php
Normal file
224
library/Icinga/Application/Libraries/Library.php
Normal file
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||
|
||||
namespace Icinga\Application\Libraries;
|
||||
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Exception\Json\JsonDecodeException;
|
||||
use Icinga\Util\Json;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
class Library
|
||||
{
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/** @var string */
|
||||
protected $jsAssetPath;
|
||||
|
||||
/** @var string */
|
||||
protected $cssAssetPath;
|
||||
|
||||
/** @var string */
|
||||
protected $staticAssetPath;
|
||||
|
||||
/** @var string */
|
||||
protected $version;
|
||||
|
||||
/** @var array */
|
||||
protected $metaData;
|
||||
|
||||
/** @var array */
|
||||
protected $assets;
|
||||
|
||||
/**
|
||||
* Create a new Library
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this library's path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path of this library's JS assets
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJsAssetPath()
|
||||
{
|
||||
$this->assets();
|
||||
return $this->jsAssetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path of this library's CSS assets
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCssAssetPath()
|
||||
{
|
||||
$this->assets();
|
||||
return $this->cssAssetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path of this library's static assets
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStaticAssetPath()
|
||||
{
|
||||
$this->assets();
|
||||
return $this->staticAssetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this library's name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->metaData()['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this library's version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
if ($this->version === null) {
|
||||
if (isset($this->metaData()['version'])) {
|
||||
$this->version = trim(ltrim($this->metaData()['version'], 'v'));
|
||||
} else {
|
||||
$versionFile = $this->path . DIRECTORY_SEPARATOR . 'VERSION';
|
||||
if (file_exists($versionFile)) {
|
||||
$this->version = trim(ltrim(file_get_contents($versionFile), 'v'));
|
||||
} else {
|
||||
$this->version = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this library's JS assets
|
||||
*
|
||||
* @return string[] Asset paths
|
||||
*/
|
||||
public function getJsAssets()
|
||||
{
|
||||
return $this->assets()['js'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this library's CSS assets
|
||||
*
|
||||
* @return string[] Asset paths
|
||||
*/
|
||||
public function getCssAssets()
|
||||
{
|
||||
return $this->assets()['css'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this library's static assets
|
||||
*
|
||||
* @return string[] Asset paths
|
||||
*/
|
||||
public function getStaticAssets()
|
||||
{
|
||||
return $this->assets()['static'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this library's autoloader
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerAutoloader()
|
||||
{
|
||||
$autoloaderPath = join(DIRECTORY_SEPARATOR, [$this->path, 'vendor', 'autoload.php']);
|
||||
if (file_exists($autoloaderPath)) {
|
||||
require_once $autoloaderPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return this library's metadata
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ConfigurationError
|
||||
* @throws JsonDecodeException
|
||||
*/
|
||||
protected function metaData()
|
||||
{
|
||||
if ($this->metaData === null) {
|
||||
$metaData = file_get_contents($this->path . DIRECTORY_SEPARATOR . 'composer.json');
|
||||
if ($metaData === false) {
|
||||
throw new ConfigurationError('Library at "%s" is not a composerized project', $this->path);
|
||||
}
|
||||
|
||||
$this->metaData = Json::decode($metaData, true);
|
||||
}
|
||||
|
||||
return $this->metaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register and return this library's assets
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function assets()
|
||||
{
|
||||
if ($this->assets !== null) {
|
||||
return $this->assets;
|
||||
}
|
||||
|
||||
$listAssets = function ($type) {
|
||||
$dir = join(DIRECTORY_SEPARATOR, [$this->path, 'asset', $type]);
|
||||
if (! is_dir($dir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->{$type . 'AssetPath'} = $dir;
|
||||
|
||||
return new RecursiveIteratorIterator(new RecursiveDirectoryIterator(
|
||||
$dir,
|
||||
RecursiveDirectoryIterator::CURRENT_AS_PATHNAME | RecursiveDirectoryIterator::SKIP_DOTS
|
||||
));
|
||||
};
|
||||
|
||||
$this->assets = [];
|
||||
|
||||
$jsAssets = $listAssets('js');
|
||||
$this->assets['js'] = is_array($jsAssets) ? $jsAssets : iterator_to_array($jsAssets);
|
||||
|
||||
$cssAssets = $listAssets('css');
|
||||
$this->assets['css'] = is_array($cssAssets) ? $cssAssets : iterator_to_array($cssAssets);
|
||||
|
||||
$staticAssets = $listAssets('static');
|
||||
$this->assets['static'] = is_array($staticAssets) ? $staticAssets : iterator_to_array($staticAssets);
|
||||
|
||||
return $this->assets;
|
||||
}
|
||||
}
|
@ -237,12 +237,13 @@ class Manager
|
||||
* Set the given module to the enabled state
|
||||
*
|
||||
* @param string $name The module to enable
|
||||
* @param bool $force Whether to ignore unmet dependencies
|
||||
*
|
||||
* @return $this
|
||||
* @throws ConfigurationError When trying to enable a module that is not installed
|
||||
* @throws SystemPermissionException When insufficient permissions for the application exist
|
||||
*/
|
||||
public function enableModule($name)
|
||||
public function enableModule($name, $force = false)
|
||||
{
|
||||
if (! $this->hasInstalled($name)) {
|
||||
throw new ConfigurationError(
|
||||
@ -260,6 +261,17 @@ class Manager
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->hasUnmetDependencies($name)) {
|
||||
if ($force) {
|
||||
Logger::warning(t('Enabling module "%s" although it has unmet dependencies'), $name);
|
||||
} else {
|
||||
throw new ConfigurationError(
|
||||
t('Module "%s" can\'t be enabled. Module has unmet dependencies'),
|
||||
$name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
clearstatcache(true);
|
||||
$target = $this->installedBaseDirs[$name];
|
||||
$link = $this->enableDir . DIRECTORY_SEPARATOR . $name;
|
||||
@ -430,6 +442,34 @@ class Manager
|
||||
return array_key_exists($name, $this->loadedModules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a module with the given name is enabled
|
||||
*
|
||||
* Passing a version constraint also verifies that the module's version matches.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name, $version = null)
|
||||
{
|
||||
if (! $this->hasEnabled($name)) {
|
||||
return false;
|
||||
} elseif ($version === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$operator = '=';
|
||||
if (preg_match('/^([<>=]{1,2})\s*v?((?:[\d.]+)(?:\D+)?)$/', $version, $match)) {
|
||||
$operator = $match[1];
|
||||
$version = $match[2];
|
||||
}
|
||||
|
||||
$modVersion = ltrim($this->getModule($name)->getVersion(), 'v');
|
||||
return version_compare($modVersion, $version, $operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently loaded modules
|
||||
*
|
||||
@ -503,6 +543,36 @@ class Manager
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given module has unmet dependencies
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUnmetDependencies($name)
|
||||
{
|
||||
$module = $this->getModule($name, false);
|
||||
|
||||
$requiredMods = $module->getRequiredModules();
|
||||
foreach ($requiredMods as $moduleName => $moduleVersion) {
|
||||
if (! $this->has($moduleName, $moduleVersion)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$libraries = Icinga::app()->getLibraries();
|
||||
|
||||
$requiredLibs = $module->getRequiredLibraries();
|
||||
foreach ($requiredLibs as $libraryName => $libraryVersion) {
|
||||
if (! $libraries->has($libraryName, $libraryVersion)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array containing all enabled module names as strings
|
||||
*
|
||||
|
@ -57,6 +57,13 @@ class Module
|
||||
*/
|
||||
private $cssdir;
|
||||
|
||||
/**
|
||||
* Directory for Javascript
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $jsdir;
|
||||
|
||||
/**
|
||||
* Base application directory
|
||||
*
|
||||
@ -553,10 +560,16 @@ class Module
|
||||
* @param string $path The file's path, relative to the module's asset or base directory
|
||||
* @param string $from The module's name
|
||||
*
|
||||
* @deprecated Deprecated with v2.9, don't use and depend on a library instead
|
||||
* @return $this
|
||||
*/
|
||||
protected function requireCssFile($path, $from)
|
||||
{
|
||||
Logger::warning(
|
||||
'Module assets are deprecated since v2.9. Please check if the module "%s" provides a library instead.',
|
||||
$from
|
||||
);
|
||||
|
||||
$module = self::get($from);
|
||||
$cssAssetDir = join(DIRECTORY_SEPARATOR, [$module->assetDir, 'css']);
|
||||
foreach ($module->getCssAssets() as $assetPath) {
|
||||
@ -631,10 +644,16 @@ class Module
|
||||
* @param string $path The file's path, relative to the module's asset or base directory
|
||||
* @param string $from The module's name
|
||||
*
|
||||
* @deprecated Deprecated with v2.9, don't use and depend on a library instead
|
||||
* @return $this
|
||||
*/
|
||||
protected function requireJsFile($path, $from)
|
||||
{
|
||||
Logger::warning(
|
||||
'Module assets are deprecated since v2.9. Please check if the module "%s" provides a library instead.',
|
||||
$from
|
||||
);
|
||||
|
||||
$module = self::get($from);
|
||||
$jsAssetDir = join(DIRECTORY_SEPARATOR, [$module->assetDir, 'js']);
|
||||
foreach ($module->getJsAssets() as $assetPath) {
|
||||
@ -835,12 +854,33 @@ class Module
|
||||
* Get the module dependencies
|
||||
*
|
||||
* @return array
|
||||
* @deprecated Use method getRequiredModules() instead
|
||||
*/
|
||||
public function getDependencies()
|
||||
{
|
||||
return $this->metadata()->depends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required libraries
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequiredLibraries()
|
||||
{
|
||||
return $this->metadata()->libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required modules
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequiredModules()
|
||||
{
|
||||
return $this->metadata()->modules ?: $this->metadata()->depends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch module metadata
|
||||
*
|
||||
@ -849,16 +889,19 @@ class Module
|
||||
protected function metadata()
|
||||
{
|
||||
if ($this->metadata === null) {
|
||||
$metadata = (object) array(
|
||||
'name' => $this->getName(),
|
||||
'version' => '0.0.0',
|
||||
'title' => null,
|
||||
'description' => '',
|
||||
'depends' => array(),
|
||||
);
|
||||
$metadata = (object) [
|
||||
'name' => $this->getName(),
|
||||
'version' => '0.0.0',
|
||||
'title' => null,
|
||||
'description' => '',
|
||||
'depends' => [],
|
||||
'libraries' => [],
|
||||
'modules' => []
|
||||
];
|
||||
|
||||
if (file_exists($this->metadataFile)) {
|
||||
$key = null;
|
||||
$simpleRequires = false;
|
||||
$file = new File($this->metadataFile, 'r');
|
||||
foreach ($file as $lineno => $line) {
|
||||
$line = rtrim($line);
|
||||
@ -877,10 +920,8 @@ class Module
|
||||
|
||||
if (strpos($line, ':') === false) {
|
||||
Logger::debug(
|
||||
$this->translate(
|
||||
"Can't process line %d in %s: Line does not specify a key:value pair"
|
||||
. " nor is it part of the description (indented with a single space)"
|
||||
),
|
||||
"Can't process line %d in %s: Line does not specify a key:value pair"
|
||||
. " nor is it part of the description (indented with a single space)",
|
||||
$lineno,
|
||||
$this->metadataFile
|
||||
);
|
||||
@ -888,27 +929,54 @@ class Module
|
||||
break;
|
||||
}
|
||||
|
||||
list($key, $val) = preg_split('/:\s+/', $line, 2);
|
||||
$key = lcfirst($key);
|
||||
$parts = preg_split('/:\s+/', $line, 2);
|
||||
if (count($parts) === 1) {
|
||||
$parts[] = '';
|
||||
}
|
||||
|
||||
list($key, $val) = $parts;
|
||||
|
||||
$key = strtolower($key);
|
||||
switch ($key) {
|
||||
case 'requires':
|
||||
if ($val) {
|
||||
$simpleRequires = true;
|
||||
$key = 'libraries';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// Shares the syntax with `Depends`
|
||||
case ' libraries':
|
||||
case ' modules':
|
||||
if ($simpleRequires && $key[0] === ' ') {
|
||||
Logger::debug(
|
||||
'Can\'t process line %d in %s: Requirements already registered by a previous line',
|
||||
$lineno,
|
||||
$this->metadataFile
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$key = ltrim($key);
|
||||
// Shares the syntax with `Depends`
|
||||
case 'depends':
|
||||
if (strpos($val, ' ') === false) {
|
||||
$metadata->depends[$val] = true;
|
||||
$metadata->{$key}[$val] = true;
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$parts = preg_split('/,\s+/', $val);
|
||||
foreach ($parts as $part) {
|
||||
if (preg_match('/^(\w+)\s+\((.+)\)$/', $part, $m)) {
|
||||
$metadata->depends[$m[1]] = $m[2];
|
||||
$metadata->{$key}[$m[1]] = $m[2];
|
||||
} else {
|
||||
// TODO: FAIL?
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
case 'description':
|
||||
if ($metadata->title === null) {
|
||||
$metadata->title = $val;
|
||||
@ -928,8 +996,6 @@ class Module
|
||||
}
|
||||
|
||||
if ($metadata->description === '') {
|
||||
// TODO: Check whether the translation module is able to
|
||||
// extract this
|
||||
$metadata->description = t(
|
||||
'This module has no description'
|
||||
);
|
||||
@ -950,6 +1016,26 @@ class Module
|
||||
return $this->cssdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the module's JS directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJsDir()
|
||||
{
|
||||
return $this->jsdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the module's JS asset directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJsAssetDir()
|
||||
{
|
||||
return join(DIRECTORY_SEPARATOR, [$this->assetDir, 'js']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the module's controller directory
|
||||
*
|
||||
@ -1312,6 +1398,11 @@ class Module
|
||||
return $this;
|
||||
}
|
||||
|
||||
Logger::warning(
|
||||
'Module assets are deprecated since v2.9. Please provide a library'
|
||||
. ' for the parts you want to make available to other modules.'
|
||||
);
|
||||
|
||||
$listAssets = function ($type) {
|
||||
$dir = join(DIRECTORY_SEPARATOR, [$this->assetDir, $type]);
|
||||
if (! is_dir($dir)) {
|
||||
|
21
library/Icinga/Application/StaticWeb.php
Normal file
21
library/Icinga/Application/StaticWeb.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||
|
||||
namespace Icinga\Application;
|
||||
|
||||
require_once dirname(__FILE__) . '/EmbeddedWeb.php';
|
||||
|
||||
class StaticWeb extends EmbeddedWeb
|
||||
{
|
||||
protected function bootstrap()
|
||||
{
|
||||
return $this
|
||||
->setupErrorHandling()
|
||||
->loadLibraries()
|
||||
->loadConfig()
|
||||
->setupLogging()
|
||||
->setupLogger()
|
||||
->setupRequest()
|
||||
->setupResponse();
|
||||
}
|
||||
}
|
@ -83,6 +83,7 @@ class Web extends EmbeddedWeb
|
||||
->setupZendAutoloader()
|
||||
->setupLogging()
|
||||
->setupErrorHandling()
|
||||
->loadLibraries()
|
||||
->loadConfig()
|
||||
->setupLogger()
|
||||
->setupRequest()
|
||||
|
@ -4,6 +4,7 @@
|
||||
namespace Icinga\Application;
|
||||
|
||||
use Icinga\Chart\Inline\PieChart;
|
||||
use Icinga\Web\Controller\StaticController;
|
||||
use Icinga\Web\JavaScript;
|
||||
use Icinga\Web\StyleSheet;
|
||||
|
||||
@ -92,6 +93,11 @@ if (in_array($path, $special)) {
|
||||
$pie = new PieChart();
|
||||
$pie->initFromRequest();
|
||||
$pie->toPng();
|
||||
} elseif (substr($path, 0, 4) === 'lib/') {
|
||||
include_once __DIR__ . '/StaticWeb.php';
|
||||
$app = StaticWeb::start();
|
||||
(new StaticController())->handle($app->getRequest());
|
||||
$app->getResponse()->sendResponse();
|
||||
} elseif (file_exists($baseDir . '/' . $path) && is_file($baseDir . '/' . $path)) {
|
||||
return false;
|
||||
} else {
|
||||
|
77
library/Icinga/Web/Controller/StaticController.php
Normal file
77
library/Icinga/Web/Controller/StaticController.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||
|
||||
namespace Icinga\Web\Controller;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\Request;
|
||||
|
||||
class StaticController
|
||||
{
|
||||
/**
|
||||
* Handle incoming request
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public function handle(Request $request)
|
||||
{
|
||||
$app = Icinga::app();
|
||||
|
||||
// +4 because strlen('/lib') === 4
|
||||
$assetPath = ltrim(substr($request->getRequestUri(), strlen($request->getBaseUrl()) + 4), '/');
|
||||
|
||||
$library = null;
|
||||
foreach ($app->getLibraries() as $candidate) {
|
||||
if (substr($assetPath, 0, strlen($candidate->getName())) === $candidate->getName()) {
|
||||
$library = $candidate;
|
||||
$assetPath = ltrim(substr($assetPath, strlen($candidate->getName())), '/');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($library === null) {
|
||||
$app->getResponse()
|
||||
->setHttpResponseCode(404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$assetRoot = $library->getStaticAssetPath();
|
||||
$filePath = $assetRoot . DIRECTORY_SEPARATOR . $assetPath;
|
||||
|
||||
// Doesn't use realpath as it isn't supposed to access files outside asset/static
|
||||
if (! is_readable($filePath) || ! is_file($filePath)) {
|
||||
$app->getResponse()
|
||||
->setHttpResponseCode(404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fileStat = stat($filePath);
|
||||
$eTag = sprintf(
|
||||
'%x-%x-%x',
|
||||
$fileStat['ino'],
|
||||
$fileStat['size'],
|
||||
(float) str_pad($fileStat['mtime'], 16, '0')
|
||||
);
|
||||
|
||||
$app->getResponse()->setHeader(
|
||||
'Cache-Control',
|
||||
'public, max-age=1814400, stale-while-revalidate=604800',
|
||||
true
|
||||
);
|
||||
|
||||
if ($request->getServer('HTTP_IF_NONE_MATCH') === $eTag) {
|
||||
$app->getResponse()
|
||||
->setHttpResponseCode(304);
|
||||
} else {
|
||||
$app->getResponse()
|
||||
->setHeader('ETag', $eTag)
|
||||
->setHeader('Content-Type', mime_content_type($filePath), true)
|
||||
->setHeader('Last-Modified', gmdate('D, d M Y H:i:s', $fileStat['mtime']) . ' GMT')
|
||||
->setBody(file_get_contents($filePath));
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,17 @@
|
||||
namespace Icinga\Web;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\FileCache;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Exception\Json\JsonDecodeException;
|
||||
use Icinga\Util\Json;
|
||||
use JShrink\Minifier;
|
||||
|
||||
class JavaScript
|
||||
{
|
||||
protected static $jsFiles = array(
|
||||
/** @var string */
|
||||
const DEFINE_RE = '/(?<!\.)define\(\s*([\'"][^\'"]*[\'"])?[,\s]*(\[[^]]*\])?[,\s]*(function\s*\([^)]*\)|[^=]*=>)/';
|
||||
|
||||
protected static $jsFiles = [
|
||||
'js/helpers.js',
|
||||
'js/icinga.js',
|
||||
'js/icinga/logger.js',
|
||||
@ -36,12 +41,16 @@ class JavaScript
|
||||
'js/icinga/behavior/filtereditor.js',
|
||||
'js/icinga/behavior/selectable.js',
|
||||
'js/icinga/behavior/modal.js'
|
||||
);
|
||||
];
|
||||
|
||||
protected static $vendorFiles = array(
|
||||
protected static $vendorFiles = [
|
||||
'js/vendor/jquery-3.4.1',
|
||||
'js/vendor/jquery-migrate-3.1.0'
|
||||
);
|
||||
];
|
||||
|
||||
protected static $baseFiles = [
|
||||
'js/define.js'
|
||||
];
|
||||
|
||||
public static function sendMinified()
|
||||
{
|
||||
@ -59,52 +68,72 @@ class JavaScript
|
||||
{
|
||||
header('Content-Type: application/javascript');
|
||||
$basedir = Icinga::app()->getBootstrapDirectory();
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
|
||||
$files = [];
|
||||
$js = $out = '';
|
||||
$min = $minified ? '.min' : '';
|
||||
|
||||
// Prepare vendor file list
|
||||
$vendorFiles = array();
|
||||
$vendorFiles = [];
|
||||
foreach (self::$vendorFiles as $file) {
|
||||
$vendorFiles[] = $basedir . '/' . $file . $min . '.js';
|
||||
$filePath = $basedir . '/' . $file . $min . '.js';
|
||||
$vendorFiles[] = $filePath;
|
||||
$files[] = $filePath;
|
||||
}
|
||||
|
||||
// Prepare Icinga JS file list
|
||||
$jsFiles = array();
|
||||
// Prepare base file list
|
||||
$baseFiles = [];
|
||||
foreach (self::$baseFiles as $file) {
|
||||
$filePath = $basedir . '/' . $file;
|
||||
$baseFiles[] = $filePath;
|
||||
$files[] = $filePath;
|
||||
}
|
||||
|
||||
// Prepare library file list
|
||||
foreach (Icinga::app()->getLibraries() as $library) {
|
||||
$files = array_merge($files, $library->getJsAssets());
|
||||
}
|
||||
|
||||
// Prepare core file list
|
||||
$coreFiles = [];
|
||||
foreach (self::$jsFiles as $file) {
|
||||
$jsFiles[] = $basedir . '/' . $file;
|
||||
$filePath = $basedir . '/' . $file;
|
||||
$coreFiles[] = $filePath;
|
||||
$files[] = $filePath;
|
||||
}
|
||||
|
||||
$sharedFiles = [];
|
||||
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) {
|
||||
$moduleFiles = [];
|
||||
foreach ($moduleManager->getLoadedModules() as $name => $module) {
|
||||
if ($module->hasJs()) {
|
||||
$jsDir = $module->getJsDir();
|
||||
foreach ($module->getJsFiles() as $path) {
|
||||
if (file_exists($path)) {
|
||||
$jsFiles[] = $path;
|
||||
$moduleFiles[$name][$jsDir][] = $path;
|
||||
$files[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($module->requiresJs()) {
|
||||
foreach ($module->getJsRequires() as $path) {
|
||||
$sharedFiles[] = $path;
|
||||
}
|
||||
$assetDir = $module->getJsAssetDir();
|
||||
foreach ($module->getJsAssets() as $path) {
|
||||
$moduleFiles[$name][$assetDir][] = $path;
|
||||
$files[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
$sharedFiles = array_unique($sharedFiles);
|
||||
$files = array_merge($vendorFiles, $jsFiles, $sharedFiles);
|
||||
|
||||
$request = Icinga::app()->getRequest();
|
||||
$noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache';
|
||||
|
||||
header('Cache-Control: public');
|
||||
|
||||
if (! $noCache && FileCache::etagMatchesFiles($files)) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
return;
|
||||
} else {
|
||||
$etag = FileCache::etagForFiles($files);
|
||||
}
|
||||
|
||||
header('ETag: "' . $etag . '"');
|
||||
header('Content-Type: application/javascript');
|
||||
|
||||
@ -120,15 +149,36 @@ class JavaScript
|
||||
$out .= ';' . ltrim(trim(file_get_contents($file)), ';') . "\n";
|
||||
}
|
||||
|
||||
foreach ($jsFiles as $file) {
|
||||
foreach ($baseFiles as $file) {
|
||||
$js .= file_get_contents($file) . "\n\n\n";
|
||||
}
|
||||
|
||||
foreach ($sharedFiles as $file) {
|
||||
if (substr($file, -7, 7) === '.min.js') {
|
||||
$out .= ';' . ltrim(trim(file_get_contents($file)), ';') . "\n";
|
||||
} else {
|
||||
$js .= file_get_contents($file) . "\n\n\n";
|
||||
// Library files need to be namespaced first before they can be included
|
||||
foreach (Icinga::app()->getLibraries() as $library) {
|
||||
foreach ($library->getJsAssets() as $file) {
|
||||
$js .= self::optimizeDefine(
|
||||
file_get_contents($file),
|
||||
$file,
|
||||
$library->getJsAssetPath(),
|
||||
$library->getName()
|
||||
) . "\n\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($coreFiles as $file) {
|
||||
$js .= file_get_contents($file) . "\n\n\n";
|
||||
}
|
||||
|
||||
foreach ($moduleFiles as $name => $paths) {
|
||||
foreach ($paths as $basePath => $filePaths) {
|
||||
foreach ($filePaths as $file) {
|
||||
$content = self::optimizeDefine(file_get_contents($file), $file, $basePath, $name);
|
||||
if (substr($file, -7, 7) === '.min.js') {
|
||||
$out .= ';' . ltrim(trim($content), ';') . "\n";
|
||||
} else {
|
||||
$js .= $content . "\n\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +188,69 @@ class JavaScript
|
||||
} else {
|
||||
$out .= $js;
|
||||
}
|
||||
|
||||
$cache->store($cacheFile, $out);
|
||||
echo $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize define() calls in the given JS
|
||||
*
|
||||
* @param string $js
|
||||
* @param string $filePath
|
||||
* @param string $basePath
|
||||
* @param string $packageName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function optimizeDefine($js, $filePath, $basePath, $packageName)
|
||||
{
|
||||
if (! preg_match(self::DEFINE_RE, $js, $match)) {
|
||||
return $js;
|
||||
}
|
||||
|
||||
try {
|
||||
$assetName = $match[1] ? Json::decode($match[1]) : '';
|
||||
if (! $assetName) {
|
||||
$assetName = explode('.', basename($filePath))[0];
|
||||
}
|
||||
|
||||
$assetName = join(DIRECTORY_SEPARATOR, array_filter([
|
||||
$packageName,
|
||||
ltrim(substr(dirname($filePath), strlen($basePath)), DIRECTORY_SEPARATOR),
|
||||
$assetName
|
||||
]));
|
||||
|
||||
$assetName = Json::encode($assetName, JSON_UNESCAPED_SLASHES);
|
||||
} catch (JsonDecodeException $_) {
|
||||
$assetName = $match[1];
|
||||
Logger::error('Can\'t optimize name of "%s". Are single quotes used instead of double quotes?', $filePath);
|
||||
}
|
||||
|
||||
try {
|
||||
$dependencies = $match[2] ? Json::decode($match[2]) : [];
|
||||
foreach ($dependencies as &$dependencyName) {
|
||||
if (preg_match('~^((?:\.\.?/)+)*(.*)~', $dependencyName, $natch)) {
|
||||
$dependencyName = join(DIRECTORY_SEPARATOR, array_filter([
|
||||
$packageName,
|
||||
ltrim(substr(
|
||||
realpath(join(DIRECTORY_SEPARATOR, [dirname($filePath), $natch[1]])),
|
||||
strlen(realpath($basePath))
|
||||
), DIRECTORY_SEPARATOR),
|
||||
$natch[2]
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$dependencies = Json::encode($dependencies, JSON_UNESCAPED_SLASHES);
|
||||
} catch (JsonDecodeException $_) {
|
||||
$dependencies = $match[2];
|
||||
Logger::error(
|
||||
'Can\'t optimize dependencies of "%s". Are single quotes used instead of double quotes?',
|
||||
$filePath
|
||||
);
|
||||
}
|
||||
|
||||
return str_replace($match[0], sprintf("define(%s, %s, %s", $assetName, $dependencies, $match[3]), $js);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class StyleSheet
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $lessFiles = array(
|
||||
protected static $lessFiles = [
|
||||
'../application/fonts/fontello-ifont/css/ifont-embedded.css',
|
||||
'css/vendor/normalize.css',
|
||||
'css/vendor/tipsy.css',
|
||||
@ -53,7 +53,7 @@ class StyleSheet
|
||||
'css/icinga/print.less',
|
||||
'css/icinga/responsive.less',
|
||||
'css/icinga/modal.less'
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* Application instance
|
||||
@ -93,6 +93,12 @@ class StyleSheet
|
||||
*/
|
||||
protected function collect()
|
||||
{
|
||||
foreach ($this->app->getLibraries() as $library) {
|
||||
foreach ($library->getCssAssets() as $lessFile) {
|
||||
$this->lessCompiler->addLessFile($lessFile);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::$lessFiles as $lessFile) {
|
||||
$this->lessCompiler->addLessFile($this->pubPath . '/' . $lessFile);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace Icinga\Module\Setup\Utils;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Exception\IcingaException;
|
||||
use Icinga\Module\Setup\Step;
|
||||
|
||||
@ -16,6 +17,8 @@ class EnableModuleStep extends Step
|
||||
|
||||
protected $errors;
|
||||
|
||||
protected $warnings;
|
||||
|
||||
public function __construct(array $moduleNames)
|
||||
{
|
||||
$this->moduleNames = $moduleNames;
|
||||
@ -35,6 +38,8 @@ class EnableModuleStep extends Step
|
||||
foreach ($this->moduleNames as $moduleName) {
|
||||
try {
|
||||
$moduleManager->enableModule($moduleName);
|
||||
} catch (ConfigurationError $e) {
|
||||
$this->warnings[$moduleName] = $e;
|
||||
} catch (Exception $e) {
|
||||
$this->errors[$moduleName] = $e;
|
||||
$success = false;
|
||||
@ -59,6 +64,9 @@ class EnableModuleStep extends Step
|
||||
if (isset($this->errors[$moduleName])) {
|
||||
$report[] = sprintf($failMessage, $moduleName);
|
||||
$report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->errors[$moduleName]));
|
||||
} elseif (isset($this->warnings[$moduleName])) {
|
||||
$report[] = sprintf($failMessage, $moduleName);
|
||||
$report[] = sprintf(mt('setup', 'WARNING: %s'), $this->warnings[$moduleName]->getMessage());
|
||||
} else {
|
||||
$report[] = sprintf($okMessage, $moduleName);
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
#about {
|
||||
h2 {
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
|
||||
.about-social i {
|
||||
font-size: 1.7em;
|
||||
color: @text-color;
|
||||
|
@ -11,6 +11,10 @@
|
||||
font-weight: @font-weight-bold;
|
||||
}
|
||||
|
||||
.error-reason {
|
||||
margin-top: 4em;
|
||||
}
|
||||
|
||||
.large-icon {
|
||||
font-size: 200%;
|
||||
}
|
||||
@ -178,6 +182,12 @@ a:hover > .icon-cancel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.name-value-table > caption {
|
||||
margin-top: .5em;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.name-value-table > tbody > tr > th {
|
||||
color: @text-color-light;
|
||||
// Reset default font-weight
|
||||
@ -350,4 +360,30 @@ a:hover > .icon-cancel {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.module-dependencies {
|
||||
.unmet-dependencies {
|
||||
background-color: @color-warning;
|
||||
color: @text-color-on-icinga-blue;
|
||||
padding: .25em .5em;
|
||||
margin-left: -.5em;
|
||||
}
|
||||
|
||||
.name-value-table {
|
||||
> caption {
|
||||
font-weight: normal;
|
||||
color: @text-color-light;
|
||||
}
|
||||
|
||||
> tbody > tr > th {
|
||||
font-weight: bold;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.missing {
|
||||
color: @color-critical;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
102
public/js/define.js
Normal file
102
public/js/define.js
Normal file
@ -0,0 +1,102 @@
|
||||
/*! Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
|
||||
|
||||
(function(window) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Provide a reference to be later required by foreign code
|
||||
*
|
||||
* @param {string} name Optional, defaults to the name (and path) of the file
|
||||
* @param {string[]} requirements Optional, list of required references, may be relative if from the same package
|
||||
* @param {function} factory Required, function that accepts as many params as there are requirements and that
|
||||
* produces a value to be referenced
|
||||
*/
|
||||
var define = function (name, requirements, factory) {
|
||||
define.defines[name] = {
|
||||
requirements: requirements,
|
||||
factory: factory,
|
||||
ref: null
|
||||
}
|
||||
|
||||
define.resolve(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given name references a value
|
||||
*
|
||||
* @param {string} name The absolute name of the reference
|
||||
* @return {boolean}
|
||||
*/
|
||||
define.has = function (name) {
|
||||
return name in define.defines && define.defines[name]['ref'] !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a reference
|
||||
*
|
||||
* @param {string} name The absolute name of the reference
|
||||
* @return {*}
|
||||
*/
|
||||
define.get = function (name) {
|
||||
return define.defines[name]['ref'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a reference
|
||||
*
|
||||
* @param {string} name The absolute name of the reference
|
||||
* @param {*} ref The value to reference
|
||||
*/
|
||||
define.set = function (name, ref) {
|
||||
define.defines[name]['ref'] = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a reference and, if successful, dependent references
|
||||
*
|
||||
* @param {string} name The absolute name of the reference
|
||||
* @return {boolean}
|
||||
*/
|
||||
define.resolve = function (name) {
|
||||
var requirements = define.defines[name]['requirements'];
|
||||
if (requirements.filter(define.has).length < requirements.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var requiredRefs = [];
|
||||
for (var i = 0; i < requirements.length; i++) {
|
||||
requiredRefs.push(define.get(requirements[i]));
|
||||
}
|
||||
|
||||
var factory = define.defines[name]['factory'];
|
||||
define.set(name, factory.apply(null, requiredRefs));
|
||||
|
||||
for (var definedName in define.defines) {
|
||||
if (define.defines[definedName]['requirements'].indexOf(name) >= 0) {
|
||||
define.resolve(definedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Require a reference
|
||||
*
|
||||
* @param {string} name The absolute name of the reference
|
||||
* @return {*}
|
||||
*/
|
||||
var require = function(name) {
|
||||
if (define.has(name)) {
|
||||
return define.get(name);
|
||||
}
|
||||
|
||||
throw new ReferenceError(name + ' is not defined');
|
||||
}
|
||||
|
||||
define.icinga = true;
|
||||
define.defines = {};
|
||||
|
||||
window.define = define;
|
||||
window.require = require;
|
||||
|
||||
})(window);
|
Loading…
x
Reference in New Issue
Block a user