Do some code refactoring

This commit is contained in:
Yonas Habteab 2022-03-28 10:59:14 +02:00
parent c2b475585f
commit cdd60f965b
36 changed files with 1983 additions and 1985 deletions

View File

@ -7,15 +7,19 @@ namespace Icinga\Controllers;
use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Forms\Dashboard\DashletForm;
use Icinga\Forms\Dashboard\HomePaneForm;
use Icinga\Forms\Dashboard\NewHomePaneForm;
use Icinga\Forms\Dashboard\RemoveDashletForm;
use Icinga\Forms\Dashboard\RemoveHomePaneForm;
use Icinga\Forms\Dashboard\WelcomeForm;
use Icinga\Model\ModuleDashlet;
use Icinga\Util\Json;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Dashboard\Pane;
use Icinga\Web\Dashboard\Settings;
use Icinga\Web\Dashboard\Setup\SetupNewDashboard;
use Icinga\Web\Notification;
use Icinga\Web\Widget\Tabextension\DashboardSettings;
use ipl\Html\HtmlElement;
use ipl\Web\Compat\CompatController;
use ipl\Web\Url;
use ipl\Web\Widget\ActionLink;
@ -40,7 +44,7 @@ class DashboardsController extends CompatController
$this->createTabs();
$activeHome = $this->dashboard->getActiveHome();
if (! $activeHome || ! $activeHome->hasPanes()) {
if (! $activeHome || ! $activeHome->hasEntries()) {
$this->getTabs()->add('dashboard', [
'active' => true,
'title' => t('Welcome'),
@ -54,8 +58,6 @@ class DashboardsController extends CompatController
})->handleRequest(ServerRequest::fromGlobals());
$this->dashboard->setWelcomeForm($welcomeForm);
} elseif (empty($activeHome->getPanes(true))) {
// TODO(TBD): What to do when the user has only disabled dashboards? Should we render the welcome screen?
} elseif (($pane = $this->getParam('pane'))) {
$this->getTabs()->activate($pane);
}
@ -71,38 +73,43 @@ class DashboardsController extends CompatController
public function homeAction()
{
$home = $this->params->getRequired('home');
if (! $this->dashboard->hasHome($home)) {
if (! $this->dashboard->hasEntry($home)) {
$this->httpNotFound(sprintf(t('Home "%s" not found'), $home));
}
$this->createTabs();
$activeHome = $this->dashboard->getActiveHome();
if (! $activeHome || empty($activeHome->getPanes(true))) {
$this->getTabs()->add($home, [
if (! $activeHome->getEntries()) {
$this->getTabs()->add($activeHome->getName(), [
'active' => true,
'title' => $home,
'title' => $activeHome->getTitle(),
'url' => Url::fromRequest()
]);
// Not to render the cog icon before the above tab
$this->createTabs();
} elseif (($pane = $this->getParam('pane'))) {
$this->createTabs();
$this->dashboard->activate($pane);
}
$this->content = $this->dashboard;
}
public function renameHomeAction()
public function editHomeAction()
{
$this->setTitle(t('Update Home'));
$home = $this->params->getRequired('home');
if (! $this->dashboard->hasHome($home)) {
if (! $this->dashboard->hasEntry($home)) {
$this->httpNotFound(sprintf(t('Home "%s" not found'), $home));
}
$homeForm = (new HomePaneForm($this->dashboard))
->on(HomePaneForm::ON_SUCCESS, function () {
$this->redirectNow('__CLOSE__');
$this->getResponse()->setAutoRefreshInterval(1);
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})
->handleRequest(ServerRequest::fromGlobals());
@ -115,19 +122,41 @@ class DashboardsController extends CompatController
$this->setTitle(t('Remove Home'));
$home = $this->params->getRequired('home');
if (! $this->dashboard->hasHome($home)) {
if (! $this->dashboard->hasEntry($home)) {
$this->httpNotFound(sprintf(t('Home "%s" not found'), $home));
}
$homeForm = (new RemoveHomePaneForm($this->dashboard))
->on(RemoveHomePaneForm::ON_SUCCESS, function () {
$this->redirectNow('__CLOSE__');
$this->getResponse()->setAutoRefreshInterval(1);
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})
->handleRequest(ServerRequest::fromGlobals());
$this->addContent($homeForm);
}
public function newPaneAction()
{
$this->setTitle(t('Add new Dashboard'));
$home = $this->params->getRequired('home');
if (! $this->dashboard->hasEntry($home)) {
$this->httpNotFound(sprintf(t('Home "%s" not found'), $home));
}
$paneForm = (new NewHomePaneForm($this->dashboard))
->on(NewHomePaneForm::ON_SUCCESS, function () {
$this->getResponse()->setAutoRefreshInterval(1);
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})
->handleRequest(ServerRequest::fromGlobals());
$this->addContent($paneForm);
}
public function editPaneAction()
{
$this->setTitle(t('Update Pane'));
@ -135,21 +164,23 @@ class DashboardsController extends CompatController
$pane = $this->params->getRequired('pane');
$home = $this->params->getRequired('home');
if (! $this->dashboard->hasHome($home)) {
if (! $this->dashboard->hasEntry($home)) {
$this->httpNotFound(sprintf(t('Home "%s" not found'), $home));
}
if (! $this->dashboard->getActiveHome()->hasPane($pane)) {
if (! $this->dashboard->getActiveHome()->hasEntry($pane)) {
$this->httpNotFound(sprintf(t('Pane "%s" not found'), $pane));
}
$paneForm = (new HomePaneForm($this->dashboard))
->on(HomePaneForm::ON_SUCCESS, function () {
$this->redirectNow('__CLOSE__');
$this->getResponse()->setAutoRefreshInterval(1);
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})
->handleRequest(ServerRequest::fromGlobals());
$paneForm->load($this->dashboard->getActiveHome()->getPane($pane));
$paneForm->load($this->dashboard->getActiveHome()->getEntry($pane));
$this->addContent($paneForm);
}
@ -161,25 +192,21 @@ class DashboardsController extends CompatController
$home = $this->params->getRequired('home');
$paneParam = $this->params->getRequired('pane');
if (! $this->dashboard->hasHome($home)) {
if (! $this->dashboard->hasEntry($home)) {
$this->httpNotFound(sprintf(t('Home "%s" not found'), $home));
}
if (! $this->dashboard->getActiveHome()->hasPane($paneParam)) {
if (! $this->dashboard->getActiveHome()->hasEntry($paneParam)) {
$this->httpNotFound(sprintf(t('Pane "%s" not found'), $paneParam));
}
$paneForm = new RemoveHomePaneForm($this->dashboard);
$paneForm->populate(['org_name' => $paneParam]);
$paneForm->on(RemoveHomePaneForm::ON_SUCCESS, function () {
$this->redirectNow('__CLOSE__');
})->handleRequest(ServerRequest::fromGlobals());
$this->getResponse()->setAutoRefreshInterval(1);
$paneForm->getElement('btn_remove')->setLabel(t('Remove Pane'));
$paneForm->prependHtml(HtmlElement::create('h1', null, sprintf(
t('Please confirm removal of dashboard pane "%s"'),
$paneParam
)));
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})->handleRequest(ServerRequest::fromGlobals());
$this->addContent($paneForm);
}
@ -191,16 +218,11 @@ class DashboardsController extends CompatController
$dashletForm = new DashletForm($this->dashboard);
$dashletForm->populate($this->getRequest()->getPost());
$dashletForm->on(DashletForm::ON_SUCCESS, function () {
$this->redirectNow('__CLOSE__');
$this->getResponse()->setAutoRefreshInterval(1);
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})->handleRequest(ServerRequest::fromGlobals());
$params = $this->getAllParams();
if ($this->getParam('url')) {
$params['url'] = rawurldecode($this->getParam('url'));
}
$dashletForm->populate($params);
$this->addContent($dashletForm);
}
@ -209,11 +231,13 @@ class DashboardsController extends CompatController
$this->setTitle(t('Edit Dashlet'));
$pane = $this->validateDashletParams();
$dashlet = $pane->getDashlet($this->getParam('dashlet'));
$dashlet = $pane->getEntry($this->getParam('dashlet'));
$dashletForm = (new DashletForm($this->dashboard))
->on(DashletForm::ON_SUCCESS, function () {
$this->redirectNow('__CLOSE__');
$this->getResponse()->setAutoRefreshInterval(1);
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})
->handleRequest(ServerRequest::fromGlobals());
@ -225,12 +249,14 @@ class DashboardsController extends CompatController
public function removeDashletAction()
{
$this->validateDashletParams();
$this->setTitle(t('Remove Dashlet'));
$this->validateDashletParams();
$removeForm = (new RemoveDashletForm($this->dashboard))
->on(RemoveDashletForm::ON_SUCCESS, function () {
$this->redirectNow('__CLOSE__');
$this->getResponse()->setAutoRefreshInterval(1);
$this->redirectNow(Url::fromPath(Dashboard::BASE_ROUTE . '/settings'));
})
->handleRequest(ServerRequest::fromGlobals());
@ -243,60 +269,109 @@ class DashboardsController extends CompatController
public function reorderWidgetsAction()
{
$this->assertHttpMethod('post');
if (! $this->getRequest()->isApiRequest()) {
$this->httpBadRequest('No API request');
}
if (! preg_match('/([^;]*);?/', $this->getRequest()->getHeader('Content-Type'), $matches)
|| $matches[1] !== 'application/json') {
$this->httpBadRequest('No JSON content');
}
$dashboards = $this->getRequest()->getPost();
$widgetType = array_pop($dashboards);
if (! isset($dashboards['dashboardData'])) {
$this->httpBadRequest(t('Invalid request data'));
}
foreach ($dashboards as $key => $panes) {
$home = $widgetType === 'Homes' ? $panes : $key;
if (! $this->dashboard->hasHome($home)) {
$this->httpNotFound(sprintf(t('Dashboard home "%s" not found'), $home));
$dashboards = Json::decode($dashboards['dashboardData'], true);
$originals = $dashboards['originals'];
unset($dashboards['Type']);
unset($dashboards['originals']);
$orgHome = null;
$orgPane = null;
if ($originals && isset($originals['originalHome'])) {
/** @var DashboardHome $orgHome */
$orgHome = $this->dashboard->getEntry($originals['originalHome']);
$orgHome->setActive()->loadDashboardEntries();
if (isset($originals['originalPane'])) {
$orgPane = $orgHome->getEntry($originals['originalPane']);
$orgHome->setEntries([$orgPane->getName() => $orgPane]);
}
}
$reroutePath = $dashboards['redirectPath'];
unset($dashboards['redirectPath']);
foreach ($dashboards as $home => $value) {
if (! $this->dashboard->hasEntry($home)) {
Notification::error(sprintf(t('Dashboard home "%s" not found'), $home));
break;
}
$home = $this->dashboard->getHome($home);
if ($widgetType === 'Homes') {
$home->setPriority($key);
$this->dashboard->manageHome($home);
/** @var DashboardHome $home */
$home = $this->dashboard->getEntry($home);
if (! is_array($value)) {
$this->dashboard->reorderWidget($home, (int) $value);
continue;
Notification::success(sprintf(t('Updated dashboard home "%s" successfully'), $home->getTitle()));
break;
}
$home->setActive();
$home->loadPanesFromDB();
foreach ($panes as $innerKey => $value) {
$pane = $widgetType === 'Dashboards' ? $value : $innerKey;
if (! $home->hasPane($pane)) {
$this->httpNotFound(sprintf(t('Dashboard pane "%s" not found'), $pane));
$home->setActive()->loadDashboardEntries();
foreach ($value as $pane => $indexOrValues) {
if (! $home->hasEntry($pane) && (! $orgHome || ! $orgHome->hasEntry($pane))) {
Notification::error(sprintf(t('Dashboard pane "%s" not found'), $pane));
break;
}
$pane = $home->getPane($pane);
if ($widgetType === 'Dashboards') {
$pane->setPriority($innerKey);
$home->managePanes($pane);
} else {
foreach ($value as $order => $dashlet) {
if (! $pane->hasDashlet($dashlet)) {
$this->httpNotFound(sprintf(t('Dashlet "%s" not found'), $dashlet));
}
$dashlet = $pane->getDashlet($dashlet);
$dashlet->setPriority($order);
$pane->manageDashlets($dashlet);
/** @var Pane $pane */
$pane = $home->hasEntry($pane) ? $home->getEntry($pane) : $orgHome->getEntry($pane);
if (! is_array($indexOrValues)) {
if ($orgHome && $orgHome->hasEntry($pane->getName()) && $home->hasEntry($pane->getName())) {
Notification::error(sprintf(
t('Dashboard "%s" already exists within "%s" home'),
$pane->getTitle(),
$home->getTitle()
));
break;
}
// Perform DB updates
$home->reorderWidget($pane, (int) $indexOrValues, $orgHome);
if ($orgHome) {
// In order to properly update the dashlets id (user + home + pane + dashlet)
$pane->manageEntry($pane->getEntries());
}
Notification::success(sprintf(
t('%s dashboard pane "%s" successfully'),
$orgHome ? 'Moved' : 'Updated',
$pane->getTitle()
));
break;
}
foreach ($indexOrValues as $dashlet => $index) {
if (! $pane->hasEntry($dashlet) && (! $orgPane || ! $orgPane->hasEntry($dashlet))) {
Notification::error(sprintf(t('Dashlet "%s" not found'), $dashlet));
break;
}
if ($orgPane && $orgPane->hasEntry($dashlet) && $pane->hasEntry($dashlet)) {
Notification::error(sprintf(
t('Dashlet "%s" already exists within "%s" dashboard pane'),
$dashlet,
$pane->getTitle()
));
break;
}
$dashlet = $pane->hasEntry($dashlet) ? $pane->getEntry($dashlet) : $orgPane->getEntry($dashlet);
$pane->reorderWidget($dashlet, (int) $index, $orgPane);
Notification::success(sprintf(
t('%s dashlet "%s" successfully'),
$orgPane ? 'Moved' : 'Updated',
$dashlet->getTitle()
));
}
}
}
exit;
$this->redirectNow($reroutePath);
}
/**
@ -320,10 +395,6 @@ class DashboardsController extends CompatController
$setupForm = new SetupNewDashboard($this->dashboard);
$setupForm->initDashlets(Dashboard::getModuleDashlets($query));
$setupForm->on(SetupNewDashboard::ON_SUCCESS, function () use ($setupForm) {
if ($setupForm->getPopulatedValue('btn_cancel')) {
$this->redirectNow('__CLOSE__');
}
$this->redirectNow($setupForm->getRedirectUrl());
})->handleRequest(ServerRequest::fromGlobals());
@ -333,8 +404,12 @@ class DashboardsController extends CompatController
public function settingsAction()
{
$this->createTabs();
// TODO(yh): This may raise an exception when the given tab name doesn't exist.
// But as ipl::Tabs() doesn't offer the possibility to check this beforehand, just ignore it for now!!
$activeHome = $this->dashboard->getActiveHome();
// We can't grant access the user to the dashboard manager if there aren't any dashboards to manage
if (! $activeHome || (! $activeHome->hasEntries() && count($this->dashboard->getEntries()) === 1)) {
$this->redirectNow(Dashboard::BASE_ROUTE);
}
$this->dashboard->activate('dashboard_settings');
$this->addControl(new ActionLink(
@ -358,7 +433,7 @@ class DashboardsController extends CompatController
{
$tabs = $this->dashboard->getTabs();
$activeHome = $this->dashboard->getActiveHome();
if ($activeHome && $activeHome->hasPanes()) {
if (($activeHome && $activeHome->hasEntries()) || count($this->dashboard->getEntries()) > 1) {
$tabs->extend(new DashboardSettings());
}
@ -371,16 +446,16 @@ class DashboardsController extends CompatController
$pane = $this->params->getRequired('pane');
$dashlet = $this->params->getRequired('dashlet');
if (! $this->dashboard->hasHome($home)) {
if (! $this->dashboard->hasEntry($home)) {
$this->httpNotFound(sprintf(t('Home "%s" not found'), $home));
}
if (! $this->dashboard->getActiveHome()->hasPane($pane)) {
if (! $this->dashboard->getActiveHome()->hasEntry($pane)) {
$this->httpNotFound(sprintf(t('Pane "%s" not found'), $pane));
}
$pane = $this->dashboard->getActiveHome()->getPane($pane);
if (! $pane->hasDashlet($dashlet)) {
$pane = $this->dashboard->getActiveHome()->getEntry($pane);
if (! $pane->hasEntry($dashlet)) {
$this->httpNotFound(sprintf(t('Dashlet "%s" not found'), $dashlet));
}

View File

@ -0,0 +1,113 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Forms\Dashboard;
use Icinga\Web\Dashboard\Common\BaseDashboard;
use Icinga\Web\Dashboard\Dashboard;
use ipl\Html\Contract\FormElement;
use ipl\Html\HtmlElement;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Url;
use ipl\Web\Widget\Icon;
/**
* Base Form for all kinds of dashboard types
*/
abstract class BaseDashboardForm extends CompatForm
{
const CREATE_NEW_HOME = 'Create new Home';
const CREATE_NEW_PANE = 'Create new Dashboard';
/**
* Dashboard instance for which this form is being rendered
*
* @var Dashboard
*/
protected $dashboard;
/**
* Create a new Dashboard Form
*
* @param Dashboard $dashboard
*/
public function __construct(Dashboard $dashboard)
{
$this->dashboard = $dashboard;
// This is needed for the modal views
$this->setAction((string) Url::fromRequest());
}
public function hasBeenSubmitted()
{
// We don't use addElement() for the form controls, so the form has no way of knowing
// that we do have a submit button and will always be submitted with autosubmit elements
return $this->hasBeenSent() && $this->getPopulatedValue('submit');
}
/**
* Populate form data from config
*
* @param BaseDashboard $dashboard
*
* @return void
*/
public function load(BaseDashboard $dashboard)
{
}
/**
* Create custom form controls
*
* @return HtmlElement
*/
protected function createFormControls()
{
return HtmlElement::create('div', ['class' => 'control-group form-controls']);
}
/**
* Create a cancel button
*
* @return FormElement
*/
protected function createCancelButton()
{
return $this->createElement('submitButton', 'btn_cancel', ['class' => 'modal-cancel', 'label' => t('Cancel')]);
}
/**
* Create a remove button
*
* @param Url $action
* @param string $label
*
* @return FormElement
*/
protected function createRemoveButton(Url $action, $label)
{
return $this->createElement('submitButton', 'btn_remove', [
'class' => 'remove-button',
'label' => [new Icon('trash'), $label],
'formaction' => (string) $action
]);
}
/**
* Create and register a submit button
*
* @param string $label
*
* @return FormElement
*/
protected function registerSubmitButton($label)
{
$submitElement = $this->createElement('submit', 'submit', ['class' => 'btn-primary', 'label' => $label]);
$this->registerElement($submitElement);
return $submitElement;
}
}

View File

@ -6,136 +6,93 @@ namespace Icinga\Forms\Dashboard;
use Exception;
use Icinga\Application\Logger;
use Icinga\Web\Dashboard\Common\BaseDashboard;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Notification;
use Icinga\Web\Dashboard\Dashlet;
use Icinga\Web\Dashboard\Pane;
use ipl\Html\HtmlElement;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Url;
/**
* Form to add an url a dashboard pane
*/
class DashletForm extends CompatForm
class DashletForm extends BaseDashboardForm
{
/**
* @var Dashboard
*/
private $dashboard;
public function __construct(Dashboard $dashboard)
{
$this->dashboard = $dashboard;
$this->setAction((string) Url::fromRequest());
}
public function hasBeenSubmitted()
{
return $this->hasBeenSent() && $this->getPopulatedValue('submit');
}
protected function assemble()
{
$requestUrl = Url::fromRequest();
$homes = $this->dashboard->getHomeKeyTitleArr();
$homes = $this->dashboard->getEntryKeyTitleArr();
$activeHome = $this->dashboard->getActiveHome();
$currentHome = $requestUrl->getParam('home', reset($homes));
$populatedHome = $this->getPopulatedValue('home', $currentHome);
$panes = [];
if ($currentHome === $populatedHome && $this->getPopulatedValue('create_new_home') !== 'y') {
if ($currentHome === $populatedHome && $populatedHome !== self::CREATE_NEW_HOME) {
if (! $currentHome || ! $activeHome) {
// Home param isn't passed through, so let's try to load based on the first home
$firstHome = $this->dashboard->rewindHomes();
$firstHome = $this->dashboard->rewindEntries();
if ($firstHome) {
$this->dashboard->loadDashboards($firstHome->getName());
$this->dashboard->loadDashboardEntries($firstHome->getName());
$panes = $firstHome->getPaneKeyTitleArr();
$panes = $firstHome->getEntryKeyTitleArr();
}
} else {
$panes = $activeHome->getPaneKeyTitleArr();
$panes = $activeHome->getEntryKeyTitleArr();
}
} elseif ($this->dashboard->hasHome($populatedHome)) {
$this->dashboard->loadDashboards($populatedHome);
} elseif ($this->dashboard->hasEntry($populatedHome)) {
$this->dashboard->loadDashboardEntries($populatedHome);
$panes = $this->dashboard->getActiveHome()->getPaneKeyTitleArr();
$panes = $this->dashboard->getActiveHome()->getEntryKeyTitleArr();
}
$this->addElement('hidden', 'org_pane', ['required' => false]);
$this->addElement('hidden', 'org_home', ['required' => false]);
$this->addElement('hidden', 'org_dashlet', ['required' => false]);
$this->addElement('checkbox', 'create_new_home', [
'class' => 'autosubmit',
'required' => false,
'disabled' => empty($homes) ?: null,
'label' => t('New Dashboard Home'),
'description' => t('Check this box if you want to add the dashboard to a new dashboard home.'),
$this->addElement('select', 'home', [
'class' => 'autosubmit',
'required' => true,
'disabled' => empty($homes) ?: null,
'value' => $populatedHome,
'multiOptions' => array_merge([self::CREATE_NEW_HOME => self::CREATE_NEW_HOME], $homes),
'label' => t('Select Home'),
'descriptions' => t('Select a dashboard home you want to add the dashboard pane to.')
]);
if (empty($homes) || $this->getPopulatedValue('create_new_home') === 'y') {
// $el->attrs->set() has no effect here anymore, so we need to register a proper callback
$this->getElement('create_new_home')
->getAttributes()
->registerAttributeCallback('checked', function () {
return true;
});
$this->addElement('text', 'home', [
if (empty($homes) || $populatedHome === self::CREATE_NEW_HOME) {
$this->addElement('text', 'new_home', [
'required' => true,
'label' => t('Dashboard Home'),
'label' => t('Home Title'),
'placeholder' => t('Enter dashboard home title'),
'description' => t('Enter a title for the new dashboard home.')
]);
} else {
$this->addElement('select', 'home', [
'required' => true,
'class' => 'autosubmit',
'value' => $currentHome,
'multiOptions' => $homes,
'label' => t('Dashboard Home'),
'descriptions' => t('Select a home you want to add the dashboard pane to.')
]);
}
$disable = empty($panes) || $this->getPopulatedValue('create_new_home') === 'y';
$this->addElement('checkbox', 'create_new_pane', [
'required' => false,
'class' => 'autosubmit',
'disabled' => $disable ?: null,
'label' => t('New Dashboard'),
'description' => t('Check this box if you want to add the dashlet to a new dashboard.'),
]);
$populatedPane = $this->getPopulatedValue('pane');
// Pane element's values are depending on the home element's value
if (! in_array($this->getPopulatedValue('pane'), $panes)) {
if ($populatedPane !== self::CREATE_NEW_PANE && ! in_array($populatedPane, $panes)) {
$this->clearPopulatedValue('pane');
}
if ($disable || $this->getValue('create_new_pane') === 'y') {
// $el->attrs->set() has no effect here anymore, so we need to register a proper callback
$this->getElement('create_new_pane')
->getAttributes()
->registerAttributeCallback('checked', function () {
return true;
});
$populatedPane = $this->getPopulatedValue('pane', reset($panes));
$disable = empty($panes) || $populatedHome === self::CREATE_NEW_HOME;
$this->addElement('select', 'pane', [
'class' => 'autosubmit',
'required' => true,
'disabled' => $disable ?: null,
'value' => ! $disable ? $populatedPane : self::CREATE_NEW_PANE, // Cheat the browser complains
'multiOptions' => array_merge([self::CREATE_NEW_PANE => self::CREATE_NEW_PANE], $panes),
'label' => t('Select Dashboard'),
'description' => t('Select a dashboard you want to add the dashlet to.'),
]);
$this->addElement('text', 'pane', [
if ($disable || $this->getPopulatedValue('pane') === self::CREATE_NEW_PANE) {
$this->addElement('text', 'new_pane', [
'required' => true,
'label' => t('New Dashboard Title'),
'label' => t('Dashboard Title'),
'placeholder' => t('Enter dashboard title'),
'description' => t('Enter a title for the new dashboard.'),
]);
} else {
$this->addElement('select', 'pane', [
'required' => true,
'value' => reset($panes),
'multiOptions' => $panes,
'label' => t('Dashboard'),
'description' => t('Select a dashboard you want to add the dashlet to.'),
]);
}
$this->addHtml(new HtmlElement('hr'));
@ -143,6 +100,7 @@ class DashletForm extends CompatForm
$this->addElement('textarea', 'url', [
'required' => true,
'label' => t('Url'),
'placeholder' => t('Enter dashlet url'),
'description' => t(
'Enter url to be loaded in the dashlet. You can paste the full URL, including filters.'
),
@ -151,40 +109,24 @@ class DashletForm extends CompatForm
$this->addElement('text', 'dashlet', [
'required' => true,
'label' => t('Dashlet Title'),
'placeholder' => t('Enter a dashlet title'),
'description' => t('Enter a title for the dashlet.'),
]);
$url = (string) Url::fromPath(Dashboard::BASE_ROUTE . '/browse');
$removeButton = null;
if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/edit-dashlet') {
$targetUrl = (clone $requestUrl)->setPath(Dashboard::BASE_ROUTE . '/remove-dashlet');
$removeButton = $this->createRemoveButton($targetUrl, t('Remove Dashlet'));
}
$element = $this->createElement('submit', 'submit', ['label' => t('Add to Dashboard')]);
$this->registerElement($element)->decorate($element);
// We might need this later to allow the user to browse dashlets when creating a dashlet
$this->addElement('submit', 'btn_browse', [
'label' => t('Browse Dashlets'),
'href' => $url,
'formaction' => $url,
$formControls = $this->createFormControls();
$formControls->add([
$this->registerSubmitButton(t('Add to Dashboard')),
$removeButton,
$this->createCancelButton()
]);
$this->getElement('btn_browse')->setWrapper($element->getWrapper());
}
/**
* Populate form data from config
*
* @param Dashlet $dashlet
*/
public function load(Dashlet $dashlet)
{
$home = Url::fromRequest()->getParam('home');
$this->populate(array(
'org_home' => $home,
'org_pane' => $dashlet->getPane()->getName(),
'pane' => $dashlet->getPane()->getTitle(),
'org_dashlet' => $dashlet->getName(),
'dashlet' => $dashlet->getTitle(),
'url' => $dashlet->getUrl()->getRelativeUrl()
));
$this->addHtml($formControls);
}
protected function onSuccess()
@ -192,27 +134,38 @@ class DashletForm extends CompatForm
$conn = Dashboard::getConn();
$dashboard = $this->dashboard;
$selectedHome = $this->getPopulatedValue('home');
if (! $selectedHome || $selectedHome === self::CREATE_NEW_HOME) {
$selectedHome = $this->getPopulatedValue('new_home');
}
$selectedPane = $this->getPopulatedValue('pane');
// If "pane" element is disabled, there will be no populated value for it
if (! $selectedPane || $selectedPane === self::CREATE_NEW_PANE) {
$selectedPane = $this->getPopulatedValue('new_pane');
}
if (Url::fromRequest()->getPath() === Dashboard::BASE_ROUTE . '/new-dashlet') {
$home = new DashboardHome($this->getValue('home'));
if ($dashboard->hasHome($home->getName())) {
$home = $dashboard->getHome($home->getName());
if ($home->getName() !== $dashboard->getActiveHome()->getName()) {
$home->setActive();
$home->loadPanesFromDB();
$currentHome = new DashboardHome($selectedHome);
if ($dashboard->hasEntry($currentHome->getName())) {
$currentHome = clone $dashboard->getEntry($currentHome->getName());
if ($currentHome->getName() !== $dashboard->getActiveHome()->getName()) {
$currentHome->setActive();
$currentHome->loadDashboardEntries();
}
}
$pane = new Pane($this->getValue('pane'));
if ($home->hasPane($pane->getName())) {
$pane = $home->getPane($pane->getName());
$currentPane = new Pane($selectedPane);
if ($currentHome->hasEntry($currentPane->getName())) {
$currentPane = clone $currentHome->getEntry($currentPane->getName());
}
$dashlet = new Dashlet($this->getValue('dashlet'), $this->getValue('url'), $pane);
if ($pane->hasDashlet($dashlet->getName())) {
$dashlet = new Dashlet($this->getValue('dashlet'), $this->getValue('url'), $currentPane);
if ($currentPane->hasEntry($dashlet->getName())) {
Notification::error(sprintf(
t('Dashlet "%s" already exists within the "%s" dashboard pane'),
$dashlet->getTitle(),
$pane->getTitle()
$currentPane->getTitle()
));
return;
@ -221,12 +174,12 @@ class DashletForm extends CompatForm
$conn->beginTransaction();
try {
$dashboard->manageHome($home);
$home->managePanes($pane);
$pane->manageDashlets($dashlet);
$dashboard->manageEntry($currentHome);
$currentHome->manageEntry($currentPane);
$currentPane->manageEntry($dashlet);
$conn->commitTransaction();
} catch (Exception $err) { // This error handling is just for debugging purpose! Will be removed!
} catch (Exception $err) {
Logger::error($err);
$conn->rollBackTransaction();
@ -235,26 +188,35 @@ class DashletForm extends CompatForm
Notification::success(sprintf(t('Created dashlet "%s" successfully'), $dashlet->getTitle()));
} else {
$orgHome = $dashboard->getHome($this->getValue('org_home'));
$orgPane = $orgHome->getPane($this->getValue('org_pane'));
$orgDashlet = $orgPane->getDashlet($this->getValue('org_dashlet'));
$orgHome = $dashboard->getEntry($this->getValue('org_home'));
$orgPane = $orgHome->getEntry($this->getValue('org_pane'));
$orgDashlet = $orgPane->getEntry($this->getValue('org_dashlet'));
$currentHome = new DashboardHome($this->getValue('home'));
if ($dashboard->hasHome($currentHome->getName())) {
$currentHome = $dashboard->getHome($currentHome->getName());
$currentHome = new DashboardHome($selectedHome);
if ($dashboard->hasEntry($currentHome->getName())) {
$currentHome = clone $dashboard->getEntry($currentHome->getName());
$activeHome = $dashboard->getActiveHome();
if ($currentHome->getName() !== $activeHome->getName()) {
$currentHome->setActive();
$currentHome->loadPanesFromDB();
$currentHome->loadDashboardEntries();
}
}
$currentPane = new Pane($this->getValue('pane'));
if ($currentHome->hasPane($currentPane->getName())) {
$currentPane = $currentHome->getPane($currentPane->getName());
$currentPane = new Pane($selectedPane);
if ($currentHome->hasEntry($currentPane->getName())) {
$currentPane = clone $currentHome->getEntry($currentPane->getName());
}
$currentPane->setHome($currentHome);
// When the user wishes to create a new dashboard pane, we have to explicitly reset the dashboard panes
// of the original home, so that it isn't considered as we want to move the pane even though it isn't
// supposed to when the original home contains a dashboard with the same name
// @see DashboardHome::managePanes() for details
$selectedPane = $this->getPopulatedValue('pane');
if ((! $selectedPane || $selectedPane === self::CREATE_NEW_PANE)
&& ! $currentHome->hasEntry($currentPane->getName())) {
$orgHome->setEntries([]);
}
$currentDashlet = clone $orgDashlet;
$currentDashlet
@ -263,7 +225,7 @@ class DashletForm extends CompatForm
->setTitle($this->getValue('dashlet'));
if ($orgPane->getName() !== $currentPane->getName()
&& $currentPane->hasDashlet($currentDashlet->getName())) {
&& $currentPane->hasEntry($currentDashlet->getName())) {
Notification::error(sprintf(
t('Failed to move dashlet "%s": Dashlet already exists within the "%s" dashboard pane'),
$currentDashlet->getTitle(),
@ -287,12 +249,18 @@ class DashletForm extends CompatForm
return;
}
if (empty($paneDiff)) {
// No dashboard diff means the dashlet is still in the same pane, so just
// reset the dashlets of the original pane
$orgPane->setEntries([]);
}
$conn->beginTransaction();
try {
$dashboard->manageHome($currentHome);
$currentHome->managePanes($currentPane, $orgHome);
$currentPane->manageDashlets($currentDashlet, $orgPane);
$dashboard->manageEntry($currentHome);
$currentHome->manageEntry($currentPane, $orgHome);
$currentPane->manageEntry($currentDashlet, $orgPane);
$conn->commitTransaction();
} catch (Exception $err) {
@ -305,4 +273,21 @@ class DashletForm extends CompatForm
Notification::success(sprintf(t('Updated dashlet "%s" successfully'), $currentDashlet->getTitle()));
}
}
public function load(BaseDashboard $dashlet)
{
$home = Url::fromRequest()->getParam('home');
/** @var Dashlet $dashlet */
$this->populate(array(
'org_home' => $home,
'org_pane' => $dashlet->getPane()->getName(),
'org_dashlet' => $dashlet->getName(),
'dashlet' => $dashlet->getTitle(),
'url' => $dashlet->getUrl()->getRelativeUrl()
));
if ($this->getPopulatedValue('pane') !== self::CREATE_NEW_PANE) {
$this->populate(['pane' => $dashlet->getPane()->getTitle()]);
}
}
}

View File

@ -5,81 +5,53 @@
namespace Icinga\Forms\Dashboard;
use Icinga\Application\Logger;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Dashboard\Common\BaseDashboard;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Notification;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Dashboard\Pane;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Url;
class HomePaneForm extends CompatForm
class HomePaneForm extends BaseDashboardForm
{
/** @var Dashboard */
protected $dashboard;
public function __construct(Dashboard $dashboard)
{
$this->dashboard = $dashboard;
// We need to set this explicitly needed for modals
$this->setAction((string) Url::fromRequest());
}
/**
* Populate form data from config
*
* @param DashboardHome|Pane $widget
*/
public function load($widget)
{
$title = $widget instanceof Pane ? $widget->getTitle() : $widget->getLabel();
$this->populate([
'org_title' => $title,
'title' => $title,
'org_name' => $widget->getName()
]);
}
protected function assemble()
{
$titleDesc = t('Edit the title of this dashboard home');
$buttonLabel = t('Update Home');
$removeButtonLabel = t('Remove Home');
$requestUrl = Url::fromRequest();
$removeTargetUrl = (clone $requestUrl)->setPath(Dashboard::BASE_ROUTE . '/remove-home');
$this->addElement('hidden', 'org_name', ['required' => false]);
$this->addElement('hidden', 'org_title', ['required' => false]);
$titleDesc = t('Edit the title of this dashboard home');
$buttonLabel = t('Update Home');
if (Url::fromRequest()->getPath() === Dashboard::BASE_ROUTE . '/edit-pane') {
if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/edit-pane') {
$titleDesc = t('Edit the title of this dashboard pane');
$buttonLabel = t('Update Pane');
$removeButtonLabel = t('Remove Pane');
$homes = $this->dashboard->getHomeKeyTitleArr();
$this->addElement('checkbox', 'create_new_home', [
'required' => false,
'class' => 'autosubmit',
'disabled' => empty($homes) ?: null,
'label' => t('New Dashboard Home'),
'description' => t('Check this box if you want to move the pane to a new dashboard home.'),
]);
$removeTargetUrl = (clone $requestUrl)->setPath(Dashboard::BASE_ROUTE . '/remove-pane');
$homes = $this->dashboard->getEntryKeyTitleArr();
$activeHome = $this->dashboard->getActiveHome();
$populatedHome = $this->getPopulatedValue('home', $activeHome->getName());
if (empty($homes) || $this->getPopulatedValue('create_new_home') === 'y') {
$this->getElement('create_new_home')->addAttributes(['checked' => 'checked']);
$this->addElement('select', 'home', [
'class' => 'autosubmit',
'required' => true,
'value' => $populatedHome,
'multiOptions' => array_merge([self::CREATE_NEW_HOME => self::CREATE_NEW_HOME], $homes),
'label' => t('Assign to Home'),
'description' => t('Select a dashboard home you want to move the dashboard to.'),
]);
$this->addElement('text', 'home', [
if (empty($homes) || $this->getPopulatedValue('home') === self::CREATE_NEW_HOME) {
$this->addElement('text', 'new_home', [
'required' => true,
'label' => t('Dashboard Home'),
'placeholder' => t('Enter dashboard home title'),
'description' => t('Enter a title for the new dashboard home.'),
]);
} else {
$this->addElement('select', 'home', [
'required' => true,
'class' => 'autosubmit',
'value' => $populatedHome,
'multiOptions' => $homes,
'label' => t('Move to Home'),
'description' => t('Select a dashboard home you want to move the dashboard to.'),
]);
}
}
@ -89,46 +61,64 @@ class HomePaneForm extends CompatForm
'description' => $titleDesc
]);
$this->addElement('submit', 'btn_update', ['label' => $buttonLabel]);
$formControls = $this->createFormControls();
$formControls->add([
$this->registerSubmitButton($buttonLabel),
$this->createRemoveButton($removeTargetUrl, $removeButtonLabel),
$this->createCancelButton()
]);
$this->addHtml($formControls);
}
protected function onSuccess()
{
$requestUrl = Url::fromRequest();
if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/edit-pane') {
$orgHome = $this->dashboard->getHome($requestUrl->getParam('home'));
$orgHome = $this->dashboard->getEntry($requestUrl->getParam('home'));
$currentHome = new DashboardHome($this->getValue('home'));
if ($this->dashboard->hasHome($currentHome->getName())) {
$currentHome = $this->dashboard->getHome($currentHome->getName());
$selectedHome = $this->getPopulatedValue('home');
if (! $selectedHome || $selectedHome === self::CREATE_NEW_HOME) {
$selectedHome = $this->getPopulatedValue('new_home');
}
$currentHome = new DashboardHome($selectedHome);
if ($this->dashboard->hasEntry($currentHome->getName())) {
/** @var DashboardHome $currentHome */
$currentHome = clone $this->dashboard->getEntry($currentHome->getName());
$activeHome = $this->dashboard->getActiveHome();
if ($currentHome->getName() !== $activeHome->getName()) {
$currentHome->setActive();
$currentHome->loadPanesFromDB();
$currentHome->setActive()->loadDashboardEntries();
}
}
$currentPane = $orgHome->getPane($this->getValue('org_name'));
$currentPane = clone $orgHome->getEntry($this->getValue('org_name'));
$currentPane
->setHome($currentHome)
->setTitle($this->getValue('title'));
if ($orgHome->getName() !== $currentHome->getName() && $currentHome->hasPane($currentPane->getName())) {
if ($orgHome->getName() !== $currentHome->getName() && $currentHome->hasEntry($currentPane->getName())) {
Notification::error(sprintf(
t('Failed to move dashboard "%s": Dashbaord pane already exists within the "%s" dashboard home'),
$currentPane->getTitle(),
$currentHome->getLabel()
$currentHome->getTitle()
));
return;
}
if ($currentHome->getName() === $orgHome->getName()) {
// There is no dashboard home diff so clear all the dashboard pane
// of the org home
$orgHome->setEntries([]);
}
$conn = Dashboard::getConn();
$conn->beginTransaction();
try {
$this->dashboard->manageHome($currentHome);
$currentHome->managePanes($currentPane, $orgHome);
$this->dashboard->manageEntry($currentHome);
$currentHome->manageEntry($currentPane, $orgHome);
$conn->commitTransaction();
} catch (\Exception $err) {
@ -139,10 +129,19 @@ class HomePaneForm extends CompatForm
Notification::success(sprintf(t('Updated dashboard pane "%s" successfully'), $currentPane->getTitle()));
} else {
$home = $this->dashboard->getActiveHome();
$home->setLabel($this->getValue('title'));
$home->setTitle($this->getValue('title'));
$this->dashboard->manageHome($home);
Notification::success(sprintf(t('Updated dashboard home "%s" successfully'), $home->getLabel()));
$this->dashboard->manageEntry($home);
Notification::success(sprintf(t('Updated dashboard home "%s" successfully'), $home->getTitle()));
}
}
public function load(BaseDashboard $dashboard)
{
$this->populate([
'org_title' => $dashboard->getTitle(),
'title' => $dashboard->getTitle(),
'org_name' => $dashboard->getName()
]);
}
}

View File

@ -3,80 +3,92 @@
namespace Icinga\Forms\Dashboard;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Dashboard\Pane;
use Icinga\Web\Notification;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Url;
class NewHomePaneForm extends CompatForm
class NewHomePaneForm extends BaseDashboardForm
{
/** @var Dashboard */
protected $dashboard;
public function __construct(Dashboard $dashboard)
{
$this->dashboard = $dashboard;
parent::__construct($dashboard);
$requestUrl = Url::fromRequest();
// We need to set this explicitly needed for modals
$this->setAction((string) $requestUrl);
if ($requestUrl->hasParam('home')) {
$this->populate(['home' => $requestUrl->getParam('home')]);
}
}
public function hasBeenSubmitted()
{
return $this->hasBeenSent()
&& ($this->getPopulatedValue('btn_cancel')
|| $this->getPopulatedValue('submit'));
}
protected function assemble()
{
$populatedHome = Url::fromRequest()->getParam('home');
$this->addElement('text', 'pane', [
'required' => true,
'label' => t('Title'),
'placeholder' => t('Create new Dashboard'),
'description' => t('Add new dashboard to this home.')
]);
$homes = array_merge([self::CREATE_NEW_HOME => self::CREATE_NEW_HOME], $this->dashboard->getEntryKeyTitleArr());
$this->addElement('select', 'home', [
'required' => true,
'class' => 'autosubmit',
'value' => $populatedHome,
'multiOptions' => $this->dashboard->getHomeKeyTitleArr(),
'multiOptions' => $homes,
'label' => t('Assign to Home'),
'description' => t('A dashboard home you want to assign the new dashboard to.'),
]);
$submitButton = $this->createElement('submit', 'submit', [
'class' => 'autosubmit',
'label' => t('Add Dashboard'),
]);
$this->registerElement($submitButton)->decorate($submitButton);
if ($this->getPopulatedValue('home') === self::CREATE_NEW_HOME) {
$this->addElement('text', 'new_home', [
'required' => true,
'label' => t('Dashboard Home'),
'placeholder' => t('Enter dashboard home title'),
'description' => t('Enter a title for the new dashboard home.'),
]);
}
$this->addElement('submit', 'btn_cancel', ['label' => t('Cancel')]);
$this->getElement('btn_cancel')->setWrapper($submitButton->getWrapper());
$submitButton = $this->createElement('submit', 'submit', [
'class' => 'btn-primary',
'label' => t('Add Dashboard')
]);
$this->registerElement($submitButton);
$formControls = $this->createFormControls();
$formControls->add([
$this->registerSubmitButton(t('Add Dashboard')),
$this->createCancelButton()
]);
$this->addHtml($formControls);
}
protected function onSuccess()
{
$requestUrl = Url::fromRequest();
$dashboard = $this->dashboard;
$conn = Dashboard::getConn();
if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/new-pane') {
$home = $this->getPopulatedValue('home');
$home = $dashboard->getHome($home);
$selectedHome = $this->getPopulatedValue('home');
if (! $selectedHome || $selectedHome === self::CREATE_NEW_HOME) {
$selectedHome = $this->getPopulatedValue('new_home');
}
if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/new-pane') {
$currentHome = new DashboardHome($selectedHome);
if ($this->dashboard->hasEntry($currentHome->getName())) {
$currentHome = clone $this->dashboard->getEntry($currentHome->getName());
if ($currentHome->getName() !== $this->dashboard->getActiveHome()->getName()) {
$currentHome->setActive()->loadDashboardEntries();
}
}
$pane = new Pane($this->getPopulatedValue('pane'));
$conn->beginTransaction();
try {
$pane = new Pane($this->getPopulatedValue('pane'));
$home->managePanes($pane);
$this->dashboard->manageEntry($currentHome);
$currentHome->manageEntry($pane);
$conn->commitTransaction();
} catch (\Exception $err) {
@ -85,8 +97,6 @@ class NewHomePaneForm extends CompatForm
}
Notification::success('Added dashboard successfully');
} else {
}
}
}

View File

@ -5,21 +5,14 @@
namespace Icinga\Forms\Dashboard;
use Icinga\Web\Notification;
use Icinga\Web\Dashboard\Dashboard;
use ipl\Html\HtmlElement;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Url;
class RemoveDashletForm extends CompatForm
class RemoveDashletForm extends BaseDashboardForm
{
/** @var Dashboard */
protected $dashboard;
public function __construct(Dashboard $dashboard)
public function hasBeenSubmitted()
{
$this->dashboard = $dashboard;
$this->setAction((string) Url::fromRequest());
return $this->hasBeenSent() && $this->getPopulatedValue('btn_remove');
}
protected function assemble()
@ -29,17 +22,20 @@ class RemoveDashletForm extends CompatForm
Url::fromRequest()->getParam('dashlet')
)));
$this->addElement('submit', 'remove_dashlet', ['label' => t('Remove Dashlet')]);
$submit = $this->registerSubmitButton(t('Remove Dashlet'));
$submit->setName('btn_remove');
$this->addHtml($submit);
}
protected function onSuccess()
{
$requestUrl = Url::fromRequest();
$home = $this->dashboard->getActiveHome();
$pane = $home->getPane($requestUrl->getParam('pane'));
$pane = $home->getEntry($requestUrl->getParam('pane'));
$dashlet = $requestUrl->getParam('dashlet');
$pane->removeDashlet($dashlet);
$pane->removeEntry($dashlet);
Notification::success(sprintf(t('Removed dashlet "%s" successfully'), $dashlet));
}

View File

@ -6,24 +6,32 @@ namespace Icinga\Forms\Dashboard;
use Icinga\Web\Notification;
use Icinga\Web\Dashboard\Dashboard;
use ipl\Web\Compat\CompatForm;
use ipl\Html\HtmlElement;
use ipl\Web\Url;
class RemoveHomePaneForm extends CompatForm
class RemoveHomePaneForm extends BaseDashboardForm
{
/** @var Dashboard */
protected $dashboard;
public function __construct(Dashboard $dashboard)
public function hasBeenSubmitted()
{
$this->dashboard = $dashboard;
$this->setAction((string) Url::fromRequest());
return $this->hasBeenSent() && $this->getPopulatedValue('btn_remove');
}
protected function assemble()
{
$this->addElement('submit', 'btn_remove', ['label' => t('Remove Home')]);
$requestRoute = Url::fromRequest();
$label = t('Remove Home');
$message = sprintf(t('Please confirm removal of dashboard home "%s"'), $requestRoute->getParam('home'));
if ($requestRoute->getPath() === Dashboard::BASE_ROUTE . '/remove-pane') {
$label = t('Remove Pane');
$message = sprintf(t('Please confirm removal of dashboard pane "%s"'), $requestRoute->getParam('pane'));
}
$this->addHtml(HtmlElement::create('h1', null, $message));
$submit = $this->registerSubmitButton($label);
$submit->setName('btn_remove');
$this->addHtml($submit);
}
protected function onSuccess()
@ -32,12 +40,12 @@ class RemoveHomePaneForm extends CompatForm
$home = $this->dashboard->getActiveHome();
if ($requestUrl->getPath() === Dashboard::BASE_ROUTE . '/remove-home') {
$this->dashboard->removeHome($home);
$this->dashboard->removeEntry($home);
Notification::success(sprintf(t('Removed dashboard home "%s" successfully'), $home->getLabel()));
Notification::success(sprintf(t('Removed dashboard home "%s" successfully'), $home->getTitle()));
} else {
$pane = $home->getPane($requestUrl->getParam('pane'));
$home->removePane($pane);
$pane = $home->getEntry($requestUrl->getParam('pane'));
$home->removeEntry($pane);
Notification::success(sprintf(t('Removed dashboard pane "%s" successfully'), $pane->getTitle()));
}

View File

@ -5,7 +5,7 @@
namespace Icinga\Forms\Dashboard;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Dashboard\DashboardHome;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Url;
@ -43,12 +43,12 @@ class WelcomeForm extends CompatForm
protected function onSuccess()
{
if ($this->getPopulatedValue('btn_use_defaults')) {
$home = $this->dashboard->getHome(DashboardHome::DEFAULT_HOME);
$home = $this->dashboard->getEntry(DashboardHome::DEFAULT_HOME);
$conn = Dashboard::getConn();
$conn->beginTransaction();
try {
$home->managePanes($this->dashboard->getSystemDefaults(), null, true);
$home->manageEntry($this->dashboard->getSystemDefaults(), null, true);
$conn->commitTransaction();
} catch (\Exception $err) {

View File

@ -6,7 +6,7 @@ use Icinga\Web\Widget\SearchDashboard;
$searchDashboard = new SearchDashboard();
$searchDashboard->setUser($this->Auth()->getUser());
if ($searchDashboard->search('dummy')->getActiveHome()->getPane('search')->hasDashlets()): ?>
if (! $searchDashboard->search('dummy')->getActiveHome()->getEntry('search')->hasEntries()): ?>
<form action="<?= $this->href('search') ?>" method="get" role="search" class="search-control">
<input type="text" name="q" id="search" class="search search-input" required
placeholder="<?= $this->translate('Search') ?> &hellip;"

View File

@ -5,9 +5,7 @@ namespace Icinga\Common;
trait DataExtractor
{
/**
* Extract data from array to this class's properties
*
* Unknown properties (no matching setter) are ignored
* Extract data from array to this class's properties Unknown properties (no matching setter) are ignored
*
* @param array $data
*
@ -28,9 +26,13 @@ trait DataExtractor
/**
* Get this class's structure as array
*
* Stringifies the attrs or set to null if it doesn't have a value, when $stringify is true
*
* @param bool $stringify Whether, the attributes should be returned unmodified
*
* @return array
*/
public function toArray()
public function toArray($stringify = true)
{
return [];
}

View File

@ -0,0 +1,267 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard\Common;
use Icinga\Common\DataExtractor;
/**
* Base class for all dashboard widget types
*
* All Icinga Web dashboard widgets should extend this class
*/
abstract class BaseDashboard implements DashboardEntry
{
use DataExtractor;
/**
* Not translatable name of this widget
*
* @var string
*/
protected $name;
/**
* The title of this widget
*
* @var string
*/
protected $title;
/**
* Unique identifier of this widget
*
* @var int|string
*/
protected $uuid;
/**
* The widget's description
*
* @var string
*/
protected $description;
/**
* The priority order of this widget
*
* @var int
*/
protected $order = 0;
/**
* Name of the owner/creator of this widget
*
* @var string
*/
protected $owner;
/**
* Create a new widget
*
* @param string $name
* @param array $properties
*/
public function __construct($name, array $properties = [])
{
$this->name = $name;
$this->title = $name;
if (! empty($properties)) {
$this->fromArray($properties);
}
}
/**
* Set this widget's unique identifier
*
* @param int|string $uuid
*
* @return $this
*/
public function setUuid($uuid)
{
$this->uuid = $uuid;
return $this;
}
/**
* Get this widget's unique identifier
*
* @return string
*/
public function getUuid()
{
return $this->uuid;
}
/**
* Set the name of this widget
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Returns the name of this widget
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the title of this widget
*
* @param string $title
*
* @return $this
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Returns the title of this widget
*
* @return string
*/
public function getTitle()
{
return $this->title !== null ? $this->title : $this->getName();
}
/**
* Set the owner of this widget
*
* @param string $owner
*
* @return $this
*/
public function setOwner($owner)
{
$this->owner = $owner;
return $this;
}
/**
* Get owner of this widget
*
* @return string
*/
public function getOwner()
{
return $this->owner;
}
/**
* Get the widget's description
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set the widget's description
*
* @param string $description
*
* @return $this
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Set the priority order of this widget
*
* @param int $order
*
* @return $this
*/
public function setPriority(int $order)
{
$this->order = $order;
return $this;
}
/**
* Get the priority order of this widget
*
* @return int
*/
public function getPriority()
{
return $this->order;
}
public function hasEntries()
{
}
public function getEntry($name)
{
}
public function hasEntry($name)
{
}
public function getEntries()
{
}
public function setEntries(array $entries)
{
}
public function addEntry(BaseDashboard $dashboard)
{
}
public function createEntry($name, $url = null)
{
}
public function getEntryKeyTitleArr()
{
}
public function removeEntry($entry)
{
}
public function removeEntries(array $entries = [])
{
}
public function manageEntry($entry, BaseDashboard $origin = null, $updateChildEntries = false)
{
}
public function loadDashboardEntries($name = '')
{
}
public function rewindEntries()
{
}
}

View File

@ -0,0 +1,126 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard\Common;
use Icinga\Exception\ProgrammingError;
use function ipl\Stdlib\get_php_type;
trait DashboardControls
{
/**
* A list of @see BaseDashboard assigned to this dashboard widget
*
* @var BaseDashboard
*/
private $dashboards = [];
public function hasEntries()
{
return ! empty($this->dashboards);
}
public function getEntry($name)
{
if (! $this->hasEntry($name)) {
throw new ProgrammingError('Trying to retrieve invalid dashboard entry "%s"', $name);
}
return $this->dashboards[$name];
}
public function hasEntry($name)
{
return array_key_exists($name, $this->dashboards);
}
public function getEntries()
{
// An entry can be added individually afterwards, it might be the case that the priority
// order gets mixed up, so we have to sort things here before being able to render them
uasort($this->dashboards, function (BaseDashboard $x, BaseDashboard $y) {
return $x->getPriority() - $y->getPriority();
});
return $this->dashboards;
}
public function setEntries(array $entries)
{
$this->dashboards = $entries;
return $this;
}
public function addEntry(BaseDashboard $dashboard)
{
if ($this->hasEntry($dashboard->getName())) {
$this->getEntry($dashboard->getName())->fromArray($dashboard->toArray(false));
} else {
$this->dashboards[$dashboard->getName()] = $dashboard;
}
return $this;
}
public function getEntryKeyTitleArr()
{
$dashboards = [];
foreach ($this->getEntries() as $dashboard) {
$dashboards[$dashboard->getName()] = $dashboard->getTitle();
}
return $dashboards;
}
public function removeEntries(array $entries = [])
{
$dashboards = ! empty($entries) ? $entries : $this->getEntries();
foreach ($dashboards as $dashboard) {
$this->removeEntry($dashboard);
}
return $this;
}
public function createEntry($name, $url = null)
{
}
public function rewindEntries()
{
return reset($this->dashboards);
}
public function reorderWidget(BaseDashboard $dashboard, $position, Sortable $origin = null)
{
if ($origin && ! $origin instanceof $this) {
throw new \InvalidArgumentException(sprintf(
__METHOD__ . ' expects parameter "$origin" to be an instance of "%s". Got "%s" instead.',
get_php_type($this),
get_php_type($origin)
));
}
if (! $this->hasEntry($dashboard->getName())) {
$dashboard->setPriority($position);
$data = [$dashboard];
} else {
$data = array_values($this->getEntries());
array_splice($data, array_search($dashboard->getName(), array_keys($this->getEntries())), 1);
array_splice($data, $position, 0, [$dashboard]);
}
foreach ($data as $index => $item) {
if (count($data) !== 1) {
$item->setPriority($index);
}
$this->manageEntry($item, $dashboard->getName() === $item->getName() ? $origin : null);
}
return $this;
}
}

View File

@ -0,0 +1,131 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard\Common;
use ipl\Web\Url;
/**
* Represents a dashboard widget types
*/
interface DashboardEntry
{
/**
* Check whether this widget doesn't contain any dashboard entries
*
* @return bool
*/
public function hasEntries();
/**
* Get a dashboard entry by the given name if exists
*
* @param string $name
*
* @return BaseDashboard
*/
public function getEntry($name);
/**
* Get whether the given dashboard entry exists
*
* @param string $name
*
* @return bool
*/
public function hasEntry($name);
/**
* Get all dashboard entries of this widget
*
* @return BaseDashboard[]
*/
public function getEntries();
/**
* Set dashboard entries of this widget
*
* @param BaseDashboard[] $entries
*
* @return $this
*/
public function setEntries(array $entries);
/**
* Add a new dashboard entry to this widget
*
* @param BaseDashboard $dashboard
*
* @return $this
*/
public function addEntry(BaseDashboard $dashboard);
/**
* Create and add a new entry to this widget
*
* @param string $name
* @param ?string|Url $url
*
* @return $this
*/
public function createEntry($name, $url = null);
/**
* Get an array with entry name=>title format
*
* @return string[]
*/
public function getEntryKeyTitleArr();
/**
* Remove the given entry from this widget
*
* @param BaseDashboard|string $entry
*
* @return $this
*/
public function removeEntry($entry);
/**
* Removes the given list of entries from this widget
*
* If there is no entries passed, all the available entries of this widget will be removed
*
* @param BaseDashboard[] $entries
*
* @return $this
*/
public function removeEntries(array $entries = []);
/**
* Manage the given widget(s)
*
* Performs all kinds of database actions for the given widget(s) except the DELETE action. If you want to
* move pane(s)|dashlet(s) from another to this widget you have to also provide the origin from which the
* given entry(ies) originated
*
* @param BaseDashboard|BaseDashboard[] $entry
* @param ?BaseDashboard $origin
* @param bool $updateChildEntries
*
* @return $this
*/
public function manageEntry($entry, BaseDashboard $origin = null, $updateChildEntries = false);
/**
* Load all the assigned entries to this widget
*
* @param ?string $name Name of the dashboard widget you want to load the dashboard entries for
*
* @return $this
*/
public function loadDashboardEntries($name = '');
/**
* Reset the current position of the internal dashboard entries pointer
*
* @return false|BaseDashboard
*/
public function rewindEntries();
}

View File

@ -11,10 +11,10 @@ use Icinga\Exception\ProgrammingError;
use Icinga\Model;
use Icinga\User;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Dashboard\Dashlet;
use Icinga\Web\Dashboard\Pane;
use Icinga\Web\HomeMenu;
use Icinga\Web\Navigation\DashboardHome;
use ipl\Orm\Query;
use ipl\Sql\Connection;
use ipl\Sql\Expression;
@ -38,17 +38,10 @@ trait DashboardManager
*/
private static $defaultPanes = [];
/**
* A list of @see DashboardHome
*
* @var DashboardHome[]
*/
private $homes = [];
public function load()
{
$this->loadHomesFromMenu();
$this->loadDashboards();
$this->setEntries((new HomeMenu())->loadHomes());
$this->loadDashboardEntries();
$this->initGetDefaultHome();
self::deployModuleDashlets();
@ -84,115 +77,33 @@ trait DashboardManager
return sha1($name, true);
}
/**
* Load dashboard homes from the navigation menu
*
* @return $this
*/
public function loadHomesFromMenu()
public function loadDashboardEntries($name = '')
{
$menu = new HomeMenu();
foreach ($menu->getItem('dashboard')->getChildren() as $home) {
if (! $home instanceof DashboardHome) {
continue;
}
$this->homes[$home->getName()] = $home;
}
return $this;
}
/**
* Load dashboards assigned to the given home or active home being loaded
*
* @param ?string $name Name of the dashboard home you want to load the dashboards for
*
* @return $this
*/
public function loadDashboards($name = null)
{
if ($name && $this->hasHome($name)) {
$home = $this->getHome($name);
if ($name && $this->hasEntry($name)) {
$home = $this->getEntry($name);
} else {
$requestRoute = Url::fromRequest();
if ($requestRoute->getPath() === Dashboard::BASE_ROUTE) {
$home = $this->initGetDefaultHome();
} else {
$homeParam = $requestRoute->getParam('home');
if (empty($homeParam) || ! $this->hasHome($homeParam)) {
if (! ($home = $this->rewindHomes())) {
if (empty($homeParam) || ! $this->hasEntry($homeParam)) {
if (! ($home = $this->rewindEntries())) {
// No dashboard homes
return $this;
}
} else {
$home = $this->getHome($homeParam);
$home = $this->getEntry($homeParam);
}
}
}
$this->activateHome($home);
$home->loadPanesFromDB();
$home->loadDashboardEntries();
return $this;
}
/**
* Get a dashboard home by the given name
*
* @param string $name
*
* @return DashboardHome
*/
public function getHome($name)
{
if ($this->hasHome($name)) {
return $this->homes[$name];
}
throw new ProgrammingError('Trying to retrieve invalid dashboard home "%s"', $name);
}
/**
* Get all dashboard homes assigned to the active user
*
* @return DashboardHome[]
*/
public function getHomes()
{
return $this->homes;
}
/**
* Set this user's dashboard homes
*
* @param DashboardHome|DashboardHome[] $homes
*
* @return $this
*/
public function setHomes($homes)
{
if ($homes instanceof DashboardHome) {
$homes = [$homes->getName() => $homes];
}
$this->homes = $homes;
return $this;
}
/**
* Get whether the given home exist
*
* @param string $name
*
* @return bool
*/
public function hasHome($name)
{
return array_key_exists($name, $this->homes);
}
/**
* Activates the given home and deactivates all other active homes
*
@ -207,7 +118,7 @@ trait DashboardManager
$activeHome->setActive(false);
}
$home->setActive(true);
$home->setActive();
return $this;
}
@ -219,7 +130,8 @@ trait DashboardManager
*/
public function getActiveHome()
{
foreach ($this->getHomes() as $home) {
/** @var DashboardHome $home */
foreach ($this->getEntries() as $home) {
if ($home->getActive()) {
return $home;
}
@ -228,123 +140,59 @@ trait DashboardManager
return null;
}
/**
* Reset the current position of the internal dashboard homes pointer
*
* @return false|DashboardHome
*/
public function rewindHomes()
{
return reset($this->homes);
}
/**
* Remove the given home
*
* @param DashboardHome|string $home
*
* @return $this
*/
public function removeHome($home)
public function removeEntry($home)
{
$name = $home instanceof DashboardHome ? $home->getName() : $home;
if (! $this->hasHome($name)) {
if (! $this->hasEntry($name)) {
throw new ProgrammingError('Trying to remove invalid dashboard home "%s"', $name);
}
$home = $home instanceof DashboardHome ? $home : $this->getHome($home);
if (! $home->isDisabled()) {
$home->removePanes();
$home = $home instanceof DashboardHome ? $home : $this->getEntry($home);
$home->removeEntries();
if ($home->getName() !== DashboardHome::DEFAULT_HOME) {
self::getConn()->delete(DashboardHome::TABLE, ['id = ?' => $home->getUuid()]);
}
return $this;
}
/**
* Remove all|given list of dashboard homes
*
* @param DashboardHome[] $homes Optional list of dashboard homes
*
* @return $this
*/
public function removeHomes(array $homes = [])
{
$homes = ! empty($homes) ? $homes : $this->getHomes();
foreach ($homes as $home) {
$this->removeHome($home);
}
return $this;
}
/**
* Manage the given home
*
* @param DashboardHome $home
*
* @return $this
*/
public function manageHome(DashboardHome $home)
public function manageEntry($entry, BaseDashboard $origin = null, $updateChildEntries = false)
{
$conn = self::getConn();
if (! $this->hasHome($home->getName())) {
$home = $entry;
if (! $this->hasEntry($home->getName())) {
$priority = $home->getName() === DashboardHome::DEFAULT_HOME ? 0 : count($this->getEntries());
$conn->insert(DashboardHome::TABLE, [
'name' => $home->getName(),
'label' => $home->getLabel(),
'label' => $home->getTitle(),
'username' => self::getUser()->getUsername(),
'priority' => count($this->getHomes()) + 1,
// highest priority is 0, so count($entries) are always lowest prio + 1
'priority' => $priority,
'type' => $home->getType() !== Dashboard::SYSTEM ? $home->getType() : Dashboard::PRIVATE_DS
]);
$home->setUuid($conn->lastInsertId());
} elseif ($home->getName() !== DashboardHome::DEFAULT_HOME) {
$conn->update(DashboardHome::TABLE, [
'label' => $home->getLabel(),
'priority' => $home->getPriority()
], ['id = ?' => $home->getUuid()]);
} else {
$conn->update(DashboardHome::TABLE, ['priority' => $home->getPriority()], ['id = ?' => $home->getUuid()]);
$conn->update(DashboardHome::TABLE, ['label' => $home->getTitle()], ['id = ?' => $home->getUuid()]);
}
return $this;
}
/**
* Get an array with home name=>title format
*
* @return array
*/
public function getHomeKeyTitleArr()
{
$panes = [];
foreach ($this->getHomes() as $home) {
if ($home->isDisabled()) {
continue;
}
$panes[$home->getName()] = $home->getLabel();
}
return $panes;
}
/**
* Get and|or init the default dashboard home
*
* @return DashboardHome
* @return BaseDashboard
*/
public function initGetDefaultHome()
{
if ($this->hasHome(DashboardHome::DEFAULT_HOME)) {
return $this->getHome(DashboardHome::DEFAULT_HOME);
if ($this->hasEntry(DashboardHome::DEFAULT_HOME)) {
return $this->getEntry(DashboardHome::DEFAULT_HOME);
}
$default = new DashboardHome(DashboardHome::DEFAULT_HOME);
$this->manageHome($default);
$this->homes[$default->getName()] = $default;
$this->manageEntry($default);
$this->addEntry($default);
return $default;
}
@ -400,7 +248,8 @@ trait DashboardManager
foreach ($moduleManager->getLoadedModules() as $module) {
foreach ($module->getDashboard() as $dashboardPane) {
$priority = 0;
foreach ($dashboardPane->getDashlets() as $dashlet) {
/** @var Dashlet $dashlet */
foreach ($dashboardPane->getEntries() as $dashlet) {
$uuid = self::getSHA1($module->getName() . $dashboardPane->getName() . $dashlet->getName());
$dashlet
->setUuid($uuid)

View File

@ -1,37 +0,0 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard\Common;
trait DisableWidget
{
/**
* A flag whether this widget is disabled
*
* @var bool
*/
private $disabled = false;
/**
* Set whether this widget should be disabled
*
* @param bool $disable
*/
public function disable(bool $disable = true)
{
$this->disabled = $disable;
return $this;
}
/**
* Get whether this widget is disabled
*
* @return bool
*/
public function isDisabled()
{
return $this->disabled;
}
}

View File

@ -6,35 +6,82 @@ namespace Icinga\Web\Dashboard\Common;
use ipl\Html\BaseHtmlElement;
use ipl\Html\HtmlElement;
use ipl\Web\Url;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;
abstract class ItemListControl extends BaseHtmlElement
{
protected $tag = 'div';
/**
* Set a class name for the collapsible control
* Get this item's unique html identifier
*
* @var string
* @return string
*/
protected $collapsibleControlClass;
protected abstract function getHtmlId();
protected function setCollapsibleControlClass($class)
/**
* Get a class name for the collapsible control
*
* @return string
*/
protected abstract function getCollapsibleControlClass();
/**
* Create an action link to be added at the end of the list
*
* @return HtmlElement
*/
protected abstract function createActionLink();
/**
* Create the appropriate item list of this control
*
* @return HtmlElement
*/
protected abstract function createItemList();
/**
* Assemble a header element for this item list
*
* @param Url $url
* @param string $header
*
* @return void
*/
protected function assembleHeader(Url $url, $header, $disable = false)
{
$this->collapsibleControlClass = $class;
$header = HtmlElement::create('h1', ['class' => 'collapsible-header'], $header);
$header->addHtml(new Link(t('Edit'), $url, [
'class' => $disable ? 'disabled' : null,
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
return $this;
$this->addHtml($header);
}
protected function assemble()
{
$this->addHtml(HtmlElement::create('div', ['class' => $this->collapsibleControlClass], [
$this->getAttributes()->add([
'id' => $this->getHtmlId(),
'class' => ['collapsible'],
'data-toggle-element' => '.dashboard-list-info',
]);
$this->addHtml(HtmlElement::create('div', ['class' => $this->getCollapsibleControlClass()], [
new Icon('angle-down', ['class' => 'expand-icon', 'title' => t('Expand')]),
new Icon('angle-up', ['class' => 'collapse-icon', 'title' => t('Collapse')])
]));
$this->getAttributes()->registerAttributeCallback('draggable', function () {
return 'true';
});
$this->addHtml($this->createItemList());
$actionLink = $this->createActionLink();
$actionLink->getAttributes()->add([
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]);
$this->addHtml($actionLink);
}
}

View File

@ -1,39 +0,0 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard\Common;
trait OrderWidget
{
/**
* The priority order of this widget
*
* @var int
*/
private $order = 0;
/**
* Set the priority order of this widget
*
* @param int $order
*
* @return $this
*/
public function setPriority(int $order)
{
$this->order = $order;
return $this;
}
/**
* Get the priority order of this widget
*
* @return int
*/
public function getPriority()
{
return $this->order;
}
}

View File

@ -2,7 +2,7 @@
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard;
namespace Icinga\Web\Dashboard\Common;
interface OverridingWidget
{

View File

@ -0,0 +1,23 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard\Common;
/**
* Sortable interface that allows to reorder the provided dashboard entry
* and update the database accordingly
*/
interface Sortable
{
/**
* Insert the dashboard entry at the given position within this dashboard entries
*
* @param BaseDashboard $dashboard
* @param $position
* @param Sortable|null $origin
*
* @return $this
*/
public function reorderWidget(BaseDashboard $dashboard, $position, Sortable $origin = null);
}

View File

@ -6,8 +6,9 @@ namespace Icinga\Web\Dashboard;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Dashboard\Common\DashboardControls;
use Icinga\Web\Dashboard\Common\DashboardEntry;
use Icinga\Web\Dashboard\Common\DashboardManager;
use Icinga\Web\Navigation\DashboardHome;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Form;
use ipl\Html\HtmlElement;
@ -23,9 +24,10 @@ use ipl\Web\Widget\Tabs;
* - Dashboard/Home: Shows all panes belonging to this home
*
*/
class Dashboard extends BaseHtmlElement
class Dashboard extends BaseHtmlElement implements DashboardEntry
{
use DashboardManager;
use DashboardControls;
/**
* Base path of our new dashboards controller
@ -35,8 +37,7 @@ class Dashboard extends BaseHtmlElement
const BASE_ROUTE = 'dashboards';
/**
* System dashboards are provided by the modules in PHP code
* and are available to all users
* System dashboards are provided by the modules in PHP code and are available to all users
*
* @var string
*/
@ -51,8 +52,7 @@ class Dashboard extends BaseHtmlElement
const PUBLIC_DS = 'public';
/**
* Private dashboards are created by any user and are only
* available to this user
* Private dashboards are created by any user and are only available to this user
*
* @var string
*/
@ -136,15 +136,11 @@ class Dashboard extends BaseHtmlElement
}
$this->tabs->disableLegacyExtensions();
if (! $activeHome || $activeHome->isDisabled()) {
if (! $activeHome) {
return $this->tabs;
}
foreach ($activeHome->getPanes() as $key => $pane) {
if ($pane->isDisabled()) {
continue;
}
foreach ($activeHome->getEntries() as $key => $pane) {
if (! $this->tabs->get($key)) {
$this->tabs->add(
$key,
@ -173,12 +169,9 @@ class Dashboard extends BaseHtmlElement
{
$active = null;
$activeHome = $this->getActiveHome();
foreach ($activeHome->getPanes() as $key => $pane) {
if ($pane->isDisabled() === false) {
$active = $key;
break;
}
foreach ($activeHome->getEntries() as $key => $pane) {
$active = $key;
break;
}
if ($active !== null) {
@ -211,7 +204,7 @@ class Dashboard extends BaseHtmlElement
if (! $active) {
if ($active = Url::fromRequest()->getParam($this->tabParam)) {
if ($activeHome->hasPane($active)) {
if ($activeHome->hasEntry($active)) {
$this->activate($active);
} else {
throw new ProgrammingError('Try to get an inexistent pane.');
@ -223,7 +216,7 @@ class Dashboard extends BaseHtmlElement
$active = $active->getName();
}
$panes = $activeHome->getPanes();
$panes = $activeHome->getEntries();
if (isset($panes[$active])) {
return $panes[$active];
}
@ -241,7 +234,7 @@ class Dashboard extends BaseHtmlElement
protected function assemble()
{
$activeHome = $this->getActiveHome();
if (! $activeHome || ($activeHome->getName() === DashboardHome::DEFAULT_HOME && ! $activeHome->hasPanes())) {
if (! $activeHome || (! $activeHome->hasEntries() && $activeHome->getName() === DashboardHome::DEFAULT_HOME)) {
$this->setAttribute('class', 'content welcome-view');
$wrapper = HtmlElement::create('div', ['class' => 'dashboard-introduction']);
@ -266,19 +259,21 @@ class Dashboard extends BaseHtmlElement
$wrapper->addHtml($this->welcomeForm);
$this->addHtml($wrapper);
} elseif (! empty($activeHome->getPanes(true))) {
$dashlets = $this->getActivePane()->getDashlets();
} elseif (! $activeHome->hasEntries()) {
$this->setAttribute('class', 'content');
$this->addHtml(HtmlElement::create('h1', null, t('No dashboard added to this dashboard home')));
} else {
$activePane = $this->getActivePane();
$this->setAttribute('data-icinga-pane', $activeHome->getName() . '|' . $this->getActivePane()->getName());
if (empty($dashlets)) {
if (! $activePane->hasEntries()) {
$this->setAttribute('class', 'content');
$dashlets = HtmlElement::create('h1', null, t('No dashlet added to this pane.'));
$this->addHtml(HtmlElement::create('h1', null, t('No dashlet added to this pane.')));
} else {
foreach ($activePane->getEntries() as $dashlet) {
$this->addHtml($dashlet->getHtml());
}
}
$this->add($dashlets);
} else {
// TODO: What to do with dashboard homes without any dashboards??
exit(0);
}
}
}

View File

@ -0,0 +1,240 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Dashboard\Common\BaseDashboard;
use Icinga\Web\Dashboard\Common\DashboardControls;
use Icinga\Web\Dashboard\Common\Sortable;
use Icinga\Web\Navigation\DashboardHomeItem;
use ipl\Stdlib\Filter;
use function ipl\Stdlib\get_php_type;
class DashboardHome extends BaseDashboard implements Sortable
{
use DashboardControls;
/**
* Name of the default home
*
* @var string
*/
const DEFAULT_HOME = 'Default Home';
/**
* Database table name
*
* @var string
*/
const TABLE = 'dashboard_home';
/**
* A type of this dashboard home
*
* @var string
*/
protected $type = Dashboard::SYSTEM;
protected $active;
/**
* Create a new dashboard home from the given home item
*
* @param DashboardHomeItem $homeItem
*
* @return DashboardHome
*/
public static function create(DashboardHomeItem $homeItem)
{
return new self($homeItem->getName(), $homeItem->getAttributes());
}
/**
* Set whether this home is active
*
* DB dashboards will load only when this home has been activated
*
* @param bool $active
*
* @return $this
*/
public function setActive($active = true)
{
$this->active = $active;
return $this;
}
/**
* Get whether this home has been activated
*
* @return bool
*/
public function getActive()
{
return $this->active;
}
/**
* Set the type of this dashboard home
*
* @param string $type
*
* @return $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get the type of this dashboard home
*
* @return string
*/
public function getType()
{
return $this->type;
}
public function removeEntry($pane)
{
$name = $pane instanceof Pane ? $pane->getName() : $pane;
if (! $this->hasEntry($name)) {
throw new ProgrammingError('Trying to remove invalid dashboard pane "%s"', $name);
}
$pane = $pane instanceof Pane ? $pane : $this->getEntry($pane);
if (! $pane->isOverriding()) {
$pane->removeEntries();
Dashboard::getConn()->delete(Pane::TABLE, [
'id = ?' => $pane->getUuid(),
'home_id = ?' => $this->getUuid()
]);
}
return $this;
}
public function loadDashboardEntries($name = '')
{
if (! $this->getActive()) {
return $this;
}
$this->setEntries([]);
$panes = \Icinga\Model\Pane::on(Dashboard::getConn())->utilize('home');
$panes
->filter(Filter::equal('home_id', $this->getUuid()))
->filter(Filter::equal('username', Dashboard::getUser()->getUsername()));
foreach ($panes as $pane) {
$newPane = new Pane($pane->name);
$newPane->fromArray([
'uuid' => $pane->id,
'title' => $pane->label,
'priority' => $pane->priority,
'home' => $this
]);
$newPane->loadDashboardEntries($name);
$this->addEntry($newPane);
}
return $this;
}
public function createEntry($name, $url = null)
{
$entry = new Pane($name);
$entry->setHome($this);
$this->addEntry($entry);
return $this;
}
public function manageEntry($entry, BaseDashboard $origin = null, $updateChildEntries = false)
{
$user = Dashboard::getUser();
$conn = Dashboard::getConn();
$panes = is_array($entry) ? $entry : [$entry];
// highest priority is 0, so count($entries) are all always lowest prio + 1
$order = count($this->getEntries());
if ($origin && ! $origin instanceof DashboardHome) {
throw new \InvalidArgumentException(sprintf(
__METHOD__ . ' expects parameter "$origin" to be an instance of "%s". Got "%s" instead.',
get_php_type($this),
get_php_type($origin)
));
}
/** @var Pane $pane */
foreach ($panes as $pane) {
$uuid = Dashboard::getSHA1($user->getUsername() . $this->getName() . $pane->getName());
if (! $pane->isOverriding()) {
if (! $this->hasEntry($pane->getName()) && (! $origin || ! $origin->hasEntry($pane->getName()))) {
$conn->insert(Pane::TABLE, [
'id' => $uuid,
'home_id' => $this->getUuid(),
'name' => $pane->getName(),
'label' => $pane->getTitle(),
'username' => $user->getUsername(),
'priority' => $order++
]);
} elseif (! $this->hasEntry($pane->getName()) || ! $origin || ! $origin->hasEntry($pane->getName())) {
$filterCondition = [
'id = ?' => $pane->getUuid(),
'home_id = ?' => $this->getUuid()
];
if ($origin && $origin->hasEntry($pane->getName())) {
$filterCondition = [
'id = ?' => $origin->getEntry($pane->getName())->getUuid(),
'home_id = ?' => $origin->getUuid()
];
}
$conn->update(Pane::TABLE, [
'id' => $uuid,
'home_id' => $this->getUuid(),
'label' => $pane->getTitle(),
'priority' => $pane->getPriority()
], $filterCondition);
} else {
// Failed to move the pane! Should have been handled already by the caller
break;
}
$pane->setHome($this);
$pane->setUuid($uuid);
}
if ($updateChildEntries) {
// Those dashboard panes are usually system defaults and go up when
// the user is clicking on the "Use System Defaults" button
$dashlets = $pane->getEntries();
$pane->setEntries([]);
$pane->manageEntry($dashlets);
}
}
}
public function toArray($stringify = true)
{
return [
'id' => $this->getUuid(),
'name' => $this->getName(),
'title' => $this->getTitle(),
'priority' => $this->getPriority()
];
}
}

View File

@ -5,13 +5,10 @@
namespace Icinga\Web\Dashboard;
use Icinga\Application\Icinga;
use Icinga\Common\DataExtractor;
use Icinga\Web\Dashboard\Common\DisableWidget;
use Icinga\Web\Dashboard\Common\BaseDashboard;
use Icinga\Web\Dashboard\Common\ModuleDashlet;
use Icinga\Web\Dashboard\Common\OrderWidget;
use Icinga\Web\Request;
use Icinga\Web\Url;
use ipl\Html\BaseHtmlElement;
use ipl\Html\HtmlElement;
use ipl\Web\Widget\Link;
@ -20,23 +17,13 @@ use ipl\Web\Widget\Link;
*
* This is the new element being used for the Dashlets view
*/
class Dashlet extends BaseHtmlElement
class Dashlet extends BaseDashboard
{
use DisableWidget;
use OrderWidget;
use ModuleDashlet;
use DataExtractor;
/** @var string Database table name */
const TABLE = 'dashlet';
protected $tag = 'div';
protected $defaultAttributes = [
'class' => 'container widget-sortable',
'draggable' => 'true'
];
/**
* The url of this Dashlet
*
@ -44,19 +31,6 @@ class Dashlet extends BaseHtmlElement
*/
protected $url;
/**
* Not translatable name of this dashlet
*
* @var string
*/
protected $name;
/**
* The title being displayed on top of the dashlet
* @var
*/
protected $title;
/**
* The pane this dashlet belongs to
*
@ -71,107 +45,21 @@ class Dashlet extends BaseHtmlElement
*/
protected $progressLabel;
/**
* Unique identifier of this dashlet
*
* @var string
*/
protected $uuid;
/**
* The dashlet's description
*
* @var string
*/
protected $description;
/**
* Create a new dashlet displaying the given url in the provided pane
*
* @param string $title The title to use for this dashlet
* @param string $name The title to use for this dashlet
* @param Url|string $url The url this dashlet uses for displaying information
* @param Pane|null $pane The pane this Dashlet will be added to
*/
public function __construct($title, $url, Pane $pane = null)
public function __construct($name, $url, Pane $pane = null)
{
$this->name = $title;
$this->title = $title;
parent::__construct($name);
$this->pane = $pane;
$this->url = $url;
}
/**
* Set the identifier of this dashlet
*
* @param string $id
*
* @return $this
*/
public function setUuid($id)
{
$this->uuid = $id;
return $this;
}
/**
* Get the unique identifier of this dashlet
*
* @return string
*/
public function getUuid()
{
return $this->uuid;
}
/**
* Setter for this name
*
* @param $name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Getter for this name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Retrieve the dashlets title
*
* @return string
*/
public function getTitle()
{
return $this->title !== null ? $this->title : $this->getName();
}
/**
* Set the title of this dashlet
*
* @param string $title
*
* @return $this
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Retrieve the dashlets url
*
@ -232,30 +120,6 @@ class Dashlet extends BaseHtmlElement
return $this->progressLabel;
}
/**
* Get the dashlet's description
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set the dashlet's description
*
* @param string $description
*
* @return $this
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Set the Pane of this dashlet
*
@ -280,11 +144,17 @@ class Dashlet extends BaseHtmlElement
return $this->pane;
}
protected function assemble()
/**
* Generate a html widget for this dashlet
*
* @return HtmlElement
*/
public function getHtml()
{
$dashletHtml = HtmlElement::create('div', ['class' => 'container']);
if (! $this->getUrl()) {
$this->addHtml(HtmlElement::create('h1', null, t($this->getTitle())));
$this->addHtml(HtmlElement::create(
$dashletHtml->addHtml(HtmlElement::create('h1', null, t($this->getTitle())));
$dashletHtml->addHtml(HtmlElement::create(
'p',
['class' => 'error-message'],
sprintf(t('Cannot create dashboard dashlet "%s" without valid URL'), t($this->getTitle()))
@ -293,10 +163,10 @@ class Dashlet extends BaseHtmlElement
$url = $this->getUrl();
$url->setParam('showCompact', true);
$this->setAttribute('data-icinga-url', $url);
$this->setAttribute('data-icinga-dashlet', $this->getName());
$dashletHtml->setAttribute('data-icinga-url', $url);
$dashletHtml->setAttribute('data-icinga-dashlet', $this->getName());
$this->addHtml(new HtmlElement('h1', null, new Link(
$dashletHtml->addHtml(new HtmlElement('h1', null, new Link(
t($this->getTitle()),
$url->getUrlWithout(['showCompact', 'limit'])->getRelativeUrl(),
[
@ -306,7 +176,7 @@ class Dashlet extends BaseHtmlElement
]
)));
$this->addHtml(HtmlElement::create(
$dashletHtml->addHtml(HtmlElement::create(
'p',
['class' => 'progress-label'],
[
@ -317,18 +187,20 @@ class Dashlet extends BaseHtmlElement
]
));
}
return $dashletHtml;
}
public function toArray()
public function toArray($stringify = true)
{
$pane = $this->getPane();
return [
'id' => $this->getUuid(),
'pane' => $this->getPane() ? $this->getPane()->getName() : null,
'name' => $this->getName(),
'url' => $this->getUrl()->getRelativeUrl(),
'label' => $this->getTitle(),
'order' => $this->getPriority(),
'disabled' => (int) $this->isDisabled(),
'id' => $this->getUuid(),
'pane' => ! $stringify ? $pane : ($pane ? $pane->getName() : null),
'name' => $this->getName(),
'url' => $this->getUrl()->getRelativeUrl(),
'label' => $this->getTitle(),
'order' => $this->getPriority(),
];
}
}

View File

@ -6,72 +6,64 @@ namespace Icinga\Web\Dashboard\ItemList;
use Icinga\Web\Dashboard\Common\ItemListControl;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Dashboard\DashboardHome;
use ipl\Html\HtmlElement;
use ipl\Web\Url;
use ipl\Web\Widget\ActionLink;
use ipl\Web\Widget\Link;
class DashboardHomeList extends ItemListControl
{
/** @var DashboardHome */
protected $home;
protected $defaultAttributes = ['class' => 'home-list-control'];
public function __construct(DashboardHome $home)
{
$this->home = $home;
$this->home->setActive();
$this->home->loadPanesFromDB();
$this->home->loadDashboardEntries();
$this->setCollapsibleControlClass('dashboard-list-info');
$this->getAttributes()
->registerAttributeCallback('class', function () {
return 'home-list-control collapsible widget-sortable';
})
->registerAttributeCallback('data-toggle-element', function () {
return '.dashboard-list-info';
})
->registerAttributeCallback('data-icinga-home', function () {
return $this->home->getName();
})
->registerAttributeCallback('id', function () {
return 'home_' . $this->home->getPriority();
});
}
protected function assemble()
protected function getHtmlId()
{
// TODO: How should disabled homes look like?
parent::assemble();
return $this->home->getUuid();
}
$header = HtmlElement::create('h1', ['class' => 'collapsible-header home'], $this->home->getLabel());
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/rename-home')->setParams([
'home' => $this->home->getName()
]);
protected function getCollapsibleControlClass()
{
return 'dashboard-list-info';
}
$header->addHtml(new Link(t('Edit'), $url, [
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
protected function createItemList()
{
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/edit-home')
->setParams(['home' => $this->home->getName()]);
$this->addHtml($header);
$disable = $this->home->getName() === DashboardHome::DEFAULT_HOME;
$this->assembleHeader($url, $this->home->getTitle(), $disable);
$list = HtmlElement::create('ul', ['class' => 'dashboard-item-list']);
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/new-dashlet');
$url->setParams(['home' => $this->home->getName()]);
// List all dashboard panes
foreach ($this->home->getPanes() as $pane) {
foreach ($this->home->getEntries() as $pane) {
$pane->setHome($this->home); // In case it's not set
$list->addHtml(new DashboardList($pane));
}
$this->addHtml($list);
$this->addHtml(new ActionLink(t('Add Dashboard'), $url, 'plus', [
'class' => 'add-dashboard',
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
return $list;
}
protected function createActionLink()
{
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/new-pane');
$url->setParams(['home' => $this->home->getName()]);
return new ActionLink(t('Add Dashboard'), $url, 'plus', ['class' => 'add-dashboard']);
}
}

View File

@ -10,67 +10,65 @@ use Icinga\Web\Dashboard\Pane;
use ipl\Html\HtmlElement;
use ipl\Web\Url;
use ipl\Web\Widget\ActionLink;
use ipl\Web\Widget\Link;
class DashboardList extends ItemListControl
{
/** @var Pane */
protected $pane;
protected $defaultAttributes = ['class' => 'dashboard-list-control'];
public function __construct(Pane $pane)
{
$this->pane = $pane;
$this->setCollapsibleControlClass('dashlets-list-info');
$this->getAttributes()
->registerAttributeCallback('class', function () {
return 'dashboard-list-control collapsible widget-sortable';
})
->registerAttributeCallback('data-toggle-element', function () {
return '.dashlets-list-info';
})
->registerAttributeCallback('data-icinga-pane', function () {
return $this->pane->getHome()->getName() . '|' . $this->pane->getName();
})
->registerAttributeCallback('id', function () {
return 'pane_' . $this->pane->getPriority();
return $this->pane->getName();
});
}
protected function assemble()
protected function getHtmlId()
{
// TODO: How should disabled dashboard panes look like?
parent::assemble();
return bin2hex($this->pane->getUuid());
}
$header = HtmlElement::create('h1', ['class' => 'collapsible-header'], $this->pane->getTitle());
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/edit-pane')->setParams([
'home' => $this->pane->getHome()->getName(),
'pane' => $this->pane->getName()
]);
protected function getCollapsibleControlClass()
{
return 'dashlets-list-info';
}
$header->addHtml(new Link(t('Edit'), $url, [
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
protected function createItemList()
{
$pane = $this->pane;
$this->getAttributes()->set('data-toggle-element', '.dashlets-list-info');
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/edit-pane')
->setParams(['home' => $pane->getHome()->getName(), 'pane' => $pane->getName()]);
$this->addHtml($header);
$this->assembleHeader($url, $pane->getTitle());
$list = HtmlElement::create('ul', ['class' => 'dashlet-item-list']);
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/new-dashlet');
$url->setParams([
'home' => $this->pane->getHome(),
'pane' => $this->pane->getName()
'home' => $pane->getHome()->getName(),
'pane' => $pane->getName()
]);
foreach ($this->pane->getDashlets() as $dashlet) {
foreach ($pane->getEntries() as $dashlet) {
$list->addHtml(new DashletListItem($dashlet, true));
}
$this->addHtml($list);
$this->addHtml(new ActionLink(t('Add Dashlet'), $url, 'plus', [
'class' => 'add-dashlet',
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
return $list;
}
protected function createActionLink()
{
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/new-dashlet');
$url->setParams([
'home' => $this->pane->getHome()->getName(),
'pane' => $this->pane->getName()
]);
return new ActionLink(t('Add Dashlet'), $url, 'plus', ['class' => 'add-dashlet']);
}
}

View File

@ -28,17 +28,13 @@ class DashletListItem extends BaseHtmlElement
$this->dashlet = $dashlet;
$this->renderEditButton = $renderEditButton;
if ($this->dashlet) {
$this->getAttributes()
->set('draggable', 'true')
->add('class', 'widget-sortable');
if ($this->dashlet && $renderEditButton) {
$this->getAttributes()
->registerAttributeCallback('data-icinga-dashlet', function () {
return $this->dashlet->getName();
})
->registerAttributeCallback('id', function () {
return 'dashlet_' . $this->dashlet->getPriority();
return bin2hex($this->dashlet->getUuid());
});
}
}
@ -63,23 +59,33 @@ class DashletListItem extends BaseHtmlElement
if (! $this->dashlet) {
$title->add(t('Custom Url'));
} elseif ($this->renderEditButton) {
$title->addHtml(new Link(
t($this->dashlet->getTitle()),
$this->dashlet->getUrl()->getUrlWithout(['showCompact', 'limit'])->getRelativeUrl(),
[
'class' => 'dashlet-title',
'aria-label' => t($this->dashlet->getTitle()),
'title' => t($this->dashlet->getTitle()),
'data-base-target' => '_next'
]
));
$pane = $this->dashlet->getPane();
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/edit-dashlet');
$url->setParams([
'home' => $pane->getHome()->getName(),
'pane' => $pane->getName(),
'dashlet' => $this->dashlet->getName()
]);
$title->addHtml(new Link(t('Edit'), $url, [
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
} else {
$title->add($this->dashlet->getTitle());
if ($this->renderEditButton) {
$pane = $this->dashlet->getPane();
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/edit-dashlet');
$url->setParams([
'home' => $pane->getHome()->getName(),
'pane' => $pane->getName(),
'dashlet' => $this->dashlet->getName()
]);
$title->addHtml(new Link(t('Edit'), $url, [
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
}
$title->getAttributes()->set('title', $this->dashlet->getTitle());
}
return $title;
@ -92,7 +98,12 @@ class DashletListItem extends BaseHtmlElement
if (! $this->dashlet) {
$section->add(t('Create a dashlet with custom url and filter'));
} else {
$section->add($this->dashlet->getDescription() ?: $this->dashlet->getTitle());
$section->getAttributes()->set(
'title',
$this->dashlet->getDescription() ?: t('There is no provided description.')
);
$section->add($this->dashlet->getDescription() ?: t('There is no provided dashlet description.'));
}
return $section;

View File

@ -1,5 +1,7 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Dashboard\ItemList;
use ipl\Html\Contract\FormElement;

View File

@ -4,142 +4,54 @@
namespace Icinga\Web\Dashboard;
use Icinga\Common\DataExtractor;
use Icinga\Web\Dashboard\Common\DisableWidget;
use Icinga\Web\Dashboard\Common\OrderWidget;
use Icinga\Web\Dashboard\Common\BaseDashboard;
use Icinga\Exception\ProgrammingError;
use Icinga\Exception\ConfigurationError;
use Icinga\Model;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Dashboard\Common\DashboardControls;
use Icinga\Web\Dashboard\Common\OverridingWidget;
use Icinga\Web\Dashboard\Common\Sortable;
use ipl\Stdlib\Filter;
use ipl\Web\Url;
use function ipl\Stdlib\get_php_type;
/**
* A pane, displaying different Dashboard dashlets
*/
class Pane implements OverridingWidget
class Pane extends BaseDashboard implements Sortable, OverridingWidget
{
use DisableWidget;
use OrderWidget;
use DataExtractor;
use DashboardControls;
const TABLE = 'dashboard';
/**
* The name of this pane
*
* @var string
*/
private $name;
/**
* The title of this pane, as displayed in the dashboard tabs
*
* @var string
*/
private $title;
/**
* An array of @see Dashlet that are displayed in this pane
*
* @var array
*/
private $dashlets = [];
protected $dashlets = [];
/**
* Whether this widget overrides another widget
*
* @var bool
*/
private $override;
/**
* Unique identifier of this pane
*
* @var string
*/
private $uuid;
/**
* Name of the owner/creator of this pane
*
* @var string
*/
private $owner;
protected $override;
/**
* Number of users who have subscribed to this pane if (public)
*
* @var int
*/
private $acceptance;
protected $acceptance;
/**
* A dashboard home this pane is a part of
*
* @var DashboardHome
*/
private $home;
/**
* Create a new pane
*
* @param string $name The pane to create
* @param array $properties
*/
public function __construct($name, array $properties = [])
{
$this->name = $name;
$this->title = $name;
if (! empty($properties)) {
$this->fromArray($properties);
}
}
/**
* Set the name of this pane
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Returns the name of this pane
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the title of this pane
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Overwrite the title of this pane
*
* @param string $title The new title to use for this pane
*
* @return $this
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
protected $home;
public function override(bool $override)
{
@ -153,54 +65,6 @@ class Pane implements OverridingWidget
return $this->override;
}
/**
* Set this pane's unique identifier
*
* @param string $uuid
*
* @return $this
*/
public function setUuid($uuid)
{
$this->uuid = $uuid;
return $this;
}
/**
* Get this pane's unique identifier
*
* @return string
*/
public function getUuid()
{
return $this->uuid;
}
/**
* Set the owner of this dashboard
*
* @param string $owner
*
* @return $this
*/
public function setOwner($owner)
{
$this->owner = $owner;
return $this;
}
/**
* Get owner of this dashboard
*
* @return string
*/
public function getOwner()
{
return $this->owner;
}
/**
* Set the number of users who have subscribed to this pane if (public)
*
@ -249,86 +113,12 @@ class Pane implements OverridingWidget
return $this;
}
/**
* Return true if a dashlet with the given name exists in this pane
*
* @param string $name The title of the dashlet to check for existence
*
* @return bool
*/
public function hasDashlet($name)
{
return array_key_exists($name, $this->dashlets);
}
/**
* Checks if the current pane has any dashlets
*
* @return bool
*/
public function hasDashlets()
{
return ! empty($this->dashlets);
}
/**
* Get a dashlet with the given name if existing
*
* @param string $name
*
* @return Dashlet
*/
public function getDashlet($name)
{
if ($this->hasDashlet($name)) {
return $this->dashlets[$name];
}
throw new ProgrammingError('Trying to access invalid dashlet: %s', $name);
}
/**
* Get all dashlets belongs to this pane
*
* @return Dashlet[]
*/
public function getDashlets()
{
uasort($this->dashlets, function (Dashlet $x, Dashlet $y) {
return $x->getPriority() - $y->getPriority();
});
return $this->dashlets;
}
/**
* Set dashlets of this pane
*
* @param Dashlet[] $dashlets
*
* @return $this
*/
public function setDashlets(array $dashlets)
{
$this->dashlets = $dashlets;
return $this;
}
/**
* Create, add and return a new dashlet
*
* @param string $name
* @param string $url
*
* @return Dashlet
*/
public function createDashlet($name, $url = null)
public function createEntry($name, $url = null)
{
$dashlet = new Dashlet($name, $url, $this);
$this->addDashlet($dashlet);
return $dashlet;
return $this;
}
/**
@ -343,9 +133,9 @@ class Pane implements OverridingWidget
public function addDashlet($dashlet, $url = null)
{
if ($dashlet instanceof Dashlet) {
$this->dashlets[$dashlet->getName()] = $dashlet;
$this->addEntry($dashlet);
} elseif (is_string($dashlet) && $url !== null) {
$this->createDashlet($dashlet, $url);
$this->createEntry($dashlet, $url);
} else {
throw new ConfigurationError('Invalid dashlet added: %s', $dashlet);
}
@ -364,31 +154,24 @@ class Pane implements OverridingWidget
*/
public function add($name, $url, $priority = 0, $description = null)
{
$dashlet = $this->createDashlet($name, $url);
$this->createEntry($name, $url);
$dashlet = $this->getEntry($name);
$dashlet
->setDescription($description)
->setPriority($priority);
$this->addDashlet($dashlet);
return $this;
}
/**
* Remove the given dashlet if it exists in this pane
*
* @param Dashlet|string $dashlet
*
* @return $this
*/
public function removeDashlet($dashlet)
public function removeEntry($dashlet)
{
$name = $dashlet instanceof Dashlet ? $dashlet->getName() : $dashlet;
if (! $this->hasDashlet($name)) {
if (! $this->hasEntry($name)) {
throw new ProgrammingError('Trying to remove invalid dashlet: %s', $name);
}
if (! $dashlet instanceof Dashlet) {
$dashlet = $this->getDashlet($dashlet);
$dashlet = $this->getEntry($dashlet);
}
Dashboard::getConn()->delete(Dashlet::TABLE, [
@ -399,38 +182,12 @@ class Pane implements OverridingWidget
return $this;
}
/**
* Removes all or a given list of dashlets from this pane
*
* @param array $dashlets Optional list of dashlets
*
* @return $this
*/
public function removeDashlets(array $dashlets = [])
public function loadDashboardEntries($name = '')
{
$dashlets = ! empty($dashlets) ? $dashlets : $this->getDashlets();
foreach ($dashlets as $dashlet) {
$this->removeDashlet($dashlet);
}
return $this;
}
/**
* Load all dashlets this dashboard is assigned to
*
* @return $this
*/
public function loadDashletsFromDB()
{
if ($this->isDisabled()) {
return $this;
}
$this->dashlets = [];
$dashlets = Model\Dashlet::on(Dashboard::getConn())->with('module_dashlet');
$dashlets->filter(Filter::equal('dashboard_id', $this->getUuid()));
$this->setEntries([]);
foreach ($dashlets as $dashlet) {
$newDashlet = new Dashlet($dashlet->name, $dashlet->url, $this);
$newDashlet->fromArray([
@ -447,44 +204,40 @@ class Pane implements OverridingWidget
return $this;
}
/**
* Manage the given dashlet(s)
*
* If you want to move the dashlet(s) from another to this pane,
* you have to also pass the origin pane
*
* @param Dashlet|Dashlet[] $dashlets
* @param ?Pane $origin
*
* @return $this
*/
public function manageDashlets($dashlets, Pane $origin = null)
public function manageEntry($entry, BaseDashboard $origin = null, $updateChildEntries = false)
{
if ($origin && ! $origin instanceof Pane) {
throw new \InvalidArgumentException(sprintf(
__METHOD__ . ' expects parameter "$origin" to be an instance of "%s". Got "%s" instead.',
get_php_type($this),
get_php_type($origin)
));
}
if (! $this->getHome()) {
throw new \LogicException(
'Dashlets cannot be managed. Please make sure to set the current dashboard home beforehand.'
);
}
$user = Dashboard::getUser();
$home = $this->getHome();
$user = Dashboard::getUser()->getUsername();
$conn = Dashboard::getConn();
$dashlets = is_array($dashlets) ? $dashlets : [$dashlets];
$order = count($this->getDashlets()) + 1;
$dashlets = is_array($entry) ? $entry : [$entry];
// highest priority is 0, so count($entries) are all always lowest prio + 1
$order = count($this->getEntries());
foreach ($dashlets as $dashlet) {
if (is_array($dashlet)) {
$this->manageDashlets($dashlet, $origin);
$this->manageEntry($dashlet, $origin);
}
if (! $dashlet instanceof Dashlet) {
break;
}
$uuid = Dashboard::getSHA1(
$user->getUsername() . $this->getHome()->getName() . $this->getName() . $dashlet->getName()
);
if (! $this->hasDashlet($dashlet->getName()) && (! $origin || ! $origin->hasDashlet($dashlet->getName()))) {
$uuid = Dashboard::getSHA1($user . $home->getName() . $this->getName() . $dashlet->getName());
if (! $this->hasEntry($dashlet->getName()) && (! $origin || ! $origin->hasEntry($dashlet->getName()))) {
$conn->insert(Dashlet::TABLE, [
'id' => $uuid,
'dashboard_id' => $this->getUuid(),
@ -495,22 +248,41 @@ class Pane implements OverridingWidget
]);
if ($dashlet->isModuleDashlet()) {
$systemUuid = Dashboard::getSHA1($dashlet->getModule() . $this->getName() . $dashlet->getName());
$conn->insert('dashlet_system', [
'dashlet_id' => $uuid,
'module_dashlet_id' => $systemUuid
]);
$systemUuid = $dashlet->getUuid();
if (! $systemUuid && $dashlet->getPane()) {
$systemUuid = Dashboard::getSHA1(
$dashlet->getModule() . $dashlet->getPane()->getName() . $dashlet->getName()
);
}
if ($systemUuid) {
$conn->insert('dashlet_system', [
'dashlet_id' => $uuid,
'module_dashlet_id' => $systemUuid
]);
}
}
} elseif (! $this->hasDashlet($dashlet->getName())
|| ! $origin
|| ! $origin->hasDashlet($dashlet->getName())) {
} elseif (! $this->hasEntry($dashlet->getName()) || ! $origin
|| ! $origin->hasEntry($dashlet->getName())) {
$filterCondition = [
'id = ?' => $dashlet->getUuid(),
'dashboard_id = ?' => $this->getUuid()
];
if ($origin && $origin->hasEntry($dashlet->getName())) {
$filterCondition = [
'id = ?' => $origin->getEntry($dashlet->getName())->getUuid(),
'dashboard_id = ?' => $origin->getUuid()
];
}
$conn->update(Dashlet::TABLE, [
'id' => $uuid,
'dashboard_id' => $this->getUuid(),
'label' => $dashlet->getTitle(),
'url' => $dashlet->getUrl()->getRelativeUrl(),
'priority' => $dashlet->getPriority()
], ['id = ?' => $dashlet->getUuid()]);
], $filterCondition);
} else {
// This should have already been handled by the caller
break;
@ -522,15 +294,15 @@ class Pane implements OverridingWidget
return $this;
}
public function toArray()
public function toArray($stringify = true)
{
$home = $this->getHome();
return [
'id' => $this->getUuid(),
'name' => $this->getName(),
'label' => $this->getTitle(),
'home' => $this->getHome() ? $this->getHome()->getName() : null,
'home' => ! $stringify ? $home : ($home ? $home->getName() : null),
'priority' => $this->getPriority(),
'disabled' => (int) $this->isDisabled()
];
}
}

View File

@ -6,7 +6,6 @@ namespace Icinga\Web\Dashboard;
use Icinga\Web\Dashboard\ItemList\DashboardHomeList;
use Icinga\Web\Dashboard\ItemList\DashboardList;
use Icinga\Web\Navigation\DashboardHome;
use ipl\Html\BaseHtmlElement;
use ipl\Web\Url;
use ipl\Web\Widget\ActionLink;
@ -27,31 +26,25 @@ class Settings extends BaseHtmlElement
protected function assemble()
{
// TODO: What we should do with disabled homes??
$activeHome = $this->dashboard->getActiveHome();
if (empty($this->dashboard->getHomes())) {
// TODO: No dashboard homes :( what should we render now??
} elseif (count($this->dashboard->getHomes()) === 1 && $activeHome->getName() === DashboardHome::DEFAULT_HOME) {
foreach ($activeHome->getPanes() as $pane) {
if (count($this->dashboard->getEntries()) === 1 && $activeHome->getName() === DashboardHome::DEFAULT_HOME) {
foreach ($activeHome->getEntries() as $pane) {
$pane->setHome($activeHome);
$this->addHtml(new DashboardList($pane));
}
$this->addHtml(new ActionLink(
t('Add Dashboard'),
Url::fromPath(Dashboard::BASE_ROUTE . '/new-dashlet'),
'plus',
[
'class' => 'add-dashboard',
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]
));
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/new-pane')
->setParams(['home' => $activeHome->getName()]);
$this->addHtml(new ActionLink(t('Add Dashboard'), $url, 'plus', [
'class' => 'add-dashboard',
'data-icinga-modal' => true,
'data-no-icinga-ajax' => true
]));
} else {
// Make a list of dashboard homes
foreach ($this->dashboard->getHomes() as $home) {
foreach ($this->dashboard->getEntries() as $home) {
$this->addHtml(new DashboardHomeList($home));
}
}

View File

@ -4,29 +4,26 @@
namespace Icinga\Web\Dashboard\Setup;
use Icinga\Forms\Dashboard\BaseDashboardForm;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Dashboard\Dashlet;
use Icinga\Web\Dashboard\Pane;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Notification;
use Icinga\Web\Dashboard\ItemList\DashletListMultiSelect;
use ipl\Html\HtmlElement;
use ipl\Html\ValidHtml;
use ipl\Web\Compat\CompatForm;
use ipl\Web\Url;
use ipl\Web\Widget\Icon;
class SetupNewDashboard extends CompatForm
class SetupNewDashboard extends BaseDashboardForm
{
/** @var Dashboard */
protected $dashboard;
/** @var array Module dashlets from the DB */
private $dashlets = [];
public function __construct(Dashboard $dashboard)
{
$this->dashboard = $dashboard;
parent::__construct($dashboard);
$this->setRedirectUrl((string) Url::fromPath(Dashboard::BASE_ROUTE));
$this->setAction($this->getRedirectUrl() . '/setup-dashboard');
@ -46,13 +43,6 @@ class SetupNewDashboard extends CompatForm
return $this;
}
public function hasBeenSubmitted()
{
return $this->hasBeenSent()
&& ($this->getPopulatedValue('btn_cancel')
|| $this->getPopulatedValue('submit'));
}
protected function assemble()
{
$this->getAttributes()->add('class', 'modal-form');
@ -60,6 +50,7 @@ class SetupNewDashboard extends CompatForm
if ($this->getPopulatedValue('btn_next')) { // Configure Dashlets
$this->dumpArbitaryDashlets();
$submitButtonLabel = t('Add Dashlets');
$this->addElement('text', 'pane', [
'required' => true,
'label' => t('Dashboard Title'),
@ -134,10 +125,8 @@ class SetupNewDashboard extends CompatForm
}
}
}
$submitButton = $this->createElement('submit', 'submit', ['label' => t('Add Dashlets')]);
$this->registerElement($submitButton)->decorate($submitButton);
} else { // Select Dashlets
$submitButtonLabel = t('Next');
$list = HtmlElement::create('ul', ['class' => 'dashlet-item-list empty-list']);
$multi = new DashletListMultiSelect();
$multi->setCheckBox($this->createElement('checkbox', 'custom_url', ['class' => 'sr-only']));
@ -169,16 +158,19 @@ class SetupNewDashboard extends CompatForm
$this->addHtml($listControl->addHtml($list));
}
$submitButton = $this->createElement('submit', 'btn_next', [
'class' => 'autosubmit',
'label' => t('Next'),
]);
$this->registerElement($submitButton)->decorate($submitButton);
}
$this->addElement('submit', 'btn_cancel', ['label' => t('Cancel')]);
$this->getElement('btn_cancel')->setWrapper($submitButton->getWrapper());
$submitButton = $this->registerSubmitButton($submitButtonLabel);
if (! $this->getPopulatedValue('btn_next')) {
$submitButton
->setName('btn_next')
->getAttributes()->add('class', 'autosubmit');
}
$formControls = $this->createFormControls();
$formControls->add([$submitButton, $this->createCancelButton()]);
$this->addHtml($formControls);
}
protected function onSuccess()
@ -190,12 +182,12 @@ class SetupNewDashboard extends CompatForm
$conn->beginTransaction();
try {
$this->dashboard->getHome(DashboardHome::DEFAULT_HOME)->managePanes($pane);
$this->dashboard->getEntry(DashboardHome::DEFAULT_HOME)->manageEntry($pane);
// If element name "dashlet" and "url" are set we need to only store one dashlet
if (($name = $this->getPopulatedValue('dashlet')) && ($url = $this->getPopulatedValue('url'))) {
$dashlet = new Dashlet($name, $url, $pane);
$pane->manageDashlets($dashlet);
$pane->manageEntry($dashlet);
} else {
foreach ($this->dashlets as $module => $dashlets) {
$moduleDashlets = [];
@ -226,7 +218,7 @@ class SetupNewDashboard extends CompatForm
$moduleDashlets[$dashlet->getName()] = $dashlet;
}
$pane->manageDashlets($moduleDashlets);
$pane->manageEntry($moduleDashlets);
}
}

View File

@ -1,10 +1,13 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web;
use Icinga\Model\Home;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Navigation\DashboardHomeItem;
use ipl\Stdlib\Filter;
/**
@ -28,7 +31,7 @@ class HomeMenu extends Menu
$homes->filter(Filter::equal('username', $user->getUsername()));
foreach ($homes as $home) {
$dashboardHome = new DashboardHome($home->name, [
$dashboardHome = new DashboardHomeItem($home->name, [
'uuid' => $home->id,
'label' => t($home->label),
'priority' => $home->priority,
@ -38,4 +41,26 @@ class HomeMenu extends Menu
$dashboardItem->addChild($dashboardHome);
}
}
/**
* Load dashboard homes form the navigation menu
*
* @return DashboardHome[]
*/
public function loadHomes()
{
$homes = [];
foreach ($this->getItem('dashboard')->getChildren() as $child) {
if (! $child instanceof DashboardHomeItem) {
continue;
}
$home = DashboardHome::create($child);
$home->setTitle($child->getLabel());
$homes[$child->getName()] = $home;
}
return $homes;
}
}

View File

@ -1,414 +0,0 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Navigation;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Dashboard\Common\DisableWidget;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Dashboard\Pane;
use Icinga\Model;
use ipl\Stdlib\Filter;
use ipl\Web\Url;
class DashboardHome extends NavigationItem
{
use DisableWidget;
/**
* Name of the default home
*
* @var string
*/
const DEFAULT_HOME = 'Default Home';
/**
* Database table name
*
* @var string
*/
const TABLE = 'dashboard_home';
/**
* A list of @see Pane assigned to this dashboard home
*
* @var Pane[]
*/
private $panes = [];
/**
* This home's unique identifier
*
* @var int
*/
private $uuid;
/**
* A type of this dashboard home
*
* @var string
*/
private $type = Dashboard::SYSTEM;
/**
* Init this dashboard home
*
* Doesn't set the url of this dashboard home if it's the default one or is disabled
* to prevent from being rendered as dropdown in the navigation bar
*
* @return void
*/
public function init()
{
if ($this->getName() !== self::DEFAULT_HOME && ! $this->isDisabled()) {
$this->setUrl(Url::fromPath(Dashboard::BASE_ROUTE . '/home', [
'home' => $this->getName()
]));
}
}
/**
* Get this dashboard home's url
*
* Parent class would always report a default url if $this->url isn't
* set, which we do it on purpose.
*
* @return \Icinga\Web\Url
*/
public function getUrl()
{
return $this->url;
}
/**
* Get whether this home has been activated
*
* @return bool
*/
public function getActive()
{
return $this->active;
}
/**
* Set whether this home is active
*
* DB dashboard will load only when this home has been activated
*
* @param bool $active
*
* @return $this
*/
public function setActive($active = true)
{
$this->active = $active;
return $this;
}
/**
* Get the type of this dashboard home
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Set the type of this dashboard home
*
* @param string $type
*
* @return $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get the uuid of this dashboard home
*
* @return int
*/
public function getUuid()
{
return $this->uuid;
}
/**
* Get the uuid of this dashboard home
*
* @param int $uuid
*
* @return $this
*/
public function setUuid(int $uuid)
{
$this->uuid = $uuid;
return $this;
}
/**
* Get a pane with the given name if exists
*
* @param string $name
*
* @return Pane
*/
public function getPane($name)
{
if (! $this->hasPane($name)) {
throw new ProgrammingError('Trying to retrieve invalid dashboard pane "%s"', $name);
}
return $this->panes[$name];
}
/**
* Get whether this home has any dashboard panes
*
* @return bool
*/
public function hasPanes()
{
return ! empty($this->panes);
}
/**
* Get whether the given pane exist
*
* @param string $name
*
* @return bool
*/
public function hasPane($name)
{
return array_key_exists($name, $this->panes);
}
/**
* Get all dashboards of this home
*
* @param bool $skipDisabled Whether to skip disabled dashboards
*
* @return Pane[]
*/
public function getPanes($skipDisabled = false)
{
// As the panes can also be added individually afterwards, it might be the case that the priority
// order gets mixed up, so we have to sort things here before being able to render them
uasort($this->panes, function (Pane $x, Pane $y) {
return $x->getPriority() - $y->getPriority();
});
return ! $skipDisabled ? $this->panes : array_filter($this->panes, function ($pane) {
return ! $pane->isDisabled();
});
}
/**
* Set dashboards of this home
*
* @param Pane|Pane[] $panes
*
* @return $this
*/
public function setPanes($panes)
{
if ($panes instanceof Pane) {
$panes = [$panes->getName() => $panes];
}
$this->panes = $panes;
return $this;
}
/**
* Add a new dashboard pane to this home
*
* @param Pane|string $pane
*
* @return $this
*/
public function addPane($pane)
{
if (! $pane instanceof Pane) {
$pane = new Pane($pane);
$pane
->setHome($this)
->setTitle($pane->getName());
}
$this->panes[$pane->getName()] = $pane;
return $this;
}
/**
* Get an array with pane name=>title format
*
* @return string[]
*/
public function getPaneKeyTitleArr()
{
$panes = [];
foreach ($this->getPanes(true) as $pane) {
$panes[$pane->getName()] = $pane->getName();
}
return $panes;
}
/**
* Remove the given pane from this home
*
* @param Pane|string $pane
*
* @return $this
*/
public function removePane($pane)
{
$name = $pane instanceof Pane ? $pane->getName() : $pane;
if (! $this->hasPane($name)) {
throw new ProgrammingError('Trying to remove invalid dashboard pane "%s"', $name);
}
$pane = $pane instanceof Pane ? $pane : $this->getPane($pane);
if (! $pane->isOverriding()) {
$pane->removeDashlets();
Dashboard::getConn()->delete(Pane::TABLE, [
'id = ?' => $pane->getUuid(),
'home_id = ?' => $this->getUuid()
]);
}
return $this;
}
/**
* Remove all/the given dashboard panes from this home
*
* @param Pane[] $panes
*
* @return $this
*/
public function removePanes(array $panes = [])
{
$panes = ! empty($panes) ? $panes : $this->getPanes();
foreach ($panes as $pane) {
$this->removePane($pane);
}
return $this;
}
/**
* Load all dashboards this user is assigned to from the DB
*
* @return $this
*/
public function loadPanesFromDB()
{
// Skip when this home is either disabled or inactive
if (! $this->getActive() || $this->isDisabled()) {
return $this;
}
$this->panes = [];
$panes = Model\Pane::on(Dashboard::getConn())->utilize('home');
$panes
->filter(Filter::equal('home_id', $this->getUuid()))
->filter(Filter::equal('username', Dashboard::getUser()->getUsername()));
foreach ($panes as $pane) {
$newPane = new Pane($pane->name);
//$newPane->disable($pane->disable);
$newPane->fromArray([
'uuid' => $pane->id,
'title' => $pane->label,
'priority' => $pane->priority,
'home' => $this
]);
$newPane->loadDashletsFromDB();
$this->panes[$newPane->getName()] = $newPane;
}
return $this;
}
/**
* Manage the given pane(s)
*
* If you want to move the pane(s) from another to this home,
* you have to also pass through the origin home with
*
* @param Pane|Pane[] $panes
* @param ?DashboardHome $origin
* @param bool $mngPaneDashlets
*
* @return $this
*/
public function managePanes($panes, DashboardHome $origin = null, $mngPaneDashlets = false)
{
$user = Dashboard::getUser();
$conn = Dashboard::getConn();
$panes = is_array($panes) ? $panes : [$panes];
$order = count($this->getPanes()) + 1;
foreach ($panes as $pane) {
$uuid = Dashboard::getSHA1($user->getUsername() . $this->getName() . $pane->getName());
if (! $pane->isOverriding()) {
if (! $this->hasPane($pane->getName()) && (! $origin || ! $origin->hasPane($pane->getName()))) {
$conn->insert(Pane::TABLE, [
'id' => $uuid,
'home_id' => $this->getUuid(),
'name' => $pane->getName(),
'label' => $pane->getTitle(),
'username' => $user->getUsername(),
'priority' => $order++
]);
} elseif (! $this->hasPane($pane->getName()) || ! $origin || ! $origin->hasPane($pane->getName())) {
$conn->update(Pane::TABLE, [
'id' => $uuid,
'home_id' => $this->getUuid(),
'label' => $pane->getTitle(),
'priority' => $pane->getPriority()
], [
'id = ?' => $pane->getUuid(),
'home_id = ?' => $this->getUuid()
]);
} else {
// Failed to move the pane! Should have been handled already by the caller
break;
}
$pane->setUuid($uuid);
} else {
// TODO(TBD)
}
$pane->setHome($this);
if ($mngPaneDashlets) {
// Those dashboard panes are usually system defaults and go up when
// the user is clicking on the "Use System Defaults" button
$dashlets = $pane->getDashlets();
$pane->setDashlets([]);
$pane->manageDashlets($dashlets);
}
}
return $this;
}
}

View File

@ -0,0 +1,42 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Navigation;
use Icinga\Web\Dashboard\Dashboard;
use Icinga\Web\Dashboard\DashboardHome;
use ipl\Web\Url;
class DashboardHomeItem extends NavigationItem
{
/**
* Init this dashboard home
*
* Doesn't set the url of this dashboard home if it's the default one
* to prevent from being rendered as dropdown in the navigation bar
*
* @return void
*/
public function init()
{
if ($this->getName() !== DashboardHome::DEFAULT_HOME) {
$this->setUrl(Url::fromPath(Dashboard::BASE_ROUTE . '/home', [
'home' => $this->getName()
]));
}
}
/**
* Get this dashboard home's url
*
* Parent class would always report a default url if $this->url isn't
* set, which we do it on purpose.
*
* @return \Icinga\Web\Url
*/
public function getUrl()
{
return $this->url;
}
}

View File

@ -5,7 +5,7 @@ namespace Icinga\Web\Widget;
use Icinga\Exception\Http\HttpNotFoundException;
use Icinga\Application\Icinga;
use Icinga\Web\Navigation\DashboardHome;
use Icinga\Web\Dashboard\DashboardHome;
use Icinga\Web\Url;
/**
@ -37,7 +37,6 @@ class SearchDashboard extends \Icinga\Web\Dashboard\Dashboard
public function __construct()
{
$this->searchHome = new DashboardHome(self::SEARCH_HOME);
$this->searchHome->setActive();
}
public function getTabs()
@ -72,7 +71,8 @@ class SearchDashboard extends \Icinga\Web\Dashboard\Dashboard
*/
public function search($searchString = '')
{
$pane = $this->searchHome->addPane(self::SEARCH_PANE)->getPane(self::SEARCH_PANE)->setTitle(t('Search'));
$pane = $this->searchHome->createEntry(self::SEARCH_PANE)->getEntry(self::SEARCH_PANE);
$pane->setTitle(t('Search'));
$this->activate(self::SEARCH_PANE);
$manager = Icinga::app()->getModuleManager();
@ -83,7 +83,7 @@ class SearchDashboard extends \Icinga\Web\Dashboard\Dashboard
$moduleSearchUrls = $module->getSearchUrls();
if (! empty($moduleSearchUrls)) {
if ($searchString === '') {
$pane->addDashlet(t('Ready to search'), 'search/hint');
$pane->createEntry(t('Ready to search'), 'search/hint');
return $this;
}
$searchUrls = array_merge($searchUrls, $moduleSearchUrls);
@ -95,8 +95,8 @@ class SearchDashboard extends \Icinga\Web\Dashboard\Dashboard
foreach (array_reverse($searchUrls) as $searchUrl) {
$title = $searchUrl->title . ': ' . $searchString;
$pane->addDashlet($title, Url::fromPath($searchUrl->url, array('q' => $searchString)));
$pane->getDashlet($title)->setProgressLabel(t('Searching'));
$pane->createEntry($title, Url::fromPath($searchUrl->url, array('q' => $searchString)));
$pane->getEntry($title)->setProgressLabel(t('Searching'));
}
return $this;
@ -104,11 +104,11 @@ class SearchDashboard extends \Icinga\Web\Dashboard\Dashboard
protected function assemble()
{
if (! $this->searchHome->getPane(self::SEARCH_PANE)->hasDashlets()) {
if ($this->searchHome->getEntry(self::SEARCH_PANE)->hasEntries()) {
throw new HttpNotFoundException(t('Page not found'));
}
$this->add($this->searchHome->getPane(self::SEARCH_PANE)->getDashlets());
$this->add($this->searchHome->getEntry(self::SEARCH_PANE)->getEntries());
}
/**

View File

@ -35,14 +35,10 @@ class DashboardSettings implements Tabextension
public function apply(Tabs $tabs)
{
$url = Url::fromPath(Dashboard::BASE_ROUTE . '/settings');
$url = empty($this->urlParam) ? $url : $url->addParams($this->urlParam);
$tabs->add(
'dashboard_settings',
[
'icon' => 'service',
'url' => (string) $url,
'priority' => -100
]
);
$url = empty($this->urlParam) ? $url : $url->addParams($this->urlParam);
$tabs->add('dashboard_settings', [
'icon' => 'service',
'url' => (string) $url,
]);
}
}

View File

@ -82,7 +82,7 @@
list-style-type: none;
label {
width: 49%;
width: 49.5%;
margin-bottom: .5em;
}
@ -93,24 +93,29 @@
.dashlet-list-item {
display: flex;
flex-direction: column;
border-radius: .4em;
border: 1px solid @gray-light;
background: @gray-lightest;
border: 1px solid @gray-light;
.rounded-corners();
section.caption, h1 {
margin-left: .5em;
font-weight: 500;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
}
h1 {
border-bottom: none;
overflow: hidden;
-webkit-line-clamp: 1;
}
.caption {
height: 3em;
overflow-y: auto;
font-size: 1.1em;
color: @text-color-light;
-webkit-line-clamp: 2;
}
}
@ -141,16 +146,10 @@
// Add new home button of the dashboard manager
.controls .add-home {
.button();
float: right;
outline: none;
cursor: pointer;
font-size: 1.2em;
font-weight: 500;
padding: .5em 1em;
line-height: normal;
text-decoration: none;
border: 2px solid @icinga-blue;
border-radius: .4em;
color: @text-color-on-icinga-blue;
background-color: @icinga-blue;
}
@ -160,27 +159,27 @@
}
.action-link.add-dashboard {
width: 81.5%;
margin-top: .5em;
width: 85%;
margin: .5em 0 0;
}
.action-link.add-dashlet, .action-link.add-dashboard {
.rounded-corners();
order: 1;
font-size: 1.2em;
font-weight: 500;
padding: .6em 1em;
border-radius: .3em;
text-decoration: none;
background-color: @low-sat-blue;
}
.dashboard-list-control .dashlet-item-list {
width: 100%;
display: flex;
margin-left: 2.2em;
display: flex;
width: 97.8%; // 100% - 2.2em margin-left
.dashlet-list-item {
width: 49.4%;
width: 49.7%;
margin-bottom: .5em;
background: transparent;
}
@ -191,14 +190,14 @@
}
.dashboard-list-control {
width: 80%;
width: 85%;
h1.collapsible-header {
margin-left: 1.5em;
}
.action-link.add-dashlet {
width: 99.5%;
width: 97.8%;
margin-left: 1.8em;
}
}
@ -213,12 +212,16 @@
border-bottom: none;
}
h1.collapsible-header a, h1.dashlet-header a {
h1.collapsible-header a:not(.dashlet-title), h1.dashlet-header a:not(.dashlet-title) {
margin-left: .3em;
font-weight: normal;
color: @icinga-blue;
}
h1.dashlet-header a.dashlet-title {
color: @text-color;
}
.dashlets-list-info, .dashboard-list-info {
.expand-icon {
margin-top: .9em;
@ -230,17 +233,22 @@
}
}
// Dashboard home list controls only
.home-list-control {
width: 100%;
h1.collapsible-header.home {
& > h1.collapsible-header {
margin-left: 1.5em;
margin-bottom: 0;
margin-bottom: -1em;
& > a.disabled {
pointer-events: none;
cursor: default;
}
}
.action-link.add-dashboard {
width: 81.6%;
margin-left: 1.3em;
.dashboard-list-control, .action-link.add-dashboard {
margin-left: 1.4em;
}
.dashboard-list-control {
@ -249,20 +257,13 @@
}
.dashlet-item-list {
margin-left: 4em;
}
.dashlets-list-info {
margin-left: 2.2em;
}
h1.collapsible-header {
margin-left: 3em;
margin-left: 2em;
width: 98%; // 100% - 2em margin-left
}
.action-link.add-dashlet {
margin-left: 3.4em;
width: 99.2%;
margin-left: 1.6em;
width: calc((49.74% * 2) - 1.2em); // 2x flex columns - own size
}
}
}
@ -292,17 +293,37 @@
}
// Make the submit buttons a bit bigger than the normal size
.modal-form.icinga-controls .control-group.form-controls input[type="submit"] {
.modal-form.icinga-controls .control-group.form-controls input[type="submit"],
.control-group.form-controls .modal-cancel {
min-width: 7em;
font-size: 1.2em;
margin-right: .4em;
padding: ~"calc(@{vertical-padding} - 2px)" @horizontal-padding;
}
// Remove and modal cancel buttons
form.icinga-form .control-group.form-controls .remove-button {
.cancel-button();
margin-right: auto;
}
.modal-form .form-controls .modal-cancel {
padding: .5em 1em;
}
.control-group.form-controls .modal-cancel {
background-color: @low-sat-blue;
border: none;
}
// Drag and drop
.drag-over {
.sortable-chosen {
.box-shadow();
background: fade(@icinga-blue, 20);
border: 5px @body-bg-color dashed;
box-shadow: 4px 4px 4px 4px @black;
}
.home-list-control.sortable-drag {
width: calc(85% + 1.4em);
}
.drag-active * {

View File

@ -4,19 +4,26 @@
'use strict';
try {
var Sortable = require('icinga/icinga-php-library/vendor/Sortable')
} catch (e) {
console.warn('Unable to provide Sortable. Library not available:', e);
return;
}
/**
* Possible type of widgets this behavior is being applied to
*
* @type {object}
*/
const WIDGET_TYPES = { Dashlets : 'Dashlets', Dashboards : 'Dashboards', DashboardHomes : 'Homes' };
const WIDGET_TYPES = { Dashlet : 'Dashlets', Dashboard : 'Dashboards', DashboardHome : 'Homes' };
Icinga.Behaviors = Icinga.Behaviors || {};
/**
* Behavior for the enhanced Icinga Web 2 dashboards
*
* @param icinga {Icinga} The current Icinga Object
* @param {Icinga} icinga The current Icinga Object
*
* @constructor
*/
@ -25,283 +32,182 @@
this.icinga = icinga;
/**
* Type of the widget which is currently being sorted
*
* @type {string}
*/
this.sortedWidgetType = WIDGET_TYPES.Dashlets;
/**
* Widget container id which is currently being dragged
*
* @type {null|string}
*/
this.containerId = null;
// Register event handlers for drag and drop functionalities
this.on('dragstart', '.widget-sortable', this.onDragStart, this);
this.on('dragover', '.widget-sortable', this.onDragOver, this);
this.on('dragleave', '.widget-sortable', this.onDragLeave, this);
this.on('dragend', '.widget-sortable', this.onDragEnd, this);
this.on('drop', '.widget-sortable', this.onDrop, this);
this.on('rendered', '#main > .container', this.onRendered, this);
this.on('end', '.dashboard-settings, .dashboard-list-control, .dashlet-list-item', this.elementDropped, this);
// This is for the normal dashboard/dashlets view
this.on('end', '.dashboard.content', this.elementDropped, this);
};
Dashboard.prototype = new Icinga.EventListener();
/**
* A user tries to drag an element, so make sure it's sortable and setup the procedure
* Get the widget type of the given element
*
* @param event {Event} The `dragstart` event triggered when starting to drag the element
* with a mouse click and begin to move it
* @param {HTMLElement} target
*
* @returns {null|string}
*/
Dashboard.prototype.onDragStart = function (event) {
let _this = event.data.self;
let $target = $(event.target);
if (! _this.isDraggable($target)) {
return false;
Dashboard.prototype.getTypeFor = function (target) {
if (target.matches('.dashboard-settings')) {
return WIDGET_TYPES.DashboardHome;
} else if (target.matches('.dashboard-item-list')) {
return WIDGET_TYPES.Dashboard;
} else if (target.matches('.dashlet-item-list') || target.matches('.dashboard.content')) {
return WIDGET_TYPES.Dashlet;
}
_this.containerId = $target.attr('id');
$target.addClass('draggable-element');
let $parent = $target.parent()[0];
// Prevents child elements from being the target of pointer events
$($parent).children('.widget-sortable').addClass('drag-active');
return null;
};
/**
* Event handler for drag over
* Set up a request with the reordered widget and post the data to the controller
*
* Check that the target el is draggable and isn't the el itself
* which is currently being dragged
*
* @param event {Event} The `drag over` event triggered when dragging over another dashlet
*/
Dashboard.prototype.onDragOver = function (event) {
let $target = $(event.target);
let _this = event.data.self;
// Moving an element arbitrarily elsewhere isn't allowed
if (! _this.isDraggable($target) || ! _this.isDraggableSiblingOf($target)) {
return false;
}
// Don't show mouse drop cursor if the target element is the draggable element
if ($target.attr('id') !== _this.containerId) {
event.preventDefault();
event.stopPropagation();
$target.addClass('drag-over');
}
};
/**
* The element doesn't get dragged over anymore, so just remove the drag-over class
*
* @param event {Event} The `drag leave` event triggered when dragging over a dashlet
* and leaving without dropping the draggable element
*/
Dashboard.prototype.onDragLeave = function (event) {
let $target = $(event.target);
let _this = event.data.self;
if (! _this.isDraggable($target) || ! _this.isDraggableSiblingOf($target)) {
return false;
}
$target.removeClass('drag-over');
};
/**
* Remove all class names added dynamically
*
* @param event {Event} The `drag end` event triggered when the draggable element is released
*/
Dashboard.prototype.onDragEnd = function (event) {
let $target = $(event.target);
let _this = event.data.self;
if (! _this.isDraggable($target) || ! _this.isDraggableSiblingOf($target)) {
return false;
}
$target.removeClass('draggable-element');
$target.removeClass('drag-over');
let $parent = $target.parent()[0];
// The draggable is now released, so we have to remove the class to enable the pointer events again
$($parent).children('.widget-sortable').removeClass('drag-active');
};
/**
* Event handler for on drop action
*
* @param event {Event} The `ondrop` event triggered when the dashlet has been dropped
*/
Dashboard.prototype.onDrop = function (event) {
let $target = $(event.target);
let _this = event.data.self;
// Don't allow to drop an element arbitrarily elsewhere
if (! _this.isDraggable($target) || ! _this.isDraggableSiblingOf($target)) {
return false;
}
// Prevent default behaviors to allow the drop event
event.preventDefault();
event.stopPropagation();
const draggable = $target.parent().children('#' + _this.containerId);
// If the target element has been located before the draggable element,
// insert the draggable before the target element otherwise after it
if ($target.nextAll().filter(draggable).length) {
draggable.insertBefore($target);
} else {
draggable.insertAfter($target);
}
// Draggable element is now dropped, so drag-over class must also be removed
$target.removeClass('drag-over');
if ($target.data('icinga-pane')) {
_this.sortedWidgetType = WIDGET_TYPES.Dashboards;
} else if ($target.data('icinga-home')) {
_this.sortedWidgetType = WIDGET_TYPES.DashboardHomes;
}
_this.sendReorderedWidgets($target);
};
/**
* Get whether the given element is draggable
*
* @param $target {jQuery}
* @param event
*
* @returns {boolean}
*/
Dashboard.prototype.isDraggable = function ($target) {
return $target.attr('draggable');
};
/**
* Get whether the given element is sibling of the element currently being dragged
*
* @param $target {jQuery}
*
* @returns {number}
*/
Dashboard.prototype.isDraggableSiblingOf = function ($target) {
return $target.parent().children('#' + this.containerId).length;
};
/**
* Set up a request with the reordered containers and post the data to the controller
*
* @param $target {jQuery}
*/
Dashboard.prototype.sendReorderedWidgets = function ($target) {
let _this = this,
Dashboard.prototype.elementDropped = function (event) {
let _this = event.data.self,
orgEvt = event.originalEvent,
data = {};
switch (_this.sortedWidgetType) {
case WIDGET_TYPES.DashboardHomes: {
let $homes = [];
$target.parent().children('.home-list-control.widget-sortable').each(function () {
let home = $(this);
if (typeof home.data('icinga-home') === 'undefined') {
_this.icinga.logger.error(
'[Dashboards] Dashboard home widget has no "icingaHome" data attribute registered: ',
home[0]
);
return;
}
if (orgEvt.to === orgEvt.from && orgEvt.newIndex === orgEvt.oldIndex) {
return false;
}
$homes.push(home.data('icinga-home'));
});
data = { ...$homes };
let item = orgEvt.item;
switch (_this.getTypeFor(orgEvt.to)) {
case WIDGET_TYPES.DashboardHome: {
let home = item.dataset.icingaHome;
data[home] = orgEvt.newIndex;
break;
}
case WIDGET_TYPES.Dashboards: {
let $home, $panes = [];
$target.parent().children('.dashboard-list-control.widget-sortable').each(function () {
let pane = $(this);
if (typeof pane.data('icinga-pane') === 'undefined') {
_this.icinga.logger.error(
'[Dashboards] Dashboard widget has no "icingaPane" data attribute registered: ',
pane[0]
);
return;
}
pane = pane.data('icinga-pane').split('|', 2);
if (! $home) {
$home = pane.shift();
}
$panes.push(pane.pop());
});
data[$home] = $panes;
break;
}
case WIDGET_TYPES.Dashlets: {
let $home, $pane, $dashlets = [];
$target.parent().children('.widget-sortable').each(function () {
let dashlet = $(this);
if (typeof dashlet.data('icinga-dashlet') === 'undefined') {
_this.icinga.logger.error(
'[Dashboards] Dashlet widget has no "icingaDashlet" data attribute registered: ',
dashlet[0]
);
return;
}
if (! $home && ! $pane) {
let pane = dashlet.parent();
if (typeof pane.data('icinga-pane') === 'undefined') {
// Nested parents
pane = pane.parent();
if (typeof pane.data('icinga-pane') === 'undefined') {
_this.icinga.logger.error(
'[Dashboards] Dashlet parent widget has no "icingaPane" data attribute registered: ',
pane[0]
);
return;
}
}
pane = pane.data('icinga-pane').split('|', 2);
$home = pane.shift();
$pane = pane.shift();
}
$dashlets.push(dashlet.data('icinga-dashlet'));
});
if ($home && $pane) {
data[$home] = { [$pane] : $dashlets };
case WIDGET_TYPES.Dashboard: {
let pane = item.dataset.icingaPane,
home = orgEvt.to.closest('.home-list-control').dataset.icingaHome;
if (orgEvt.to !== orgEvt.from) {
data.originals = {
originalHome : orgEvt.from.closest('.home-list-control').dataset.icingaHome,
originalPane : pane
};
}
data[home] = { [pane] : orgEvt.newIndex };
break;
}
case WIDGET_TYPES.Dashlet: {
let dashlet = item.dataset.icingaDashlet,
pane,
home;
if (orgEvt.to.matches('.dashboard.content')) {
let parentData = orgEvt.to.dataset.icingaPane.split('|', 2);
home = parentData.shift();
pane = parentData.shift();
data.redirectPath = 'dashboards';
} else { // Dashboard manager view
let parent = orgEvt.to.closest('.dashboard-list-control');
pane = parent.dataset.icingaPane;
home = parent.closest('.home-list-control').dataset.icingaHome;
if (orgEvt.to !== orgEvt.from) {
let parent = orgEvt.from.closest('.dashboard-list-control');
data.originals = {
originalHome : parent.closest('.home-list-control').dataset.icingaHome,
originalPane : parent.dataset.icingaPane
}
}
}
dashlet = { [dashlet] : orgEvt.newIndex };
data[home] = { [pane] : dashlet };
}
}
if (Object.keys(data).length) {
data.Type = _this.sortedWidgetType;
data.Type = _this.getTypeFor(orgEvt.to);
if (! data.hasOwnProperty('originals')) {
data.originals = null;
}
$.ajax({
context : _this,
type : 'post',
url : _this.icinga.config.baseUrl + '/dashboards/reorder-widgets',
headers : { 'Accept' : 'application/json' },
contentType : 'application/json',
data : JSON.stringify(data),
});
if (! data.hasOwnProperty('redirectPath')) {
data.redirectPath = 'dashboards/settings';
}
data = { dashboardData : JSON.stringify(data) };
let url = _this.icinga.config.baseUrl + '/dashboards/reorder-widgets';
_this.icinga.loader.loadUrl(url, $('#col1'), data, 'post');
}
};
/**
* Get whether the given element is a valid target of the drag & drop events
*
* @param to
* @param from
* @param item
* @param event
*
* @returns {boolean}
*/
Dashboard.prototype.isValid = function (to, from, item, event) {
if (typeof from.options.group === 'undefined' || typeof to.options.group === 'undefined') {
return false;
}
return from.options.group.name === to.options.group.name;
};
Dashboard.prototype.onRendered = function (e) {
let _this = e.data.self;
$(e.target).find('.dashboard-settings, .dashboard.content, .dashboard-item-list, .dashlet-item-list').each(function () {
let groupName = _this.getTypeFor(this),
draggable,
handle;
switch (groupName) {
case WIDGET_TYPES.DashboardHome: {
groupName = WIDGET_TYPES.DashboardHome;
draggable = '.home-list-control';
handle = '.home-list-control > h1';
break;
}
case WIDGET_TYPES.Dashboard: {
groupName = WIDGET_TYPES.Dashboard;
draggable = '.dashboard-list-control';
handle = '.dashboard-list-control > h1'
break;
}
case WIDGET_TYPES.Dashlet: {
groupName = WIDGET_TYPES.Dashlet;
if (this.matches('.dashboard.content')) {
draggable = '> .container';
} else {
draggable = '.dashlet-list-item';
}
handle = draggable;
}
}
let options = {
scroll : true,
invertSwap : true,
dataIdAttr : 'id',
direction : 'vertical',
draggable : draggable,
handle : handle,
group : {
name : groupName,
put : _this['isValid'],
}
};
Sortable.create(this, options);
});
};
Icinga.Behaviors.Dashboard = Dashboard;
})(Icinga, jQuery);