mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-05-28 04:36:06 -04:00
CheckStatistics: Make progress animatable and enhance overall layout
This commit is contained in:
parent
5bbf2932d3
commit
515ae13e32
3 changed files with 428 additions and 223 deletions
|
|
@ -8,10 +8,13 @@ use Icinga\Date\DateFormatter;
|
|||
use Icinga\Module\Icingadb\Widget\CheckAttempt;
|
||||
use Icinga\Module\Icingadb\Widget\EmptyState;
|
||||
use Icinga\Util\Format;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\FormattedString;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\HtmlString;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Common\Card;
|
||||
use ipl\Web\Widget\HorizontalKeyValue;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
use ipl\Web\Widget\TimeAgo;
|
||||
use ipl\Web\Widget\TimeSince;
|
||||
|
|
@ -20,11 +23,28 @@ use ipl\Web\Widget\VerticalKeyValue;
|
|||
|
||||
class CheckStatistics extends Card
|
||||
{
|
||||
const TOP_LEFT_BUBBLE_FLAG = <<<'SVG'
|
||||
<svg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path class='bg' d='M0 0L13 13L3.15334e-06 13L0 0Z'/>
|
||||
<path class='border' fill-rule='evenodd' clip-rule='evenodd'
|
||||
d='M0 0L3.3959e-06 14L14 14L0 0ZM1 2.41421L1 13L11.5858 13L1 2.41421Z'/>
|
||||
</svg>
|
||||
SVG;
|
||||
|
||||
const TOP_RIGHT_BUBBLE_FLAG = <<<'SVG'
|
||||
<svg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path class='bg' d="M12 0L-1 13L12 13L12 0Z"/>
|
||||
<path class='border' fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M12 0L12 14L-2 14L12 0ZM11 2.41421L11 13L0.414213 13L11 2.41421Z"/>
|
||||
</svg>
|
||||
SVG;
|
||||
|
||||
|
||||
protected $object;
|
||||
|
||||
protected $tag = 'div';
|
||||
|
||||
protected $defaultAttributes = ['class' => 'progress-bar check-statistics'];
|
||||
protected $defaultAttributes = ['class' => ['progress-bar', 'check-statistics']];
|
||||
|
||||
public function __construct($object)
|
||||
{
|
||||
|
|
@ -35,96 +55,155 @@ class CheckStatistics extends Card
|
|||
{
|
||||
$hPadding = 10;
|
||||
$durationScale = 80;
|
||||
$checkInterval = $this->getCheckInterval();
|
||||
|
||||
$timeline = Html::tag('div', ['class' => 'check-timeline timeline']);
|
||||
|
||||
$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;
|
||||
|
||||
$nextCheckTime = $this->object->state->next_check->getTimestamp();
|
||||
$checkInterval = $this->getCheckInterval();
|
||||
$now = time();
|
||||
$executionTime = ($this->object->state->execution_time / 1000) + ($this->object->state->latency / 1000);
|
||||
|
||||
$nextCheckTime = $this->object->state->next_check !== null
|
||||
? $this->object->state->next_check->getTimestamp()
|
||||
: null;
|
||||
if ($this->object->state->is_overdue) {
|
||||
$nextCheckTime = $this->object->state->next_update->getTimestamp();
|
||||
$leftNow = $durationScale + $hPadding / 2;
|
||||
|
||||
$overdueScale = ($durationScale / 2) * (time() - $nextCheckTime) / (10 * $checkInterval);
|
||||
if ($overdueScale > $durationScale / 2) {
|
||||
$overdueScale = $durationScale / 2;
|
||||
}
|
||||
$durationScale = 60;
|
||||
|
||||
$durationScale -= $overdueScale;
|
||||
$overdueBar = Html::tag('div', [
|
||||
'class' => 'timeline-overlay check-overdue',
|
||||
'style' => sprintf(
|
||||
'left: %F%%; width: %F%%;',
|
||||
$hPadding + $durationScale,
|
||||
$overdueScale + $hPadding / 2
|
||||
$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 {
|
||||
$leftNow = $durationScale * (1 - ($nextCheckTime - time()) / $checkInterval);
|
||||
if ($leftNow > $durationScale) {
|
||||
$leftNow = $durationScale;
|
||||
$progressBar->addHtml(new HtmlElement('div', Attributes::create(['class' => 'now'])));
|
||||
}
|
||||
|
||||
if ($nextCheckTime !== null && $nextCheckTime < $now) {
|
||||
$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;
|
||||
} else {
|
||||
$leftNow = 100 * (1 - ($nextCheckTime - time()) / ($nextCheckTime - $lastUpdateTime));
|
||||
if ($leftNow > 100) {
|
||||
$leftNow = 100;
|
||||
} elseif ($leftNow < 0) {
|
||||
$leftNow = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$above = Html::tag('ul', ['class' => 'above']);
|
||||
$now = Html::tag(
|
||||
'li',
|
||||
[
|
||||
'class' => 'now positioned',
|
||||
'style' => sprintf('left: %F%%', $hPadding + $leftNow)
|
||||
],
|
||||
Html::tag(
|
||||
'div',
|
||||
['class' => 'bubble'],
|
||||
Html::tag(
|
||||
'strong',
|
||||
t('Now')
|
||||
)
|
||||
)
|
||||
);
|
||||
$above->add($now);
|
||||
$progressBar->getAttributes()->add('style', sprintf('width: %s%%', $leftNow));
|
||||
|
||||
$markerLast = Html::tag('div', [
|
||||
'class' => 'marker start',
|
||||
'style' => 'left: ' . $hPadding . '%',
|
||||
'title' => $this->object->state->last_update !== null
|
||||
? DateFormatter::formatDateTime($this->object->state->last_update->getTimestamp())
|
||||
: null
|
||||
]);
|
||||
$markerNext = Html::tag('div', [
|
||||
'class' => 'marker end',
|
||||
'style' => sprintf('left: %F%%', $hPadding + $durationScale),
|
||||
$leftExecutionEnd = $nextCheckTime !== null ? $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
|
||||
]);
|
||||
$markerNow = Html::tag('div', [
|
||||
'class' => 'marker now',
|
||||
'style' => sprintf('left: %F%%', $hPadding + $leftNow),
|
||||
]);
|
||||
]));
|
||||
$markerExecutionEnd = new HtmlElement('div', Attributes::create([
|
||||
'class' => ['highlighted', 'marker'],
|
||||
'style' => sprintf('left: %F%%', $hPadding + $leftExecutionEnd),
|
||||
]));
|
||||
|
||||
$timeline->add([
|
||||
$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,
|
||||
$markerNow,
|
||||
$markerNext,
|
||||
$overdueBar
|
||||
]);
|
||||
$markerExecutionEnd,
|
||||
$markerNext
|
||||
)->add($overdueBar);
|
||||
|
||||
$lastUpdate = Html::tag(
|
||||
$executionStart = new HtmlElement(
|
||||
'li',
|
||||
['class' => 'start'],
|
||||
Html::tag(
|
||||
Attributes::create(['class' => 'left']),
|
||||
new HtmlElement(
|
||||
'div',
|
||||
['class' => 'bubble upwards'],
|
||||
new VerticalKeyValue(t('Last update'), $this->object->state->last_update !== null
|
||||
? new TimeAgo($this->object->state->last_update->getTimestamp())
|
||||
: t('PENDING'))
|
||||
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)
|
||||
)
|
||||
);
|
||||
$interval = Html::tag(
|
||||
$executionEnd = new HtmlElement(
|
||||
'li',
|
||||
['class' => 'interval'],
|
||||
Attributes::create([
|
||||
'class' => 'positioned',
|
||||
'style' => sprintf('left: %F%%', $hPadding + $leftExecutionEnd)
|
||||
]),
|
||||
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)
|
||||
)
|
||||
);
|
||||
|
||||
$intervalLine = new HtmlElement(
|
||||
'li',
|
||||
Attributes::create([
|
||||
'class' => 'interval-line',
|
||||
'style' => sprintf(
|
||||
'left: %F%%; width: %F%%;',
|
||||
$hPadding + $leftExecutionEnd,
|
||||
$durationScale - $leftExecutionEnd
|
||||
)
|
||||
]),
|
||||
new VerticalKeyValue(
|
||||
t('Interval'),
|
||||
$checkInterval
|
||||
|
|
@ -132,6 +211,29 @@ class CheckStatistics extends Card
|
|||
: (new EmptyState(t('n. a.')))->setTag('span')
|
||||
)
|
||||
);
|
||||
$executionLine = new HtmlElement(
|
||||
'li',
|
||||
Attributes::create([
|
||||
'class' => ['interval-line', 'execution-line'],
|
||||
'style' => sprintf('left: %F%%; width: %F%%;', $hPadding, $leftExecutionEnd)
|
||||
]),
|
||||
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')
|
||||
)
|
||||
)
|
||||
);
|
||||
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(
|
||||
|
|
@ -145,34 +247,34 @@ class CheckStatistics extends Card
|
|||
? new VerticalKeyValue(t('Overdue'), new TimeSince($nextCheckTime))
|
||||
: new VerticalKeyValue(
|
||||
t('Next Check'),
|
||||
$nextCheckTime !== null ? new TimeUntil($nextCheckTime) : t('PENDING')
|
||||
$nextCheckTime !== null
|
||||
? ($nextCheckTime > $now
|
||||
? new TimeUntil($nextCheckTime)
|
||||
: new TimeAgo($nextCheckTime))
|
||||
: t('PENDING')
|
||||
);
|
||||
}
|
||||
|
||||
$nextCheck = Html::tag(
|
||||
$nextCheck = new HtmlElement(
|
||||
'li',
|
||||
['class' => 'end'],
|
||||
Html::tag(
|
||||
Attributes::create(['class' => 'right']),
|
||||
new HtmlElement(
|
||||
'div',
|
||||
['class' => 'bubble upwards'],
|
||||
Attributes::create(['class' => ['bubble', 'upwards']]),
|
||||
$nextCheckBubbleContent
|
||||
)
|
||||
);
|
||||
|
||||
$below = Html::tag(
|
||||
'ul',
|
||||
[
|
||||
'class' => 'below',
|
||||
'style' => sprintf('width: %F%%;', $durationScale)
|
||||
]
|
||||
);
|
||||
$below->add([
|
||||
$lastUpdate,
|
||||
$interval,
|
||||
$nextCheck
|
||||
]);
|
||||
$above->addHtml($executionLine);
|
||||
|
||||
$body->add([$above, $timeline, $below]);
|
||||
$below->addHtml(
|
||||
$executionStart,
|
||||
$executionEnd,
|
||||
$intervalLine,
|
||||
$nextCheck
|
||||
);
|
||||
|
||||
$body->addHtml($above, $timeline, $below);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -185,23 +287,6 @@ class CheckStatistics extends Card
|
|||
return ! ($this->object->active_checks_enabled || $this->object->passive_checks_enabled);
|
||||
}
|
||||
|
||||
protected function assembleFooter(BaseHtmlElement $footer)
|
||||
{
|
||||
$footer->add(new HorizontalKeyValue(
|
||||
t('Scheduling Source') . ':',
|
||||
$this->object->state->scheduling_source ?? (new EmptyState(t('n. a.')))->setTag('span')
|
||||
));
|
||||
|
||||
if ($this->object->timeperiod->id) {
|
||||
$footer->add(new HorizontalKeyValue(
|
||||
t('Timeperiod') . ':',
|
||||
$this->object->timeperiod->display_name ?? $this->object->timeperiod->name
|
||||
));
|
||||
|
||||
$footer->addAttributes(['class' => 'space-between']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function assembleHeader(BaseHtmlElement $header)
|
||||
{
|
||||
$checkSource = (new EmptyState(t('n. a.')))->setTag('span');
|
||||
|
|
@ -213,26 +298,28 @@ class CheckStatistics extends Card
|
|||
];
|
||||
}
|
||||
|
||||
$header->add([
|
||||
$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),
|
||||
new VerticalKeyValue(
|
||||
t('Execution time'),
|
||||
$this->object->state->execution_time
|
||||
? Format::seconds($this->object->state->execution_time / 1000)
|
||||
: (new EmptyState(t('n. a.')))->setTag('span')
|
||||
),
|
||||
new VerticalKeyValue(
|
||||
t('Latency'),
|
||||
$this->object->state->latency
|
||||
? Format::seconds($this->object->state->latency / 1000)
|
||||
: (new EmptyState(t('n. a.')))->setTag('span')
|
||||
)
|
||||
]);
|
||||
new VerticalKeyValue(t('Check Source'), $checkSource)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -268,13 +355,13 @@ class CheckStatistics extends Card
|
|||
parent::assemble();
|
||||
|
||||
if ($this->isChecksDisabled()) {
|
||||
$this->add(Html::tag(
|
||||
$this->addHtml(new HtmlElement(
|
||||
'div',
|
||||
['class' => 'checks-disabled-overlay'],
|
||||
Html::tag(
|
||||
Attributes::create(['class' => 'checks-disabled-overlay']),
|
||||
new HtmlElement(
|
||||
'strong',
|
||||
['class' => 'notes'],
|
||||
t('active and passive checks are disabled')
|
||||
Attributes::create(['class' => 'notes']),
|
||||
Text::create(t('active and passive checks are disabled'))
|
||||
)
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,36 @@
|
|||
.progress-bar() {
|
||||
&.progress-bar {
|
||||
--hPadding: 10%;
|
||||
--duration-scale: 80%;
|
||||
|
||||
.above,
|
||||
.below {
|
||||
list-style-type: none;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
}
|
||||
padding: 0;
|
||||
|
||||
.above {
|
||||
padding: ~"calc(1em + 1px) 10%";
|
||||
position: relative;
|
||||
height: ~"calc(2em + 2px)";
|
||||
}
|
||||
|
||||
.below {
|
||||
padding: 1.25em 10%;
|
||||
> .left {
|
||||
position: absolute;
|
||||
left: var(--hPadding);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
> .right {
|
||||
position: absolute;
|
||||
left: ~"calc(var(--hPadding) + var(--duration-scale))";
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.positioned {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
:not(.positioned).end .bubble {
|
||||
// to move the center of the bubble to the end of the wrapper (for check-statistics end bubble only).
|
||||
transform: translate(50%, 0);
|
||||
}
|
||||
|
||||
.bubble {
|
||||
.rounded-corners(.25em);
|
||||
background-color: @body-bg-color;
|
||||
|
|
@ -48,7 +55,7 @@
|
|||
z-index: 5;
|
||||
}
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
background-color: @body-bg-color;
|
||||
border-bottom: 1px solid @gray-light;
|
||||
border-right: 1px solid @gray-light;
|
||||
|
|
@ -65,33 +72,33 @@
|
|||
left: 50%;
|
||||
}
|
||||
|
||||
&.upwards:before {
|
||||
&.upwards::before {
|
||||
bottom: auto;
|
||||
top: -.5em;
|
||||
top: -7/12em;
|
||||
transform: rotate(225deg);
|
||||
}
|
||||
|
||||
&.right {
|
||||
&.right-aligned {
|
||||
// This is (.675em (:before placement) + .5em (half :before width)) + 1px (:before border)
|
||||
transform: translate(~"calc(-1.175em - 1px)", 0);
|
||||
|
||||
&::before {
|
||||
top: auto;
|
||||
left: 1.175em;
|
||||
bottom: -.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.right:before {
|
||||
bottom: auto;
|
||||
left: 1.175em;
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
&.left {
|
||||
&.left-aligned {
|
||||
// entire width (moves the right border in place of the left) + (.675em (:before placement) + .5em (half :before width)) + 1px (:before border)
|
||||
transform: translate(~"calc(-100% + 1.175em + 1px)", 0);
|
||||
}
|
||||
|
||||
&.left:before {
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: .675em;
|
||||
top: -.5em;
|
||||
&::before {
|
||||
top: auto;
|
||||
left: auto;
|
||||
right: .675em;
|
||||
bottom: -.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +123,8 @@
|
|||
}
|
||||
|
||||
.timeline {
|
||||
@marker-gap: 1/12em;
|
||||
|
||||
.rounded-corners(.5em);
|
||||
background-color: @gray-lighter;
|
||||
height: 1em;
|
||||
|
|
@ -129,50 +138,79 @@
|
|||
background-color: @gray-light;
|
||||
height: .857em;
|
||||
margin-left: -.857/2em;
|
||||
margin-top: -1/12em;
|
||||
width: .857em;
|
||||
z-index: 2;
|
||||
|
||||
position: absolute;
|
||||
top: 2/12em;
|
||||
top: @marker-gap;
|
||||
|
||||
&.now {
|
||||
background-color: @gray;
|
||||
}
|
||||
|
||||
&.start,
|
||||
&.end {
|
||||
&.highlighted {
|
||||
background-color: @icinga-blue;
|
||||
}
|
||||
|
||||
&.left {
|
||||
left: var(--hPadding);
|
||||
}
|
||||
|
||||
&.right {
|
||||
left: ~"calc(var(--hPadding) + var(--duration-scale))";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-overlay {
|
||||
height: 100%;
|
||||
opacity: .6;
|
||||
position: absolute;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 1em;
|
||||
width: .5em;
|
||||
|
||||
.progress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: var(--hPadding);
|
||||
width: var(--duration-scale);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: .5em + @marker-gap;
|
||||
height: 1em + (@marker-gap * 2);
|
||||
margin-top: -@marker-gap;
|
||||
|
||||
.rounded-corners(.5em);
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
position: absolute;
|
||||
left: -.5em - @marker-gap;
|
||||
}
|
||||
|
||||
> .bar {
|
||||
width: 0; // set by progress-bar.js
|
||||
height: 1em + (@marker-gap * 2);
|
||||
margin-top: -@marker-gap;
|
||||
}
|
||||
|
||||
&::before,
|
||||
> .bar {
|
||||
background-color: @gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-bottom-left-radius: .5em;
|
||||
border-top-left-radius: .5em;
|
||||
left: -.5em;
|
||||
.timeline-overlay {
|
||||
position: absolute;
|
||||
left: ~"calc(var(--hPadding) + var(--duration-scale))";
|
||||
width: var(--overlay-scale);
|
||||
height: 1em + (@marker-gap * 2);
|
||||
margin-top: -@marker-gap;
|
||||
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-bottom-right-radius: .5em;
|
||||
border-top-right-radius: .5em;
|
||||
right: -.5em;
|
||||
.progress > .bar,
|
||||
.timeline-overlay {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.now {
|
||||
width: .25em;
|
||||
|
||||
border: solid @default-bg;
|
||||
border-width: 1px 0 1px 0;
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,68 +2,148 @@
|
|||
position: relative;
|
||||
.card();
|
||||
.progress-bar();
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-top: 1px solid @gray-light;
|
||||
|
||||
&.space-between {
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.key {
|
||||
width: auto;
|
||||
margin-right: .28125em; //calculated width
|
||||
font-size: .83333333em;
|
||||
}
|
||||
}
|
||||
|
||||
.check-attempt {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
&.progress-bar .below {
|
||||
padding: 0;
|
||||
margin-left: 10%;
|
||||
margin-right: auto;
|
||||
.bubble {
|
||||
&.top-left-aligned,
|
||||
&.top-right-aligned {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
svg {
|
||||
position: absolute;
|
||||
top: -1em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
&:before {
|
||||
background-color: @gray;
|
||||
content: "";
|
||||
display: block;
|
||||
height: .25em;
|
||||
width: 100%;
|
||||
.bg {
|
||||
fill: @body-bg-color;
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
top: ~"calc(50% - .125em)";
|
||||
.border {
|
||||
fill: @gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.top-left-aligned {
|
||||
transform: unset;
|
||||
border-top-left-radius: 0;
|
||||
|
||||
svg {
|
||||
left: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.top-right-aligned {
|
||||
transform: translate(-100%);
|
||||
border-top-right-radius: 0;
|
||||
|
||||
svg {
|
||||
right: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.interval {
|
||||
background-color: @body-bg-color;
|
||||
position: relative;
|
||||
// ATTENTION!: `&.progress-bar {` must not be used here, seems to confuse the less parser!!!!111
|
||||
|
||||
&.progress-bar .timeline .progress.running {
|
||||
&::before,
|
||||
> .bar {
|
||||
background: @state-ok;
|
||||
}
|
||||
}
|
||||
|
||||
.check-overdue {
|
||||
background-color: @color-down;
|
||||
opacity: 1;
|
||||
&.progress-bar .check-timeline {
|
||||
margin-top: .5em;
|
||||
}
|
||||
&.progress-bar .above {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: @color-down;
|
||||
.interval-line {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: ~"calc(50% - .125em)";
|
||||
display: block;
|
||||
height: .25em;
|
||||
width: 100%;
|
||||
content: "";
|
||||
|
||||
background-color: @gray-light;
|
||||
}
|
||||
|
||||
.vertical-key-value {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
|
||||
padding: 0 .2em;
|
||||
background-color: @body-bg-color;
|
||||
}
|
||||
|
||||
.start,
|
||||
.end {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: .25em;
|
||||
height: 1em;
|
||||
background-color: @gray;
|
||||
}
|
||||
|
||||
.start {
|
||||
left: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.end {
|
||||
right: 0;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.execution-line .vertical-key-value {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.check-overdue {
|
||||
--duration-scale: 60%;
|
||||
--overlay-scale: 20%;
|
||||
|
||||
.above {
|
||||
.now {
|
||||
position: absolute;
|
||||
right: var(--hPadding);
|
||||
bottom: 0;
|
||||
|
||||
.bubble {
|
||||
// to move the center of the bubble to the end of the wrapper.
|
||||
transform: translate(50%, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-overlay {
|
||||
background: linear-gradient(90deg, @gray-light 0, @color-down 2em);
|
||||
opacity: 1;
|
||||
|
||||
&::after {
|
||||
background-color: @color-down;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.checks-disabled.progress-bar {
|
||||
.timeline {
|
||||
.marker {
|
||||
&.start,
|
||||
&.end {
|
||||
&.highlighted {
|
||||
background-color: @gray;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue