From 66e5158ccc840e5f2075014921fb9883d56a94c9 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 27 Oct 2016 17:58:31 +0000 Subject: [PATCH] Dashboard: replace it with a modular one fixes #12997 --- .../controllers/DashboardController.php | 34 +++ application/controllers/IndexController.php | 133 ++-------- application/forms/ApplyMigrationsForm.php | 18 +- .../views/scripts/dashboard/index.phtml | 20 ++ .../views/scripts/dashlets/default.phtml | 5 + application/views/scripts/index/actions.phtml | 17 -- application/views/scripts/index/index.phtml | 127 ---------- library/Director/Acl.php | 54 ++++ library/Director/Dashboard/Dashboard.php | 163 ++++++++++++ .../Dashboard/Dashlet/ActivityLogDashlet.php | 35 +++ .../Dashlet/ApiUserObjectDashlet.php | 20 ++ .../Dashlet/CommandObjectDashlet.php | 20 ++ .../Director/Dashboard/Dashlet/Dashlet.php | 239 ++++++++++++++++++ .../Dashboard/Dashlet/DatafieldDashlet.php | 30 +++ .../Dashboard/Dashlet/DatalistDashlet.php | 30 +++ .../Dashboard/Dashlet/DeploymentDashlet.php | 117 +++++++++ .../Dashlet/EndpointObjectDashlet.php | 56 ++++ .../Dashboard/Dashlet/HostObjectDashlet.php | 23 ++ .../Dashboard/Dashlet/ImportSourceDashlet.php | 65 +++++ .../Director/Dashboard/Dashlet/JobDashlet.php | 59 +++++ .../Dashlet/NotificationObjectDashlet.php | 29 +++ .../Dashlet/ServiceObjectDashlet.php | 23 ++ .../Dashboard/Dashlet/SyncDashlet.php | 65 +++++ .../Dashlet/TimeperiodObjectDashlet.php | 23 ++ .../Dashboard/Dashlet/UserObjectDashlet.php | 23 ++ .../Dashboard/Dashlet/ZoneObjectDashlet.php | 20 ++ library/Director/Dashboard/DataDashboard.php | 28 ++ .../Dashboard/DeploymentDashboard.php | 28 ++ .../Director/Dashboard/ObjectsDashboard.php | 31 +++ .../Objects/DirectorDeploymentLog.php | 10 + 30 files changed, 1284 insertions(+), 261 deletions(-) create mode 100644 application/controllers/DashboardController.php create mode 100644 application/views/scripts/dashboard/index.phtml create mode 100644 application/views/scripts/dashlets/default.phtml delete mode 100644 application/views/scripts/index/actions.phtml delete mode 100644 application/views/scripts/index/index.phtml create mode 100644 library/Director/Acl.php create mode 100644 library/Director/Dashboard/Dashboard.php create mode 100644 library/Director/Dashboard/Dashlet/ActivityLogDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/CommandObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/Dashlet.php create mode 100644 library/Director/Dashboard/Dashlet/DatafieldDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/DatalistDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/DeploymentDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/HostObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/ImportSourceDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/JobDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/NotificationObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/SyncDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/UserObjectDashlet.php create mode 100644 library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php create mode 100644 library/Director/Dashboard/DataDashboard.php create mode 100644 library/Director/Dashboard/DeploymentDashboard.php create mode 100644 library/Director/Dashboard/ObjectsDashboard.php diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php new file mode 100644 index 00000000..c9fced25 --- /dev/null +++ b/application/controllers/DashboardController.php @@ -0,0 +1,34 @@ +getRequest()->isGet()) { + $this->setAutorefreshInterval(10); + } + + $this->view->title = $this->translate('Icinga Director'); + $this->singleTab($this->translate('Overview')); + $dashboards = array(); + foreach (array('Objects', 'Deployment', 'Data') as $name) { + $dashboard = Dashboard::loadByName($name, $this->db(), $this->view); + if ($dashboard->isAvailable()) { + $dashboards[$name] = $dashboard; + } + } + + if (empty($dashboards)) { + throw new NotFoundError('Got no "%s" dashboard for you', $name); + } + + $this->view->dashboards = $dashboards; + } +} diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index 34f06792..d94f0207 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -4,125 +4,38 @@ namespace Icinga\Module\Director\Controllers; use Exception; use Icinga\Module\Director\Db\Migrations; -use Icinga\Module\Director\Objects\DirectorJob; -use Icinga\Module\Director\Objects\ImportSource; -use Icinga\Module\Director\Objects\SyncRule; -use Icinga\Module\Director\Web\Controller\ActionController; -class IndexController extends ActionController +class IndexController extends DashboardController { public function indexAction() { - if ($this->getRequest()->isGet()) { - $this->setAutorefreshInterval(10); - } - - if (! $this->Config()->get('db', 'resource') - || !$this->fetchStats() - || !$this->hasDeploymentEndpoint() - ) { - $this->getTabs()->add('overview', array( - 'url' => $this->getRequest()->getUrl(), - 'label' => $this->translate('Configuration') - ))->activate('overview'); - $this->view->title = $this->translate('Configuration'); - $this->view->form = $this->loadForm('kickstart')->handleRequest(); - - } else { - $this->getTabs()->add('overview', array( - 'url' => $this->getRequest()->getUrl(), - 'label' => $this->translate('Overview') - ))->activate('overview'); + $this->view->dashboards = array(); + if ($this->Config()->get('db', 'resource')) { $migrations = new Migrations($this->db()); - if ($migrations->hasPendingMigrations()) { - $this->view->migrationsForm = $this + if (! $migrations->hasSchema() || !$this->hasDeploymentEndpoint()) { + $this->showKickstartForm(); + } elseif ($migrations->hasPendingMigrations()) { + $this->view->form = $this ->loadForm('applyMigrations') ->setMigrations($migrations) ->handleRequest(); + parent::indexAction(); + } else { + parent::indexAction(); } - - try { - $this->fetchSyncState() - ->fetchImportState() - ->fetchJobState(); - } catch (Exception $e) { - } + } else { + $this->showKickstartForm(); } + + $this->setViewScript('dashboard/index'); } - protected function fetchSyncState() + protected function showKickstartForm() { - $syncs = SyncRule::loadAll($this->db()); - if (count($syncs) > 0) { - $state = 'ok'; - } else { - $state = null; - } - - foreach ($syncs as $sync) { - if ($sync->sync_state !== 'in-sync') { - if ($sync->sync_state === 'failing') { - $state = 'critical'; - break; - } else { - $state = 'warning'; - } - } - } - - $this->view->syncState = $state; - - return $this; - } - - protected function fetchImportState() - { - $srcs = ImportSource::loadAll($this->db()); - if (count($srcs) > 0) { - $state = 'ok'; - } else { - $state = null; - } - - foreach ($srcs as $src) { - if ($src->import_state !== 'in-sync') { - if ($src->import_state === 'failing') { - $state = 'critical'; - break; - } else { - $state = 'warning'; - } - } - } - - $this->view->importState = $state; - - return $this; - } - - protected function fetchJobState() - { - $jobs = DirectorJob::loadAll($this->db()); - if (count($jobs) > 0) { - $state = 'ok'; - } else { - $state = null; - } - - foreach ($jobs as $job) { - if ($job->isPending()) { - $state = 'pending'; - } elseif (! $job->lastAttemptSucceeded()) { - $state = 'critical'; - break; - } - } - - $this->view->jobState = $state; - - return $this; + $this->singleTab($this->translate('Kickstart')); + $this->view->form = $this->loadForm('kickstart')->handleRequest(); } protected function hasDeploymentEndpoint() @@ -135,16 +48,4 @@ class IndexController extends ActionController return $this->view->hasDeploymentEndpoint; } - - protected function fetchStats() - { - try { - $this->view->stats = $this->db()->getObjectSummary(); - $this->view->undeployedActivities = $this->db()->countActivitiesSinceLastDeployedConfig(); - } catch (Exception $e) { - return false; - } - - return true; - } } diff --git a/application/forms/ApplyMigrationsForm.php b/application/forms/ApplyMigrationsForm.php index c1d954e4..02025ade 100644 --- a/application/forms/ApplyMigrationsForm.php +++ b/application/forms/ApplyMigrationsForm.php @@ -12,7 +12,23 @@ class ApplyMigrationsForm extends QuickForm public function setup() { - $this->setSubmitLabel($this->translate('Apply schema migrations')); + if ($this->migrations->hasSchema()) { + $count = $this->migrations->countPendingMigrations(); + if ($count === 1) { + $this->setSubmitLabel( + $this->translate('Apply a pending schema migration') + ); + } else { + $this->setSubmitLabel( + sprintf( + $this->translate('Apply %d pending schema migrations'), + $count + ) + ); + } + } else { + $this->setSubmitLabel($this->translate('Create schema')); + } } public function onSuccess() diff --git a/application/views/scripts/dashboard/index.phtml b/application/views/scripts/dashboard/index.phtml new file mode 100644 index 00000000..9a12fc04 --- /dev/null +++ b/application/views/scripts/dashboard/index.phtml @@ -0,0 +1,20 @@ +
+tabs ?> +
+ +
+errorMessage): ?> +

