From 0a9aa20dfa74af5a858c5f59a038bb8d4947c1f2 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 24 Jan 2017 16:44:00 +0100 Subject: [PATCH 1/3] Implement DetailviewExtensionHook refs #2104 --- .../partials/object/detail-content.phtml | 1 + .../scripts/show/components/extensions.phtml | 4 ++ .../Hook/DetailviewExtensionHook.php | 40 +++++++++++++++++++ .../Controller/MonitoredObjectController.php | 7 ++++ 4 files changed, 52 insertions(+) create mode 100644 modules/monitoring/application/views/scripts/show/components/extensions.phtml create mode 100644 modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php diff --git a/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml b/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml index c7d454a0b..5fa2a46c0 100644 --- a/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml +++ b/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml @@ -1,6 +1,7 @@
render('show/components/output.phtml') ?> render('show/components/grapher.phtml') ?> + render('show/components/extensions.phtml') ?>

translate('Problem handling') ?>

diff --git a/modules/monitoring/application/views/scripts/show/components/extensions.phtml b/modules/monitoring/application/views/scripts/show/components/extensions.phtml new file mode 100644 index 000000000..263b7e472 --- /dev/null +++ b/modules/monitoring/application/views/scripts/show/components/extensions.phtml @@ -0,0 +1,4 @@ +init(); + } + + /** + * Overwrite this function for hook initialization, e.g. loading the hook's config + */ + protected function init() + { + } + + /** + * Shall return valid HTML to include in the detail view + * + * @param MonitoredObject $object The object to generate HTML for + * + * @return string + */ + abstract public function getHtmlForObject(MonitoredObject $object); +} diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php index 9ea05f02b..91d97f932 100644 --- a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php +++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php @@ -10,6 +10,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm; +use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook; use Icinga\Web\Hook; use Icinga\Web\Url; use Icinga\Web\Widget\Tabextension\DashboardAction; @@ -96,6 +97,12 @@ abstract class MonitoredObjectController extends Controller } $this->view->showInstance = $this->backend->select()->from('instance')->count() > 1; $this->view->object = $this->object; + + $this->view->extensionsHtml = array(); + foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) { + /** @var DetailviewExtensionHook $hook */ + $this->view->extensionsHtml[] = $hook->getHtmlForObject($this->object); + } } /** From fb2abf40f88053752df812b13c7f9be2a7f8898d Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 21 Feb 2017 12:23:21 +0100 Subject: [PATCH 2/3] Make the view available to DetailviewExtensionHooks refs #2104 --- .../Hook/DetailviewExtensionHook.php | 31 +++++++++++++++++++ .../Controller/MonitoredObjectController.php | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php index cdb7374e2..b86573f9c 100644 --- a/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php +++ b/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Monitoring\Hook; use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Web\View; /** * Base class for hooks extending the detail view of monitored objects @@ -12,6 +13,13 @@ use Icinga\Module\Monitoring\Object\MonitoredObject; */ abstract class DetailviewExtensionHook { + /** + * The view the generated HTML will be included in + * + * @var View + */ + private $view; + /** * Create a new hook * @@ -37,4 +45,27 @@ abstract class DetailviewExtensionHook * @return string */ abstract public function getHtmlForObject(MonitoredObject $object); + + /** + * Get {@link view} + * + * @return View + */ + public function getView() + { + return $this->view; + } + + /** + * Set {@link view} + * + * @param View $view + * + * @return $this + */ + public function setView($view) + { + $this->view = $view; + return $this; + } } diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php index 91d97f932..52cd4afbd 100644 --- a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php +++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php @@ -101,7 +101,7 @@ abstract class MonitoredObjectController extends Controller $this->view->extensionsHtml = array(); foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) { /** @var DetailviewExtensionHook $hook */ - $this->view->extensionsHtml[] = $hook->getHtmlForObject($this->object); + $this->view->extensionsHtml[] = $hook->setView($this->view)->getHtmlForObject($this->object); } } From 389bf8f4ea24b7e185b78363e01b293c660f404d Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 21 Feb 2017 13:23:17 +0100 Subject: [PATCH 3/3] Document DetailviewExtensionHook refs #2104 Signed-off-by: Eric Lippmann --- .../doc/01-hooks/01-detailviewextension.md | 76 ++++++++++++++++++ .../doc/res/detailviewextension-01.png | Bin 0 -> 10714 bytes 2 files changed, 76 insertions(+) create mode 100644 modules/monitoring/doc/01-hooks/01-detailviewextension.md create mode 100644 modules/monitoring/doc/res/detailviewextension-01.png diff --git a/modules/monitoring/doc/01-hooks/01-detailviewextension.md b/modules/monitoring/doc/01-hooks/01-detailviewextension.md new file mode 100644 index 000000000..480085710 --- /dev/null +++ b/modules/monitoring/doc/01-hooks/01-detailviewextension.md @@ -0,0 +1,76 @@ +# Detail View Extension Hook + +## About + +This hook can be used to easily extend the detail view of monitored objects (hosts and services). + +## How it works + +### Directory structure + +* `icingaweb2/modules/example` + * `library/Example/ProvidedHook/Monitoring/DetailviewExtension/Simple.php` + * `run.php` + +### Files + +#### run.php + +```php +provideHook( + 'monitoring/DetailviewExtension', + 'Icinga\Module\Example\ProvidedHook\Monitoring\DetailviewExtension\Simple' +); +``` + +#### Simple.php + +```php +name) as $c) { + if (isset($stats[$c])) { + ++$stats[$c]; + } else { + $stats[$c] = 1; + } + } + + ksort($stats); + + $view = $this->getView(); + + $thead = ''; + $tbody = ''; + foreach ($stats as $c => $amount) { + $thead .= ''; + $tbody .= ''; + } + + return '

