SVG;
const TOP_RIGHT_BUBBLE_FLAG = <<<'SVG'
SVG;
protected $object;
protected $tag = 'div';
protected $defaultAttributes = ['class' => ['progress-bar', 'check-statistics']];
public function __construct($object)
{
$this->object = $object;
}
protected function assembleBody(BaseHtmlElement $body)
{
$styleElement = (new StyleWithNonce())
->setModule('icingadb');
$hPadding = 10;
$durationScale = 80;
$checkInterval = $this->getCheckInterval();
$timeline = new HtmlElement('div', Attributes::create(['class' => ['check-timeline', 'timeline']]));
$above = new HtmlElement('ul', Attributes::create(['class' => 'above']));
$below = new HtmlElement('ul', Attributes::create(['class' => 'below']));
$progressBar = new HtmlElement('div', Attributes::create(['class' => 'bar']));
$overdueBar = null;
$now = time();
$executionTime = ($this->object->state->execution_time / 1000) + ($this->object->state->latency / 1000);
$nextCheckTime = $this->object->state->next_check !== null && ! $this->isChecksDisabled()
? $this->object->state->next_check->getTimestamp()
: null;
if ($this->object->state->is_overdue) {
$nextCheckTime = $this->object->state->next_update->getTimestamp();
$durationScale = 60;
$overdueBar = new HtmlElement(
'div',
Attributes::create(['class' => 'timeline-overlay']),
new HtmlElement('div', Attributes::create(['class' => 'now']))
);
$above->addHtml(new HtmlElement(
'li',
Attributes::create(['class' => 'now']),
new HtmlElement(
'div',
Attributes::create(['class' => 'bubble']),
new HtmlElement('strong', null, Text::create(t('Now')))
)
));
$this->getAttributes()->add('class', 'check-overdue');
} else {
$progressBar->addHtml(new HtmlElement('div', Attributes::create(['class' => 'now'])));
}
if ($nextCheckTime !== null && ! $this->object->state->is_overdue && $nextCheckTime < $now) {
// If the next check is already in the past but not overdue, it means the check is probably running.
// Icinga only updates the state once the check reports a result, that's why we have to simulate the
// execution start and end time, as well as the next check time.
$lastUpdateTime = $nextCheckTime;
$nextCheckTime = $this->object->state->next_update->getTimestamp() - $executionTime;
$executionEndTime = $lastUpdateTime + $executionTime;
} else {
$lastUpdateTime = $this->object->state->last_update !== null
? $this->object->state->last_update->getTimestamp() - $executionTime
: null;
$executionEndTime = $this->object->state->last_update !== null
? $this->object->state->last_update->getTimestamp()
: null;
}
if ($this->object->state->is_overdue) {
$leftNow = 100;
} elseif ($nextCheckTime === null) {
$leftNow = 0;
} elseif (! $this->object->state->is_reachable && time() - $executionEndTime > $checkInterval * 2) {
// We have no way of knowing whether the dependency pauses check scheduling.
// The only way to detect this, is to measure how old the last update is.
$nextCheckTime = null;
$leftNow = 0;
} elseif ($nextCheckTime - $lastUpdateTime <= 0) {
$leftNow = 0;
} else {
$leftNow = 100 * (1 - ($nextCheckTime - time()) / ($nextCheckTime - $lastUpdateTime));
if ($leftNow > 100) {
$leftNow = 100;
} elseif ($leftNow < 0) {
$leftNow = 0;
}
}
$styleElement->addFor($progressBar, ['width' => sprintf('%F%%', $leftNow)]);
$leftExecutionEnd = $nextCheckTime !== null && $nextCheckTime - $lastUpdateTime > 0 ? $durationScale * (
1 - ($nextCheckTime - $executionEndTime) / ($nextCheckTime - $lastUpdateTime)
) : 0;
$markerLast = new HtmlElement('div', Attributes::create([
'class' => ['highlighted', 'marker', 'left'],
'title' => $lastUpdateTime !== null ? DateFormatter::formatDateTime($lastUpdateTime) : null
]));
$markerNext = new HtmlElement('div', Attributes::create([
'class' => ['highlighted', 'marker', 'right'],
'title' => $nextCheckTime !== null ? DateFormatter::formatDateTime($nextCheckTime) : null
]));
$markerExecutionEnd = new HtmlElement('div', Attributes::create(['class' => ['highlighted', 'marker']]));
$styleElement->addFor($markerExecutionEnd, [
'left' => sprintf('%F%%', $hPadding + $leftExecutionEnd)
]);
$progress = new HtmlElement('div', Attributes::create([
'class' => ['progress', time() < $executionEndTime ? 'running' : null]
]), $progressBar);
if ($nextCheckTime !== null) {
$progress->addAttributes([
'data-animate-progress' => true,
'data-start-time' => $lastUpdateTime,
'data-end-time' => $nextCheckTime,
'data-switch-after' => $executionTime,
'data-switch-class' => 'running'
]);
}
$timeline->addHtml(
$progress,
$markerLast,
$markerExecutionEnd,
$markerNext
)->add($overdueBar);
$executionStart = new HtmlElement(
'li',
Attributes::create(['class' => 'left']),
new HtmlElement(
'div',
Attributes::create(['class' => ['bubble', 'upwards', 'top-right-aligned']]),
new VerticalKeyValue(
t('Execution Start'),
$lastUpdateTime ? new TimeAgo($lastUpdateTime) : t('PENDING')
),
HtmlString::create(self::TOP_RIGHT_BUBBLE_FLAG)
)
);
$executionEnd = new HtmlElement(
'li',
Attributes::create(['class' => 'positioned']),
new HtmlElement(
'div',
Attributes::create(['class' => ['bubble', 'upwards', 'top-left-aligned']]),
new VerticalKeyValue(
t('Execution End'),
$executionEndTime !== null
? ($executionEndTime > $now
? new TimeUntil($executionEndTime)
: new TimeAgo($executionEndTime))
: t('PENDING')
),
HtmlString::create(self::TOP_LEFT_BUBBLE_FLAG)
)
);
$styleElement->addFor($executionEnd, ['left' => sprintf('%F%%', $hPadding + $leftExecutionEnd)]);
$intervalLine = new HtmlElement(
'li',
Attributes::create(['class' => 'interval-line']),
new VerticalKeyValue(t('Interval'), Format::seconds($checkInterval))
);
$styleElement->addFor($intervalLine, [
'left' => sprintf('%F%%', $hPadding + $leftExecutionEnd),
'width' => sprintf('%F%%', $durationScale - $leftExecutionEnd)
]);
$executionLine = new HtmlElement(
'li',
Attributes::create(['class' => ['interval-line', 'execution-line']]),
new VerticalKeyValue(
sprintf('%s / %s', t('Execution Time'), t('Latency')),
FormattedString::create(
'%s / %s',
$this->object->state->execution_time !== null
? Format::seconds($this->object->state->execution_time / 1000)
: (new EmptyState(t('n. a.')))->setTag('span'),
$this->object->state->latency !== null
? Format::seconds($this->object->state->latency / 1000)
: (new EmptyState(t('n. a.')))->setTag('span')
)
)
);
$styleElement->addFor($executionLine, [
'left' => sprintf('%F%%', $hPadding),
'width' => sprintf('%F%%', $leftExecutionEnd)
]);
if ($executionEndTime !== null) {
$executionLine->addHtml(new HtmlElement('div', Attributes::create(['class' => 'start'])));
$executionLine->addHtml(new HtmlElement('div', Attributes::create(['class' => 'end'])));
}
if ($this->isChecksDisabled()) {
$nextCheckBubbleContent = new VerticalKeyValue(
t('Next Check'),
t('n.a')
);
$this->addAttributes(['class' => 'checks-disabled']);
} else {
$nextCheckBubbleContent = $this->object->state->is_overdue
? new VerticalKeyValue(t('Overdue'), new TimeSince($nextCheckTime))
: new VerticalKeyValue(
t('Next Check'),
$nextCheckTime !== null
? ($nextCheckTime > $now
? new TimeUntil($nextCheckTime)
: new TimeAgo($nextCheckTime))
: t('PENDING')
);
}
$nextCheck = new HtmlElement(
'li',
Attributes::create(['class' => 'right']),
new HtmlElement(
'div',
Attributes::create(['class' => ['bubble', 'upwards']]),
$nextCheckBubbleContent
)
);
$above->addHtml($executionLine);
$below->addHtml(
$executionStart,
$executionEnd,
$intervalLine,
$nextCheck
);
$body->addHtml($above, $timeline, $below, $styleElement);
}
/**
* Checks if both active and passive checks are disabled
*
* @return bool
*/
protected function isChecksDisabled(): bool
{
return ! ($this->object->active_checks_enabled || $this->object->passive_checks_enabled);
}
protected function assembleHeader(BaseHtmlElement $header)
{
$checkSource = (new EmptyState(t('n. a.')))->setTag('span');
if ($this->object->state->check_source) {
$checkSource = Text::create($this->object->state->check_source);
}
$header->addHtml(
new VerticalKeyValue(t('Command'), $this->object->checkcommand_name),
new VerticalKeyValue(
t('Scheduling Source'),
$this->object->state->scheduling_source ?? (new EmptyState(t('n. a.')))->setTag('span')
)
);
if ($this->object->timeperiod->id) {
$header->addHtml(new VerticalKeyValue(
t('Timeperiod'),
$this->object->timeperiod->display_name ?? $this->object->timeperiod->name
));
}
$header->addHtml(
new VerticalKeyValue(
t('Attempts'),
new CheckAttempt((int) $this->object->state->check_attempt, (int) $this->object->max_check_attempts)
),
new VerticalKeyValue(t('Check Source'), $checkSource)
);
}
/**
* Get the active `check_interval` OR `check_retry_interval`
*
* @return int
*/
protected function getCheckInterval(): int
{
if (! ($this->object->state->is_problem && $this->object->state->state_type === 'soft')) {
return $this->object->check_interval;
}
$delay = ($this->object->state->execution_time + $this->object->state->latency) / 1000 + 5;
$interval = $this->object->state->next_check->getTimestamp()
- $this->object->state->last_update->getTimestamp();
// In case passive check is used, the check_retry_interval has no effect.
// Since there is no flag in the database to check if the passive check was triggered.
// We have to manually check if the check_retry_interval matches the calculated interval.
if (
$this->object->check_retry_interval - $delay <= $interval
&& $this->object->check_retry_interval + $delay >= $interval
) {
return $this->object->check_retry_interval;
}
return $this->object->check_interval;
}
protected function assemble()
{
parent::assemble();
if ($this->isChecksDisabled()) {
$this->addHtml(new HtmlElement(
'div',
Attributes::create(['class' => 'checks-disabled-overlay']),
new HtmlElement(
'strong',
Attributes::create(['class' => 'notes']),
Text::create(t('active and passive checks are disabled'))
)
));
}
}
}