Skip to content

Commit a211b5e

Browse files
committed
Add donut graphs to deployments, replica sets, daemon sets and stateful
sets
1 parent 98abc4a commit a211b5e

File tree

7 files changed

+271
-0
lines changed

7 files changed

+271
-0
lines changed

library/Kubernetes/Donut.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/* Icinga Kubernetes Web | (c) 2023 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Kubernetes;
6+
7+
use ipl\Html\BaseHtmlElement;
8+
use ipl\Html\Html;
9+
use ipl\Html\HtmlString;
10+
11+
class Donut extends BaseHtmlElement
12+
{
13+
protected $tag = 'div';
14+
15+
protected $defaultAttributes = ['class' => 'donut'];
16+
17+
/**
18+
* The donut data
19+
*
20+
* @var array|\Traversable
21+
*/
22+
protected $data = [];
23+
24+
protected $heading;
25+
26+
protected $headingLevel;
27+
28+
protected $labelCallback;
29+
30+
/**
31+
* Get data to display
32+
*
33+
* @return array|\Traversable
34+
*/
35+
public function getData()
36+
{
37+
return $this->data;
38+
}
39+
40+
/**
41+
* Set the data to display
42+
*
43+
* @param array|\Traversable $data
44+
*
45+
* @return $this
46+
*/
47+
public function setData($data)
48+
{
49+
if (! is_array($data) && ! $data instanceof \Traversable) {
50+
throw new \InvalidArgumentException('Data must be an array or an instance of Traversable');
51+
}
52+
53+
$this->data = $data;
54+
55+
return $this;
56+
}
57+
58+
public function setHeading($heading, $level)
59+
{
60+
$this->heading = $heading;
61+
$this->headingLevel = (int) $level;
62+
63+
return $this;
64+
}
65+
66+
public function setLabelCallback(callable $callback)
67+
{
68+
$this->labelCallback = $callback;
69+
70+
return $this;
71+
}
72+
73+
public function assemble()
74+
{
75+
$donut = new \Icinga\Chart\Donut();
76+
$legend = new Table();
77+
78+
foreach ($this->data as $index => $value) {
79+
$donut->addSlice((int) $value, ['class' => 'segment-' . $index]);
80+
$legend->addRow(
81+
[
82+
Html::tag('span', ['class' => 'badge badge-' . $index]),
83+
call_user_func($this->labelCallback, $index),
84+
$value
85+
]
86+
);
87+
}
88+
89+
$this->add([Html::tag("h{$this->headingLevel}", $this->heading), new HtmlString($donut->render()), $legend]);
90+
}
91+
}

