Dashboard: replace it with a modular one

fixes #12997
This commit is contained in:
Thomas Gelf 2016-10-27 17:58:31 +00:00
parent 2b094067a5
commit 66e5158ccc
30 changed files with 1284 additions and 261 deletions

View File

@ -0,0 +1,34 @@
<?php
namespace Icinga\Module\Director\Controllers;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Acl;
use Icinga\Module\Director\Dashboard\Dashboard;
use Icinga\Module\Director\Web\Controller\ActionController;
class DashboardController extends ActionController
{
public function indexAction()
{
if ($this->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;
}
}

View File

@ -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();
}
} else {
$this->showKickstartForm();
}
try {
$this->fetchSyncState()
->fetchImportState()
->fetchJobState();
} catch (Exception $e) {
}
}
$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;
}
}

View File

@ -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()

View File

@ -0,0 +1,20 @@
<div class="controls">
<?= $this->tabs ?>
</div>
<div class="content">
<?php if ($this->errorMessage): ?>
<p class="error"><?= $this->errorMessage ?></p>
<?php endif ?>
<?= $this->form ?>
<?php foreach ($this->dashboards as $dashboard): ?>
<h1><?= $this->escape($dashboard->getTitle()) ?></h1>
<ul class="main-actions" data-base-target="_next">
<?php foreach ($dashboard->dashlets() as $dashlet): ?>
<li>
<?= $dashlet->render() ?>
</li>
<?php endforeach ?>
</ul>
<?php endforeach ?>
</div>

View File

@ -0,0 +1,5 @@
<a<?= $dashlet->renderClassAttribute()?> href="<?= $this->url($dashlet->getUrl()) ?>">
<?= $this->escape($dashlet->getTitle()) ?>
<?= $this->icon($this->dashlet->getIconName()) ?>
<p><?= $dashlet->getEscapedSummary() ?></p>
</a>

View File

@ -1,17 +0,0 @@
<h1 style="clear: both"><?= $this->escape($title) ?></h1>
<ul class="main-actions" data-base-target="_next">
<?php foreach ($actions as $a): ?>
<li>
<a<?php
if (array_key_exists(4, $a)) {
echo ' class="state-' . $a[4] . '"';
}
?> href="<?= $this->url($a[2]) ?>">
<?= $this->icon($a[0]) ?>
<?= $this->escape($a[1]) ?>
<p><?= $a[3] ?></p>
</a>
</li>
<?php endforeach ?>
</ul>

View File

@ -1,127 +0,0 @@
<div class="controls">
<?= $this->tabs ?>
</div>
<div class="content">
<?php if ($this->errorMessage): ?>
<p class="error"><?= $this->errorMessage ?></p>
<?php endif ?>
<?php if (! $this->stats): ?>
<?= $this->form ?>
</div>
<?php return; endif ?>
<?php
function statSummary($self, $type) {
$stat = $self->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 '. <span class="error">' . sprintf(
$self->translate('A total of %d config changes happened since your last deployed config has been rendered'),
$self->undeployedActivities
) . '</span>';
}
if (!$this->hasDeploymentEndpoint) {
echo $this->form;
}
if ($this->migrationsForm) {
echo '<h1>' . $this->translate('There are pending database schema migrations') . "</h2>\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')),
)
);
?>
<?php foreach ($all as $title => $actions): ?>
<?= $this->partial(
'index/actions.phtml',
array(
'actions' => $actions,
'title' => $title,
)
) ?>
<?php endforeach ?>
</div>

54
library/Director/Acl.php Normal file
View File

@ -0,0 +1,54 @@
<?php
namespace Icinga\Module\Director;
use Icinga\Authentication\Auth;
use Icinga\Exception\AuthenticationException;
class Acl
{
protected $auth;
private static $instance;
public static function instance()
{
if (self::$instance === null) {
self::$instance = new static(Auth::getInstance());
}
return self::$instance;
}
public function __construct(Auth $auth)
{
$this->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();
}
}

View File

@ -0,0 +1,163 @@
<?php
namespace Icinga\Module\Director\Dashboard;
use Countable;
use Exception;
use Icinga\Web\View;
use Icinga\Module\Director\Dashboard\Dashlet\Dashlet;
use Icinga\Module\Director\Db;
use Zend_Db_Select as ZfSelect;
abstract class Dashboard implements Countable
{
protected $name;
protected $dashlets;
protected $dashletNames;
protected $db;
protected $view;
final private function __construct()
{
}
public static function loadByName($name, Db $db, View $view)
{
$class = __NAMESPACE__ . '\\' . ucfirst($name) . 'Dashboard';
$dashboard = new $class();
$dashboard->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
);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
class ActivityLogDashlet extends Dashlet
{
protected $icon = 'book';
public function getTitle()
{
return $this->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');
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
class ApiUserObjectDashlet extends Dashlet
{
protected $icon = 'lock-open-alt';
protected $requiredStats = array('apiuser');
public function getTitle()
{
return $this->translate('Api users');
}
public function getUrl()
{
return 'director/apiusers';
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
class CommandObjectDashlet extends Dashlet
{
protected $icon = 'wrench';
protected $requiredStats = array('command');
public function getTitle()
{
return $this->view->translate('Commands');
}
public function getUrl()
{
return 'director/commands';
}
}

View File

@ -0,0 +1,239 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\View;
use Icinga\Module\Director\Acl;
use Icinga\Module\Director\Dashboard\Dashboard;
abstract class Dashlet
{
protected $sectionName;
protected $icon = 'help';
protected $supportsLegacyConfig;
protected $view;
protected $db;
protected $stats;
protected $requiredStats = array();
public function __construct(Dashboard $dashboard)
{
$this->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);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
class DatafieldDashlet extends Dashlet
{
protected $icon = 'edit';
public function getTitle()
{
return $this->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');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
class DatalistDashlet extends Dashlet
{
protected $icon = 'sort-name-up';
public function getTitle()
{
return $this->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');
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Exception;
use Icinga\Module\Director\Objects\DirectorDeploymentLog;
use Icinga\Exception\ProgrammingError;
class DeploymentDashlet extends Dashlet
{
protected $icon = 'wrench';
protected $undeployedActivities;
protected $lastDeployment;
public function getTitle()
{
return $this->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');
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
class EndpointObjectDashlet extends Dashlet
{
protected $icon = 'cloud';
protected $requiredStats = array('endpoint');
protected $hasDeploymentEndpoint;
public function getTitle()
{
return $this->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;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Icinga\Exception\ProgrammingError;
class HostObjectDashlet extends Dashlet
{
protected $icon = 'host';
protected $requiredStats = array('host', 'hostgroup');
public function getTitle()
{
return $this->translate('Host objects');
}
public function getUrl()
{
return 'director/hosts';
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
use Icinga\Module\Director\Objects\ImportSource;
class ImportSourceDashlet extends Dashlet
{
protected $icon = 'database';
public function getTitle()
{
return $this->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');
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
use Icinga\Module\Director\Objects\DirectorJob;
class JobDashlet extends Dashlet
{
protected $icon = 'clock';
public function getTitle()
{
return $this->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';
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Icinga\Exception\ProgrammingError;
class NotificationObjectDashlet extends Dashlet
{
protected $icon = 'megaphone';
protected $requiredStats = array('notification');
public function getTitle()
{
return $this->translate('Notifications.');
}
public function getSummary()
{
return $this->translate('Schedule your notifications.')
. ' ' . parent::getSummary();
}
public function getUrl()
{
return 'director/notifications';
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Icinga\Exception\ProgrammingError;
class ServiceObjectDashlet extends Dashlet
{
protected $icon = 'services';
protected $requiredStats = array('service', 'servicegroup');
public function getTitle()
{
return $this->translate('Monitored Services');
}
public function getUrl()
{
return 'director/services';
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
use Icinga\Module\Director\Objects\SyncRule;
class SyncDashlet extends Dashlet
{
protected $icon = 'flapping';
public function getTitle()
{
return $this->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');
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Icinga\Exception\ProgrammingError;
class TimeperiodObjectDashlet extends Dashlet
{
protected $icon = 'calendar';
protected $requiredStats = array('timeperiod');
public function getTitle()
{
return $this->translate('Timeperiods');
}
public function getUrl()
{
return 'director/timeperiods';
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Icinga\Exception\ProgrammingError;
class UserObjectDashlet extends Dashlet
{
protected $icon = 'users';
protected $requiredStats = array('user', 'usergroup');
public function getTitle()
{
return $this->translate('Users / Contacts');
}
public function getUrl()
{
return 'director/users';
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
class ZoneObjectDashlet extends Dashlet
{
protected $icon = 'globe';
protected $requiredStats = array('zone');
public function getTitle()
{
return $this->translate('Zones');
}
public function getUrl()
{
return 'director/zones';
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Icinga\Module\Director\Dashboard;
use Icinga\Module\Director\Dashboard\Dashlet\Dashlet;
class DataDashboard extends Dashboard
{
protected $name;
protected $dashletNames = array(
'ImportSource',
'Sync',
'Job',
'Datafield',
'Datalist',
);
public function getTitle()
{
return $this->translate('Do more with your data');
}
public function getDescription()
{
return $this->translate('...');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Icinga\Module\Director\Dashboard;
use Icinga\Module\Director\Dashboard\Dashlet\Dashlet;
class DeploymentDashboard extends Dashboard
{
protected $name;
protected $dashletNames = array(
'Deployment',
'ActivityLog',
'ApiUserObject',
'EndpointObject',
'ZoneObject',
);
public function getTitle()
{
return $this->translate('Deploy configuration to your Icinga nodes');
}
public function getDescription()
{
return $this->translate('...');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Icinga\Module\Director\Dashboard;
use Countable;
use Icinga\Module\Director\Dashboard\Dashlet\Dashlet;
class ObjectsDashboard extends Dashboard
{
protected $name;
protected $dashletNames = array(
'HostObject',
'ServiceObject',
'CommandObject',
'UserObject',
'NotificationObject',
'TimeperiodObject',
);
public function getTitle()
{
return $this->translate('Define whatever you want to be monitored');
}
public function getDescription()
{
return $this->translate('...');
}
}

View File

@ -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();