' + . $view->escape(sprintf($view->translate('A %s named "%s"'), $object->getType(), $object->name)) + . '

' + . '

Character stats

' + . '
' . $view->escape($c) . '' . $amount . '
' + . '' . $thead . '' + . '' . $tbody . '' + . '
'; + } +} +``` + +## How it looks like + +![Screenshot](res/detailviewextension-01.png) diff --git a/modules/monitoring/doc/res/detailviewextension-01.png b/modules/monitoring/doc/res/detailviewextension-01.png new file mode 100644 index 0000000000000000000000000000000000000000..a5ddaf188aae851b09bf69e55aee02d3282ec150 GIT binary patch literal 10714 zcmb7KcR1Dm+gE&ZlI#%KLS`Y#tV2RplD$H9o$O6U2_buvy~i=L6J=*R_RN;O=W~1h zdFJoAuIISKr87S7_vgM}>wfzwDLlQ7M}>!lg>_vI4D;xhcJAY#!0Kd!!$ z*090CB51n!z7)YhK#he(k0m4dSjBPVZ-SFH+0&!*+=Z!II9Dh?8b0_#gu-WZEF=nd zyzRw3{7)zPUMy;ix!P}DX`8REpV@*yr^LLU((v;y?3(*tjGw%3G1A|q$KYebSL0n= zn|tH+;%x~D6jKOx+Scu}-0Xsaf}Atk-52+FGOzll5MiC%`}&vyi{1eXs}dWlg$C=< z8Wxr{F4h-etoLJ=u>KJIr~mrTPk5h6^h=+x<@fJ8yA^Vz=|qiFa&q#1Te$^>J3*mB zzrky2YD!$cV(ak4%j>d-$GMLWPP=Wkiu0cqVxR8n^*Wd1y}!9?h1Qj0^(S*Y!O6{> z?H>4Q&9cvuVtNu%vikc~#FDxew)uE@^ZszMu}y6sT34R*sP0GUxgQ?P26rV0?}xYR zaFTByx_Un9=Fmy)nVLvt%SliF(6umG=jI~qk~hh*MOhFO5h=!JU6`92Ce7?9EG|B2 z$}cv*TwYq*-``(aQ6Zk1T31^uifkDijFd{9euInaJK4$CxwR~kz(u2dH!Lj7n~3V6 zwb7e5ZyX#PWMpKFjM8>98`7Ue`3D5t#Fx_69y{2a?W|@dk&%-6sLp9`Yl}}n5EL9- zfkrn}J3Bk`^6|B|wS~*3`}#KTEcG=9Qa>e+{MynMwqK=} z;bmY*E-fw1%lq7F^gI4xpSEbNZc<3da}AAb8Ysf>|*q&@vqcy2nz0R&9%n~qU^1$Q`nZ{P<40M*zQt1B9et2))seY)u3ykqS6^IPn?U&z;cPDF7S6;q4-D)E3Ql|q3R;Wnls%{S zDbT6j8+VutVK#ev5PJJrXlQ62|F)lYf@kT@(skpu_Vyci|0F0RJ+PYj(;BvEH?U-@ zO3<@pOD$k|uv*gfPH}E-ZkpJ%D^c`{Hv>DnIi^4-PT-9}$uNoEAB>*I6NH_u?bL*J zz58jhgl}kA7%`GnF)@wJeD+tMFi?qlmbBb+I#OnVNWMzS z@<2#PN=k}1M$Wt8-S&K^SzpHZ$``2JdxpR0eSS1H+E`l)2?=fBd`(UDZqTpU9n|6^ zZ*Od5x^u@2_UQ2NuswLYhXD#n$p$cIoVGmUNe3wTJ0D)<9ZW0Sy`(4 z=441VcJ}3&p8*G-ZxJlC^VA#h#@r&vEGQrlw|U7PG3U9x&_#%kzq7L=Vl{(d$s(nrlGsh}BO)rQ!AY)gQ_S@NQaV}5v`kY^Z}Qu>Z?I~L z8y62^oO|Z|S+Cn8rweL$=6N@5Cgat$wO#jCHIkL2pR3fP^Wx&Dku9#|w*u7@RoEkD zLfn7)->CJn9Agw07vFwOfQRR-U#Yd8G&rbw!uiAHs@HdHk@>n#*J8DWn2_VnI69ThkCv8}y}iA^qZNeZM4WQ~KEov@S{cWOhtpQi3o-lVJ&k!2 zW4Rt1;b^@(;-SW3XSv(_Z8TjA(BPaN(Jh^w_haN>ziN#G2RFC38=vdHeECvcJ^JAf zcvs`Ql$SlfmX?Nh8uRGZIBw4TG(aDm?oSHF$nEa#ralc8;JJgJ69Rqa7_M1md#COR z-PcS&Ln0(}91F`wdPJ?R$Klp%{}gpja^0lt!onzN=81_3p@g*tg?nGl8V0_|rYmQM zx6iRhv{g*HYZYQpBc)3r{M(y)37HCO>=8NNzELvlEQp^w9qla5+m>nSz>PRP+I`$Q zj=>1>@)j2rZ3OVtMT@%FySNC~MQ!l+=--`sS6N;@*ws~7RYleO;edCFp&VA5m1R10 zP3xdiTTQLPFOr78X4TB1XM4TYwimN1gS9=e5YEVeUq3tSv9w>x7XU*I$F+HR<^A_ z`voy_wmDc)PR{5qrCOox=~8CW+p%xcQ&UCe1MIf~#P`Q+5DaMb&mLcC>G@24?`&_U zvMvAka1B$arzk6nM6}Lr9UUD#7M+6^cN&)z>Qt8lQnj_U5i?2UX;lQ#BLKegG#clu z=d2GuT%!Q4+}$1aU83`%pKw}8`1+L|WW!98-_emX3bj6vtJb%)^R&04*~5;OwJFSb zM~Uoqge(+-x3|QjNAKr4NEp&o*gqhXWz((g?V&Lt6T&u(ew;bx=?CdP^o~@#y;+r@5s=3#%U#ICen!j|^Y{YwCKtPt6 zL?Djl)~ycxyR>ik=(YzAPupp*5_|Qp{9l?0&T z4od+SV3Xkd2@MSm^D-d;fxVDpQT(Dm$+=h?f1d8IuCD4-+XH&o%*(;n_4X=zKHBJc zN_ToZd0toO)}{0%GLj`knngZ#r)5Ckd#G>0}<3xv4xpgYfFn#w#v{Aa#nUS|D7FMKS=`%3uF)pc^JY|y9VGw%3&lMmt5(%#-@%8y8nkB=W3Qm3Gx z2x|?GiP6x}d1GkEXSb}f8i(2EyhYGF##W{oDF62F-@m}()HiNCN$oj`97CNR8?RLG zf5GtGaqyF5jA~Dak5BYCJFLD(^!Q|!?%WiG64V;EcW@}wswl|Ky(YehPRNr=JzPu; z24VoE(PLU;Jtu2uI9vR>y{fvpD@lBPdb(j@o0^J>k&zL~bV86Cc|q7WZ{Actg8Jp+ z;qf^BTT!T8>6K3i5cJ^H4;&Md1um`f@W{wwbxwenrQYgv9}K2UDQM=OQ<>@lrIds^qZ!>fP)9&=s)vnyB7p+EVEhlht#7~i+? zrl@miW+v;|i_E^;%*-di{b4NfDQtLJ>Q`~RfU<7#8DIPnn*0gk203}`GgvL*)vFha z12R48WFhh6<>eh4ANM$z_N{f&1Ja*B5sHb4RiGwWWQx^V_j5c7E%(_k zE<9)r+wV6G4GkRo6vag43~^5wuMVx}UAy!?Ewy6?sT zbUsR2TBDgs?(AW$CnqOAe*7rPjY}WfWY}-ZP+{*hd5YL{6rl~qR5%Yc<^h|JF7d@c z!=NH$Uq`y=%~e)b=Ihmq-M?Q}T&&DYlAe*F5dUDakx+a!qR;shSBq-HvSoa9baZ@N zhb2s>*7=6-I&NbY`&c?>ZTe1v3p!1o? z11_{0_Eo+Iqo6BiW@d_xu3zy6B)^d=6(je{0Igc6EB{>O)vIYxhHocohT(>+WO`p3 z^mw0ssq5ny7!hmhEg;?c^Rwfe9Cq#~Ep2UWEv@!BYgUDL)7#QNt3j=7ZEfYbX0PiG zv&cp3o0vHK9l39*=&;z$;No&DIO$4yigD7RCwd?(3`!+Q$nFBewYe*5Yu(l-YJq&K z?f(|{rU1#D9`6TJ-kV8}(k(72Y4kfV)7L1DIMOmMQKs5hZKYx0cRR8TW!_&KkBf-- z@B(%lh{X(r3Xb}7^csLZCMM>x=OcO|5P%>I*~#z#KW=WQx6c3ld%9nLeky)`*yV|h zTl9XyZC}UootSY)bVf$TdfoBw(NST3e%s;V*S-WaN1&*QkkAJpUF-7m^CRX(Kp)?} zeY@iA_|aB74>1y8^!Dw43VAgmEz=Dmhx5X(h`hxA%~YJ3nTbX}PgZh-N^y23U=|oK zBWuy8f7h;Bt+ zEocQUE-qW4@vg3}lew?DwXX5aZZ9k3iTJoV20R~!A`9rpk5*^kyJ{Uo=5 zXN^rv2$vWnJdw#oTR*t5tk-e#RXKKh&r;W_4kYLK(ASV@x!6NB4bSbsLs*qEA<@)?(-sL z7_lgXtT2YvhAytUc5>zoWHGPB#y!DWT zAso0YKHhb53#>i}httzjV&pu}2N7n*5Cj9i)0UBT9SChDwi>nhp46u$!>b@$6UE$| z$G%yCuVs8tc5d}_A9R!@VCSEf5Iwiu8v+t#m+en}y!~!nS(B2IQeEvrZ{k#HoZ@Kj zd0iFcOM`w9@8mlho8z_W^|Qk+4=`I`f%Ek0X|*b`JtQliF_ZWxNlSlH=Pc)yGT8W* z7!uMmHdX@`FOXVLj2L4hGAHVB=KAbed-jV=>CjT%t77)6Lpd*Vqvhs%)1NUhfz_*7 zXJmRzWL`F6#Os;pj%w%tVF;r{9K1jaqXg3Gsw!p@zqq)#L(-Ui zFJ>9EdY)>osHiA9uG~&KG_SDG!OH3qb^m1Y*rlmplIs6Y!gbNRA_3M5+lL2#yI3QJ zP5CFjSut^O zs*T~<>M>5sV4y==jdE4#**6441&h^^m7KXuhpMtvvoWjb+hNzm!jueh+zaK2aNp1mJ z2-rb0e{f>L9b^@=3=0bj-iII1zxxv|gbd6f(jd>84A7~mhVS0Jb9Jo&J^I>HGk>o; zSpu2dJvr$C${;Z@@iejGe}s5+3`YD^R+8b-(a{kRx5@CFL?!^uKx!`aW!@(73l3I9 zA}_~vu2snSr*P|3sc371X#rXBE0_*NPzs+5x!6ng8*!aM{{H?!K`e;N3_hdLU%n`5 zYHF&g{xae<`5g}hY{p$uTDk)%4)6hp&*I)d;8&r9p!T_{%F6KyInPI5Vq&rf%;DKG zP>`3`|UX1&heQNCKi%Q~=pD?*%~D>v^1(9hQ)TsEXpzgV?wv z{oyPqm)E>E$=kr(i65`>iHq0kvI+qNfj%Sx9tQ*$;p4OEjJ*%y52%@y6_-Kf`E!P- zb}I(BBJ(oMWF>kZaFif&%HNLrTMPw&V-1jGtVu{ps(0F+@9tI#Wv)M%2>_>SQ@(;J zI+Of#}8!$TAD$;{?#dmcaBYh4q@`ru?UOcaQboAfxZ?J+HNowppXvV#l+kqq2VN0$Vzh8kn^ zkz^#UOE^mDTN1Dw+88!T0v3Bt77-QYyB!q~L5zcTKRt3g-uv0yZ1_`)Ryy<#tO#PZ zfFx>1l9V1(0$LHsl;q?(fE<;TmDlVh+?OikQhOYyzF!Ju&QwS^`dcxXk(RcTAa+UO zLDjoz}9)<4#jl8pDBiv6qT z?m(=^l%JNC)@?L2I{JZe2c?gaqoX4To2O54k)Il;^;sPWd|#-kVaCQj6D{usq5%b; zr%EySKqqqR)_fx5qeEVBlx7{)0G2CBgM*;zn^c&pi88pa5GeE#4z1Ot@(_5wC=V-S%t8yO%$ zJRK{e&qhsPO4i<X-VNcDV_{QtJLb_cmRQJIbP98Vt{)wP3}y7a4L+EBO_9 zKc&uZ#K2^OfRfW2H{P?=XnEJUU9$Z;E-r5SWwbNMc;l|bH!%}}O&-h$952Z0`b-X8j;$bRhRHq2n7${9C=zFQ$&&gYVnZJx~t^WG+=Gq>m{Zfxcxce{$!=wEm-?l+$iK|XoL6v3aL~q`RNPS-Y0t^ldgRG7qx?iI_I`K2+@fJ{@Cz97c+Rg9ExvU7a$~6F(k^W@JP&Ulo!M^M}~( zlsnFDkb&tI(%my?0#boQ(7@0zzw7IJRwjq3SElCXM-T=OBk$a~V`XJU;%C&CA#Yvj zI*{|yyAMio!QYH5HQ_qT+e<2P2>sazXOq1hBN*~h4p!Q$AdIdcn@YDa(r^VMa@xs;;7wr8NIhdK8CAJZEbJ`w~t=8fm5S z+)inzT7gQL?f#w%Nw%$T?kKmQ9eG*y5&3|GGDfTFO6UQOl!JG}mynQ<@bISDO(LYF zfL6Nlc#V?{@En36tW`xv2SP_^?V^x`27OZt3maS8yxd&ilxqk?R&MUf;v(Ye)mJ6M zgZqp=Z2md$m*wRjgJ0$^Lk10F9!mUQEg?)d@nH&?!IlG~#Rm`6)0GGM`-yPiCVpbD z);{v?``X(ntXJ-ya^A4;R5NH$PyV`)_BfS8#?H)%l}Kd%!_%nc!4IBq_iRU|V+Z}> zs|m>P+`1}168LoU7oNwM?8cc&nJ{+w2?_0rb{$t;At;eR+?3v`k>>s@Ah&HzyE6ZY z?%JGnU}mknbE-(wPxr{+mD}i=jAzGQbizZ!6V^O#fD!xk`L{zMk^c7WfsKD~FrmE> zi_8|pGv*c+C1^Cni~4AFC`hnLzJzq(D9h1k8AU~y;l5yx(9zKWiuUvOUtV4=E{>xA z1WbSJ0}}F`&!6%AQyfKfA&=XfZ3YT~nQef-f10uxj1L~sXXWS92v|mkh4t0B9jkLj zYIAqBw0!#XsfeE)oAxS7ySBvnGOFiYux`jz&+Z3;LuJQe{p`6!RKre>jr%Ff87lsE>rK{YP;BYw1@Eyc zbv-pg;rYD2bl4Sa@A7#mQB03<9(As6cAQ5YXT5x@0k7)9ppJRCVD_$oFSA<#oDX<^ zaTXxrY_{geq507NrSta0rPh?x)W)Ab($}J?Ev{q0A72#MiHRbNkdav>CEH~fT|=}wWZouS ziBAyni6Rfd=?MswfBQDn*GH?fytL{rA6gCz{(yw|UFt zOyWBYnS6Fw*tJqVZCZ`L=;3V}nC>6Dhl3PXl{vSnC1>bIrNro2jA&clY@YwjLWE~q z-1Szq-_A^)+3wZZgUiWIXKy`PiB9e}kuvmW+z)S(x2-U2v->4gcJ|0J+Arn7u}xgR zb9*gzXf>ozJl=|-%%dYCA|fIHZrIq^n?@ncvmie$H&$k5ygKC}n=fi03Y6ljCes)m z1;n~b3Em;lhAL3X)!f9yBg6#?yG@4e_GP^ ztJMto?{Sjj6O5bD7&odXX!#OR8B2(3kCV^NI#)~3tT+FPF>qTCN<3wWtsE#g3k$wB zQSE%0kYUHn^X@OqjZC?Dp$dCy;ayQg^7mJJYxZ(JU*c%O3{l=hDWpERG}wx(9WJ>1 zFL!r#EG;Y``<@R6cSq-O5<-ul3N+@8Srhxg=AXi5dD*g3v)5s+sZAHe#r!<^sa}ze z(8cU}GS``#o0~&5FIwC~3~374+)y};V`&ZfFCCjykA+8g$@>RosLxwJJYw0FrR6QG zKbd?J<`_#Tv&7k#)Ty439JyzDrElEspKYmnzjIP-Pq{zik`X=qX@h)b{N76iuUTcD zGpA7UUv5?*_g~hLFi_&)gAWAbF6OXyOF(aJX{oiX?PAypBhgJqhqbYWrOTe*8yjaC zu&+XhIx@06GxKB48p3V}&MlrS9?*h3kXKY(*mOjr(NJy%Xv+S>77fvc;IyoVCa8AX zbG|8-JGQmy7JD(u)Kq_yW}O~vnwNMN2gT`KyZDs8qgO5UgS+K3$JA+=j%{F|b zDHy5?{R&dxxN3-Z^(McMl698J zq$JqmPWZiDYw&#C1->QsoE4Ksb5orn062299WXbxtv&Ka-# z2TlJU$BW>Q5%!0)FG|_imDu$j&eGHc3OoD6pPQV%@qI&BX&UU@gXznBbYa|^o;5`>U$9olD5Or8>H||mLhGSqHEh>+kWv>K`~6TK3ORolaof; zq*D0m9A)r6(_4NrrOAA}%5}P7D8xJGyFRf-3 zWJk0VT2gw??)rR@Os(&xuQ2H*$8{R&HFK2xGQmcXYggi`EfK$7S38WcluKUEUR_P> zpc%9nwKng`jmP~w4$_)~46nySrwP)7aM?MY`V+4}CeSHx)P@KNvwN5yspJkIE{-z6 zI?|P$w*It2mx@Lj}2D z?71Z{vncam)rjec_{!UpDKpe;d$CT)OC4^E65^hzW7L(NfD&KL3u z)h{_;F!>k>&2Hr_3*=AdhW@H39GLQLOyYUzs?$4^HQ1bR=d`@^DuYkwuOLi0MOiMn z?l%hs&Gbb9mxmf}LN@x!!YitGg?-8=1>xm{>o0qQA$%Y& zOU)d`9V5-u&B637gw=n7dfj5&GEeV_YvIyd!GJkBPOYBXfPsMlyno-yE<`+_;i32t z$xw5KD^eWp`jPm9X81KtAE|D|t6S&ld>|4cqBb5BVrd#a!wjc>X10?UDJ#+Doq5-g zf14OCBuA+FRNdZ@e5K{eR%ZwC;U%LF$z#ci5py3DxHg5g2e;-V=eswh;w!B2mnN{}-! z%mn>&d~onyijE<5IOy^3Jv*Hd-6e5^W@O-0Y+?7|#~_Q)0v=XElip7^C%E})bW#+? z+ALLfig8wY_vwA2-SPw|afp`<&OyqZEV#_N(NOZQ7#kuOEK6-Ct