library/Kubernetes/Table.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/* Icinga Kubernetes Web | (c) 2023 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Kubernetes;
6+
7+
use ipl\Html\BaseHtmlElement;
8+
use ipl\Html\Html;
9+
10+
class Table extends BaseHtmlElement
11+
{
12+
protected $tag = 'table';
13+
14+
protected $rows = [];
15+
16+
public function addRow(array $cells, $attributes = null)
17+
{
18+
$row = Html::tag('tr', $attributes);
19+
20+
foreach ($cells as $cell) {
21+
$row->add(Html::tag('td', $cell));
22+
}
23+
24+
$this->rows[] = $row;
25+
}
26+
27+
public function renderContent()
28+
{
29+
$tbody = Html::tag('tbody');
30+
31+
foreach ($this->rows as $row) {
32+
$tbody->add($row);
33+
}
34+
35+
$this->add($tbody);
36+
37+
return parent::renderContent(); // TODO: Change the autogenerated stub
38+
}
39+
}

library/Kubernetes/Web/DaemonSetDetail.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Icinga\Module\Kubernetes\Web;
44

55
use Icinga\Module\Kubernetes\Common\Database;
6+
use Icinga\Module\Kubernetes\Donut;
67
use Icinga\Module\Kubernetes\Model\DaemonSet;
78
use Icinga\Module\Kubernetes\Model\Event;
89
use Icinga\Module\Kubernetes\Model\ReplicaSet;
@@ -49,6 +50,28 @@ protected function assemble()
4950
t('Created') => new TimeAgo($this->daemonSet->created->getTimestamp())
5051
]));
5152

53+
$data = [
54+
$this->daemonSet->number_available,
55+
$this->daemonSet->number_ready - $this->daemonSet->number_available,
56+
$this->daemonSet->desired_number_scheduled - $this->daemonSet->number_ready,
57+
$this->daemonSet->number_unavailable
58+
];
59+
60+
$donut = (new Donut())
61+
->setData($data)
62+
->setLabelCallback(function ($index) {
63+
$labels = [
64+
'Available',
65+
'Ready but not yet available',
66+
'Not yet ready',
67+
'Not yet scheduled or failing'
68+
];
69+
return HtmlElement::create('span',
70+
null,
71+
$labels[$index]);
72+
});
73+
$this->addHtml($donut);
74+
5275
$this->addHtml(new ConditionTable($this->daemonSet, (new ReplicaSetCondition())->getColumnDefinitions()));
5376

5477
$this->addHtml(new HtmlElement(

library/Kubernetes/Web/DeploymentDetail.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Icinga\Module\Kubernetes\Web;
66

7+
use Icinga\Module\Kubernetes\Donut;
78
use Icinga\Module\Kubernetes\Model\Deployment;
89
use Icinga\Module\Kubernetes\Model\DeploymentCondition;
910
use ipl\Html\Attributes;
@@ -46,6 +47,28 @@ protected function assemble()
4647
t('Created') => new TimeAgo($this->deployment->created->getTimestamp())
4748
]));
4849

50+
$data = [
51+
$this->deployment->available_replicas,
52+
$this->deployment->desired_replicas - $this->deployment->actual_replicas,
53+
$this->deployment->actual_replicas - $this->deployment->ready_replicas,
54+
$this->deployment->unavailable_replicas,
55+
];
56+
57+
$donut = (new Donut())
58+
->setData($data)
59+
->setLabelCallback(function ($index) {
60+
$labels = [
61+
'Available',
62+
'Ready but not yet available',
63+
'Not yet ready',
64+
'Not yet scheduled or failing'
65+
];
66+
return HtmlElement::create('span',
67+
null,
68+
$labels[$index]);
69+
});
70+
$this->addHtml($donut);
71+
4972
$this->addHtml(new ConditionTable($this->deployment, (new DeploymentCondition())->getColumnDefinitions()));
5073

5174
$this->addHtml(new HtmlElement(

library/Kubernetes/Web/ReplicaSetDetail.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Icinga\Module\Kubernetes\Model\Event;
99
use Icinga\Module\Kubernetes\Model\ReplicaSet;
1010
use Icinga\Module\Kubernetes\Model\replicaSetCondition;
11+
use Icinga\Module\Kubernetes\Donut;
1112
use ipl\Html\Attributes;
1213
use ipl\Html\BaseHtmlElement;
1314
use ipl\Html\HtmlElement;
@@ -46,6 +47,28 @@ protected function assemble()
4647
t('Created') => new TimeAgo($this->replicaSet->created->getTimestamp())
4748
]));
4849

50+
$data = [
51+
$this->replicaSet->available_replicas,
52+
$this->replicaSet->ready_replicas - $this->replicaSet->available_replicas,
53+
$this->replicaSet->actual_replicas - $this->replicaSet->ready_replicas,
54+
$this->replicaSet->desired_replicas - $this->replicaSet->actual_replicas,
55+
];
56+
57+
$donut = (new Donut())
58+
->setData($data)
59+
->setLabelCallback(function ($index) {
60+
$labels = [
61+
'Available',
62+
'Ready but not yet available',
63+
'Not yet ready',
64+
'Not yet scheduled or failing'
65+
];
66+
return HtmlElement::create('span',
67+
null,
68+
$labels[$index]);
69+
});
70+
$this->addHtml($donut);
71+
4972
$this->addHtml(new ConditionTable($this->replicaSet, (new ReplicaSetCondition())->getColumnDefinitions()));
5073

5174
$this->addHtml(new HtmlElement(

library/Kubernetes/Web/StatefulSetDetail.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Icinga\Module\Kubernetes\Web;
44

5+
use Icinga\Module\Kubernetes\Donut;
56
use Icinga\Module\Kubernetes\Model\StatefulSet;
67
use Icinga\Module\Kubernetes\Model\StatefulSetCondition;
78
use ipl\Html\Attributes;
@@ -42,6 +43,28 @@ protected function assemble()
4243
t('Created') => new TimeAgo($this->statefulSet->created->getTimestamp())
4344
]));
4445

46+
$data = [
47+
$this->statefulSet->available_replicas,
48+
$this->statefulSet->ready_replicas - $this->statefulSet->available_replicas,
49+
$this->statefulSet->actual_replicas - $this->statefulSet->ready_replicas,
50+
$this->statefulSet->desired_replicas - $this->statefulSet->actual_replicas
51+
];
52+
53+
$donut = (new Donut())
54+
->setData($data)
55+
->setLabelCallback(function ($index) {
56+
$labels = [
57+
'Available',
58+
'Ready but not yet available',
59+
'Not yet ready',
60+
'Not yet scheduled or failing'
61+
];
62+
return HtmlElement::create('span',
63+
null,
64+
$labels[$index]);
65+
});
66+
$this->addHtml($donut);
67+
4568
$this->addHtml(new ConditionTable($this->statefulSet, (new StatefulSetCondition())->getColumnDefinitions()));
4669

4770
$this->addHtml(new HtmlElement(

public/css/module.less

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,52 @@ body {
433433
#grid li:nth-child(6n+4) {
434434
margin-left: 0.5%;
435435
}
436+
437+
@donut-segment-color-0: #44bb77;
438+
@donut-segment-color-1: #77aaff;
439+
@donut-segment-color-2: #aa44ff;
440+
@donut-segment-color-3: #ff5566;
441+
442+
.donut {
443+
align-self: flex-start;
444+
padding: 1em;
445+
446+
447+
.donut-graph {
448+
.segment-0 {
449+
stroke: @donut-segment-color-0;
450+
}
451+
452+
.segment-1 {
453+
stroke: @donut-segment-color-1;
454+
}
455+
456+
.segment-2 {
457+
stroke: @donut-segment-color-2;
458+
}
459+
460+
.segment-3 {
461+
stroke: @donut-segment-color-3;
462+
}
463+
}
464+
465+
.badge {
466+
height: 1.75em;
467+
468+
&.badge-0 {
469+
background: @donut-segment-color-0;
470+
}
471+
472+
&.badge-1 {
473+
background: @donut-segment-color-1;
474+
}
475+
476+
&.badge-2 {
477+
background: @donut-segment-color-2;
478+
}
479+
480+
&.badge-3 {
481+
background: @donut-segment-color-3;
482+
}
483+
}
484+
}

0 commit comments

Comments
 (0)