Fix stacktrace for missing database connection

- Add error handling for database connection failures
- Suppress stacktrace and add error messages for user and admin
- Improve user experience with clear error messages
This commit is contained in:
Jolien Trog 2025-06-04 13:01:58 +02:00
parent cca7f2db1f
commit 15b0148542
8 changed files with 189 additions and 37 deletions

View File

@ -6,6 +6,7 @@ namespace Icinga\Controllers;
use Icinga\Application\Hook\DbMigrationHook;
use Icinga\Application\MigrationManager;
use Icinga\Exception\IcingaException;
use Throwable;
use Zend_Controller_Plugin_ErrorHandler;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
@ -96,6 +97,9 @@ class ErrorController extends ActionController
$mm = MigrationManager::instance();
$action = $this->getRequest()->getActionName();
$controller = $this->getRequest()->getControllerName();
$hasPending = false;
try {
$hasPending = $mm->hasPendingMigrations();
if ($action !== 'hint' && $controller !== 'migrations' && $mm->hasMigrations($moduleName)) {
// The view renderer from IPL web doesn't render the HTML content set in the respective
// controller if the error_handler request param is set, as it doesn't support error
@ -108,6 +112,9 @@ class ErrorController extends ActionController
return;
}
} catch (Throwable $e) {
//suppress
}
$this->getResponse()->setHttpResponseCode(500);
$module = $modules->hasLoaded($moduleName) ? $modules->getModule($moduleName) : null;

View File

