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; } }