icingaweb2-module-director/library/Director/Web/Table/ActivityLogTable.php

296 lines
8.4 KiB
PHP
Raw Normal View History

<?php
namespace Icinga\Module\Director\Web\Table;
2021-12-06 23:03:06 +01:00
use gipfl\Format\LocalTimeFormat;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use Icinga\Module\Director\Auth\Permission;
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',
];
2021-12-06 23:03:06 +01:00
/** @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;
2021-12-06 23:03:06 +01:00
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(Permission::SHOW_CONFIG)) {
// 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;
}
}