errorMessage ?>

+ +form ?> +dashboards as $dashboard): ?> +

escape($dashboard->getTitle()) ?>

+ + +
diff --git a/application/views/scripts/dashlets/default.phtml b/application/views/scripts/dashlets/default.phtml new file mode 100644 index 00000000..52ade1c5 --- /dev/null +++ b/application/views/scripts/dashlets/default.phtml @@ -0,0 +1,5 @@ +renderClassAttribute()?> href="url($dashlet->getUrl()) ?>"> +escape($dashlet->getTitle()) ?> +icon($this->dashlet->getIconName()) ?> +

getEscapedSummary() ?>

+ diff --git a/application/views/scripts/index/actions.phtml b/application/views/scripts/index/actions.phtml deleted file mode 100644 index a59992a6..00000000 --- a/application/views/scripts/index/actions.phtml +++ /dev/null @@ -1,17 +0,0 @@ -

escape($title) ?>

- - diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml deleted file mode 100644 index 6113bf6f..00000000 --- a/application/views/scripts/index/index.phtml +++ /dev/null @@ -1,127 +0,0 @@ -
-tabs ?> -
- -
-errorMessage): ?> -

errorMessage ?>

- -stats): ?> -form ?> -
- -stats[$type]; - - if ((int) $stat->cnt_total === 0) { - return $self->translate('No object has been defined yet'); - } - - if ((int) $stat->cnt_total === 1) { - if ($stat->cnt_template > 0) { - $msg = $self->translate('One template has been defined'); - } elseif ($stat->cnt_external > 0) { - $msg = $self->translate('One external object has been defined, it will not be deployed'); - } else { - $msg = $self->translate('One object has been defined'); - } - - } else { - $msg = sprintf( - $self->translate('%d objects have been defined'), - $stat->cnt_total - ); - } - - $extra = array(); - if ($stat->cnt_total !== $stat->cnt_object) { - - if ($stat->cnt_template > 0) { - $extra[] = sprintf( - $self->translate('%d of them are templates'), - $stat->cnt_template - ); - } - if ($stat->cnt_external > 0) { - $extra[] = sprintf( - $self->translate('%d have been externally defined and will not be deployed'), - $stat->cnt_external - ); - } - } - - if (array_key_exists($type . 'group', $self->stats)) { - $groupstat = $self->stats[$type . 'group']; - if ((int) $groupstat->cnt_total === 0) { - $extra[] = $self->translate('no related group exists'); - } elseif ((int) $groupstat->cnt_total === 1) { - $extra[] = $self->translate('one related group exists'); - } else { - $extra[] = sprintf( - $self->translate('%s related group objects have been created'), - $groupstat->cnt_total - ); - } - } - - if (empty($extra)) { - return $msg; - } - - return $msg . ', ' . implode(', ', $extra); -} - -function pendingDeployments($self) { - if ($self->undeployedActivities === 0) return ''; - return '. ' . sprintf( - $self->translate('A total of %d config changes happened since your last deployed config has been rendered'), - $self->undeployedActivities - ) . ''; -} -if (!$this->hasDeploymentEndpoint) { - echo $this->form; -} - -if ($this->migrationsForm) { - echo '

' . $this->translate('There are pending database schema migrations') . "

\n"; - echo $this->migrationsForm; -} - -$all = array( - $this->translate('Define whatever you want to be monitored') => array( - array('host', $this->translate('Host objects'), 'director/hosts', statSummary($this, 'host')), - array('services', $this->translate('Monitored Services'), 'director/services', statSummary($this, 'service')), - array('wrench', $this->translate('Commands'), 'director/commands', statSummary($this, 'command')), - array('users', $this->translate('Users / Contacts'), 'director/users', statSummary($this, 'user')), - array('megaphone', $this->translate('Notifications'), 'director/notifications', $this->translate('Schedule your notifications.') . ' ' . statSummary($this, 'notification')), - array('calendar', $this->translate('Timeperiods'), 'director/timeperiods', statSummary($this, 'timeperiod')), - ), - $this->translate('Deploy configuration to your Icinga nodes') => array( - array('wrench', $this->translate('Deployment'), 'director/config/deployments', $this->translate('Config deployment') . pendingDeployments($this), $this->undeployedActivities ? 'warning' : 'ok'), - array('book', $this->translate('Activity Log'), 'director/config/activities', $this->translate('Wondering about what changed why? Track your changes!'), 'ok'), - array('lock-open-alt', $this->translate('Api users'), 'director/apiusers', statSummary($this, 'apiuser')), - array('cloud', $this->translate('Endpoints'), 'director/endpoints', statSummary($this, 'endpoint') . ( $this->hasDeploymentEndpoint ? '' : '. ' . $this->translate('None could be used for deployments right now')), $this->hasDeploymentEndpoint ? '' : 'critical'), - array('globe', $this->translate('Zones'), 'director/zones', statSummary($this, 'zone')), - ), - $this->translate('Do more with your data') => array( - array('database', $this->translate('Import data sources'), 'director/list/importsource', $this->translate('Define and manage imports from various data sources'), $this->importState), - array('flapping', $this->translate('Synchronize'), 'director/list/syncrule', $this->translate('Define how imported data should be synchronized with Icinga'), $this->syncState), - array('clock', $this->translate('Jobs'), 'director/jobs', $this->translate('Schedule and automate Import, Syncronization, Config Deployment, Housekeeping and more'), $this->jobState), - array('edit', $this->translate('Define data fields'), 'director/data/fields', $this->translate('Data fields make sure that configuration fits your rules')), - array('sort-name-up', $this->translate('Provide data lists'), 'director/data/lists', $this->translate('Provide data lists to make life easier for your users')), - ) -); -?> - - $actions): ?> -partial( - 'index/actions.phtml', - array( - 'actions' => $actions, - 'title' => $title, - ) -) ?> - - diff --git a/library/Director/Acl.php b/library/Director/Acl.php new file mode 100644 index 00000000..05c4d1aa --- /dev/null +++ b/library/Director/Acl.php @@ -0,0 +1,54 @@ +auth = $auth; + } + + public function hasPermission($name) + { + return $this->auth->hasPermission($name); + } + + protected function getUser() + { + if (null === ($user = $this->auth->getUser())) { + throw new AuthenticationException('Authenticated user required'); + } + + return $user; + } + + public function listRoleNames() + { + return array_map( + array($this, 'getNameForRole'), + $this->getUser()->getRoles() + ); + } + + protected function getNameForRole($role) + { + return $role->getName(); + } +} diff --git a/library/Director/Dashboard/Dashboard.php b/library/Director/Dashboard/Dashboard.php new file mode 100644 index 00000000..2f90b964 --- /dev/null +++ b/library/Director/Dashboard/Dashboard.php @@ -0,0 +1,163 @@ +db = $db; + $dashboard->name = $name; + $dashboard->view = $view; + return $dashboard; + } + + + public function getName() + { + return $this->name; + } + + abstract public function getTitle(); + + abstract public function getDescription(); + + public function count() + { + return count($this->dashlets()); + } + + public function isAvailable() + { + return $this->count() > 0; + } + + public function getDb() + { + return $this->db; + } + + public function getView() + { + return $this->view; + } + + protected function translate($msg) + { + return $this->view->translate($msg); + } + + public function dashlets() + { + if ($this->dashlets === null) { + if ($this->dashletNames === null) { + $this->dashlets = Dashlet::loadAll($this); + } else { + $this->dashlets = Dashlet::loadByNames( + $this->dashletNames, + $this + ); + } + $this->fetchDashletSummaries(); + } + + return $this->dashlets; + } + + protected function fetchDashletSummaries() + { + $types = array(); + foreach ($this->dashlets as $dashlet) { + foreach ($dashlet->listRequiredStats() as $objectType) { + $types[$objectType] = $objectType; + } + } + + if (empty($types)) { + return; + } + + try { + $stats = $this->getObjectSummary($types); + } catch (Exception $e) { + $stats = array(); + } + + $failing = array(); + foreach ($this->dashlets as $key => $dashlet) { + foreach ($dashlet->listRequiredStats() as $objectType) { + if (array_key_exists($objectType, $stats)) { + $dashlet->addStats($objectType, $stats[$objectType]); + } else { + $failing[] = $key; + } + } + } + + foreach ($failing as $key) { + unset($this->dashlets[$key]); + } + } + + public function getObjectSummary($types) + { + $queries = array(); + + foreach ($types as $type) { + $queries[] = $this->makeSummaryQuery($type); + } + + $query = $this->db->select()->union($queries, ZfSelect::SQL_UNION_ALL); + $result = array(); + foreach ($this->db->fetchAll($query) as $row) { + $result[$row->icinga_type] = $row; + } + + return $result; + } + + protected function makeSummaryQuery($type) + { + return $this->db->select()->from( + array('o' => 'icinga_' . $type), + array( + 'icinga_type' => "('" . $type . "')", + 'cnt_object' => $this->getCntSql('object'), + 'cnt_template' => $this->getCntSql('template'), + 'cnt_external' => $this->getCntSql('external_object'), + 'cnt_total' => 'COUNT(*)', + ) + ); + } + + protected function getCntSql($objectType) + { + return sprintf( + "COALESCE(SUM(CASE WHEN o.object_type = '%s' THEN 1 ELSE 0 END), 0)", + $objectType + ); + } +} diff --git a/library/Director/Dashboard/Dashlet/ActivityLogDashlet.php b/library/Director/Dashboard/Dashlet/ActivityLogDashlet.php new file mode 100644 index 00000000..05265116 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/ActivityLogDashlet.php @@ -0,0 +1,35 @@ +translate('Activity Log'); + } + + public function getEscapedSummary() + { + return $this->translate( + 'Wondering about what changed why? Track your changes!' + ); + } + + public function listCssClasses() + { + return 'state-ok'; + } + + public function getUrl() + { + return 'director/config/activities'; + } + + public function listRequiredPermissions() + { + return array('director/activitylog'); + } +} diff --git a/library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php b/library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php new file mode 100644 index 00000000..8fe78a68 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php @@ -0,0 +1,20 @@ +translate('Api users'); + } + + public function getUrl() + { + return 'director/apiusers'; + } +} diff --git a/library/Director/Dashboard/Dashlet/CommandObjectDashlet.php b/library/Director/Dashboard/Dashlet/CommandObjectDashlet.php new file mode 100644 index 00000000..1243424a --- /dev/null +++ b/library/Director/Dashboard/Dashlet/CommandObjectDashlet.php @@ -0,0 +1,20 @@ +view->translate('Commands'); + } + + public function getUrl() + { + return 'director/commands'; + } +} diff --git a/library/Director/Dashboard/Dashlet/Dashlet.php b/library/Director/Dashboard/Dashlet/Dashlet.php new file mode 100644 index 00000000..56c71a43 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/Dashlet.php @@ -0,0 +1,239 @@ +view = $dashboard->getView(); + $this->db = $dashboard->getDb(); + } + + public function listRequiredStats() + { + return $this->requiredStats; + } + + public function addStats($type, $stats) + { + $this->stats[$type] = $stats; + } + + public static function loadAll(Dashboard $dashboard) + { + $dashlets = array(); + + foreach (new DirectoryIterator(__DIR__) as $file) { + if ($file->isDot()) { + continue; + } + $filename = $file->getFilename(); + if (preg_match('/^(\w+)Dashlet\.php$/', $filename, $match)) { + $dashlet = static::loadByName($match[1], $dashboard); + + if ($dashlet->isAllowed()) { + $dashlets[] = $dashlet; + } + } + } + + return $dashlets; + } + + public static function loadByName($name, Dashboard $dashboard) + { + $class = __NAMESPACE__ . '\\' . $name . 'Dashlet'; + return new $class($dashboard); + } + + public static function loadByNames(array $names, Dashboard $dashboard) + { + $prefix = __NAMESPACE__ . '\\'; + $dashlets = array(); + foreach ($names as $name) { + $class = $prefix . $name . 'Dashlet'; + $dashlet = new $class($dashboard); + + if ($dashlet->isAllowed()) { + $dashlets[] = $dashlet; + } + } + + return $dashlets; + } + + public function renderClassAttribute() + { + $classes = $this->listCssClasses(); + if (empty($classes)) { + return ''; + } else { + if (! is_array($classes)) { + $classes = array($classes); + } + + return ' class="' . implode(' ', $classes) . '"'; + } + } + + public function listCssClasses() + { + return array(); + } + + public function getSectionName() + { + if ($this->sectionName === null) { + throw new ProgrammingError( + 'Dashlets without a sectionName are not allowed' + ); + } + + return $this->sectionName; + } + + public function getIconName() + { + return $this->icon; + } + + public function render() + { + return $this->view->partial( + 'dashlets/' . $this->getViewScript() . '.phtml', + array('dashlet' => $this) + ); + } + + public function listRequiredPermissions() + { + return array($this->getUrl()); + } + + public function getViewScript() + { + return 'default'; + } + + public function isAllowed() + { + $acl = Acl::instance(); + foreach ($this->listRequiredPermissions() as $perm) { + if (! $acl->hasPermission($perm)) { + return false; + } + } + + return true; + } + + public function getSummary() + { + $result = ''; + if (! empty($this->requiredStats)) { + $result .= $this->statSummary(current($this->requiredStats)); + } + + return $result; + } + + public function getEscapedSummary() + { + return $this->view->escape( + $this->getSummary() + ); + } + + protected function translate($msg) + { + return $this->view->translate($msg); + } + + protected function statSummary($type) + { + $view = $this->view; + $stat = $this->stats[$type]; + + if ((int) $stat->cnt_total === 0) { + return $view->translate('No object has been defined yet'); + } + + if ((int) $stat->cnt_total === 1) { + if ($stat->cnt_template > 0) { + $msg = $view->translate('One template has been defined'); + } elseif ($stat->cnt_external > 0) { + $msg = $view->translate( + 'One external object has been defined, it will not be deployed' + ); + } else { + $msg = $view->translate('One object has been defined'); + } + + } else { + $msg = sprintf( + $view->translate('%d objects have been defined'), + $stat->cnt_total + ); + } + + $extra = array(); + if ($stat->cnt_total !== $stat->cnt_object) { + + if ($stat->cnt_template > 0) { + $extra[] = sprintf( + $view->translate('%d of them are templates'), + $stat->cnt_template + ); + } + if ($stat->cnt_external > 0) { + $extra[] = sprintf( + $view->translate( + '%d have been externally defined and will not be deployed' + ), + $stat->cnt_external + ); + } + } + + if (array_key_exists($type . 'group', $this->stats)) { + $groupstat = $this->stats[$type . 'group']; + if ((int) $groupstat->cnt_total === 0) { + $extra[] = $view->translate('no related group exists'); + } elseif ((int) $groupstat->cnt_total === 1) { + $extra[] = $view->translate('one related group exists'); + } else { + $extra[] = sprintf( + $view->translate('%s related group objects have been created'), + $groupstat->cnt_total + ); + } + } + + if (empty($extra)) { + return $msg; + } + + return $msg . ', ' . implode(', ', $extra); + } +} diff --git a/library/Director/Dashboard/Dashlet/DatafieldDashlet.php b/library/Director/Dashboard/Dashlet/DatafieldDashlet.php new file mode 100644 index 00000000..48d08255 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/DatafieldDashlet.php @@ -0,0 +1,30 @@ +translate('Define data fields'); + } + + public function getEscapedSummary() + { + return $this->translate( + 'Data fields make sure that configuration fits your rules' + ); + } + + public function getUrl() + { + return 'director/data/fields'; + } + + public function listRequiredPermissions() + { + return array('director/data'); + } +} diff --git a/library/Director/Dashboard/Dashlet/DatalistDashlet.php b/library/Director/Dashboard/Dashlet/DatalistDashlet.php new file mode 100644 index 00000000..8170851d --- /dev/null +++ b/library/Director/Dashboard/Dashlet/DatalistDashlet.php @@ -0,0 +1,30 @@ +translate('Provide data lists'); + } + + public function getEscapedSummary() + { + return $this->translate( + 'Provide data lists to make life easier for your users' + ); + } + + public function getUrl() + { + return 'director/data/lists'; + } + + public function listRequiredPermissions() + { + return array('director/data'); + } +} diff --git a/library/Director/Dashboard/Dashlet/DeploymentDashlet.php b/library/Director/Dashboard/Dashlet/DeploymentDashlet.php new file mode 100644 index 00000000..e01a0093 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/DeploymentDashlet.php @@ -0,0 +1,117 @@ +translate('Config Deployment'); + } + + public function hasUndeployedActivities() + { + return $this->undeployedActivities() > 0; + } + + public function undeployedActivities() + { + if ($this->undeployedActivities === null) { + try { + $this->undeployedActivities = $this->db + ->countActivitiesSinceLastDeployedConfig(); + } catch (Exception $e) { + $this->undeployedActivities = 0; + } + } + + return $this->undeployedActivities; + } + + public function lastDeploymentFailed() + { + return ! $this->lastDeployment()->succeeded(); + } + + public function lastDeploymentPending() + { + return $this->lastDeployment()->isPending(); + } + + public function listCssClasses() + { + try { + if ($this->lastDeploymentFailed()) { + return array('state-critical'); + } elseif ($this->lastDeploymentPending()) { + return array('state-pending'); + } elseif ($this->hasUndeployedActivities()) { + return array('state-warning'); + } else { + return array('state-ok'); + } + } catch (Exception $e) { + return null; + } + } + + protected function lastDeployment() + { + if ($this->lastDeployment === null) { + $this->lastDeployment = DirectorDeploymentLog::loadLatest($this->db); + } + + return $this->lastDeployment; + } + + public function getSummary() + { + $msgs = array(); + $cnt = $this->undeployedActivities(); + + try { + if ($this->lastDeploymentFailed()) { + $msgs[] = $this->translate('The last deployment did not succeed'); + } elseif ($this->lastDeploymentPending()) { + $msgs[] = $this->translate('The last deployment is currently pending'); + } + } catch (Exception $e) { + } + + if ($cnt === 0) { + $msgs[] = $this->translate('There are no pending changes'); + } else { + + $msgs[] = sprintf( + $this->translate( + 'A total of %d config changes happened since your last' + . ' deployed config has been rendered' + ), + $cnt + ); + } + + return implode('. ', $msgs) . '.'; + } + + public function getUrl() + { + return 'director/config/deployments'; + } + + public function listRequiredPermissions() + { + return array('director/deploy'); + } +} diff --git a/library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php b/library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php new file mode 100644 index 00000000..2a00d796 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php @@ -0,0 +1,56 @@ +translate('Endpoints'); + } + + public function getUrl() + { + return 'director/endpoints'; + } + + protected function hasDeploymentEndpoint() + { + if ($this->hasDeploymentEndpoint === null) { + try { + $this->hasDeploymentEndpoint = $this->db->hasDeploymentEndpoint(); + } catch (Exception $e) { + return false; + } + } + + return $this->hasDeploymentEndpoint; + } + + public function listCssClasses() + { + if (! $this->hasDeploymentEndpoint()) { + return 'state-critical'; + } + } + + public function getSummary() + { + $msg = parent::getSummary(); + if (! $this->hasDeploymentEndpoint()) { + $msg .= '. ' . $this->translate( + 'None could be used for deployments right now' + ); + } + + return $msg; + } +} diff --git a/library/Director/Dashboard/Dashlet/HostObjectDashlet.php b/library/Director/Dashboard/Dashlet/HostObjectDashlet.php new file mode 100644 index 00000000..de7d7e90 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/HostObjectDashlet.php @@ -0,0 +1,23 @@ +translate('Host objects'); + } + + public function getUrl() + { + return 'director/hosts'; + } +} diff --git a/library/Director/Dashboard/Dashlet/ImportSourceDashlet.php b/library/Director/Dashboard/Dashlet/ImportSourceDashlet.php new file mode 100644 index 00000000..4901a888 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/ImportSourceDashlet.php @@ -0,0 +1,65 @@ +translate('Import data sources'); + } + + public function listCssClasses() + { + try { + return $this->fetchStateClass(); + } catch (Exception $e) { + return 'state-critical'; + } + } + + public function getSummary() + { + return $this->translate( + 'Define and manage imports from various data sources' + ); + } + + protected function fetchStateClass() + { + $srcs = ImportSource::loadAll($this->db); + if (count($srcs) > 0) { + $state = 'state-ok'; + } else { + $state = null; + } + + foreach ($srcs as $src) { + if ($src->import_state !== 'in-sync') { + if ($src->import_state === 'failing') { + $state = 'state-critical'; + break; + } else { + $state = 'state-warning'; + } + } + } + + return $state; + } + + public function getUrl() + { + return 'director/list/importsource'; + } + + public function listRequiredPermissions() + { + return array('director/sync'); + } +} diff --git a/library/Director/Dashboard/Dashlet/JobDashlet.php b/library/Director/Dashboard/Dashlet/JobDashlet.php new file mode 100644 index 00000000..730c957d --- /dev/null +++ b/library/Director/Dashboard/Dashlet/JobDashlet.php @@ -0,0 +1,59 @@ +translate('Jobs'); + } + + public function listCssClasses() + { + try { + return $this->fetchStateClass(); + } catch (Exception $e) { + return 'state-critical'; + } + } + + public function getSummary() + { + return $this->translate( + 'Schedule and automate Import, Syncronization, Config Deployment,' + . ' Housekeeping and more' + ); + } + + protected function fetchStateClass() + { + $jobs = DirectorJob::loadAll($this->db); + if (count($jobs) > 0) { + $state = 'state-ok'; + } else { + $state = null; + } + + foreach ($jobs as $job) { + if ($job->isPending()) { + $state = 'state-pending'; + } elseif (! $job->lastAttemptSucceeded()) { + $state = 'state-critical'; + break; + } + } + + return $state; + } + + public function getUrl() + { + return 'director/jobs'; + } +} diff --git a/library/Director/Dashboard/Dashlet/NotificationObjectDashlet.php b/library/Director/Dashboard/Dashlet/NotificationObjectDashlet.php new file mode 100644 index 00000000..5613f600 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/NotificationObjectDashlet.php @@ -0,0 +1,29 @@ +translate('Notifications.'); + } + + public function getSummary() + { + return $this->translate('Schedule your notifications.') + . ' ' . parent::getSummary(); + } + + public function getUrl() + { + return 'director/notifications'; + } +} diff --git a/library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php b/library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php new file mode 100644 index 00000000..63cdb8af --- /dev/null +++ b/library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php @@ -0,0 +1,23 @@ +translate('Monitored Services'); + } + + public function getUrl() + { + return 'director/services'; + } +} diff --git a/library/Director/Dashboard/Dashlet/SyncDashlet.php b/library/Director/Dashboard/Dashlet/SyncDashlet.php new file mode 100644 index 00000000..5288a928 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/SyncDashlet.php @@ -0,0 +1,65 @@ +translate('Synchronize'); + } + + public function listCssClasses() + { + try { + return $this->fetchStateClass(); + } catch (Exception $e) { + return 'state-critical'; + } + } + + public function getSummary() + { + return $this->translate( + 'Define how imported data should be synchronized with Icinga' + ); + } + + protected function fetchStateClass() + { + $syncs = SyncRule::loadAll($this->db); + if (count($syncs) > 0) { + $state = 'state-ok'; + } else { + $state = null; + } + + foreach ($syncs as $sync) { + if ($sync->sync_state !== 'in-sync') { + if ($sync->sync_state === 'failing') { + $state = 'state-critical'; + break; + } else { + $state = 'state-warning'; + } + } + } + + return $state; + } + + public function getUrl() + { + return 'director/list/syncrule'; + } + + public function listRequiredPermissions() + { + return array('director/sync'); + } +} diff --git a/library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php b/library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php new file mode 100644 index 00000000..44191296 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php @@ -0,0 +1,23 @@ +translate('Timeperiods'); + } + + public function getUrl() + { + return 'director/timeperiods'; + } +} diff --git a/library/Director/Dashboard/Dashlet/UserObjectDashlet.php b/library/Director/Dashboard/Dashlet/UserObjectDashlet.php new file mode 100644 index 00000000..463b84cc --- /dev/null +++ b/library/Director/Dashboard/Dashlet/UserObjectDashlet.php @@ -0,0 +1,23 @@ +translate('Users / Contacts'); + } + + public function getUrl() + { + return 'director/users'; + } +} diff --git a/library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php b/library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php new file mode 100644 index 00000000..f3cfe224 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php @@ -0,0 +1,20 @@ +translate('Zones'); + } + + public function getUrl() + { + return 'director/zones'; + } +} diff --git a/library/Director/Dashboard/DataDashboard.php b/library/Director/Dashboard/DataDashboard.php new file mode 100644 index 00000000..2968d51e --- /dev/null +++ b/library/Director/Dashboard/DataDashboard.php @@ -0,0 +1,28 @@ +translate('Do more with your data'); + } + + public function getDescription() + { + return $this->translate('...'); + } +} diff --git a/library/Director/Dashboard/DeploymentDashboard.php b/library/Director/Dashboard/DeploymentDashboard.php new file mode 100644 index 00000000..ecca721d --- /dev/null +++ b/library/Director/Dashboard/DeploymentDashboard.php @@ -0,0 +1,28 @@ +translate('Deploy configuration to your Icinga nodes'); + } + + public function getDescription() + { + return $this->translate('...'); + } +} diff --git a/library/Director/Dashboard/ObjectsDashboard.php b/library/Director/Dashboard/ObjectsDashboard.php new file mode 100644 index 00000000..b4c9bcb0 --- /dev/null +++ b/library/Director/Dashboard/ObjectsDashboard.php @@ -0,0 +1,31 @@ +translate('Define whatever you want to be monitored'); + } + + public function getDescription() + { + return $this->translate('...'); + } +} diff --git a/library/Director/Objects/DirectorDeploymentLog.php b/library/Director/Objects/DirectorDeploymentLog.php index 3cbf6ff2..95791310 100644 --- a/library/Director/Objects/DirectorDeploymentLog.php +++ b/library/Director/Objects/DirectorDeploymentLog.php @@ -50,6 +50,16 @@ class DirectorDeploymentLog extends DbObject return $this->config; } + public function isPending() + { + return $this->dump_succeeded === 'y' && $this->startup_log === null; + } + + public function succeeded() + { + return $this->startup_succeeded === 'y'; + } + public function configEquals(IcingaConfig $config) { return $this->config_checksum === $config->getChecksum();