From c79647cf64cd268c90a230ea93f2f0d40418cc95 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Wed, 11 Dec 2024 08:44:22 +0100 Subject: [PATCH 01/25] configuration.php: Add dashboard url to menu section --- configuration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configuration.php b/configuration.php index 4739a225..9f937fa5 100644 --- a/configuration.php +++ b/configuration.php @@ -10,7 +10,8 @@ $section = $this->menuSection( 'Kubernetes', [ - 'icon' => 'globe' + 'icon' => 'globe', + 'url' => 'kubernetes/dashboard', ] ); From 32130348ffe392f38c5af9ab97c936cbd713dfce Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Wed, 11 Dec 2024 10:01:58 +0100 Subject: [PATCH 02/25] module.less: Add `kubernetes-dashboard` styling --- public/css/module.less | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/public/css/module.less b/public/css/module.less index 3d83db7d..bd761861 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -30,3 +30,85 @@ padding-left: 0; } } + +.kubernetes-dashboard > ul { + margin: 0; + padding: 0; + min-width: 38em; + max-width: 80em; + + li { + list-style-type: none; + + text-align: left; + display: inline-block; + padding: 0; + clear: both; + width: 25em; + min-width: 16em; + vertical-align: top; + + a { + i { + font-size: 3em; + display: block; + float: left; + line-height: 1em; + margin-right: 0.3em; + color: @text-color-light; + } + + &.state-critical i { + color: @color-critical; + } + + &.state-warning i { + color: @color-warning; + } + + &.state-ok i { + color: @color-ok; + } + + &.state-unknown i { + color: @color-unknown; + } + + &.state-pending i { + color: @color-pending; + } + + border-left: 0.5em solid transparent; + padding: 1em; + color: @text-color; + font-weight: bold; + display: block; + text-decoration: none; + min-height: 10em; + + overflow: hidden; + + &.active { + border-color: @icinga-blue; + background-color: @tr-active-color; + } + + &:hover { + background-color: @tr-hover-color; + text-decoration: none; + } + + &:active, &:focus { + background-color: @tr-hover-color; + outline: none; + } + } + + p { + font-weight: normal; + margin-bottom: 0.5em; + padding-left: 4.5em; + color: @text-color-light; + } + } +} From 92b9934cfb7eedce25daf476716a9e7fb05aad9f Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Wed, 11 Dec 2024 11:10:45 +0100 Subject: [PATCH 03/25] icons.less: Add event icon style --- public/css/icons.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/css/icons.less b/public/css/icons.less index d5baae74..03cbd1e9 100644 --- a/public/css/icons.less +++ b/public/css/icons.less @@ -58,6 +58,10 @@ } } +.kicon-event:before { + content: "\e94d"; +} + .kicon-ingress:before { content: "\e904"; } From 7b7b50eddde3e2a2d7fec44945e1dc809c9d7f13 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Wed, 11 Dec 2024 13:48:15 +0100 Subject: [PATCH 04/25] Add event icon font --- assets/Icinga-Kubernetes.svg | 1 + assets/Icinga-Kubernetes.ttf | Bin 5000 -> 5528 bytes assets/Icinga-Kubernetes.woff | Bin 5076 -> 5608 bytes 3 files changed, 1 insertion(+) diff --git a/assets/Icinga-Kubernetes.svg b/assets/Icinga-Kubernetes.svg index ae82af2d..9821be32 100644 --- a/assets/Icinga-Kubernetes.svg +++ b/assets/Icinga-Kubernetes.svg @@ -24,4 +24,5 @@ + \ No newline at end of file diff --git a/assets/Icinga-Kubernetes.ttf b/assets/Icinga-Kubernetes.ttf index ede13328b9eaa5ac6d0226c5f55c51475d25fa62..2743a38e3e46e604dcfbdd69edec2610c4532b5d 100644 GIT binary patch delta 1916 zcmaJ>U2GIp82#?d>`!;LyR+L)|LC^eF5RUq2<~OURr{=nef?q+`M-gx>N{!$P z;A=<6#`6=qZ<+UqD!d33(%He(rYG0G0e%jA=U8eYM;qxQ@EhPwnbg?u)Sj~Y;CFF| zH*?vO<2X2mI>B|T7!NhjdSdC+c!u00aTSA?$`yP9AAcu@K=!}Rx3Cut z8mGzI%I#ir(KNAwEM?SEPC%jC`z?i;^XA9qMe_r5%6#86s>`c>LMCRsP*H7US(ouP zTlc`P^6J-YkGO`!87wTP0Ii`GbgqN8(@xq)hbd#!Ibs3LmCz#1&Jaszn-gk83maia zjWOzI&g(aCvMcGNuE=Y2eM66|=!;j9X-wz)j(h$3cU(T+r~AE-t;k=$3TI?RkryZV zcR3ah>U@o?d^7zz@}-mPBBq`V=QTfwhJnO>h!$tN6uEd=_4@r@HXtj-q$0278S$a3 z^-e>oU9G66S1qYJFNT|)YM`;$f zE`B)y(73*hMO0jueQOWi-)cKrnxiqbfnon<&Br3f&)yKecfH~Ks*PB7(M#{Zy69r4 zEfbZ72c8&q#4_y%hg{B~frHPw-9!Cs?>{~A`{g4wwLPt^P2twop4uA0SzFf?jW&i` z*MTj25ku3(`Tu(qkgaF&Sa~q#35DcruzW+k$5UVLk?UvGWo<2fUaKe-5=Moh)LyA7 z_XR{z5;?ha^=|pALS7&;XQWR2CEDp>bU0q!z^bd%N|%qhoDmJJ+0M}H*h37><-vr^ z5z2+f8*xB zXY-z21N@axku|VSoH1vJq)zBXsT0ZnJDQe!kB0@zI0_YR13w>E#AmP$|CxoTpp-MR6I*QX)>Y_e6L_?IJ3A)F=7wUvBh3i65+$WwD7o?!{hvS6f zy7Lof(RJ1}=icOg1WEV_n`qkT3&ip+o8xHk(4+S0MPS{=67Y*Q=1#2In0xMC8+)jZ z&T?Or$`J8EDPaM4#Kt1nU{_YX2-JG3_N=fp3Qwb Y3j^I947aVQtQa(I1XXoqrKTeHH;EmG*Z=?k delta 1679 zcmbVMUrbw782`?_w=F_%;oi%IbX%dN3{X}mlro*g)x|isnT#;l1B*iGYGIZ__-D6> zZ8nyfNTSKjw8q3RV2}~c1*W7ruY*B|4F*vZG2FTI426#Tv8AS&LFF2Y6(HIGluWe$E|-h=%m z?7`Gj{MlIJrvD6)!WY;x8OuylGv#5w0Nb06O(qt;TfGT;9E%jEr)KA{Q4|d}c}Wzn z8!xi`tj1VoPq#k_u97N9z{kjYr^YOpCXWWD5@zNv^%7=a#uLKPwSah7+_@yKgOEv~ zomjeqx+y|YI!q@hWjyC>t#*XM{wBMJg~EG7x_`Ivm3?3K{N20kr_69fksBhB?tWQ` z6i%Hu?NlQx!x^;SZf%v^suS)KbAL&=C9A4j=;ZA`vA7bkVs5OyjrlUe>}EuD_D}Vx zzZ9lBII`!{eY^d7NDqa3S{#sqFVx-J$NZ3HDcqL27qMaALxV+MlU-M8>q5Tn-mucs zB3xo*BHy{usM_&rG#Va~wP+zannU-=XwIcZH@5?{K-6+vE|9EgGSaST;!4zFZ(n^s zs>wO_7TTc&4!1p5ERWZ%7JgOSZbhh5-G)cLe(Kt`-W}wpJd+^{!6v~ z5ZU|(|E=2pK#puizpLGN-Q~-E$mK8-dbu1+?g2~gNEUK`G)>DZd&_DIUxdyEY?(wN zV+))O#b2v%Wa201Dk~Sp*oA_?6Co7QE_yseUBTAYVAqhxBRCox1_B+;tzAJ_+d2CF z>S!LB$&Ro)JWKQc%F=#S&S};9`Kl(r^PFDO>yxW|URm}Qk+|PViPKegG3C*;I-4Zf z>NL%B%Uz>rz*0So&v8}Oa@C7MmcI(9!2?%*AE}P~NmUZRM)dRT4u11Xy?M`Em-vCh zOMnAl6^s$rO>oi%{6%Sl&+0QD+D1zY=w5*F(%e!K@ z^(CF$M-KY5%!}X$%UA;bx{PgjDMMv!2Y;cAD`^K4__HEC2Brxf3kz6xql`uH364h) zB*EA$J8V>kGiwd7gU^?7CH1gfhvF0IWGrxGek?JQPRu1{=@5?g1f?lSF$&NTnx`?$ uI|C*`bEvb%TCFep2;&}M$Uq`FpNh@U0M<=nCn@Y{hR@UafA`#`PyYp{n_pW1 diff --git a/assets/Icinga-Kubernetes.woff b/assets/Icinga-Kubernetes.woff index 1931f87bd5c2432356e4edbe658d7d8d4407b305..e0a22431501aff75cccbea7cdd943e9754485529 100644 GIT binary patch delta 2079 zcmaJ?ZERCz6h800x9!%uwYTeTx57T!m9>ly!FKl!b>bvsn=(E+Hlu{Ex|R*;)@2O& z5N$O^6Ji3%jS+NUVle(dW|4U5)IKr10XjX##vj z@hf%;aU`4(el`M3V_S?q_RJv4p?Xq~vjW~B%Fuoqr5sJt(L2hWE#|yw!hyC|I{F3oK>o)GO+Q-SIdu+G3l*DNSR+EpG zQY)>Yc3O|F_t7pIGxj-RKFyWTA}!8Fme4v9YD5bgqmFvx8%IlCzkZ!v%qDe3UaIS> zdu2tRznIKoJGt#Zl~@0P(+9Tc-YU?R<=;Pr_hm(q=a2GlITiyR zt{HbXHp;o0>eUTycSD0)Zn&nFwYB(VP*MC6mS0hV7X#HEpD0QqSI%GR^t>tL1tN1= z>cXGIN_r9rd|LDa7NN7LONtfTHor?`CE`mt(Ql`!+Gvq=Hx|xs^ipYVmx@lgC8&w3`?U$)4`v zrp98Sz`CaSFu;H^0dPPZwc{y1uegoNTld2k_INx0Cb=}*ZWT7)<}}~UIlC-Awh?J9 z#E8>8o74rqD0NZ2mCvU67P7~ran2XC#tI)TWs>K-xEm?3Tlf#?A;=fzocOzK+fl}Q z9%0L$b6p62u-sIO$-124)Jfg6jdoC)#^?ymu`9w7;cMZlP!#*b6XL8?BmLzFPB^YQ zKXVpcCtNcXYb%~c7G6S#ri>|HEbp>)%w7!Ty%vYF0qZuFfDhT2k9O6@e28zdv746A z2|iq~6Mwy_BOtc@k$;~Z-UaNvJO$R?a1`fcpSpgq5TC^wqRjbZQ)Ves?e mpTpGsuqHgPAhV#cKQnwNo0>r43{G>1vXr8U1wDZ{FSQJWE3$t{LzjY*7 zva!s>C7PV6EE>biuDsv|voPZln7d@r=@MO{5%p&14U2lStUW$o&yhH@n7FTbzwddU z_x*mJx8HZ}H{ES?pYHDJB9741yHr*j-y}{yrMh$7hg;f+xDt?q%qkSyqr_-@4BRmM zBh0Ffe*JzbKEb%p;j>(3u2lNWYv67XmH)x4r>x=Xa58=zp%uhCm_2v#$}7Vl!EM2> zXIAot`0nuNWR^&dSAr;Rvi}3)VfthOi89;xd1kd6ftk^G78}c#;QNYJPYap&XcF9~ z*u21O`rD=3V<#sjp%0vuCjbe;P5nb|FXz&~=U!@h0Zb{5&f~|CZ_{Pu4FeozY*G<( z#xGSO38>8s-@QC5929oU3#%X`l4%D{-%M>3r5GKgW0ck}IU6e+;Ygs)?&ZRf-C-@T zQ?GXH$(?`jfcq)iAC;w=Xtb?IlB4+($IdvF=wg2s{Wn`%#jxT;xX3)3=kG|0BIR3H z|4%F|M$Me{rMIzOwx7ElRh&I1I~CwgwEnE5tS`L-60s3kDx7nwisIyECAm*DuFZX^wjfecv>L+mRJB;`1o^UuBO+=G}@%AC+#N zxUsFbngX~}A=*PP(|+hp&;;rKrCNJ}Z1xBLtvc{njcod2d6T|g?$3S1Lc4RD*RN>l%;(q2WHQ!BLdWpx4PInCAK zm&*J;N%9p?xZjF}(-co3?NwE`O%!czRrTKSxMUSrtb+3ep33r96|;Pf1A>M2t^7Vv z5&e_OMD~p6=X=d~tmbGuG*(4+;n>%LXDk}#2&={_k+_C{Ww=$ommlXp=C_1q;i7O&T(Bv&j6G^! zaMU|yio0v{9$_6cUO&aFxpG>vPQ~HX7thRMi3&x-gAm^wsE%wyj*3k%?r3=belgtK7y80bK17%20~p6r@8mMMGG3988iXVH3Kb`EyS(? From 7e74ff83414c68355c9d09d4d503396fcc7fe0a1 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Wed, 11 Dec 2024 13:54:31 +0100 Subject: [PATCH 05/25] common.less: Remove box-shadow style --- public/css/common.less | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/css/common.less b/public/css/common.less index 83e55852..122f6604 100644 --- a/public/css/common.less +++ b/public/css/common.less @@ -29,11 +29,6 @@ pre { } .controls { - // Copied from Icinga DB Web - -moz-box-shadow: 0 0 0 1px var(--gray-lighter, #4b4b4b); - -webkit-box-shadow: 0 0 0 1px var(--gray-lighter, #4b4b4b); - box-shadow: 0 0 0 1px var(--gray-lighter, #4b4b4b); - a.subject { cursor: default; pointer-events: none; From b1eb53dd6b7c6d3f285cacc5121cf140a971d3fd Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Thu, 12 Dec 2024 09:08:30 +0100 Subject: [PATCH 06/25] Resize SVG icon to smaller dimensions --- assets/Icinga-Kubernetes.svg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/Icinga-Kubernetes.svg b/assets/Icinga-Kubernetes.svg index 9821be32..09f5573e 100644 --- a/assets/Icinga-Kubernetes.svg +++ b/assets/Icinga-Kubernetes.svg @@ -10,7 +10,7 @@ - + @@ -18,9 +18,9 @@ - + - + From ad72ea0d4cfba13742ef0ad3bda72d4bec4bb311 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Thu, 12 Dec 2024 10:39:20 +0100 Subject: [PATCH 07/25] Introduce new class `DashboardController` --- .../controllers/DashboardController.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 application/controllers/DashboardController.php diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php new file mode 100644 index 00000000..142d41d8 --- /dev/null +++ b/application/controllers/DashboardController.php @@ -0,0 +1,34 @@ +addTitleTab($this->translate('Kubernetes')); + + $names = $this->params->getValues('name', $mainDashboards); + + foreach ($names as $name) { + $dashboard = Dashboard::loadByName($name); + $this->addContent($dashboard); + } + } +} From 4fad4d29da5aa680f0404646c5c2eb17cfc863eb Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Thu, 12 Dec 2024 10:41:07 +0100 Subject: [PATCH 08/25] Introduce new `Dashboard` and `Dashlet` classes --- .../Dashboard/AdditionalDashboard.php | 17 ++++ .../Dashboard/ClusterManagementDashboard.php | 18 ++++ .../Dashboard/ClusterServiceDashlet.php | 32 +++++++ .../Kubernetes/Dashboard/ConfigMapDashlet.php | 29 ++++++ .../Dashboard/ConfigurationDashboard.php | 18 ++++ .../Kubernetes/Dashboard/CronJobDashlet.php | 29 ++++++ .../Kubernetes/Dashboard/DaemonSetDashlet.php | 46 +++++++++ library/Kubernetes/Dashboard/Dashboard.php | 95 +++++++++++++++++++ library/Kubernetes/Dashboard/Dashlet.php | 75 +++++++++++++++ .../Dashboard/DeploymentDashlet.php | 46 +++++++++ library/Kubernetes/Dashboard/EventDashlet.php | 29 ++++++ .../Kubernetes/Dashboard/IngressDashlet.php | 29 ++++++ library/Kubernetes/Dashboard/JobDashlet.php | 47 +++++++++ .../Kubernetes/Dashboard/NamespaceDashlet.php | 43 +++++++++ .../Dashboard/NetworkingDashboard.php | 18 ++++ library/Kubernetes/Dashboard/NodeDashlet.php | 46 +++++++++ .../Dashboard/ObservabilityDashboard.php | 18 ++++ .../PersistentVolumeClaimDashlet.php | 44 +++++++++ .../Dashboard/PersistentVolumeDashlet.php | 47 +++++++++ library/Kubernetes/Dashboard/PodDashlet.php | 47 +++++++++ .../Dashboard/ReplicaSetDashlet.php | 46 +++++++++ .../Kubernetes/Dashboard/SecretDashlet.php | 29 ++++++ .../Kubernetes/Dashboard/ServiceDashlet.php | 31 ++++++ .../Dashboard/StatefulSetDashlet.php | 46 +++++++++ .../Kubernetes/Dashboard/StorageDashboard.php | 18 ++++ .../Dashboard/WorkloadsDashboard.php | 23 +++++ 26 files changed, 966 insertions(+) create mode 100644 library/Kubernetes/Dashboard/AdditionalDashboard.php create mode 100644 library/Kubernetes/Dashboard/ClusterManagementDashboard.php create mode 100644 library/Kubernetes/Dashboard/ClusterServiceDashlet.php create mode 100644 library/Kubernetes/Dashboard/ConfigMapDashlet.php create mode 100644 library/Kubernetes/Dashboard/ConfigurationDashboard.php create mode 100644 library/Kubernetes/Dashboard/CronJobDashlet.php create mode 100644 library/Kubernetes/Dashboard/DaemonSetDashlet.php create mode 100644 library/Kubernetes/Dashboard/Dashboard.php create mode 100644 library/Kubernetes/Dashboard/Dashlet.php create mode 100644 library/Kubernetes/Dashboard/DeploymentDashlet.php create mode 100644 library/Kubernetes/Dashboard/EventDashlet.php create mode 100644 library/Kubernetes/Dashboard/IngressDashlet.php create mode 100644 library/Kubernetes/Dashboard/JobDashlet.php create mode 100644 library/Kubernetes/Dashboard/NamespaceDashlet.php create mode 100644 library/Kubernetes/Dashboard/NetworkingDashboard.php create mode 100644 library/Kubernetes/Dashboard/NodeDashlet.php create mode 100644 library/Kubernetes/Dashboard/ObservabilityDashboard.php create mode 100644 library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php create mode 100644 library/Kubernetes/Dashboard/PersistentVolumeDashlet.php create mode 100644 library/Kubernetes/Dashboard/PodDashlet.php create mode 100644 library/Kubernetes/Dashboard/ReplicaSetDashlet.php create mode 100644 library/Kubernetes/Dashboard/SecretDashlet.php create mode 100644 library/Kubernetes/Dashboard/ServiceDashlet.php create mode 100644 library/Kubernetes/Dashboard/StatefulSetDashlet.php create mode 100644 library/Kubernetes/Dashboard/StorageDashboard.php create mode 100644 library/Kubernetes/Dashboard/WorkloadsDashboard.php diff --git a/library/Kubernetes/Dashboard/AdditionalDashboard.php b/library/Kubernetes/Dashboard/AdditionalDashboard.php new file mode 100644 index 00000000..03ebc654 --- /dev/null +++ b/library/Kubernetes/Dashboard/AdditionalDashboard.php @@ -0,0 +1,17 @@ +translate('Additional Categories'); + } +} diff --git a/library/Kubernetes/Dashboard/ClusterManagementDashboard.php b/library/Kubernetes/Dashboard/ClusterManagementDashboard.php new file mode 100644 index 00000000..90739664 --- /dev/null +++ b/library/Kubernetes/Dashboard/ClusterManagementDashboard.php @@ -0,0 +1,18 @@ +translate('Cluster management'); + } +} diff --git a/library/Kubernetes/Dashboard/ClusterServiceDashlet.php b/library/Kubernetes/Dashboard/ClusterServiceDashlet.php new file mode 100644 index 00000000..d5c6a9ae --- /dev/null +++ b/library/Kubernetes/Dashboard/ClusterServiceDashlet.php @@ -0,0 +1,32 @@ +translate('Cluster Services'); + } + + public function getSummary() + { + return $this->translate( + "Core components that manage and support the Kubernetes control plane, networking, and storage, + ensuring the cluster's overall health and operation" + ); + } + + public function getUrl() + { + return 'kubernetes/services?label.name=kubernetes.io%2Fcluster-service&label.value=true'; + } +} diff --git a/library/Kubernetes/Dashboard/ConfigMapDashlet.php b/library/Kubernetes/Dashboard/ConfigMapDashlet.php new file mode 100644 index 00000000..f3325688 --- /dev/null +++ b/library/Kubernetes/Dashboard/ConfigMapDashlet.php @@ -0,0 +1,29 @@ +translate('Config Maps'); + } + + public function getSummary() + { + return $this->translate('Store configuration data as key-value pairs'); + } + + public function getUrl() + { + return 'kubernetes/config-maps'; + } +} diff --git a/library/Kubernetes/Dashboard/ConfigurationDashboard.php b/library/Kubernetes/Dashboard/ConfigurationDashboard.php new file mode 100644 index 00000000..5b162060 --- /dev/null +++ b/library/Kubernetes/Dashboard/ConfigurationDashboard.php @@ -0,0 +1,18 @@ +translate('Configuration'); + } +} diff --git a/library/Kubernetes/Dashboard/CronJobDashlet.php b/library/Kubernetes/Dashboard/CronJobDashlet.php new file mode 100644 index 00000000..3e557e94 --- /dev/null +++ b/library/Kubernetes/Dashboard/CronJobDashlet.php @@ -0,0 +1,29 @@ +translate('Cron Jobs'); + } + + public function getSummary() + { + return $this->translate('Schedule Jobs to run at specific times'); + } + + public function getUrl() + { + return 'kubernetes/cron-jobs'; + } +} diff --git a/library/Kubernetes/Dashboard/DaemonSetDashlet.php b/library/Kubernetes/Dashboard/DaemonSetDashlet.php new file mode 100644 index 00000000..213f1fc7 --- /dev/null +++ b/library/Kubernetes/Dashboard/DaemonSetDashlet.php @@ -0,0 +1,46 @@ +translate('Daemon Sets'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total daemon sets, %s are in OK state, %s are Critical, %s are in Warning state, and + %s are Unknown', + $this->getIcingaStateCount(), + $this->getIcingaStateCount('ok'), + $this->getIcingaStateCount('critical'), + $this->getIcingaStateCount('warning'), + $this->getIcingaStateCount('unknown') + ) + ); + } + + private function getIcingaStateCount(string $icingaState = '') + { + return $icingaState === '' + ? DaemonSet::on(Database::connection())->count() + : DaemonSet::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); + } + + public function getUrl() + { + return 'kubernetes/daemon-sets'; + } +} diff --git a/library/Kubernetes/Dashboard/Dashboard.php b/library/Kubernetes/Dashboard/Dashboard.php new file mode 100644 index 00000000..ce1aa5ce --- /dev/null +++ b/library/Kubernetes/Dashboard/Dashboard.php @@ -0,0 +1,95 @@ +name = $name; + + return $dashboard; + } + + public static function exists($name) + { + return class_exists(__NAMESPACE__ . '\\' . ucfirst($name) . 'Dashboard'); + } + + public function render() + { + $this + ->setSeparator("\n") + ->add(Html::tag('h1', null, $this->getTitle())) + ->add($this->renderDashlets()); + + return parent::render(); + } + + public function renderDashlets() + { + $ul = Html::tag('ul', [ + 'class' => 'main-actions', + 'data-base-target' => '_self' + ]); + + foreach ($this->dashlets() as $dashlet) { + $ul->add($dashlet); + } + + return $ul; + } + + abstract public function getTitle(); + + public function dashlets() + { + if ($this->dashlets === null) { + $this->loadDashlets(); + } + + return $this->dashlets; + } + + public function loadDashlets() + { + $names = $this->getDashletNames(); + + if (empty($names)) { + $this->dashlets = array(); + } else { + $this->dashlets = Dashlet::loadByNames( + $this->dashletNames, + ); + } + } + + public function getDashletNames() + { + return $this->dashletNames; + } +} diff --git a/library/Kubernetes/Dashboard/Dashlet.php b/library/Kubernetes/Dashboard/Dashlet.php new file mode 100644 index 00000000..ca640e87 --- /dev/null +++ b/library/Kubernetes/Dashboard/Dashlet.php @@ -0,0 +1,75 @@ +icon; + } + + abstract public function getTitle(); + + abstract public function getUrl(); + + protected function assemble() + { + $this->addHtml(( + new Link( + [ + $this->getTitle(), + new HtmlElement( + 'i', + new Attributes(['class' => $this->getIconName()]) + ), + new HtmlElement( + 'p', + null, + new Text($this->getSummary()) + ) + ], + $this->getUrl(), + ) + )); + } +} diff --git a/library/Kubernetes/Dashboard/DeploymentDashlet.php b/library/Kubernetes/Dashboard/DeploymentDashlet.php new file mode 100644 index 00000000..d805e294 --- /dev/null +++ b/library/Kubernetes/Dashboard/DeploymentDashlet.php @@ -0,0 +1,46 @@ +translate('Deployments'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total deployments, %s are in OK state, %s are Critical, %s are in Warning state, and + %s are Unknown', + $this->getIcingaStateCount(), + $this->getIcingaStateCount('ok'), + $this->getIcingaStateCount('critical'), + $this->getIcingaStateCount('warning'), + $this->getIcingaStateCount('unknown') + ) + ); + } + + private function getIcingaStateCount(string $icingaState = '') + { + return $icingaState === '' + ? Deployment::on(Database::connection())->count() + : Deployment::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); + } + + public function getUrl() + { + return 'kubernetes/deployments'; + } +} diff --git a/library/Kubernetes/Dashboard/EventDashlet.php b/library/Kubernetes/Dashboard/EventDashlet.php new file mode 100644 index 00000000..1e1412b3 --- /dev/null +++ b/library/Kubernetes/Dashboard/EventDashlet.php @@ -0,0 +1,29 @@ +translate('Events'); + } + + public function getSummary() + { + return $this->translate('Record changes and issues within the cluster'); + } + + public function getUrl() + { + return 'kubernetes/events'; + } +} diff --git a/library/Kubernetes/Dashboard/IngressDashlet.php b/library/Kubernetes/Dashboard/IngressDashlet.php new file mode 100644 index 00000000..03f3da60 --- /dev/null +++ b/library/Kubernetes/Dashboard/IngressDashlet.php @@ -0,0 +1,29 @@ +translate('Ingresses'); + } + + public function getSummary() + { + return $this->translate('Manage HTTP/HTTPS traffic into the cluster'); + } + + public function getUrl() + { + return 'kubernetes/ingresses'; + } +} diff --git a/library/Kubernetes/Dashboard/JobDashlet.php b/library/Kubernetes/Dashboard/JobDashlet.php new file mode 100644 index 00000000..eb909490 --- /dev/null +++ b/library/Kubernetes/Dashboard/JobDashlet.php @@ -0,0 +1,47 @@ +translate('Jobs'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total jobs, %s are in OK state, %s are Pending, %s are Critical, %s are in Warning + state, and %s are Unknown', + $this->getIcingaStateCount(), + $this->getIcingaStateCount('ok'), + $this->getIcingaStateCount('pending'), + $this->getIcingaStateCount('critical'), + $this->getIcingaStateCount('warning'), + $this->getIcingaStateCount('unknown') + ) + ); + } + + private function getIcingaStateCount(string $icingaState = '') + { + return $icingaState === '' + ? Job::on(Database::connection())->count() + : Job::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); + } + + public function getUrl() + { + return 'kubernetes/jobs'; + } +} diff --git a/library/Kubernetes/Dashboard/NamespaceDashlet.php b/library/Kubernetes/Dashboard/NamespaceDashlet.php new file mode 100644 index 00000000..7f812c9e --- /dev/null +++ b/library/Kubernetes/Dashboard/NamespaceDashlet.php @@ -0,0 +1,43 @@ +translate('Namespaces'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total namespaces, %s are Active, %s are Terminating', + $this->getPhaseCount(), + $this->getPhaseCount('Active'), + $this->getPhaseCount('Terminating') + ) + ); + } + + private function getPhaseCount(string $phase = '') + { + return $phase === '' + ? NamespaceModel::on(Database::connection())->count() + : NamespaceModel::on(Database::connection())->filter(Filter::equal('phase', $phase))->count(); + } + + public function getUrl() + { + return 'kubernetes/namespaces'; + } +} diff --git a/library/Kubernetes/Dashboard/NetworkingDashboard.php b/library/Kubernetes/Dashboard/NetworkingDashboard.php new file mode 100644 index 00000000..5caa03fe --- /dev/null +++ b/library/Kubernetes/Dashboard/NetworkingDashboard.php @@ -0,0 +1,18 @@ +translate('Networking'); + } +} diff --git a/library/Kubernetes/Dashboard/NodeDashlet.php b/library/Kubernetes/Dashboard/NodeDashlet.php new file mode 100644 index 00000000..15d2bc00 --- /dev/null +++ b/library/Kubernetes/Dashboard/NodeDashlet.php @@ -0,0 +1,46 @@ +translate('Nodes'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total nodes, %s are in OK state, %s are Critical, %s are in Warning state, and %s are + Unknown', + $this->getIcingaStateCount(), + $this->getIcingaStateCount('ok'), + $this->getIcingaStateCount('critical'), + $this->getIcingaStateCount('warning'), + $this->getIcingaStateCount('unknown') + ) + ); + } + + private function getIcingaStateCount(string $icingaState = '') + { + return $icingaState === '' + ? Node::on(Database::connection())->count() + : Node::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); + } + + public function getUrl() + { + return 'kubernetes/nodes'; + } +} diff --git a/library/Kubernetes/Dashboard/ObservabilityDashboard.php b/library/Kubernetes/Dashboard/ObservabilityDashboard.php new file mode 100644 index 00000000..92f75d99 --- /dev/null +++ b/library/Kubernetes/Dashboard/ObservabilityDashboard.php @@ -0,0 +1,18 @@ +translate('Observability'); + } +} diff --git a/library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php b/library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php new file mode 100644 index 00000000..5a071b64 --- /dev/null +++ b/library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php @@ -0,0 +1,44 @@ +translate('Persistent Volume Claims'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total persistent volume claims, %s are Bound, %s are Pending, %s are Lost', + $this->getPhaseCount(), + $this->getPhaseCount('Bound'), + $this->getPhaseCount('Pending'), + $this->getPhaseCount('Lost') + ) + ); + } + + private function getPhaseCount(string $phase = '') + { + return $phase === '' + ? PersistentVolumeClaim::on(Database::connection())->count() + : PersistentVolumeClaim::on(Database::connection())->filter(Filter::equal('phase', $phase))->count(); + } + + public function getUrl() + { + return 'kubernetes/persistent-volume-claims'; + } +} diff --git a/library/Kubernetes/Dashboard/PersistentVolumeDashlet.php b/library/Kubernetes/Dashboard/PersistentVolumeDashlet.php new file mode 100644 index 00000000..ffe4a31f --- /dev/null +++ b/library/Kubernetes/Dashboard/PersistentVolumeDashlet.php @@ -0,0 +1,47 @@ +translate('Persistent Volumes'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total persistent volumes, %s are Bound, %s are Available, %s are Pending, %s are + Released, %s are Failed', + $this->getPhaseCount(), + $this->getPhaseCount('Bound'), + $this->getPhaseCount('Available'), + $this->getPhaseCount('Pending'), + $this->getPhaseCount('Released'), + $this->getPhaseCount('Failed') + ) + ); + } + + private function getPhaseCount(string $phase = '') + { + return $phase === '' + ? PersistentVolume::on(Database::connection())->count() + : PersistentVolume::on(Database::connection())->filter(Filter::equal('phase', $phase))->count(); + } + + public function getUrl() + { + return 'kubernetes/persistent-volumes'; + } +} diff --git a/library/Kubernetes/Dashboard/PodDashlet.php b/library/Kubernetes/Dashboard/PodDashlet.php new file mode 100644 index 00000000..c8508f48 --- /dev/null +++ b/library/Kubernetes/Dashboard/PodDashlet.php @@ -0,0 +1,47 @@ +translate('Pods'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total pods, %s are in OK state, %s are Pending, %s are Critical, %s are in Warning + state, and %s are Unknown', + $this->getIcingaStateCount(), + $this->getIcingaStateCount('ok'), + $this->getIcingaStateCount('pending'), + $this->getIcingaStateCount('critical'), + $this->getIcingaStateCount('warning'), + $this->getIcingaStateCount('unknown') + ) + ); + } + + private function getIcingaStateCount(string $icingaState = '') + { + return $icingaState === '' + ? Pod::on(Database::connection())->count() + : Pod::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); + } + + public function getUrl() + { + return 'kubernetes/pods'; + } +} diff --git a/library/Kubernetes/Dashboard/ReplicaSetDashlet.php b/library/Kubernetes/Dashboard/ReplicaSetDashlet.php new file mode 100644 index 00000000..4395aa6c --- /dev/null +++ b/library/Kubernetes/Dashboard/ReplicaSetDashlet.php @@ -0,0 +1,46 @@ +translate('Replica Sets'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total replica sets, %s are in OK state, %s are Critical, %s are in Warning state, and + %s are Unknown', + $this->getIcingaStateCount(), + $this->getIcingaStateCount('ok'), + $this->getIcingaStateCount('critical'), + $this->getIcingaStateCount('warning'), + $this->getIcingaStateCount('unknown') + ) + ); + } + + private function getIcingaStateCount(string $icingaState = '') + { + return $icingaState === '' + ? ReplicaSet::on(Database::connection())->count() + : ReplicaSet::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); + } + + public function getUrl() + { + return 'kubernetes/replica-sets'; + } +} diff --git a/library/Kubernetes/Dashboard/SecretDashlet.php b/library/Kubernetes/Dashboard/SecretDashlet.php new file mode 100644 index 00000000..90f3b3cf --- /dev/null +++ b/library/Kubernetes/Dashboard/SecretDashlet.php @@ -0,0 +1,29 @@ +translate('Secrets'); + } + + public function getSummary() + { + return $this->translate('Store sensitive data (e.g., passwords, tokens) in an encrypted format'); + } + + public function getUrl() + { + return 'kubernetes/secrets'; + } +} diff --git a/library/Kubernetes/Dashboard/ServiceDashlet.php b/library/Kubernetes/Dashboard/ServiceDashlet.php new file mode 100644 index 00000000..71051faa --- /dev/null +++ b/library/Kubernetes/Dashboard/ServiceDashlet.php @@ -0,0 +1,31 @@ +translate('Services'); + } + + public function getSummary() + { + return $this->translate( + 'Expose Pods within or outside the cluster. Types: ClusterIP, NodePort, LoadBalancer, ExternalName' + ); + } + + public function getUrl() + { + return 'kubernetes/services'; + } +} diff --git a/library/Kubernetes/Dashboard/StatefulSetDashlet.php b/library/Kubernetes/Dashboard/StatefulSetDashlet.php new file mode 100644 index 00000000..cd7dcb19 --- /dev/null +++ b/library/Kubernetes/Dashboard/StatefulSetDashlet.php @@ -0,0 +1,46 @@ +translate('Stateful Sets'); + } + + public function getSummary() + { + return $this->translate( + sprintf( + 'Out of %s total stateful sets, %s are in OK state, %s are Critical, %s are in Warning state, + and %s are Unknown', + $this->getIcingaStateCount(), + $this->getIcingaStateCount('ok'), + $this->getIcingaStateCount('critical'), + $this->getIcingaStateCount('warning'), + $this->getIcingaStateCount('unknown') + ) + ); + } + + private function getIcingaStateCount(string $icingaState = '') + { + return $icingaState === '' + ? StatefulSet::on(Database::connection())->count() + : StatefulSet::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); + } + + public function getUrl() + { + return 'kubernetes/stateful-sets'; + } +} diff --git a/library/Kubernetes/Dashboard/StorageDashboard.php b/library/Kubernetes/Dashboard/StorageDashboard.php new file mode 100644 index 00000000..9df1ec7f --- /dev/null +++ b/library/Kubernetes/Dashboard/StorageDashboard.php @@ -0,0 +1,18 @@ +translate('Storage'); + } +} diff --git a/library/Kubernetes/Dashboard/WorkloadsDashboard.php b/library/Kubernetes/Dashboard/WorkloadsDashboard.php new file mode 100644 index 00000000..74c33b48 --- /dev/null +++ b/library/Kubernetes/Dashboard/WorkloadsDashboard.php @@ -0,0 +1,23 @@ +translate('Workloads'); + } +} From 216414b1bbd92e293d236c78cf69877866ad28b3 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:24:24 +0100 Subject: [PATCH 09/25] Introduce new trait `BeforeAssemble.php` --- library/Kubernetes/Common/BeforeAssemble.php | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 library/Kubernetes/Common/BeforeAssemble.php diff --git a/library/Kubernetes/Common/BeforeAssemble.php b/library/Kubernetes/Common/BeforeAssemble.php new file mode 100644 index 00000000..a9b7ae70 --- /dev/null +++ b/library/Kubernetes/Common/BeforeAssemble.php @@ -0,0 +1,30 @@ +hasBeenAssembled === false && ! $this->beforeAssemble) { + $this->beforeAssemble = true; + $this->beforeAssemble(); + } + + return parent::ensureAssembled(); + } + + /** + * Hook method to perform actions before assembling the object. + */ + protected function beforeAssemble(): void + { + } +} From 05863e6dfea43b12fd7290e4b18e25c473dc7c38 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:25:07 +0100 Subject: [PATCH 10/25] Introduce new class `FormatString.php` --- library/Kubernetes/Common/FormatString.php | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 library/Kubernetes/Common/FormatString.php diff --git a/library/Kubernetes/Common/FormatString.php b/library/Kubernetes/Common/FormatString.php new file mode 100644 index 00000000..228b2045 --- /dev/null +++ b/library/Kubernetes/Common/FormatString.php @@ -0,0 +1,48 @@ +args = array_merge($this->args, $args); + + return $this; + } + + /** + * Convert the format string to a formatted string using the provided arguments. + * + * @return string The formatted string. + */ + public function __toString(): string + { + return ! empty($this->args) ? vsprintf(strtr($this->format, $this->args), $this->args) : $this->format; + } +} From 1da42593a869799109ab122e33977352d31fe3ee Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:30:19 +0100 Subject: [PATCH 11/25] Rename function from `createUrl` to `createDetailUrl` --- library/Kubernetes/Web/EventListItem.php | 2 +- library/Kubernetes/Web/Factory.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/Kubernetes/Web/EventListItem.php b/library/Kubernetes/Web/EventListItem.php index c3b5e2ec..8a63130c 100644 --- a/library/Kubernetes/Web/EventListItem.php +++ b/library/Kubernetes/Web/EventListItem.php @@ -49,7 +49,7 @@ protected function assembleTitle(BaseHtmlElement $title): void $kind = strtolower($this->item->reference_kind); $icon = Factory::createIcon($kind); - $url = Factory::createUrl($kind); + $url = Factory::createDetailUrl($kind); if ($url !== null) { $content = new HtmlDocument(); diff --git a/library/Kubernetes/Web/Factory.php b/library/Kubernetes/Web/Factory.php index 2cd8ad59..82bcab9d 100644 --- a/library/Kubernetes/Web/Factory.php +++ b/library/Kubernetes/Web/Factory.php @@ -25,6 +25,7 @@ use ipl\Html\Attributes; use ipl\Html\HtmlElement; use ipl\Html\ValidHtml; +use ipl\Orm\Model; use ipl\Stdlib\Filter\Rule; use ipl\Web\Url; use ipl\Web\Widget\EmptyState; @@ -138,7 +139,7 @@ public static function createList(string $kind, Rule $filter): ValidHtml } } - public static function createUrl(string $kind): ?Url + public static function createDetailUrl(string $kind): ?Url { $kind = strtolower(str_replace(['_', '-'], '', $kind)); From 325d634997900dc60fa6e161dc390c52101f3184 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:33:32 +0100 Subject: [PATCH 12/25] Factory.php: Add new `createListUrl` function --- library/Kubernetes/Web/Factory.php | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/library/Kubernetes/Web/Factory.php b/library/Kubernetes/Web/Factory.php index 82bcab9d..03e61dc6 100644 --- a/library/Kubernetes/Web/Factory.php +++ b/library/Kubernetes/Web/Factory.php @@ -164,4 +164,36 @@ public static function createDetailUrl(string $kind): ?Url default => null }; } + + public static function createListUrl(string $kind): ?Url + { + $kind = strtolower(str_replace(['_', '-'], '', $kind)); + + $controller = match ($kind) { + 'configmap', + 'container', + 'cronjob', + 'daemonset', + 'deployment', + 'event', + 'job', + 'namespace', + 'node', + 'persistentvolume', + 'persistentvolumeclaim', + 'pod', + 'replicaset', + 'secret', + 'service', + 'statefulset' => "{$kind}s", + 'ingress' => 'ingresses', + default => null + }; + + if ($controller !== null) { + return Url::fromPath("kubernetes/$controller"); + } + + return null; + } } From 37317fb95f92a6a906c446dcae00089d7e56e78b Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:34:24 +0100 Subject: [PATCH 13/25] Factory.php: Add new `createModel` function --- library/Kubernetes/Web/Factory.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/library/Kubernetes/Web/Factory.php b/library/Kubernetes/Web/Factory.php index 03e61dc6..c3b29257 100644 --- a/library/Kubernetes/Web/Factory.php +++ b/library/Kubernetes/Web/Factory.php @@ -139,6 +139,31 @@ public static function createList(string $kind, Rule $filter): ValidHtml } } + public static function createModel(string $kind): ?Model + { + $kind = strtolower(str_replace(['_', '-'], '', $kind)); + + return match ($kind) { + 'configmap' => new ConfigMap(), + 'cronjob' => new CronJob(), + 'daemonset' => new DaemonSet(), + 'deployment' => new Deployment(), + 'event' => new Event(), + 'ingress' => new Ingress(), + 'job' => new Job(), + 'namespace' => new NamespaceModel(), + 'node' => new Node(), + 'persistentvolume' => new PersistentVolume(), + 'persistentvolumeclaim' => new PersistentVolumeClaim(), + 'pod' => new Pod(), + 'replicaset' => new ReplicaSet(), + 'secret' => new Secret(), + 'service' => new Service(), + 'statefulset' => new StatefulSet(), + default => null, + }; + } + public static function createDetailUrl(string $kind): ?Url { $kind = strtolower(str_replace(['_', '-'], '', $kind)); From 745611a88b6ac0645687517fc621555247da676f Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:36:18 +0100 Subject: [PATCH 14/25] Introduce new class `IcingaStateDashlet` --- .../Dashboard/IcingaStateDashlet.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 library/Kubernetes/Dashboard/IcingaStateDashlet.php diff --git a/library/Kubernetes/Dashboard/IcingaStateDashlet.php b/library/Kubernetes/Dashboard/IcingaStateDashlet.php new file mode 100644 index 00000000..556f5408 --- /dev/null +++ b/library/Kubernetes/Dashboard/IcingaStateDashlet.php @@ -0,0 +1,50 @@ +kind)::on(Database::connection())) + ->columns([ + 'icinga_state', + 'count' => new Expression('COUNT(*)') + ]); + + $q->getSelectBase()->groupBy('icinga_state'); + + $counts = array_fill_keys( + [ + 'ok', + 'warning', + 'critical', + 'unknown', + 'pending' + ], + 0 + ); + + foreach ($q as $count) { + $counts[$count->icinga_state] = $count->count; + } + + $counts['total'] = array_sum($counts); + + return array_combine( + array_map(fn($key) => '{' . $key . '}', array_keys($counts)), + $counts + ); + } + + protected function beforeAssemble(): void + { + $this->summary->addArgs($this->getIcingaStateCounts()); + } +} From 3f2628fe08a6d6982ea76bf78d16f5b7f0bf0079 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:37:34 +0100 Subject: [PATCH 15/25] Introduce new class `KubernetesPhaseDashlet` --- .../Dashboard/KubernetesPhaseDashlet.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php diff --git a/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php b/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php new file mode 100644 index 00000000..21873443 --- /dev/null +++ b/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php @@ -0,0 +1,53 @@ +kind)::on(Database::connection())) + ->columns([ + 'phase', + 'count' => new Expression('COUNT(*)') + ]); + + $q->getSelectBase()->groupBy('phase'); + + $counts = array_fill_keys( + [ + 'Bound', + 'Failed', + 'Lost', + 'Released', + 'Available', + 'Active', + 'Terminating', + 'Pending', + ], + 0 + ); + + foreach ($q as $count) { + $counts[$count->phase] = $count->count; + } + + $counts['total'] = array_sum($counts); + + return array_combine( + array_map(fn($key) => '{' . $key . '}', array_keys($counts)), + $counts + ); + } + + protected function beforeAssemble(): void + { + $this->summary->addArgs($this->getKubernetesPhaseCounts()); + } +} From 2ff420aa44293ee4056a41c67392e1ab5fc30add Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:40:59 +0100 Subject: [PATCH 16/25] Refactor `DashboardController.php` --- .../controllers/DashboardController.php | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index 142d41d8..2d0778e6 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -4,31 +4,27 @@ namespace Icinga\Module\Kubernetes\Controllers; -use Icinga\Module\Kubernetes\Dashboard\Dashboard; +use Icinga\Module\Kubernetes\Dashboard\ClusterManagementDashboard; +use Icinga\Module\Kubernetes\Dashboard\ConfigurationDashboard; +use Icinga\Module\Kubernetes\Dashboard\NetworkingDashboard; +use Icinga\Module\Kubernetes\Dashboard\ObservabilityDashboard; +use Icinga\Module\Kubernetes\Dashboard\StorageDashboard; +use Icinga\Module\Kubernetes\Dashboard\WorkloadsDashboard; use Icinga\Module\Kubernetes\Web\Controller; -use ipl\Web\Compat\CompatForm; class DashboardController extends Controller { - public function indexAction() + public function indexAction(): void { - $mainDashboards = [ - 'Workloads', - 'Networking', - 'Storage', - 'Configuration', - 'ClusterManagement', - 'Observability', - 'Additional' - ]; - $this->addTitleTab($this->translate('Kubernetes')); - $names = $this->params->getValues('name', $mainDashboards); - - foreach ($names as $name) { - $dashboard = Dashboard::loadByName($name); - $this->addContent($dashboard); - } + $this->content->addHtml( + new ClusterManagementDashboard(), + new WorkloadsDashboard(), + new StorageDashboard(), + new NetworkingDashboard(), + new ConfigurationDashboard(), + new ObservabilityDashboard(), + ); } } From 2ee30b2742d24433ca824bb937d380832501418a Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:43:29 +0100 Subject: [PATCH 17/25] Refactor `Dashboard.php` --- library/Kubernetes/Dashboard/Dashboard.php | 94 ++++------------------ 1 file changed, 15 insertions(+), 79 deletions(-) diff --git a/library/Kubernetes/Dashboard/Dashboard.php b/library/Kubernetes/Dashboard/Dashboard.php index ce1aa5ce..f037096f 100644 --- a/library/Kubernetes/Dashboard/Dashboard.php +++ b/library/Kubernetes/Dashboard/Dashboard.php @@ -4,92 +4,28 @@ namespace Icinga\Module\Kubernetes\Dashboard; -use Icinga\Module\Kubernetes\Common\Database; -use ipl\Html\Html; -use ipl\Html\HtmlDocument; +use Icinga\Module\Kubernetes\Common\BeforeAssemble; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\Text; use ipl\I18n\Translation; -abstract class Dashboard extends HtmlDocument +abstract class Dashboard extends BaseHtmlElement { + use BeforeAssemble; use Translation; - /** @var string */ - protected $name; + protected $tag = 'ul'; - /** @var Dashlet[] */ - protected $dashlets; + abstract protected function getTitle(): string; - protected $dashletNames; - - /** - * @param string $name - * - * @return self - */ - public static function loadByName(string $name) - { - $class = __NAMESPACE__ . '\\' . ucfirst($name) . 'Dashboard'; - $dashboard = new $class(); - $dashboard->name = $name; - - return $dashboard; - } - - public static function exists($name) - { - return class_exists(__NAMESPACE__ . '\\' . ucfirst($name) . 'Dashboard'); - } - - public function render() - { - $this - ->setSeparator("\n") - ->add(Html::tag('h1', null, $this->getTitle())) - ->add($this->renderDashlets()); - - return parent::render(); - } - - public function renderDashlets() - { - $ul = Html::tag('ul', [ - 'class' => 'main-actions', - 'data-base-target' => '_self' - ]); - - foreach ($this->dashlets() as $dashlet) { - $ul->add($dashlet); - } - - return $ul; - } - - abstract public function getTitle(); - - public function dashlets() - { - if ($this->dashlets === null) { - $this->loadDashlets(); - } - - return $this->dashlets; - } - - public function loadDashlets() - { - $names = $this->getDashletNames(); - - if (empty($names)) { - $this->dashlets = array(); - } else { - $this->dashlets = Dashlet::loadByNames( - $this->dashletNames, - ); - } - } - - public function getDashletNames() + protected function beforeAssemble(): void { - return $this->dashletNames; + $this->setWrapper(new HtmlElement( + 'section', + new Attributes(['class' => 'kubernetes-dashboard']), + new HtmlElement('h2', null, new Text($this->getTitle())) + )); } } From f0590c0fca6594645d04cbb926fe21f85e422d4b Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:47:00 +0100 Subject: [PATCH 18/25] Refactor `Dashlet.php` --- library/Kubernetes/Dashboard/Dashlet.php | 77 ++++++++---------------- 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/library/Kubernetes/Dashboard/Dashlet.php b/library/Kubernetes/Dashboard/Dashlet.php index ca640e87..229a8f8d 100644 --- a/library/Kubernetes/Dashboard/Dashlet.php +++ b/library/Kubernetes/Dashboard/Dashlet.php @@ -4,72 +4,47 @@ namespace Icinga\Module\Kubernetes\Dashboard; -use ipl\Html\Attributes; +use Icinga\Module\Kubernetes\Common\BeforeAssemble; +use Icinga\Module\Kubernetes\Common\FormatString; +use Icinga\Module\Kubernetes\Web\Factory; use ipl\Html\BaseHtmlElement; -use ipl\Html\HtmlDocument; use ipl\Html\HtmlElement; use ipl\Html\Text; use ipl\I18n\Translation; use ipl\Web\Widget\Link; -abstract class Dashlet extends BaseHtmlElement +class Dashlet extends BaseHtmlElement { + use BeforeAssemble; use Translation; protected $tag = 'li'; - /** - * @param $name - * - * @return mixed - */ - public static function loadByName($name) - { - /** @var Dashlet */ - $class = __NAMESPACE__ . '\\' . $name . 'Dashlet'; - - return new $class(); - } - - public static function loadByNames(array $names) - { - $dashlets = []; - foreach ($names as $name) { - $dashlet = static::loadByName($name); - - $dashlets[] = $dashlet; - } + protected FormatString $summary; - return $dashlets; + public function __construct( + protected string $kind, + protected string $title, + string $summary, + protected ?string $url = null + ) { + $this->url = $url !== null ? $url : Factory::createListUrl($kind); + $this->summary = new FormatString($summary); } - public function getIconName() - { - return $this->icon; - } - - abstract public function getTitle(); - - abstract public function getUrl(); - - protected function assemble() + protected function assemble(): void { - $this->addHtml(( - new Link( - [ - $this->getTitle(), - new HtmlElement( - 'i', - new Attributes(['class' => $this->getIconName()]) - ), - new HtmlElement( - 'p', - null, - new Text($this->getSummary()) - ) - ], - $this->getUrl(), - ) + $this->addHtml(new Link( + [ + $this->title, + Factory::createIcon($this->kind), + new HtmlElement( + 'p', + null, + new Text($this->summary) + ) + ], + $this->url )); } } From 89979685bd340b12d269712c14ea878e9a64d2f3 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:51:59 +0100 Subject: [PATCH 19/25] Refactor `*.Dashboard` classes --- .../Dashboard/ClusterManagementDashboard.php | 28 ++++++-- .../Dashboard/ConfigurationDashboard.php | 23 ++++-- .../Dashboard/NetworkingDashboard.php | 35 +++++++-- .../Dashboard/ObservabilityDashboard.php | 18 +++-- .../Kubernetes/Dashboard/StorageDashboard.php | 29 ++++++-- .../Dashboard/WorkloadsDashboard.php | 71 ++++++++++++++++--- 6 files changed, 163 insertions(+), 41 deletions(-) diff --git a/library/Kubernetes/Dashboard/ClusterManagementDashboard.php b/library/Kubernetes/Dashboard/ClusterManagementDashboard.php index 90739664..ed21fbe9 100644 --- a/library/Kubernetes/Dashboard/ClusterManagementDashboard.php +++ b/library/Kubernetes/Dashboard/ClusterManagementDashboard.php @@ -6,13 +6,29 @@ class ClusterManagementDashboard extends Dashboard { - protected $dashletNames = [ - 'Namespace', - 'Node' - ]; - - public function getTitle() + public function getTitle(): string { return $this->translate('Cluster management'); } + + protected function assemble(): void + { + $this->addHtml( + new KubernetesPhaseDashlet( + 'namespace', + $this->translate('Namespaces'), + $this->translate( + 'Out of {total} total Namespaces, {Active} are Active, {Terminating} are Terminating.' + ) + ), + new IcingaStateDashlet( + 'node', + $this->translate('Nodes'), + $this->translate( + 'Out of {total} total Nodes, {ok} are in OK state, {critical} are Critical, {warning} are + in Warning state, and {unknown} are Unknown.' + ) + ) + ); + } } diff --git a/library/Kubernetes/Dashboard/ConfigurationDashboard.php b/library/Kubernetes/Dashboard/ConfigurationDashboard.php index 5b162060..1097515e 100644 --- a/library/Kubernetes/Dashboard/ConfigurationDashboard.php +++ b/library/Kubernetes/Dashboard/ConfigurationDashboard.php @@ -6,13 +6,24 @@ class ConfigurationDashboard extends Dashboard { - protected $dashletNames = [ - 'ConfigMap', - 'Secret' - ]; - - public function getTitle() + public function getTitle(): string { return $this->translate('Configuration'); } + + protected function assemble(): void + { + $this->addHtml( + new Dashlet( + 'configmap', + $this->translate('Config Maps'), + $this->translate('Store configuration data as key-value pairs.') + ), + new Dashlet( + 'secret', + $this->translate('Secrets'), + $this->translate('Store sensitive data (e.g., passwords, tokens) in an encrypted format.') + ) + ); + } } diff --git a/library/Kubernetes/Dashboard/NetworkingDashboard.php b/library/Kubernetes/Dashboard/NetworkingDashboard.php index 5caa03fe..8f1e3578 100644 --- a/library/Kubernetes/Dashboard/NetworkingDashboard.php +++ b/library/Kubernetes/Dashboard/NetworkingDashboard.php @@ -6,13 +6,36 @@ class NetworkingDashboard extends Dashboard { - protected $dashletNames = [ - 'Service', - 'Ingress' - ]; - - public function getTitle() + protected function getTitle(): string { return $this->translate('Networking'); } + + protected function assemble(): void + { + $this->addHtml( + new Dashlet( + 'service', + $this->translate('Services'), + $this->translate( + 'Expose Pods within or outside the cluster. Types: ClusterIP, NodePort, LoadBalancer, + ExternalName.' + ) + ), + new Dashlet( + 'service', + $this->translate('Cluster Services'), + $this->translate( + "Core components that manage and support the Kubernetes control plane, networking, and + storage, ensuring the cluster's overall health and operation." + ), + 'kubernetes/services?label.name=kubernetes.io%2Fcluster-service&label.value=true', + ), + new Dashlet( + 'ingress', + $this->translate('Ingresses'), + $this->translate('Manage HTTP/HTTPS traffic into the cluster.') + ), + ); + } } diff --git a/library/Kubernetes/Dashboard/ObservabilityDashboard.php b/library/Kubernetes/Dashboard/ObservabilityDashboard.php index 92f75d99..4e57f236 100644 --- a/library/Kubernetes/Dashboard/ObservabilityDashboard.php +++ b/library/Kubernetes/Dashboard/ObservabilityDashboard.php @@ -6,13 +6,19 @@ class ObservabilityDashboard extends Dashboard { - protected $dashletNames = [ - 'Event', - // TODO: Metrics - ]; - - public function getTitle() + public function getTitle(): string { return $this->translate('Observability'); } + + protected function assemble(): void + { + $this->addHtml( + new Dashlet( + 'event', + $this->translate('Events'), + $this->translate('Record changes and issues within the cluster.') + ) + ); + } } diff --git a/library/Kubernetes/Dashboard/StorageDashboard.php b/library/Kubernetes/Dashboard/StorageDashboard.php index 9df1ec7f..d6da8222 100644 --- a/library/Kubernetes/Dashboard/StorageDashboard.php +++ b/library/Kubernetes/Dashboard/StorageDashboard.php @@ -6,13 +6,30 @@ class StorageDashboard extends Dashboard { - protected $dashletNames = [ - 'PersistentVolume', - 'PersistentVolumeClaim' - ]; - - public function getTitle() + public function getTitle(): string { return $this->translate('Storage'); } + + protected function assemble(): void + { + $this->addHtml( + new KubernetesPhaseDashlet( + 'persistentvolume', + $this->translate('Persistent Volumes'), + $this->translate( + 'Out of {total} total Persistent Volumes, {Bound} are Bound, {Available} are Available, + {Pending} are Pending, {Released} are Released, and {Failed} are Failed.' + ) + ), + new KubernetesPhaseDashlet( + 'persistentvolumeclaim', + $this->translate('PVCs'), + $this->translate( + 'Out of {total} total Persistent Volume Claims, {Bound} are Bound, {Pending} are Pending, + {Lost} are Lost.' + ) + ) + ); + } } diff --git a/library/Kubernetes/Dashboard/WorkloadsDashboard.php b/library/Kubernetes/Dashboard/WorkloadsDashboard.php index 74c33b48..db18f2ef 100644 --- a/library/Kubernetes/Dashboard/WorkloadsDashboard.php +++ b/library/Kubernetes/Dashboard/WorkloadsDashboard.php @@ -6,18 +6,67 @@ class WorkloadsDashboard extends Dashboard { - protected $dashletNames = [ - 'Cronjob', - 'DaemonSet', - 'Deployment', - 'Job', - 'Pod', - 'ReplicaSet', - 'StatefulSet' - ]; - - public function getTitle() + protected function getTitle(): string { return $this->translate('Workloads'); } + + protected function assemble(): void + { + $this->addHtml( + new Dashlet( + 'cronjob', + $this->translate('Cron Jobs'), + $this->translate('Schedule Jobs to run at specific times.') + ), + new IcingaStateDashlet( + 'daemonset', + $this->translate('Daemon Sets'), + $this->translate( + 'Out of {total} total Daemon Sets, {ok} are in OK state, {critical} are Critical, + {warning} are in Warning state, and {unknown} are Unknown.' + ) + ), + new IcingaStateDashlet( + 'deployment', + $this->translate('Deployments'), + $this->translate( + 'Out of {total} total Deployments, {ok} are in OK state, {critical} are Critical, + {warning} are in Warning state, and {unknown} are Unknown.' + ) + ), + new IcingaStateDashlet( + 'job', + $this->translate('Jobs'), + $this->translate( + 'Out of {total} total Jobs, {ok} are in OK state, {critical} are Critical, {warning} are in + Warning state, {unknown} are Unknown and {pending} are in Pending State.' + ) + ), + new IcingaStateDashlet( + 'pod', + $this->translate('Pods'), + $this->translate( + 'Out of {total} total Pods, {ok} are in OK state, {critical} are Critical, {warning} are in + Warning state, {unknown} are Unknown, and {pending} are in Pending State.' + ) + ), + new IcingaStateDashlet( + 'replicaset', + $this->translate('Replica Sets'), + $this->translate( + 'Out of {total} total Replica Sets, {ok} are in OK state, {critical} are Critical, + {warning} are in Warning state, and {unknown} are Unknown.' + ) + ), + new IcingaStateDashlet( + 'statefulset', + $this->translate('Stateful Sets'), + $this->translate( + 'Out of {total} total Stateful Sets, {ok} are in OK state, {critical} are Critical, + {warning} are in Warning state, and {unknown} are Unknown.' + ) + ) + ); + } } From 13cdd68d9ce55ac41e83257d48be059ffa8d4410 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 11:54:58 +0100 Subject: [PATCH 20/25] Delete unnecessary classes --- .../Dashboard/AdditionalDashboard.php | 17 ------- .../Dashboard/ClusterServiceDashlet.php | 32 ------------- .../Kubernetes/Dashboard/ConfigMapDashlet.php | 29 ------------ .../Kubernetes/Dashboard/CronJobDashlet.php | 29 ------------ .../Kubernetes/Dashboard/DaemonSetDashlet.php | 46 ------------------ .../Dashboard/DeploymentDashlet.php | 46 ------------------ library/Kubernetes/Dashboard/EventDashlet.php | 29 ------------ .../Kubernetes/Dashboard/IngressDashlet.php | 29 ------------ library/Kubernetes/Dashboard/JobDashlet.php | 47 ------------------- .../Kubernetes/Dashboard/NamespaceDashlet.php | 43 ----------------- library/Kubernetes/Dashboard/NodeDashlet.php | 46 ------------------ .../PersistentVolumeClaimDashlet.php | 44 ----------------- .../Dashboard/PersistentVolumeDashlet.php | 47 ------------------- library/Kubernetes/Dashboard/PodDashlet.php | 47 ------------------- .../Dashboard/ReplicaSetDashlet.php | 46 ------------------ .../Kubernetes/Dashboard/SecretDashlet.php | 29 ------------ .../Kubernetes/Dashboard/ServiceDashlet.php | 31 ------------ .../Dashboard/StatefulSetDashlet.php | 46 ------------------ 18 files changed, 683 deletions(-) delete mode 100644 library/Kubernetes/Dashboard/AdditionalDashboard.php delete mode 100644 library/Kubernetes/Dashboard/ClusterServiceDashlet.php delete mode 100644 library/Kubernetes/Dashboard/ConfigMapDashlet.php delete mode 100644 library/Kubernetes/Dashboard/CronJobDashlet.php delete mode 100644 library/Kubernetes/Dashboard/DaemonSetDashlet.php delete mode 100644 library/Kubernetes/Dashboard/DeploymentDashlet.php delete mode 100644 library/Kubernetes/Dashboard/EventDashlet.php delete mode 100644 library/Kubernetes/Dashboard/IngressDashlet.php delete mode 100644 library/Kubernetes/Dashboard/JobDashlet.php delete mode 100644 library/Kubernetes/Dashboard/NamespaceDashlet.php delete mode 100644 library/Kubernetes/Dashboard/NodeDashlet.php delete mode 100644 library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php delete mode 100644 library/Kubernetes/Dashboard/PersistentVolumeDashlet.php delete mode 100644 library/Kubernetes/Dashboard/PodDashlet.php delete mode 100644 library/Kubernetes/Dashboard/ReplicaSetDashlet.php delete mode 100644 library/Kubernetes/Dashboard/SecretDashlet.php delete mode 100644 library/Kubernetes/Dashboard/ServiceDashlet.php delete mode 100644 library/Kubernetes/Dashboard/StatefulSetDashlet.php diff --git a/library/Kubernetes/Dashboard/AdditionalDashboard.php b/library/Kubernetes/Dashboard/AdditionalDashboard.php deleted file mode 100644 index 03ebc654..00000000 --- a/library/Kubernetes/Dashboard/AdditionalDashboard.php +++ /dev/null @@ -1,17 +0,0 @@ -translate('Additional Categories'); - } -} diff --git a/library/Kubernetes/Dashboard/ClusterServiceDashlet.php b/library/Kubernetes/Dashboard/ClusterServiceDashlet.php deleted file mode 100644 index d5c6a9ae..00000000 --- a/library/Kubernetes/Dashboard/ClusterServiceDashlet.php +++ /dev/null @@ -1,32 +0,0 @@ -translate('Cluster Services'); - } - - public function getSummary() - { - return $this->translate( - "Core components that manage and support the Kubernetes control plane, networking, and storage, - ensuring the cluster's overall health and operation" - ); - } - - public function getUrl() - { - return 'kubernetes/services?label.name=kubernetes.io%2Fcluster-service&label.value=true'; - } -} diff --git a/library/Kubernetes/Dashboard/ConfigMapDashlet.php b/library/Kubernetes/Dashboard/ConfigMapDashlet.php deleted file mode 100644 index f3325688..00000000 --- a/library/Kubernetes/Dashboard/ConfigMapDashlet.php +++ /dev/null @@ -1,29 +0,0 @@ -translate('Config Maps'); - } - - public function getSummary() - { - return $this->translate('Store configuration data as key-value pairs'); - } - - public function getUrl() - { - return 'kubernetes/config-maps'; - } -} diff --git a/library/Kubernetes/Dashboard/CronJobDashlet.php b/library/Kubernetes/Dashboard/CronJobDashlet.php deleted file mode 100644 index 3e557e94..00000000 --- a/library/Kubernetes/Dashboard/CronJobDashlet.php +++ /dev/null @@ -1,29 +0,0 @@ -translate('Cron Jobs'); - } - - public function getSummary() - { - return $this->translate('Schedule Jobs to run at specific times'); - } - - public function getUrl() - { - return 'kubernetes/cron-jobs'; - } -} diff --git a/library/Kubernetes/Dashboard/DaemonSetDashlet.php b/library/Kubernetes/Dashboard/DaemonSetDashlet.php deleted file mode 100644 index 213f1fc7..00000000 --- a/library/Kubernetes/Dashboard/DaemonSetDashlet.php +++ /dev/null @@ -1,46 +0,0 @@ -translate('Daemon Sets'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total daemon sets, %s are in OK state, %s are Critical, %s are in Warning state, and - %s are Unknown', - $this->getIcingaStateCount(), - $this->getIcingaStateCount('ok'), - $this->getIcingaStateCount('critical'), - $this->getIcingaStateCount('warning'), - $this->getIcingaStateCount('unknown') - ) - ); - } - - private function getIcingaStateCount(string $icingaState = '') - { - return $icingaState === '' - ? DaemonSet::on(Database::connection())->count() - : DaemonSet::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); - } - - public function getUrl() - { - return 'kubernetes/daemon-sets'; - } -} diff --git a/library/Kubernetes/Dashboard/DeploymentDashlet.php b/library/Kubernetes/Dashboard/DeploymentDashlet.php deleted file mode 100644 index d805e294..00000000 --- a/library/Kubernetes/Dashboard/DeploymentDashlet.php +++ /dev/null @@ -1,46 +0,0 @@ -translate('Deployments'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total deployments, %s are in OK state, %s are Critical, %s are in Warning state, and - %s are Unknown', - $this->getIcingaStateCount(), - $this->getIcingaStateCount('ok'), - $this->getIcingaStateCount('critical'), - $this->getIcingaStateCount('warning'), - $this->getIcingaStateCount('unknown') - ) - ); - } - - private function getIcingaStateCount(string $icingaState = '') - { - return $icingaState === '' - ? Deployment::on(Database::connection())->count() - : Deployment::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); - } - - public function getUrl() - { - return 'kubernetes/deployments'; - } -} diff --git a/library/Kubernetes/Dashboard/EventDashlet.php b/library/Kubernetes/Dashboard/EventDashlet.php deleted file mode 100644 index 1e1412b3..00000000 --- a/library/Kubernetes/Dashboard/EventDashlet.php +++ /dev/null @@ -1,29 +0,0 @@ -translate('Events'); - } - - public function getSummary() - { - return $this->translate('Record changes and issues within the cluster'); - } - - public function getUrl() - { - return 'kubernetes/events'; - } -} diff --git a/library/Kubernetes/Dashboard/IngressDashlet.php b/library/Kubernetes/Dashboard/IngressDashlet.php deleted file mode 100644 index 03f3da60..00000000 --- a/library/Kubernetes/Dashboard/IngressDashlet.php +++ /dev/null @@ -1,29 +0,0 @@ -translate('Ingresses'); - } - - public function getSummary() - { - return $this->translate('Manage HTTP/HTTPS traffic into the cluster'); - } - - public function getUrl() - { - return 'kubernetes/ingresses'; - } -} diff --git a/library/Kubernetes/Dashboard/JobDashlet.php b/library/Kubernetes/Dashboard/JobDashlet.php deleted file mode 100644 index eb909490..00000000 --- a/library/Kubernetes/Dashboard/JobDashlet.php +++ /dev/null @@ -1,47 +0,0 @@ -translate('Jobs'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total jobs, %s are in OK state, %s are Pending, %s are Critical, %s are in Warning - state, and %s are Unknown', - $this->getIcingaStateCount(), - $this->getIcingaStateCount('ok'), - $this->getIcingaStateCount('pending'), - $this->getIcingaStateCount('critical'), - $this->getIcingaStateCount('warning'), - $this->getIcingaStateCount('unknown') - ) - ); - } - - private function getIcingaStateCount(string $icingaState = '') - { - return $icingaState === '' - ? Job::on(Database::connection())->count() - : Job::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); - } - - public function getUrl() - { - return 'kubernetes/jobs'; - } -} diff --git a/library/Kubernetes/Dashboard/NamespaceDashlet.php b/library/Kubernetes/Dashboard/NamespaceDashlet.php deleted file mode 100644 index 7f812c9e..00000000 --- a/library/Kubernetes/Dashboard/NamespaceDashlet.php +++ /dev/null @@ -1,43 +0,0 @@ -translate('Namespaces'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total namespaces, %s are Active, %s are Terminating', - $this->getPhaseCount(), - $this->getPhaseCount('Active'), - $this->getPhaseCount('Terminating') - ) - ); - } - - private function getPhaseCount(string $phase = '') - { - return $phase === '' - ? NamespaceModel::on(Database::connection())->count() - : NamespaceModel::on(Database::connection())->filter(Filter::equal('phase', $phase))->count(); - } - - public function getUrl() - { - return 'kubernetes/namespaces'; - } -} diff --git a/library/Kubernetes/Dashboard/NodeDashlet.php b/library/Kubernetes/Dashboard/NodeDashlet.php deleted file mode 100644 index 15d2bc00..00000000 --- a/library/Kubernetes/Dashboard/NodeDashlet.php +++ /dev/null @@ -1,46 +0,0 @@ -translate('Nodes'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total nodes, %s are in OK state, %s are Critical, %s are in Warning state, and %s are - Unknown', - $this->getIcingaStateCount(), - $this->getIcingaStateCount('ok'), - $this->getIcingaStateCount('critical'), - $this->getIcingaStateCount('warning'), - $this->getIcingaStateCount('unknown') - ) - ); - } - - private function getIcingaStateCount(string $icingaState = '') - { - return $icingaState === '' - ? Node::on(Database::connection())->count() - : Node::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); - } - - public function getUrl() - { - return 'kubernetes/nodes'; - } -} diff --git a/library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php b/library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php deleted file mode 100644 index 5a071b64..00000000 --- a/library/Kubernetes/Dashboard/PersistentVolumeClaimDashlet.php +++ /dev/null @@ -1,44 +0,0 @@ -translate('Persistent Volume Claims'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total persistent volume claims, %s are Bound, %s are Pending, %s are Lost', - $this->getPhaseCount(), - $this->getPhaseCount('Bound'), - $this->getPhaseCount('Pending'), - $this->getPhaseCount('Lost') - ) - ); - } - - private function getPhaseCount(string $phase = '') - { - return $phase === '' - ? PersistentVolumeClaim::on(Database::connection())->count() - : PersistentVolumeClaim::on(Database::connection())->filter(Filter::equal('phase', $phase))->count(); - } - - public function getUrl() - { - return 'kubernetes/persistent-volume-claims'; - } -} diff --git a/library/Kubernetes/Dashboard/PersistentVolumeDashlet.php b/library/Kubernetes/Dashboard/PersistentVolumeDashlet.php deleted file mode 100644 index ffe4a31f..00000000 --- a/library/Kubernetes/Dashboard/PersistentVolumeDashlet.php +++ /dev/null @@ -1,47 +0,0 @@ -translate('Persistent Volumes'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total persistent volumes, %s are Bound, %s are Available, %s are Pending, %s are - Released, %s are Failed', - $this->getPhaseCount(), - $this->getPhaseCount('Bound'), - $this->getPhaseCount('Available'), - $this->getPhaseCount('Pending'), - $this->getPhaseCount('Released'), - $this->getPhaseCount('Failed') - ) - ); - } - - private function getPhaseCount(string $phase = '') - { - return $phase === '' - ? PersistentVolume::on(Database::connection())->count() - : PersistentVolume::on(Database::connection())->filter(Filter::equal('phase', $phase))->count(); - } - - public function getUrl() - { - return 'kubernetes/persistent-volumes'; - } -} diff --git a/library/Kubernetes/Dashboard/PodDashlet.php b/library/Kubernetes/Dashboard/PodDashlet.php deleted file mode 100644 index c8508f48..00000000 --- a/library/Kubernetes/Dashboard/PodDashlet.php +++ /dev/null @@ -1,47 +0,0 @@ -translate('Pods'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total pods, %s are in OK state, %s are Pending, %s are Critical, %s are in Warning - state, and %s are Unknown', - $this->getIcingaStateCount(), - $this->getIcingaStateCount('ok'), - $this->getIcingaStateCount('pending'), - $this->getIcingaStateCount('critical'), - $this->getIcingaStateCount('warning'), - $this->getIcingaStateCount('unknown') - ) - ); - } - - private function getIcingaStateCount(string $icingaState = '') - { - return $icingaState === '' - ? Pod::on(Database::connection())->count() - : Pod::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); - } - - public function getUrl() - { - return 'kubernetes/pods'; - } -} diff --git a/library/Kubernetes/Dashboard/ReplicaSetDashlet.php b/library/Kubernetes/Dashboard/ReplicaSetDashlet.php deleted file mode 100644 index 4395aa6c..00000000 --- a/library/Kubernetes/Dashboard/ReplicaSetDashlet.php +++ /dev/null @@ -1,46 +0,0 @@ -translate('Replica Sets'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total replica sets, %s are in OK state, %s are Critical, %s are in Warning state, and - %s are Unknown', - $this->getIcingaStateCount(), - $this->getIcingaStateCount('ok'), - $this->getIcingaStateCount('critical'), - $this->getIcingaStateCount('warning'), - $this->getIcingaStateCount('unknown') - ) - ); - } - - private function getIcingaStateCount(string $icingaState = '') - { - return $icingaState === '' - ? ReplicaSet::on(Database::connection())->count() - : ReplicaSet::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); - } - - public function getUrl() - { - return 'kubernetes/replica-sets'; - } -} diff --git a/library/Kubernetes/Dashboard/SecretDashlet.php b/library/Kubernetes/Dashboard/SecretDashlet.php deleted file mode 100644 index 90f3b3cf..00000000 --- a/library/Kubernetes/Dashboard/SecretDashlet.php +++ /dev/null @@ -1,29 +0,0 @@ -translate('Secrets'); - } - - public function getSummary() - { - return $this->translate('Store sensitive data (e.g., passwords, tokens) in an encrypted format'); - } - - public function getUrl() - { - return 'kubernetes/secrets'; - } -} diff --git a/library/Kubernetes/Dashboard/ServiceDashlet.php b/library/Kubernetes/Dashboard/ServiceDashlet.php deleted file mode 100644 index 71051faa..00000000 --- a/library/Kubernetes/Dashboard/ServiceDashlet.php +++ /dev/null @@ -1,31 +0,0 @@ -translate('Services'); - } - - public function getSummary() - { - return $this->translate( - 'Expose Pods within or outside the cluster. Types: ClusterIP, NodePort, LoadBalancer, ExternalName' - ); - } - - public function getUrl() - { - return 'kubernetes/services'; - } -} diff --git a/library/Kubernetes/Dashboard/StatefulSetDashlet.php b/library/Kubernetes/Dashboard/StatefulSetDashlet.php deleted file mode 100644 index cd7dcb19..00000000 --- a/library/Kubernetes/Dashboard/StatefulSetDashlet.php +++ /dev/null @@ -1,46 +0,0 @@ -translate('Stateful Sets'); - } - - public function getSummary() - { - return $this->translate( - sprintf( - 'Out of %s total stateful sets, %s are in OK state, %s are Critical, %s are in Warning state, - and %s are Unknown', - $this->getIcingaStateCount(), - $this->getIcingaStateCount('ok'), - $this->getIcingaStateCount('critical'), - $this->getIcingaStateCount('warning'), - $this->getIcingaStateCount('unknown') - ) - ); - } - - private function getIcingaStateCount(string $icingaState = '') - { - return $icingaState === '' - ? StatefulSet::on(Database::connection())->count() - : StatefulSet::on(Database::connection())->filter(Filter::equal('icinga_state', $icingaState))->count(); - } - - public function getUrl() - { - return 'kubernetes/stateful-sets'; - } -} From 83f966f0d16a7c4b9218da4095bef730d14d6cac Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 14:42:39 +0100 Subject: [PATCH 21/25] Introduce new Form `ClusterForm.php` --- library/Kubernetes/Web/ClusterForm.php | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 library/Kubernetes/Web/ClusterForm.php diff --git a/library/Kubernetes/Web/ClusterForm.php b/library/Kubernetes/Web/ClusterForm.php new file mode 100644 index 00000000..513aebb2 --- /dev/null +++ b/library/Kubernetes/Web/ClusterForm.php @@ -0,0 +1,44 @@ +columns(['uuid', 'name']); + + foreach ($clusters as $cluster) { + $label = $cluster->name ?? (string) Uuid::fromBytes($cluster->uuid); + + yield (string) Uuid::fromBytes($cluster->uuid) => $label; + } + } + + protected function assemble(): void + { + $this->addElement( + 'select', + 'cluster_uuid', + [ + 'required' => true, + 'class' => 'autosubmit', + 'label' => $this->translate('Cluster'), + 'options' => [ + static::ALL_CLUSTERS => $this->translate('All clusters'), + ] + iterator_to_array($this->yieldClusters()), + ], + ); + } +} From 8e08edc1da84dd714b8b8f691c14f08a77610d67 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 14:47:51 +0100 Subject: [PATCH 22/25] Add multi-cluster support --- .../controllers/DashboardController.php | 34 +++++++++++++++++++ .../Dashboard/IcingaStateDashlet.php | 19 ++++++++--- .../Dashboard/KubernetesPhaseDashlet.php | 19 ++++++++--- library/Kubernetes/Web/ListController.php | 10 ++++++ 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index 2d0778e6..142eb5a3 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -4,13 +4,18 @@ namespace Icinga\Module\Kubernetes\Controllers; +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Kubernetes\Common\Database; use Icinga\Module\Kubernetes\Dashboard\ClusterManagementDashboard; use Icinga\Module\Kubernetes\Dashboard\ConfigurationDashboard; use Icinga\Module\Kubernetes\Dashboard\NetworkingDashboard; use Icinga\Module\Kubernetes\Dashboard\ObservabilityDashboard; use Icinga\Module\Kubernetes\Dashboard\StorageDashboard; use Icinga\Module\Kubernetes\Dashboard\WorkloadsDashboard; +use Icinga\Module\Kubernetes\Model\Cluster; +use Icinga\Module\Kubernetes\Web\ClusterForm; use Icinga\Module\Kubernetes\Web\Controller; +use Icinga\Web\Session; class DashboardController extends Controller { @@ -18,6 +23,30 @@ public function indexAction(): void { $this->addTitleTab($this->translate('Kubernetes')); + $cluster = (new ClusterForm()) + ->populate( + [ + 'cluster_uuid' => Session::getSession() + ->getNamespace('kubernetes') + ->get('cluster_uuid', ClusterForm::ALL_CLUSTERS) + ] + ) + ->on(ClusterForm::ON_SUCCESS, function (ClusterForm $form) { + $session = Session::getSession() + ->getNamespace('kubernetes'); + $clusterUuid = $form->getElement('cluster_uuid')->getValue(); + if ($clusterUuid === ClusterForm::ALL_CLUSTERS) { + $session->set('cluster_uuid', null); + } else { + $session->set('cluster_uuid', $clusterUuid); + } + }) + ->handleRequest(ServerRequest::fromGlobals()); + + if ($this->isMultiCluster()) { + $this->addContent($cluster); + } + $this->content->addHtml( new ClusterManagementDashboard(), new WorkloadsDashboard(), @@ -27,4 +56,9 @@ public function indexAction(): void new ObservabilityDashboard(), ); } + + protected function isMultiCluster(): bool + { + return Cluster::on(Database::connection())->count() > 1; + } } diff --git a/library/Kubernetes/Dashboard/IcingaStateDashlet.php b/library/Kubernetes/Dashboard/IcingaStateDashlet.php index 556f5408..cc33f159 100644 --- a/library/Kubernetes/Dashboard/IcingaStateDashlet.php +++ b/library/Kubernetes/Dashboard/IcingaStateDashlet.php @@ -6,17 +6,26 @@ use Icinga\Module\Kubernetes\Common\Database; use Icinga\Module\Kubernetes\Web\Factory; +use Icinga\Web\Session; use ipl\Sql\Expression; +use ipl\Stdlib\Filter; +use Ramsey\Uuid\Uuid; class IcingaStateDashlet extends Dashlet { protected function getIcingaStateCounts(): array { - $q = (Factory::createModel($this->kind)::on(Database::connection())) - ->columns([ - 'icinga_state', - 'count' => new Expression('COUNT(*)') - ]); + $q = (Factory::createModel($this->kind)::on(Database::connection())); + + $clusterUuid = Session::getSession()->getNamespace('kubernetes')->get('cluster_uuid'); + if ($clusterUuid !== null) { + $q->filter(Filter::equal('cluster_uuid', Uuid::fromString($clusterUuid)->getBytes())); + } + + $q->columns([ + 'icinga_state', + 'count' => new Expression('COUNT(*)') + ]); $q->getSelectBase()->groupBy('icinga_state'); diff --git a/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php b/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php index 21873443..452184ea 100644 --- a/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php +++ b/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php @@ -6,17 +6,26 @@ use Icinga\Module\Kubernetes\Common\Database; use Icinga\Module\Kubernetes\Web\Factory; +use Icinga\Web\Session; use ipl\Sql\Expression; +use ipl\Stdlib\Filter; +use Ramsey\Uuid\Uuid; class KubernetesPhaseDashlet extends Dashlet { protected function getKubernetesPhaseCounts(): array { - $q = (Factory::createModel($this->kind)::on(Database::connection())) - ->columns([ - 'phase', - 'count' => new Expression('COUNT(*)') - ]); + $q = (Factory::createModel($this->kind)::on(Database::connection())); + + $clusterUuid = Session::getSession()->getNamespace('kubernetes')->get('cluster_uuid'); + if ($clusterUuid !== null) { + $q->filter(Filter::equal('cluster_uuid', Uuid::fromString($clusterUuid)->getBytes())); + } + + $q->columns([ + 'phase', + 'count' => new Expression('COUNT(*)') + ]); $q->getSelectBase()->groupBy('phase'); diff --git a/library/Kubernetes/Web/ListController.php b/library/Kubernetes/Web/ListController.php index 7b4f3185..d7aa8854 100644 --- a/library/Kubernetes/Web/ListController.php +++ b/library/Kubernetes/Web/ListController.php @@ -6,10 +6,13 @@ use Icinga\Module\Kubernetes\Common\Auth; use Icinga\Module\Kubernetes\TBD\ObjectSuggestions; +use Icinga\Web\Session; use ipl\Orm\Query; +use ipl\Stdlib\Filter; use ipl\Web\Compat\SearchControls; use ipl\Web\Control\LimitControl; use ipl\Web\Control\SortControl; +use Ramsey\Uuid\Uuid; abstract class ListController extends Controller { @@ -39,6 +42,13 @@ public function indexAction(): void $q = Auth::getInstance()->withRestrictions($this->getPermission(), $this->getQuery()); + $clusterUuid = Session::getSession() + ->getNamespace('kubernetes') + ->get('cluster_uuid'); + if ($clusterUuid !== null) { + $q->filter(Filter::equal('cluster_uuid', Uuid::fromString($clusterUuid)->getBytes())); + } + $limitControl = $this->createLimitControl(); $sortControl = $this->createSortControl($q, $this->getSortColumns()); $paginationControl = $this->createPaginationControl($q); From 4471bc74b79118a550e5d2798fbd38517b9f52b7 Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 15:57:13 +0100 Subject: [PATCH 23/25] module.less: Enhance layout and style for form controls --- public/css/module.less | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/css/module.less b/public/css/module.less index bd761861..c66f095b 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -112,3 +112,14 @@ } } } + +.control-label-group { + flex-direction: row; + font-size: 1.2em; + text-align: left; + width: 5em; +} + +.icinga-controls select { + max-width: 280px; +} From 24dbb5077e2611289468021d94ff70f7e5f595af Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 16:39:49 +0100 Subject: [PATCH 24/25] Auth.php: Update Kubernetes resources to lowercase in permissions array --- library/Kubernetes/Common/Auth.php | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/library/Kubernetes/Common/Auth.php b/library/Kubernetes/Common/Auth.php index 90d34303..1fe75d17 100644 --- a/library/Kubernetes/Common/Auth.php +++ b/library/Kubernetes/Common/Auth.php @@ -32,22 +32,22 @@ class Auth public const SHOW_STATEFUL_SETS = 'kubernetes/stateful-sets/show'; public const PERMISSIONS = [ - 'ConfigMap' => self::SHOW_CONFIG_MAPS, - 'CronJob' => self::SHOW_CRON_JOBS, - 'DaemonSet' => self::SHOW_DAEMON_SETS, - 'Deployment' => self::SHOW_DEPLOYMENTS, - 'Event' => self::SHOW_EVENTS, - 'Ingress' => self::SHOW_INGRESSES, - 'Job' => self::SHOW_JOBS, - 'Namespace' => self::SHOW_NAMESPACES, - 'Node' => self::SHOW_NODES, - 'PersistentVolume' => self::SHOW_PERSISTENT_VOLUMES, - 'PersistentVolumeClaim' => self::SHOW_PERSISTENT_VOLUME_CLAIMS, - 'Pod' => self::SHOW_PODS, - 'ReplicaSet' => self::SHOW_REPLICA_SETS, - 'Secret' => self::SHOW_SECRETS, - 'Service' => self::SHOW_SERVICES, - 'StatefulSet' => self::SHOW_STATEFUL_SETS, + 'configmap' => self::SHOW_CONFIG_MAPS, + 'cronjob' => self::SHOW_CRON_JOBS, + 'daemonset' => self::SHOW_DAEMON_SETS, + 'deployment' => self::SHOW_DEPLOYMENTS, + 'event' => self::SHOW_EVENTS, + 'ingress' => self::SHOW_INGRESSES, + 'job' => self::SHOW_JOBS, + 'namespace' => self::SHOW_NAMESPACES, + 'node' => self::SHOW_NODES, + 'persistentvolume' => self::SHOW_PERSISTENT_VOLUMES, + 'persistentvolumeclaim' => self::SHOW_PERSISTENT_VOLUME_CLAIMS, + 'pod' => self::SHOW_PODS, + 'replicaset' => self::SHOW_REPLICA_SETS, + 'secret' => self::SHOW_SECRETS, + 'service' => self::SHOW_SERVICES, + 'statefulset' => self::SHOW_STATEFUL_SETS, ]; protected IcingaAuth $auth; From 2d28cc0f5ca198388902bda4ae6178d024ad681c Mon Sep 17 00:00:00 2001 From: Jonada Hoxha Date: Mon, 16 Dec 2024 16:40:46 +0100 Subject: [PATCH 25/25] Apply restrictions to model queries --- library/Kubernetes/Dashboard/IcingaStateDashlet.php | 7 ++++++- library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/library/Kubernetes/Dashboard/IcingaStateDashlet.php b/library/Kubernetes/Dashboard/IcingaStateDashlet.php index cc33f159..c31efb21 100644 --- a/library/Kubernetes/Dashboard/IcingaStateDashlet.php +++ b/library/Kubernetes/Dashboard/IcingaStateDashlet.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Kubernetes\Dashboard; +use Icinga\Module\Kubernetes\Common\Auth; use Icinga\Module\Kubernetes\Common\Database; use Icinga\Module\Kubernetes\Web\Factory; use Icinga\Web\Session; @@ -15,7 +16,11 @@ class IcingaStateDashlet extends Dashlet { protected function getIcingaStateCounts(): array { - $q = (Factory::createModel($this->kind)::on(Database::connection())); + $q = Auth::getInstance() + ->withRestrictions( + Auth::PERMISSIONS[$this->kind], + Factory::createModel($this->kind)::on(Database::connection()) + ); $clusterUuid = Session::getSession()->getNamespace('kubernetes')->get('cluster_uuid'); if ($clusterUuid !== null) { diff --git a/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php b/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php index 452184ea..ab79ac4c 100644 --- a/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php +++ b/library/Kubernetes/Dashboard/KubernetesPhaseDashlet.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Kubernetes\Dashboard; +use Icinga\Module\Kubernetes\Common\Auth; use Icinga\Module\Kubernetes\Common\Database; use Icinga\Module\Kubernetes\Web\Factory; use Icinga\Web\Session; @@ -15,7 +16,11 @@ class KubernetesPhaseDashlet extends Dashlet { protected function getKubernetesPhaseCounts(): array { - $q = (Factory::createModel($this->kind)::on(Database::connection())); + $q = Auth::getInstance() + ->withRestrictions( + Auth::PERMISSIONS[$this->kind], + Factory::createModel($this->kind)::on(Database::connection()) + ); $clusterUuid = Session::getSession()->getNamespace('kubernetes')->get('cluster_uuid'); if ($clusterUuid !== null) {