@ -19,6 +19,9 @@ use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\Web\Compat\CompatController;
use ipl\Web\Widget\ActionLink;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;
use Throwable;
class MigrationsController extends CompatController
{
@ -55,6 +58,7 @@ class MigrationsController extends CompatController
$migrateListForm = new MigrationForm();
$migrateListForm->setAttribute('id', $this->getRequest()->protectId('migration-form'));
try {
$migrateListForm->setRenderDatabaseUserChange(! $mm->validateDatabasePrivileges());
if ($canApply && $mm->hasPendingMigrations()) {
@ -69,11 +73,34 @@ class MigrationsController extends CompatController
$migrateListForm->registerElement($migrateAllButton);
// Make sure it looks familiar, even if not inside a form
$migrateAllButton->setWrapper(new HtmlElement('div', Attributes::create(['class' => 'icinga-controls'])));
$migrateAllButton->setWrapper(
new HtmlElement('div', Attributes::create(['class' => 'icinga-controls']))
);
$this->controls->getAttributes()->add('class', 'default-layout');
$this->addControl($migrateAllButton);
}
} catch (Throwable $e) {
$this->addContent(
new HtmlElement(
'div',
new Attributes(['class' => 'db-connection-warning']),
new Icon('warning'),
new HtmlElement('ul', null),
new HtmlElement(
'p',
null,
new Text(
$this->translate(' No Configuration Database selected.
To establish a valid database connection set the Configuration Database field. ')
)
),
new HtmlElement('ul', null),
new Link($this->translate('Configuration Database'), 'config/general/')
)
);
return;
}
$this->handleFormatRequest($mm->toArray());

View File

@ -4,10 +4,15 @@
namespace Icinga\Controllers;
use Icinga\Common\Database;
use Icinga\Web\Notification;
use Icinga\Web\RememberMe;
use Icinga\Web\RememberMeUserDevicesList;
use ipl\Html\Attributes;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\Web\Compat\CompatController;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;
use Throwable;
/**
* MyDevicesController
@ -49,6 +54,46 @@ class MyDevicesController extends CompatController
public function indexAction()
{
try {
$this->getDb();
} catch (Throwable $e) {
$hasConfigPermission = $this->hasPermission('config/*');
if ($hasConfigPermission) {
$this->addContent(
new HtmlElement(
'div',
new Attributes(['class' => 'db-connection-warning']),
new Icon('warning'),
new HtmlElement(
'p',
null,
new Text($this->translate(
'No Configuration Database selected.'
. 'To establish a valid database connection set the Configuration Database field.'
))
),
new Link($this->translate('Configuration Database'), 'config/general/')
)
);
} else {
$this->addContent(
new HtmlElement(
'div',
new Attributes(['class' => 'db-connection-warning']),
new Icon('warning'),
new HtmlElement(
'p',
null,
new Text($this->translate(
'No Configuration Database selected.'
. 'You don`t have permission to change this setting. Please contact an administrator.'
))
)
)
);
}
return;
}
$name = $this->auth->getUser()->getUsername();
$data = (new RememberMeUserDevicesList())
@ -57,12 +102,6 @@ class MyDevicesController extends CompatController
->setUrl('my-devices/delete');
$this->addContent($data);
if (! $this->hasDb()) {
Notification::warning(
$this->translate("Users can't stay logged in without a database configuration backend")
);
}
}
public function deleteAction()

View File

@ -3,9 +3,11 @@
namespace Icinga\Forms\Config\General;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Data\ResourceFactory;
use Icinga\Web\Form;
use ipl\Html\Text;
/**
* Configuration form for general application options
@ -100,6 +102,16 @@ class ApplicationConfigForm extends Form
)
);
$config = Config::app()->getSection('global');
if (!isset($config->config_resource)) {
$missingConfigResource =
Text::create(
$this->translate("No Configuration Database selected.
Please set the field to establish a valid database connection.")
);
$this->warning($missingConfigResource, false);
}
return $this;
}
}

View File

@ -17,6 +17,7 @@ use Icinga\Web\Notification;
use Icinga\Web\Session;
use Icinga\Web\StyleSheet;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\I18n\GettextTranslator;
use ipl\I18n\Locale;
use ipl\I18n\StaticTranslator;
@ -185,7 +186,15 @@ class PreferenceForm extends Form
false
);
}
$config = Config::app()->getSection('global');
if (!isset($config->config_resource)) {
$missingConfigResource =
Text::create($this->translate(
'No Configuration Database selected.'
. 'To establish a valid database connection set the Configuration Database field.'
));
$this->warning($missingConfigResource, false);
}
$themeFile = StyleSheet::getThemeFile(Config::app()->get('themes', 'default'));
if (! (bool) Config::app()->get('themes', 'disabled', false)) {
$themes = Icinga::app()->getThemes();
@ -439,7 +448,7 @@ class PreferenceForm extends Form
public function isSubmitted()
{
if (parent::isSubmitted()) {
if (($this->getElement('btn_submit') !== null ) && parent::isSubmitted()) {
return true;
}

View File

@ -1,8 +1,10 @@
<?php
use Icinga\Application\Config;
use Icinga\Application\MigrationManager;
use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
use ipl\Html\HtmlElement;
use ipl\Web\Widget\EmptyState;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\StateBadge;
@ -93,8 +95,6 @@ use ipl\Web\Widget\StateBadge;
]
);
?>
</div>
</div>
<?php
$mm = MigrationManager::instance();
@ -102,11 +102,36 @@ use ipl\Web\Widget\StateBadge;
try {
$hasPending = $mm->hasPendingMigrations();
} catch (Throwable $e) {
throw new LogicException('Please check the database connection in Configuration -> Application -> Resources');
// suppress
}
if ($hasPending): ?>
?>
</div>
</div>
<?php $config = Config::app()->getSection('global') ?>
<div class="pending-migrations clearfix">
<?php if($mm->getPendingMigrations() || (!isset($config->config_resource))) : ?>
<h2><?= $this->translate('Pending Migrations') ?></h2>
<?php endif ?>
<table >
<tr>
<th>
<?php if (!isset($config->config_resource)) : ?>
<?= new EmptyState(
$this->translate('No Configuration Database selected. To establish a valid database connection set: '))
?><td>
<?= $this->qlink(
$this->translate('Configuration Database'),
'config/general/',
array('name' => ('Configuration Database')),
array('title' => sprintf($this->translate('Go to Application/General')), 'Configuration Database')
) ?>
</td>
<?php endif ?>
</th>
</tr>
</table>
<?php if ($hasPending): ?>
<table class="name-value-table migrations">
<?php foreach ($mm->getPendingMigrations() as $migration): ?>
<tr>

View File

@ -82,6 +82,7 @@ class StyleSheet
'css/icinga/health.less',
'css/icinga/php-diff.less',
'css/icinga/pending-migration.less',
'css/icinga/db-connection-warning.less',
];
/**

View File

@ -0,0 +1,32 @@
.db-connection-warning {
border-radius: .25em;
display: flex;
align-items: center;
margin: 0 0 1em 0;
padding: 1em 1em;
background-color: fade(#ffaa44, 40%);
color: #fff;
p {
margin: 0;
align-items: center;
}
a {
margin: 0;
align-items: center;
margin-left: .5em;
color: #00c3ed;
}
i {
font-size: 2em;
opacity: .4;
align-self: flex-start;
}
ul {
list-style: none;
}
}