Skip to content

Commit 13868e8

Browse files
Timeline: Visualize future rotations
- Introduce class FutureEntry
1 parent 4cd1af7 commit 13868e8

File tree

4 files changed

+117
-11
lines changed

4 files changed

+117
-11
lines changed

library/Notifications/Widget/TimeGrid/BaseGrid.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DateInterval;
88
use DateTime;
99
use Generator;
10+
use Icinga\Module\Notifications\Widget\Timeline\FutureEntry;
1011
use ipl\Html\Attributes;
1112
use ipl\Html\BaseHtmlElement;
1213
use ipl\Html\HtmlElement;
@@ -421,6 +422,20 @@ final protected function yieldFixedEntries(Traversable $entries): Generator
421422
}
422423

423424
$rowStart = $position + $rowStartModifier;
425+
426+
if ($entry instanceof FutureEntry) {
427+
$gridArea = $this->getGridArea(
428+
$rowStart,
429+
$rowStart + 1,
430+
1,
431+
$gridBorderAt + 1
432+
);
433+
434+
yield $gridArea => $entry;
435+
436+
continue;
437+
}
438+
424439
if ($rowStart > $lastRow) {
425440
$lastRow = $rowStart;
426441
}

library/Notifications/Widget/Timeline.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Icinga\Module\Notifications\Widget\TimeGrid\Timescale;
1515
use Icinga\Module\Notifications\Widget\TimeGrid\Util;
1616
use Icinga\Module\Notifications\Widget\Timeline\Entry;
17+
use Icinga\Module\Notifications\Widget\Timeline\FutureEntry;
1718
use Icinga\Module\Notifications\Widget\Timeline\MinimalGrid;
1819
use Icinga\Module\Notifications\Widget\Timeline\Rotation;
1920
use IntlDateFormatter;
@@ -173,7 +174,9 @@ public function getEntries(): Traversable
173174

174175
$occupiedCells = [];
175176
foreach ($rotations as $rotation) {
177+
$entryFound = false;
176178
foreach ($rotation->fetchTimeperiodEntries($this->start, $this->getGrid()->getGridEnd()) as $entry) {
179+
$entryFound = true;
177180
if (! $this->minimalLayout) {
178181
$entry->setPosition($maxPriority - $rotation->getPriority());
179182

@@ -182,6 +185,10 @@ public function getEntries(): Traversable
182185

183186
$occupiedCells += $getDesiredCells($entry);
184187
}
188+
189+
if (! $entryFound && ! $this->minimalLayout) {
190+
yield (new FutureEntry())->setPosition($maxPriority - $rotation->getPriority());
191+
}
185192
}
186193

187194
$entryToCellsMap = new SplObjectStorage();
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Notifications\Widget\Timeline;
6+
7+
use Icinga\Module\Notifications\Widget\TimeGrid\Entry;
8+
use ipl\Html\Attributes;
9+
use ipl\Html\BaseHtmlElement;
10+
use ipl\Html\HtmlElement;
11+
use ipl\Web\Widget\Icon;
12+
13+
/**
14+
* FutureEntry
15+
*
16+
* Visualize a future entry of the rotation
17+
*
18+
* @extends Entry<0>
19+
*/
20+
class FutureEntry extends Entry
21+
{
22+
public function __construct()
23+
{
24+
parent::__construct(0);
25+
26+
$this->setContinuationType(Entry::TO_NEXT_GRID);
27+
}
28+
29+
public function getColor(int $transparency): string
30+
{
31+
// --base-disabled (#d0d3da) -> hsl(222, 12%, 84%) + transparency
32+
return sprintf('~"hsl(222 12%% 84%% / %d%%)"', $transparency);
33+
}
34+
35+
protected function assembleContainer(BaseHtmlElement $container): void
36+
{
37+
$futureBadge = new HtmlElement(
38+
'div',
39+
new Attributes([
40+
'title' => $this->translate('Rotation starts in the future'),
41+
$container->getAttributes()->get('class')
42+
]),
43+
new Icon('angle-right')
44+
);
45+
46+
$container
47+
->setAttribute('class', 'future-entry') // override the default class
48+
->addHtml($futureBadge);
49+
}
50+
}

public/css/timeline.less

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,53 @@
4444
}
4545
}
4646

47-
.overlay .entry {
48-
margin-top: 1em;
49-
margin-bottom: 1em;
50-
z-index: 2; // overlap the .clock .time-hand
47+
.overlay {
48+
.entry {
49+
margin-top: 1em;
50+
margin-bottom: 1em;
51+
z-index: 2; // overlap the .clock .time-hand
52+
53+
.title {
54+
height: 100%;
55+
flex-wrap: nowrap;
56+
align-items: baseline;
57+
padding: .15em .5em;
58+
59+
.name {
60+
.text-ellipsis();
61+
}
62+
}
63+
}
5164

52-
.title {
53-
height: 100%;
54-
flex-wrap: nowrap;
55-
align-items: baseline;
56-
padding: .15em .5em;
65+
.future-entry {
66+
display: flex;
67+
justify-content: end;
5768

58-
.name {
59-
.text-ellipsis();
69+
.entry {
70+
display: flex;
71+
align-items: center;
72+
justify-content: end;
73+
flex-shrink: 0;
74+
position: relative;
75+
padding-right: .25em;
76+
width: 3em;
77+
78+
&:before,
79+
&:after {
80+
content: '';
81+
display: block;
82+
position: absolute;
83+
border: 1px solid var(--entry-border-color);
84+
top: -1px;
85+
bottom: -1px;
86+
left: 2px;
87+
width: 100%;
88+
.rounded-corners(0.25em);
89+
}
90+
91+
&:after {
92+
left: 5px; // 2px before + 1px border + 2px after
93+
}
6094
}
6195
}
6296
}

0 commit comments

Comments
 (0)