summaryrefslogtreecommitdiffstats
path: root/library/Director/Web/Table/ActivityLogTable.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Director/Web/Table/ActivityLogTable.php294
1 files changed, 294 insertions, 0 deletions
diff --git a/library/Director/Web/Table/ActivityLogTable.php b/library/Director/Web/Table/ActivityLogTable.php
new file mode 100644
index 0000000..5460bc2
--- /dev/null
+++ b/library/Director/Web/Table/ActivityLogTable.php
@@ -0,0 +1,294 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Table;
+
+use gipfl\Format\LocalTimeFormat;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
+use Icinga\Module\Director\Util;
+use ipl\Html\Html;
+use ipl\Html\HtmlElement;
+
+class ActivityLogTable extends ZfQueryBasedTable
+{
+ protected $filters = [];
+
+ protected $lastDeployedId;
+
+ protected $extraParams = [];
+
+ protected $columnCount;
+
+ protected $hasObjectFilter = false;
+
+ protected $searchColumns = [
+ 'author',
+ 'object_name',
+ 'object_type',
+ ];
+
+ /** @var LocalTimeFormat */
+ protected $timeFormat;
+
+ protected $ranges = [];
+
+ /** @var ?object */
+ protected $currentRange = null;
+ /** @var ?HtmlElement */
+ protected $currentRangeCell = null;
+ /** @var int */
+ protected $rangeRows = 0;
+ protected $continueRange = false;
+ protected $currentRow;
+
+ public function __construct($db)
+ {
+ parent::__construct($db);
+ $this->timeFormat = new LocalTimeFormat();
+ }
+
+ public function assemble()
+ {
+ $this->getAttributes()->add('class', 'activity-log');
+ }
+
+ public function setLastDeployedId($id)
+ {
+ $this->lastDeployedId = $id;
+ return $this;
+ }
+
+ protected function fetchQueryRows()
+ {
+ $rows = parent::fetchQueryRows();
+ // Hint -> DESC, that's why they are inverted
+ if (empty($rows)) {
+ return $rows;
+ }
+ $last = $rows[0]->id;
+ $first = $rows[count($rows) - 1]->id;
+ $db = $this->db();
+ $this->ranges = $db->fetchAll(
+ $db->select()
+ ->from('director_activity_log_remark')
+ ->where('first_related_activity <= ?', $last)
+ ->where('last_related_activity >= ?', $first)
+ );
+
+ return $rows;
+ }
+
+
+ public function renderRow($row)
+ {
+ $this->currentRow = $row;
+ $this->splitByDay($row->ts_change_time);
+ $action = 'action-' . $row->action. ' ';
+ if ($row->id > $this->lastDeployedId) {
+ $action .= 'undeployed';
+ } else {
+ $action .= 'deployed';
+ }
+
+ $columns = [
+ $this::td($this->makeLink($row))->setSeparator(' '),
+ ];
+ if (! $this->hasObjectFilter) {
+ $columns[] = $this->makeRangeInfo($row->id);
+ }
+ $columns[] = $this::td($this->timeFormat->getTime($row->ts_change_time));
+
+ return $this::tr($columns)->addAttributes(['class' => $action]);
+ }
+
+ /**
+ * Hint: cloned from parent class and modified
+ * @param int $timestamp
+ */
+ protected function renderDayIfNew($timestamp)
+ {
+ $day = $this->getDateFormatter()->getFullDay($timestamp);
+
+ if ($this->lastDay !== $day) {
+ $this->nextHeader()->add(
+ $this::th($day, [
+ 'colspan' => $this->hasObjectFilter ? 2 : 3,
+ 'class' => 'table-header-day'
+ ])
+ );
+
+ $this->lastDay = $day;
+ if ($this->currentRangeCell) {
+ if ($this->currentRange->first_related_activity <= $this->currentRow->id) {
+ $this->currentRangeCell->addAttributes(['class' => 'continuing']);
+ $this->continueRange = true;
+ } else {
+ $this->continueRange = false;
+ }
+ }
+ $this->currentRangeCell = null;
+ $this->currentRange = null;
+ $this->rangeRows = 0;
+ $this->nextBody();
+ }
+ }
+
+ protected function makeRangeInfo($id)
+ {
+ $range = $this->getRangeForId($id);
+ if ($range === null) {
+ if ($this->currentRangeCell) {
+ $this->currentRangeCell->getAttributes()->remove('class', 'continuing');
+ }
+ $this->currentRange = null;
+ $this->currentRangeCell = null;
+ $this->rangeRows = 0;
+ return $this::td();
+ }
+
+ if ($range === $this->currentRange) {
+ $this->growCurrentRange();
+ return null;
+ }
+ $this->startRange($range);
+
+ return $this->currentRangeCell;
+ }
+
+ protected function startRange($range)
+ {
+ $this->currentRangeCell = $this::td($this->renderRangeComment($range), [
+ 'colspan' => $this->rangeRows = 1,
+ 'class' => 'comment-cell'
+ ]);
+ if ($this->continueRange) {
+ $this->currentRangeCell->addAttributes(['class' => 'continued']);
+ $this->continueRange = false;
+ }
+ $this->currentRange = $range;
+ }
+
+ protected function renderRangeComment($range)
+ {
+ // The only purpose of this container is to avoid hovered rows from influencing
+ // the comments background color, as we're using the alpha channel to lighten it
+ // This can be replaced once we get theme-safe colors for such messages
+ return Html::tag('div', [
+ 'class' => 'range-comment-container',
+ ], Link::create($this->continueRange ? '' : $range->remark, '#', null, [
+ 'title' => $range->remark,
+ 'class' => 'range-comment'
+ ]));
+ }
+
+ protected function growCurrentRange()
+ {
+ $this->rangeRows++;
+ $this->currentRangeCell->setAttribute('rowspan', $this->rangeRows);
+ }
+
+ protected function getRangeForId($id)
+ {
+ foreach ($this->ranges as $range) {
+ if ($id >= $range->first_related_activity && $id <= $range->last_related_activity) {
+ return $range;
+ }
+ }
+
+ return null;
+ }
+
+ protected function makeLink($row)
+ {
+ $type = $row->object_type;
+ $name = $row->object_name;
+ if (substr($type, 0, 7) === 'icinga_') {
+ $type = substr($type, 7);
+ }
+
+ if (Util::hasPermission('director/showconfig')) {
+ // Later on replacing, service_set -> serviceset
+
+ // multi column key :(
+ if ($type === 'service' || $this->hasObjectFilter) {
+ $object = "\"$name\"";
+ } else {
+ $object = Link::create(
+ "\"$name\"",
+ 'director/' . str_replace('_', '', $type),
+ ['name' => $name],
+ ['title' => $this->translate('Jump to this object')]
+ );
+ }
+
+ return [
+ '[' . $row->author . ']',
+ Link::create(
+ $row->action,
+ 'director/config/activity',
+ array_merge(['id' => $row->id], $this->extraParams),
+ ['title' => $this->translate('Show details related to this change')]
+ ),
+ str_replace('_', ' ', $type),
+ $object
+ ];
+ } else {
+ return sprintf(
+ '[%s] %s %s "%s"',
+ $row->author,
+ $row->action,
+ $type,
+ $name
+ );
+ }
+ }
+
+ public function filterObject($type, $name)
+ {
+ $this->hasObjectFilter = true;
+ $this->filters[] = ['l.object_type = ?', $type];
+ $this->filters[] = ['l.object_name = ?', $name];
+
+ return $this;
+ }
+
+ public function filterHost($name)
+ {
+ $db = $this->db();
+ $filter = '%"host":' . json_encode($name) . '%';
+ $this->filters[] = ['('
+ . $db->quoteInto('l.old_properties LIKE ?', $filter)
+ . ' OR '
+ . $db->quoteInto('l.new_properties LIKE ?', $filter)
+ . ')', null];
+
+ return $this;
+ }
+
+ public function getColumns()
+ {
+ return [
+ 'author' => 'l.author',
+ 'action' => 'l.action_name',
+ 'object_name' => 'l.object_name',
+ 'object_type' => 'l.object_type',
+ 'id' => 'l.id',
+ 'change_time' => 'l.change_time',
+ 'ts_change_time' => 'UNIX_TIMESTAMP(l.change_time)',
+ ];
+ }
+
+ public function prepareQuery()
+ {
+ $query = $this->db()->select()->from(
+ ['l' => 'director_activity_log'],
+ $this->getColumns()
+ )->order('change_time DESC')->order('id DESC')->limit(100);
+
+ foreach ($this->filters as $filter) {
+ $query->where($filter[0], $filter[1]);
+ }
+
+ return $query;
+ }
+}