From ec8152bd518572517f8f68cac63fdb2dd40dbb8f Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 17 Feb 2025 20:47:00 +0100 Subject: [PATCH 1/3] Add booted state. --- .../runner/qemu/GuestAgentClient.java | 5 -- .../vmoperator/runner/qemu/Runner.java | 60 ++++++++++++------- .../vmoperator/runner/qemu/StatusUpdater.java | 15 +++-- .../runner/qemu/events/RunnerStateChange.java | 23 ++++++- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java index 4d1c764..f3928f5 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/GuestAgentClient.java @@ -33,7 +33,6 @@ import java.util.logging.Level; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpGuestGetOsinfo; import org.jdrupes.vmoperator.runner.qemu.events.GuestAgentCommand; -import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady; import org.jdrupes.vmoperator.runner.qemu.events.OsinfoEvent; import org.jdrupes.vmoperator.runner.qemu.events.VserportChangeEvent; import org.jgrapes.core.Channel; @@ -188,10 +187,6 @@ public class GuestAgentClient extends Component { logger.fine(() -> "guest agent(in): " + line); try { var response = mapper.readValue(line, ObjectNode.class); - if (response.has("QMP")) { - rep.fire(new MonitorReady()); - return; - } if (response.has("return") || response.has("error")) { QmpCommand executed = executing.poll(); logger.fine( diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index c8b9f44..b258e1a 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -61,6 +61,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpReset; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; +import org.jdrupes.vmoperator.runner.qemu.events.OsinfoEvent; import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; @@ -619,8 +620,8 @@ public class Runner extends Component { } /** - * On monitor ready. - * + * When the monitor is ready, send QEMU its initial configuration. + * * @param event the event */ @Handler @@ -629,28 +630,14 @@ public class Runner extends Component { } /** - * On configure qemu. - * - * @param event the event - */ - @Handler(priority = -1000) - public void onConfigureQemuFinal(ConfigureQemu event) { - if (state == RunState.STARTING) { - fire(new MonitorCommand(new QmpCont())); - state = RunState.RUNNING; - rep.fire(new RunnerStateChange(state, "VmStarted", - "Qemu has been configured and is continuing")); - } - } - - /** - * On configure qemu. + * Whenever a new QEMU configuration is available, check if it + * is supposed to trigger a reset. * * @param event the event */ @Handler public void onConfigureQemu(ConfigureQemu event) { - if (state == RunState.RUNNING) { + if (state.vmActive()) { if (resetCounter != null && event.configuration().resetCounter != null && event.configuration().resetCounter > resetCounter) { @@ -660,6 +647,36 @@ public class Runner extends Component { } } + /** + * As last step when handling a new configuration, check if + * QEMU is suspended after startup and should be continued. + * + * @param event the event + */ + @Handler(priority = -1000) + public void onConfigureQemuFinal(ConfigureQemu event) { + if (state == RunState.STARTING) { + state = RunState.BOOTING; + fire(new MonitorCommand(new QmpCont())); + rep.fire(new RunnerStateChange(state, "VmStarted", + "Qemu has been configured and is continuing")); + } + } + + /** + * Receiving the OSinfo means that the OS has been booted. + * + * @param event the event + */ + @Handler + public void onOsinfo(OsinfoEvent event) { + if (state == RunState.BOOTING) { + state = RunState.BOOTED; + rep.fire(new RunnerStateChange(state, "VmBooted", + "The VM has started the guest agent.")); + } + } + /** * On process exited. * @@ -675,6 +692,7 @@ public class Runner extends Component { mayBeStartQemu(QemuPreps.CloudInit); return; } + // No other process(es) may exit during startup if (state == RunState.STARTING) { logger.severe(() -> "Process " + procDef.name @@ -683,7 +701,9 @@ public class Runner extends Component { rep.fire(new Stop()); return; } - if (procDef.equals(qemuDefinition) && state == RunState.RUNNING) { + + // No processes may exit while the VM is running normally + if (procDef.equals(qemuDefinition) && state.vmActive()) { rep.fire(new Exit(event.exitValue())); } logger.info(() -> "Process " + procDef.name diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index d4548bf..f9644c8 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -1,6 +1,6 @@ /* * VM-Operator - * Copyright (C) 2023,2024 Michael N. Lipp + * Copyright (C) 2023,2025 Michael N. Lipp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -31,7 +31,6 @@ import io.kubernetes.client.openapi.JSON; import io.kubernetes.client.openapi.models.EventsV1Event; import java.io.IOException; import java.math.BigDecimal; -import java.util.Set; import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; @@ -66,9 +65,6 @@ public class StatusUpdater extends VmDefUpdater { private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); - private static final Set RUNNING_STATES - = Set.of(RunState.RUNNING, RunState.TERMINATING); - private long observedGeneration; private boolean guestShutdownStops; private boolean shutdownByGuest; @@ -186,16 +182,23 @@ public class StatusUpdater extends VmDefUpdater { } vmStub.updateStatus(vmDef, from -> { JsonObject status = from.statusJson(); - boolean running = RUNNING_STATES.contains(event.runState()); + boolean running = event.runState().vmRunning(); updateCondition(vmDef, vmDef.statusJson(), "Running", running, event.reason(), event.message()); + updateCondition(vmDef, vmDef.statusJson(), "Booted", + event.runState() == RunState.BOOTED, event.reason(), + event.message()); if (event.runState() == RunState.STARTING) { status.addProperty("ram", GsonPtr.to(from.data()) .getAsString("spec", "vm", "maximumRam").orElse("0")); status.addProperty("cpus", 1); + + // In case we had an irregular shutdown + status.remove("osinfo"); } else if (event.runState() == RunState.STOPPED) { status.addProperty("ram", "0"); status.addProperty("cpus", 0); + status.remove("osinfo"); } // In case console connection was still present diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java index bb6ab10..829cc88 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java @@ -18,6 +18,7 @@ package org.jdrupes.vmoperator.runner.qemu.events; +import java.util.EnumSet; import org.jgrapes.core.Channel; import org.jgrapes.core.Components; import org.jgrapes.core.Event; @@ -29,10 +30,28 @@ import org.jgrapes.core.Event; public class RunnerStateChange extends Event { /** - * The state. + * The states. */ public enum RunState { - INITIALIZING, STARTING, RUNNING, TERMINATING, STOPPED + INITIALIZING, STARTING, BOOTING, BOOTED, TERMINATING, STOPPED; + + /** + * Checks if the state is one of the states in which the VM is running. + * + * @return true, if is running + */ + public boolean vmRunning() { + return EnumSet.of(BOOTING, BOOTED, TERMINATING).contains(this); + } + + /** + * Checks if the state is one of the states in which the VM is active. + * + * @return true, if is active + */ + public boolean vmActive() { + return EnumSet.of(BOOTING, BOOTED).contains(this); + } } private final RunState state; From bccc4ac2190bc3bf1f8099e69d3b0977ca648229 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 18 Feb 2025 12:15:50 +0100 Subject: [PATCH 2/3] Add pretty os name to displayed data. --- .../org/jdrupes/vmoperator/vmmgmt/VmMgmt-view.ftl.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.jdrupes.vmoperator.vmmgmt/resources/org/jdrupes/vmoperator/vmmgmt/VmMgmt-view.ftl.html b/org.jdrupes.vmoperator.vmmgmt/resources/org/jdrupes/vmoperator/vmmgmt/VmMgmt-view.ftl.html index 4dfc8d7..6ec6ce3 100644 --- a/org.jdrupes.vmoperator.vmmgmt/resources/org/jdrupes/vmoperator/vmmgmt/VmMgmt-view.ftl.html +++ b/org.jdrupes.vmoperator.vmmgmt/resources/org/jdrupes/vmoperator/vmmgmt/VmMgmt-view.ftl.html @@ -129,6 +129,9 @@ + + + From 777ae73c745ecb769b3196dd468fc03b9d63e4fe Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 18 Feb 2025 16:50:43 +0100 Subject: [PATCH 3/3] Add OS icons. --- .../vmoperator/vmaccess/osicons/Licenses.txt | 16 + .../vmoperator/vmaccess/osicons/almalinux.svg | 16 + .../vmoperator/vmaccess/osicons/arch.svg | 20 + .../vmoperator/vmaccess/osicons/debian.svg | 9 + .../vmoperator/vmaccess/osicons/fedora.svg | Bin 0 -> 41315 bytes .../vmoperator/vmaccess/osicons/tux.svg | 438 ++++++++++++++++++ .../vmoperator/vmaccess/osicons/ubuntu.svg | 8 + .../vmoperator/vmaccess/osicons/unknown.svg | 84 ++++ .../vmoperator/vmaccess/osicons/windows.svg | 1 + .../vmaccess/browser/VmAccess-functions.ts | 33 +- .../vmaccess/browser/VmAccess-style.scss | 14 + 11 files changed, 634 insertions(+), 5 deletions(-) create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/Licenses.txt create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/almalinux.svg create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/arch.svg create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/debian.svg create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/fedora.svg create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/tux.svg create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/ubuntu.svg create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/unknown.svg create mode 100644 org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/windows.svg diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/Licenses.txt b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/Licenses.txt new file mode 100644 index 0000000..f67978f --- /dev/null +++ b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/Licenses.txt @@ -0,0 +1,16 @@ +almalinux.svg: + Source: https://commons.wikimedia.org/wiki/File:AlmaLinux_Icon_Logo.svg + License: https://github.com/AlmaLinux/wiki/blob/master/LICENSE + +archlinux.svg: + Source: https://commons.wikimedia.org/wiki/File:Arch_Linux_%22Crystal%22_icon.svghttps://commons.wikimedia.org/wiki/File:Arch_Linux_%22Crystal%22_icon.svg + License: GPL v2 or later + +debian.svg: + Source: https://commons.wikimedia.org/wiki/File:Openlogo-debianV2.svg + License : LGPL + +tux.svg: + Source: https://commons.wikimedia.org/wiki/File:Tux.svghttps://commons.wikimedia.org/wiki/File:Tux.svg + License: Creative Commons CC0 1.0 Universal Public Domain Dedication. Creative Commons CC0 1.0 Universal Public Domain Dedication. + diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/almalinux.svg b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/almalinux.svg new file mode 100644 index 0000000..b2e050a --- /dev/null +++ b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/almalinux.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/arch.svg b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/arch.svg new file mode 100644 index 0000000..ca8204c --- /dev/null +++ b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/arch.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/debian.svg b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/debian.svg new file mode 100644 index 0000000..685f632 --- /dev/null +++ b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/debian.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/fedora.svg b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/fedora.svg new file mode 100644 index 0000000000000000000000000000000000000000..78717917f660f45dd4c2e9d11c8af3b32c901589 GIT binary patch literal 41315 zcmXV21yoc|7oVji6cj{4SOr8vQc_|ufF(q_MWsZfq@-6&q`M@PkmgT`G!`M<9ZE=p zbS~e#^*efw@!p%cbMNo=9Rlv$R-&O~rUU@cV3lub0YC!(O1w-#2LGVFcU}|zL1L|- zt^hz$DAnP8QuuGQxw4iz0G^isz`X!qAO06^4ggmn02b~6ARPw)qvN|uO&R!r?18G% zO>m6-O0D@A3IB)O1*?9Ge2$!mf`{(psb*LBF~Htb(0Mw%H0BX!qWcX0cW2bNPq#*J zzI}Y?8r6vt2eq7!nQ4{%11LH9Lxj>!wr5R=EU2+II`#gQcq=NMdO89;x`+j+Tj;#v z8$>VolU^=)d79-eBu}uTKUgdm%#0gPbZqQqadP%KjOBQBcy&k(Pyk>tHa*kA5pBF5 zYs``zruNdd`e#(y6T|4AS@lnOV}%Ry^25wOGz_2SI}`i(eGpGkzap3R1;YX58S*_o z-c#!xzauEQxHyk=^vyqYkm7^BvnI4SL3DkIdC8(kYp= zFW64(O{^Y%m zUPgaXHnPjpYZ_#TFk}iC$Tya0Nmu^&8AVcjpm-Gbwa7+e^V@EDAten#v1K2X--e%D zzf+#RrMJz%wDGoRJ6hbeb{hfIO=1FLNE#UG473;MKXbFpyEG@;=ie6Zrj0sJN1d528CoOt?Gt0WQ1Y15CPhQA0jNu0W+N&mQZHN3&hM1uq zOP5+#myo>n2%YO9n7MR|(yvCc%LWHgVk;%8e}aZ@@Dol-kzL`OztOO^6)Dqr_hG(( zNO#49Be}4`{&O}J(QM0Z<}L9T2++jg5{uUIQT>V?!o`e+^CH;i%B1yXe=Rl>WMmhV zLt{Lm`hf`oKfjP_`%r@=AoPaHh(W{{>zv+FSs>IxWr;P{R#Zg7i1*y!$8znuZJ@_k`fwQlwrxuOIs~imPQ$2_kzD~ z{w>O3B`_$&X;ZDyG@Qs8jHUxkbh+n6_Jf0J%;%Sc`A!htO5J&mzs2%1(zKZ`U#uk9 z<-$ZFn5@bj($6auni;P0h#;J#UO4{RfHa>{-nEvd))<%I=(ku>;JupEMr?Vu*X8P! zs-s!$)BnWnct^j@_*%bn>y}8RUN`zQ3RgeOlUrr^ZVzgOXQU(nW>*nYUaiF*y`)|& z9eNX>UG%F8&uPf3UaIk0Ii4OoON2t~n8$2Rl|-*cb1%20OIkWJT&r_2Y7=MfdQdYE z>>DMCpd0fBGC2o5WayX?T@yfa1ki;wT;^w8u923@KW*zI`%S>rg)d{ip#Fjo9{{8< zv>^CkU1Kthl7@p)i%T0mpA zokkQPj03UgWps;zqQ&L^WwQhHbH>)id|~t?y{i5XpRrbB?25bb@c8jl>d4*Wm zhhM!Bp?VPjcGKH+_k)j8-debPNs^#mH<3(s`ewj!ce?t1ID zyysfNdzxv&b|Lo`^0$hfX6y(XZs=mou_S<2=t{(_2lh|hN|jTu-apRz&C~=R$@pLu ziIEo^$Mm)b6jyd)+L^X45tvh7@Au`^g%cUIbIKA%*>N`kxM0pDYZ1IxXj!tDj6R7_ zv)*3nk^7oy;7AE85pezp1@?YPY0O^RX=B?mPG&`pg_$#T-KnG#&k!`8SH#ktMuF+~ z1C~ptF4>*)85pf-;2^B?Rcx)U#Mk@h-`d9(1!qIoi2){JJgR6S^t<9j&C?hx9XVpc zzp(9gn28EowlXs95<|UZ$?tM!A$O>o3gFR>AaEL!s+MBrml<@FkgmE{fC7_QkA)iW zA6Zld3}kG*B?#x5v=u$upE7NDaAl*to*e*I4n8}lJbYSZ$zI5agB+IvL72NgH%S*8 zu=|hX+-WN@w*ACKLvx6x zVZk_JKLQ4-_y-wEri(tm#upoEZ!;sv+?;&$75dZxKb_)<@T(CR0M^7hevU8STGn4V z{yun~z`J_Qc)2>fntAncv50jzjOU+q3*@pZQI4(hCyNya5U7^S%AL(WzgRIl!P&-1 z0|1d-UC^Gd^>g3dwJWZsgk^A?^KZ=xn9gA(j~`{QaNUARh3hm~DU957Wjjv+5z*uv z%Wq(4VnJE=IIk9+{RRSyVo|rwLoXR`Gw0Fy#8d|u_@2R|~L6_+oZB)p-PQs-IvuIZHJvJQ0h zpfROW}A7ziHGK#0XrS1hfxMU!S1R7A+}HD92EHju?FHmJ6GbGVBNn#EHH zIn3&227bYLNAJ@gA&17#p*9VEI^nnRLuvhI5Fw_<7VOR>JPUtuP^ag{40i(T3qt+A zg_R{UBjpp`D*gm9HU2abT`t(yV9H%p<>jD-d~oJF@8RdX`mRXu3@Kb@^0H}>o%B++dr z!l@6Vp#t2FXce?@6+sS7`=8>z;cIjJ{4d+ZgaBg5|Ml_KTgFt8rspT3`s7vKhCx-L zh_@@=uY!ERU(tks!Hg$}`z_lv#O*~iL_iIpsHmzM8=N4xD`XQ~wNsCspwiCSXurGt zzN%VL5OkR2LA#&|o93m;1A>&VJM8;!o#Aw-O^;M224Fh-$C&++l@)?V%;g$~*T)Vj zZ^Pl%O%84)77o)`6qE-oZ_i2|yA7+?$8=}pqsfjdxNzV8oRAKA5o;|LC*)h4hE2a( zRW4pTr0=*yOZXY&6Kk*H)ch;1fl%MKm!U$S9W&dEWm|8K?m^cL;R8Z0bLAc{JQg|L z|EIkWKhK2Bzf|VtZ=S)NFzp>DE?KmRFqWk1nu4p8_nRqBp176*suVDM%ob^AbNf zj_EcmxM6h(8!+UuE}HtRx|*ADBQ!RBjt|s}PM1!T|J1!jc)|62gj}6V``j@H;dvb2 zs*z9iR7D_XFc-98xOX8IF~b&{4VDtr4xH4JbCNd!YZ3%YyOZpW6xlIkl-xZrQ~L)q zn|oW528CLWn1RE1LZlMb?x~DxMWpEzGTYei>nY5#pJaFSQ8< zyU-y@`@(b5xr#;O@Ce4eRajyu!Q}X&|L~l)%QX?N2q2IP@@*|q42V~?VlR&r3JYbu zh`DvxOb)n~bqc z8(SGAi?yossn9e(oINai9wAZ}-o=C*9AMqKuc_F3PR>4CzKMch<}5Jzylv8$@FQX$ zp#hBl(M?n_En9l!pu}{D=nVR{NWnq(Ch6860;aLv2AP{@`N2Q1-m~Ix|Mq?MpR-Wku5^*<6OL4l{Ty$>h_{Y zY#=MwDs0^QL$8L^m~}fLRN~lnZ2=J z&bW{jjSZTAkc9TVu-&i9L{?ii^EFsLz>ODoU+xg|tA2#93{)n*|AiWmjQ^`Tkso2Y zuMoDls@fb^cqPen$MgdPK-i6*S-Tve+rK45D#H*Eg()2C_jGwvB@+8XU3eD1F>q3a zc_TF)OZYgb#FJlZ?rz!B{-#S`k9EQ^m{6v{xS! zy{V6Pvp(WVc1pG)P{eif70RjVgJ4KBBV&ehHUp>6cJoB9VXt`#;TdX9$93uXOEhsPubx zZQv=gMCxlRytl5d$XQd~+zrI|0Xa6qqf4jx-jzJnvm?I`r68#oD`+4p^B`%WB`XBs z0WGepPdmKe`zE2bMR-L;f6P#R+<9r0i{>Jtt|U#51n0fyYFU)*xF~mN62Vt@t5IFQ zSAPb%32;H<8l(z#4x`=k<&qJtU~Pz|S`aKx_A$|7N8pM;&*J7RUz*AuoL2 zd};efvRIXq0dWn0sNiSWI7bg-!Lg7gr15Er4b_0p?fy|7p6dwMD2VwOT0D2ujX#Gp z8a^iiVP#hPUtK3e_2lUjwP0+3_NEN#8xuX7cSNL#9ysTC!P-io*uJ;~?}n8itbe^@ zp-Q!o<%q!_igEq%T3wk?*U(=U4pS9m`KC0~;Ie)7N>8A5JUxO`520N%-H4?iIkXMp z{s4N_peie~WM-HUp`1$vn1||#<=J092fXG?!V;DkIf?PS%q(~~_uH`Sd^!}xi*`$Q zhXQ_O*Q49rHUwGC1t`ln?Gf@}B6>g(%(pT$*rA?@M-NS55m7zu5KRiHSR%MsZ zDsgJ;aBrGO4hJHIN#e9({fL62HDdkHAti*0dyopxIH$2jnD`Rj2pd=j<3N)Nn2q8y6^5)Tk<0O`-?1?R*0&K9uWI-3e`t7ON;ufE8`2Vrv0O&70x zB*9evM*`+q5A_Mk73MaQXML$e80{Iz1nv=5*d4hyH8_K~cPun}nA;{|er-1iP6wmc zt3{MrZa{=%JB62>l4(E;DQL>c?-$iJIEhQXM+QQM5PXh{o^;0}#%C#4EM#YJhN_>M zo065!3QWeCxbD1?7L4{@7;B7xAA_dg@?i6J(5ibk=oprR<_bP#7F`u ziPp@eDbYW6zlT}rfpXzIRsW9b#c_^*h91gG(G@GP*VLL)m~)Z(4nSYYY#Y(JFm-gd z+W^rDaQK}qh4+n|%MZc+s0h-_pp#E~HAhg?{x_Xr5(YsP?i3%GcPX#;*^MCO3q-uh zTzGuor?U;RUUli(1kkKGQ{C$j5PzwP)K)m)E|cfTk8>$cC!MbgcabB=MBy^Z1zX*X zf3m(lLxxav25=0Ep?WfjXja!SBDPxURq;?Mpyi&p-x z2&F_Gn(}|Pel7alJedmW??XkzA4DYGI1L|RaD2dA{+c;{xPtT9j_6ICe7v0Bn0)h` z7boJ0UNroHS+WnGFhtX8e9^tJK0Tw$Rxgo7@Be19mHRYZSvf%q51(q|o?p}XboK(4 zlmOKix6934hX@sr%a=h|qt$5Hw$5UFBUdy5skBCIA!3oMH3chTA0QOiDj)pX*DTmd zj_!>`j2f_!$yj`P&_ ziBEK4i@F*_Sm5w}hURUNtq~X>6_Ladf%oi(6GrTTUfaI^7Y+t5T>Oz!I<2l^{!?sP zx&jLKUvd9;-s%*rbczr{g0}!_^L@?V;t^%&TrP;2-#@BjiXS3y1*0gTX7=BTElIeB zgVg%hQqsi&iEbGJ>el4?n@q|rZjW6@$f9Wd>^$bt-^Bq-j|e*_3td# zgc5b`>i??TJ?ii;_=LGbP*}BBVM;mAZF*>b-oTn}mbahr=8S>L7*q@UrqmA_$m zMYM?rgsF<2?e#P5Zp1==0Wb{)J}wQWi?u4WUlgC8xz}^HAjvAPLZ4m=`BbDsv_Z{G zq-w<%5y4qg5B(#j5bKmk9YsVbL2-JYvzGOk`|@XrH;|2nLjN*nst84bufg$qU5mK- z@k(uxy`Uxum3AYs6{2`d*22#T2bU7zXX+|MQm_l~;1+jgViYFATj>eH54*PQ@7{2U z;&cr3xsYG_KL<0a9gs~AJBbX;XF6?SDC-UWP==t+I)uhES zOta9u1T#_f`zkcFUOa(3!&?@-^VL6MZ2IzN^)%h?Ks41r%8bG439KeTM3c@uOzNi6 zUYqO)9~$q?R;wRH-u}(NM1zz&VCq@aC^P#FKFI3*%??O zpJb=Ixb}KLsn<`tn-qc=^OWJUA{S|a%7c&(1)8m{^iC7;VN9XtZoPBIyPI39m|`^o zqIC7URU%IFx=!+)MZrCF{Rf``S2zuFpW1r3ij4iDNPtY&sS|OsChnGUhf@F+TZYV|Jd6- zlbtP^LA;Dzw$*r~VS7FJJhqDwG+nR_ivMKcHgJghxD64N>UDSi7BS7}+1@kl~tSxv=3 zjUgEfg2^vF(J+y*o<0kq)uamRx7IUn_9w8FNTRhyK5(v2kN%-fN}S?WfUSY9+Vh{3 zIwrL3`H^|i+ofEkHQC0-%o98$~x^Fv;Iq#>^?_0)JhKHB$Q%ZTLbjsB%f*2|uQ77BwD)RIBXAS3!r171?2nm?Qux6)x2Ty!TwpBjsTj zqO3B$4E;~#wtEd$HAN*!@hFUMsIpnMjf33AcA^HBR&ln7921krT7*|ctoK}emaU7T zU4TFV-@q>Z<>(iR&k=FC8%j|NhjbakV+ox-iAI~7nP=_n7A~=(Ck&FR3;$a(R}Q8^MQ<%#FF^=x$d;Z#I5NDt1@mG zBGme8?s%%0_H3^R7)lT=uaCIV-=Z`&3%?oz0WMkm#@uDL^R%g!e!m^y) z4Yyo2!-4dzgKH;9nnIt=XP#_)N9dEMhktSAr?Zfp{Myehv`Y>qo8Ep15jba<$rqBV z<0>3`d5@pX$)zhx#Pn*8nsH*~*7sLdNAGK~`D3k=;OkG7Z|2E6@)5IPinDietUFjZA7r5`l@i@^OXAKp?E<<^^Tea|%-P%rEp#tH))Xog#imz(fhE-GxFx5j{#AbeZvtu7Qv85Vb0|bhG%ji zub)T-9fp%W|4FsE=X_v0`n@&l)F={cbQNJix{>dX%^YXJbwPqc5_LiXl{h6bmCAOVSHWYmsc+U{} zs}Zy6*dwSzVX=%1iqd{hJ9m7-s&iM;U3o0!PHc6a=vn3psKF-3Cbo6&&4KS86{ z>1!H?q)ZhqwP_$(!+M|75#?5P&!elQ7ZuN~C29Kn-rs&rwm7eTrE;;%XLulnlO8Sk z^Nh;T_5QnGwQ%F+WEf@`D{fCk>~TQ@u&$16?JMKn?)u?6W1@qmt&Gq+m2cPUFfN%I z@wufyKYwm%R^HXrzh=8oaJnkyhBCxo+g=goWTI8-OG=!%h7&Q_~)grq1e zq}e)pJhPXoN>SXppD(|zp>3^lymQD_^IuuC5I8Oh>EqhDE3vPMxXG-4y@QjUUCP`r z?nCFv$yAz1ywgg{YRMp4ADLoFaBWoe>TBm9M%B?+NR;JF?JCWxJ>m#XgW;LEPKE0l zBT_s0nsa$!ZsX7PfcnQB%e7O=mG>;-@dxV{5;_aRgaCU{_u7LWb+=D9z7jnU{_OoF z;g_bA!@mmXebHhKH8V+f+%_3{xRVjsu6Ovh|*I>8dG zWADU%|K}(6)-a8xVBwfwcVgIYe{Bm1c)j<|MEukBiA1B^FEgYhM7uY>i*A3459IeM zInU)Ut@qY+DfKGgS$((;z2a3%i+C6Xlw}kpVsN7@ z7LhbGxv}_Nj}MX}?VC#XXFK$Dxu`zaa`wTj4b4v$rMMN{Oss4CWlSxV8=~$*id>!X z82<_IB)?Bpx^c{XDl7k{(^GzwBxSSzTseV5(=VRg+F!}I0oA9*5&uRjrXAq#{JvSu zPWvrfn0S&RbF2byG4)jN%?u~ng7Ns4rhqJTF3S?NQKr~4Cs+!ia^@?W5)!^*b1 zEeC=<;y5n@Pm2|k#cH_^_)tCRBBYz|bn+ z*X5{MUG3X@AX&}8G2mm=oecwMiX2tN*hNw6?I~eD+^KWefh3cBjbx*5y~gM&Me&Me zLAr0`b&+cY|9ZIzo>&HG{3$+UoAYi;d=%~khjRhJZTl1QE1EyoQ!!}OLE5!KA>v7& z57-R{#NaE4`FLl@{g`B5%WvlRxtY;StTVaJLt30J^x)kp-M5s#%Nrj9Gm%ELr>WKp zgvG3f9dZG$2Y|xb?8?cbKkMfoX*`9eHt$@VKO}T|IO9-jY-@cFLH4wEBHXKR8%#{AUKeO8KuaA?D4 ze>~Sc?RMM((XZjOPk{xRDd@WQzg7@W{}(Mt%WS*jwlKjqQgBk7j6@{6CsS(* zse~+h)1V7;8y@B?CKCAhWMT4w4}KI6B^<`2eP&=l;9S5lYD8C6ZTn*_(DyKqxCY^ufiYwJ+G3^e;+XYX7U>MDQT2R3YGU7 zzpIdLvOx^9JY4FW@#(&SOlqhqFGl|!1#S+N><3`wFl6Lk`VY}&@GP|~ zx({&Z%h1RyM4@snPdnYm$Vs{zWEaS0C!leyQm#MNw)C~ZG_h@P-iBR>2Nc=okgnKE zr#d^|p1Th`ED)P-VM3|^m>vWj_4gVtZmdivxRcfgkuF*_&rzTBfJH~a+nhezt-c9^ zb#H`lGA`em#iVT?o=^bs!u)zR$1z0c@Aj3;Gg;AlXB(wqjUzL-uy>C`fjwu3ztpRr_0 zV}WsP9TuGJIrUD0CFpMPJxiye--}#5DO0Rq(pu~XozHRc1>V4-d3K!pWBa*oeZO+` zBy6Y*R)4-uk#MjA^tp3Xi1d8*Cz*Zb521RZ4qv{H7WP?r%yx<* zJu&+2neY{d>6VT}M$-D3oxJ>*B}U0=_`ZA3ypLG%scU%R-PE4fQFA#{xfDdoqULSt z$S4y$Ip;;}1`XDdbr=BoDi^hv0_U3NDeK?+JkVlK68Rj5#$7$dB&#%$4k(`4Z0Mbe zbP;X53hFyv={2~FH9y`bpIi$H$NzeVXV%ffoWpp^n}}ME>ay=Q{&-yrgy{YlN5rX~ zfED8`+s#vdHnn>#T?KI*d{EjSH^{SatJLWH8%l}A$G71q91IWHuGq?bDWV2zC3R9| ziZ}S?a!{-_7Z^wrzU5s&w=v)Z-XU2wB@S17Y26`WP>~5t8e3EIAYl;+YBJ7LI`jI+ zff!nnuy)~e^SWCoS`4A5(bPUoO9pmzVG@pC392PQr8der>&f@gD?wfu6W{Adu5}#y zK@SwXCRh*DXPUK7!buu)P9)%oZivewOZ}rl$?CK64*z8EBv78ZCsM6JLNNn8kT!MR z)g%r^FsaWt5WTpIzXjJG2w61Fia&Dz$&j;0C?;1Dloc%WWWQhFM8S1Vu;ahq93jTUsUOyxNr(Z?m= zx-L+waz1K~5SUUX1-qJ-_iqlQXx*R%BC|OsqM41DI4IDvw-ajb9Wl@YIZKm+D^5G? zU{V`~Hu)WqDSXI@NAlQ>L_J#kP8 zMr$Of^k+r;iU!+66t_^0Gp1XO&2t>A2@sYE(wQbV_zWVc&|XlzXqKdi(p%DPsQP{4 zi0Zk6tm`sq<+@-;%X`L2pFOenH5$&LXFm`0SfB_i5dJ7UkqXwH<*wY7_^MnY2_2U2 z)r0g$3?+ZrajbjO*--s)8FZP00!WFb zUd@3X3!lgRl)iWM%qu|-8Da&(rxGk&wQTrB%lNP0q* z)}wiq?2Hy|{q9IkS58@vWSw)a)8}~3I?=lZe+bc3_}(O@2&raxS&&1?!5dm7vrhB6 zFL`Zq$kHLVNc7e@jG$qMl;zM?+T9Zii>Ee|HeeY|qZly+c+pz33(t>b272SWM`%nv ztKO?7vQ(DkXB&GBpMKO{kIs`>$$`I%g+46&enS?`koD;U29}C?%O!bl$cGKU^tC{$ z$msZB)cz~*Y?d64WtilednS9n^>8mI$3nrSB>nWs8_>4hE|`cn%I)fN>D{yN(k_Qp zkoRR?*GgeQ#I20e`)?(sNt(h8Df6K{k5J{M0$Ahu!Lg(su8|jw|687j1c*Yo@xL;S ztbP+^t`4Mse~`P8VT#e~gsA~}X*=Z;@DQCz#h!LviFZf33I^OtZ5)id%L|=*o(b4V zcW#Z#I|p~I<-|2?2QPlEeF+N>cNm;vCB(i5g+s?0TDvoS8y=w1NozYW!k&8CU4hnY zlZ#64EMu-?31@OH30ND@eHIRb>-RZV1kpiZC-=>U`!oNDm(TSrFE4nzkNKXX=I84S zAP#i#vcG2*8pp|)I&j;Fj4SU{ntdZ2rL5Z zc6fYifu+jM{i7t$$37)6>;16(g7IgHlah0}+g10)FJi&G!4=Z3jzzcGwLV~p-|Ezx zPhbC{T)F>3ZhxoIotCWgnx-Jbo`1Tr|E?_U<;YmUP@Tu;OTQ@JkX!+vRyvz`{`Kh5 zso#M+!U$qZ-b^r`RC>RH$M>T|GEvE7^K*bPSC#`&Q@vL z>Nd{$J?k5c?=IG*@w712&g$Q`ULacEMcvwO8l+5;$%<>FChM#_yq^dwanK;FE(vJd zdOhaxZLMA{`$R7_Zk}qPBbFbAW4KkdCA067@~Oj>vR6DdaBL<$Su0UoVQKfcYHCYN z3s)Zx;JUMra>q1X2LCCFu-r>J^KGWBM8GrKEVAcF7`yR&-Jf_H7_72OHNbh2t5drw z*D2!Ct&wtykLS>cQDx4vFMg+%A7kgZJC^&cV`}mz^6j+zS&8re#$BYz527RKG;Y}Q z&!bU0V2We(>>FcQsMdKDOr&oFz*l>nl+U&9Hdho#nij8(Y?UzXJo0juxSwta%a{dn z??c;}?()tO?~bg~wC(?8cRxeYsoGz}c%{m%)m-6e{xc>uFP>Frf*(iHsp}MPpq0Q+ z^i*Bl=~|}7i(0tbUOzr+74VsVf5p>Q!nXBj3CcK!H;9&`bMZ^gp7))*kI-+2T2{tQ z48s* zkYzd01e@K-Pxl%*X=cOVN^LyP19i1oxhU9GJoKo{#y&d8elk^08r|9ngoVd>cDBI} zf67ZwzY6t!s3vs*)G#_nW+(Ch&jX_1BYs$xuMf=($wrf~KUz_lhoSNNj9ljL#Pf$A zGx#!AWh=CiG@na${DOSe6~WHA1xW=^GkY}FdMlR_=86v+zV1inxk!`4WgK9UTFePU zr?A`l>d18e_jBXTh61;*YZXchiJQSnpXPxok@v5!n*7ATvtv`kP0{KO*bIxVdHI|I zOe+2P8{|CU0qd-_+Xwl>0}VKl%l#|My|L`WIqKw<6daAvXa2OpVLVlE8rop99=XTU zv|!GP0l@4mfPv^2B{rjxtX26k?J=IrDDpP^b~;?j?s0FN`7KQ)Ynqw&#u(;zUVVpR zXLvwJxY!CeSJAl>VDg%4pA!u)ADSZNGb}Uh?*)4Oc(OhT`wsgnXRd92S7c*|Q)T>* z8YokBIC)>As*feDhg}n=^tzc&6xi+B`&MzVO0L&^UU0cDLV|^YgPO5bZ~t#e)z*ku z@QZMc#;q&8sZV~YqiMLaY+;{nvZc{>w-^{mRpYhzX#O<8HG&tcxE)=h#mu}c+l5`#ANYc>vab~0O3=vq5G*6EtXFPNBkM|;Dpi-BomWD*_t1m&Eap@ zJT~$0zER?Pw2$&{;s}W9b59w3?_-Dk+GK^Q7XfI~f;cBwjHkriPAt1Sg}? zdOjbdh$2Q!BW2_8v_=6FB;vQ9}}*&-VoTaX{-SkWd$U_{J5 zIiBTCsRZlHe@efYQ2+U=kvx)iC3uVkh3!6;tuyvAIztjpYX0aq@7rQQi7>Wg2dm11 z-`cY9R?M5AS;u>j+(+HGi`ScqmHNfzRrLmgxc&L^{M2r}odDCJ8&K zr{7wgR{M%*(lpwb%6NLh6xl;$ujQoi!uz;L+(uI!XWOfB#hFSio1}&3#`o+w*pH*ow zoR!HZ|7iVt*jmdq_?}geuys6C!5@bIQ!%EJKKPqPY1VLlWnhr|V$UU* z|0uJY%lje&BC~cVq2+?DKX7V`1J3a3o7++{I2jzL(6mU1+b)ma_OI9Ua;u8id{EvVXSGRec3m)UZgAK@!Ci#*RxqaQuSH8}tO5mv6R_U2jT7S(5S!IKqII zC9=m7NF7`?gZXJuczio-vaglU=?Y(O^7opMNqrTQRw~xrJsWv_im$3+JC9Z* zHB&MttEKcZdQ4_0Pk^||2<3&HL5rQ+K9b%hLGY?_u>!E;1LB=-&1$ea`sod|^F zlYQplmf~|vx3OmvFgFX%O>YW5~+_io#Zt+EtjWmvK2EN?TFw?JCK&_6t7i6`bjO@-iOG|`|+W2{C1t;?88rs~Xm`hRgcL0cw9^%@J=YueeAROS`Wm3#AgtypV191yY z(MDsdTJ|A$*ixR@v6>1IN7DLeIQbfR6}NK=)RPLFEOnMRJJgp{41*4ma4R}WE%X$7 z2~puecRd3Lagka*571%OcPVkDQ!DaMo*9Dq#?w6Ljl8x|1L@~pAdc_pB65W^EM1Z8 zLDRE>bgmay%^~p~ire2r8S{B5)KG+bMk?D+$ED#V`OjhL+xBq2#;iLHC?D)_kn)7l z%ml17RXpPXk9oh&!W5(a0U2hWON;t@*z>6nBJ>d{fG;szvJ@$%MvIbyMd(IMCjFnC zd*U;yDU)%05bjUkq(yx1Bk2cZyyiwlc>C0OVnDL}PL;%_QRngW^|w<3{}##54Sx^s zr=(0C5TS?S-|=SSB#@z`is^G?x$}w{L>3zxF)9bGp4>-*-9ks~00_}OKNWIKMERgh zjs}E@T_QGLEIkX5EbyESuLA=B_t3B2vAeTA1^&yM@f+e>J9el$MRsda5xgF-{9At@ zOnpP(KNHy_cmv!bI?^Q~cK;I)NWu}@Fl8_tjs0Z;1HZ&3-XsiP)`HuGU$6kOm3=U!iEf!-h+IVVQIfai+& zN+~^1%sYl;zHYEzYWmcD4uaX`Qallueo@4}4XaDY1>1=B>GN=x*VU8v4nz6*tjREn zrQDDjRwfyrFO^PGf`C%j-RiGkHg_XvACQ%J)TU%9gE4fu+(bV@no3~L2I6Iw~wtb~d4#)}sgJU?y7xK%Ww8?&Vh=k9< zw!N&>SZ~Y6Kq`$c!-}REix*r#v$*QNrSv=o0u7+i|F3*;e)e-Bbdq!=8}j`3IZ-KF zQXJs~#g1Q4o4qla_Scoumeo&KDWUM)X~G^I?Trn|>f?}rEJ4XZQd+}pw;PNX(o4X@ z%sVLWAHyZqaEiQv3a5HDk3Xi5f zq8_eWO)QY%TcqTP0NvtN&e@whsfn9k?@vB?9e6hw?6d|kf(YkJns5rooDVdCAxos3 zk%aC}6`i+1qBwBqW?%MG5ru|#@_=LOb7Azz-Lc!Ku#>qvF1+)9dC0t<+P68v7&6>N z8m@BP=X#mRTqesI5SB=k8obU!6Ng3Z!dox!R!UM45-5qeM&vX@y=SE&yWjMZ_&nBb zw8IKlv%h3CUvVX$1hpH<1Aq8M&_6y-{}_s*S;Tc3)V`0W_52lVx;)&IfTpPbnAa*C z+!dM+9I4^$JRrGEMV|?;FC-5R=CyNb5YJ^dUcLj&Hqu2!^nrmmAOX|g?nQS!sua#9 zs_2GQ%%u^v`>O%{_8NlF+O6&E=}P;|UR=zq0iS;}+Ny8`b3cHG&70E+Q>`-|Xp*Cs zU0X{~bp5D&a;>6!WkswpqXsypN$WB4`e(HoN=9{UynkPiUOVv*DybgCN&X z&rE!mAWCQ@`z95fFMU<@rYYY`Z^``9PvNKcT+@uIv`A1S@4;G#FFP(x5ah|mKZH|% zrvx?L04Z3WG}f?tbm2`}=WuklgT4eU3GF-WCG5N)fC>+VWwCR?;$w6YUsJZi-xaVh0~BhG&YkPXw1{Omy!& z;@5F(QvyVXUsWoXU&`{6O1|+BTSA3^{g7UCSs;az?uY>~lBK{Y--nm`* zZ*tT^DV!U=`gMAsaCIQicw}ArIUGmy`4ez{;kNL3B8tzu_@~XEp;61;p2JFiY9xg# zAChJoeJgC7Irj4Wcy}T?C6P$nHFL8~sod7gIN~{q;#t}d#o8NUuf=l+Lf{2H*N=I% zCEl;cn@Ucn=8yE$!GaDPn3AHvg$YToE8>)DOB`hS;qd{w6ppz}7}jb#EUz>Lrj*0% z*zf3@I4lbPNIdmIb=4X)Nk=w8Pq#_N2PWN@>_e>E@CYh2fpL>E(G03H*S1wm&>|R|e5p|$f4U3D%hp;x&lQ7vv_BDGmn$Gq$ z+ls`^lRJS2<}+zr#+VSum?p+L)1IsvJFL%aYb|d;#j|4%89t;=>07go(^_ge@xti4 z^c0uff+i~LiP-oW;$Lv^M|bq=<&TZLuaiwnxt)9E5+QvtN_{r}N)9q?4Y-~V%MB_o8aD`ljz60$BOD-kZ` z+dt{BC2Sz`n{IQFLYwwe?GHypu-a_4Lr@TB0&FUQ8AucqBV8@vJ-Gi-^HQHLVK>^O z!n*N^iCD!gmcY}xhP0B0iu672dcA*AnR-1>e1#I*SXps_CbEbSbqIemgENh{10Ifb z{YgAl_k#(8hV@{to#*l@e6EV&NxP(+SU$^@# z%7XCSX?ol23*ARvy;C)mt4TBrx^JU@z30d4=WpB~yRCWrTFUC99V?0=e%apT`5r(H zc{;jeJuKy;;;yT2UR_=w{j~7&5R&zm2AnL0Q(bOAaT0f}ue1w8i|zr4osy>=i|hW@#lC+}5N?oZj&dUs|4W zj}jzApwkHEdQq)h1}wf1{ahriJr*Q&+W(?@3SrN$#@D(x-0~gWhsd_X!^oe`j~H#Z zd~)7{akeR#Vp{`r`3R?4@k6iW{a5u0`15S&exv0&3$iSW!Ch)Sg4Eq+o}~`Ifk9eA zZ%#!JcmpdCKZBnPp|_l2;@Gkg8A0<5Cs?g};GU+aiznbH>iUrd@w=>(-_`eE({o zLr}&THd~Ppa7orp{5s*1VC9hgQ{fGPHxZ_A+jq3Z0Lef;sGti-OX>|V={Y}g+}A1U z|8EN`-qgdH4^C?$96|hHJg7Fyw_3yzO9A*dE=E`h?Pkkqi?)!%h>%QdI(ivG1#98r z^)r}H$JK9uCdGmOi{Sb?sxM8q7q^2%?E`3n5Ll1?QHTAoqYyg<7ZqfWZG>d)au&eq z6Zw8e{q)KI#^~AiwV}aF#}Ec#QC~l3rQ!jR8(dPCNg8QO%$VUNR8n1L$ibWKujN%; z0)ZAfAXI?zb;5xCD|kYXnC{d-2s&!{Kd z0>}uF4ujnO^k)5~dM5PineZ43ldCu+A=2*{(_)`?EBuUk<(ry z7F>Pl(-~1f--erRBBEy|bQ#k~y>bk3IL!k~)l^+Pd>1i|HMHz^0YL8&(S!=NlSi{4qSzAGj*kh%cZAo>Q1V*p>4n$v}G472Zl@uqJM8-;k1N? zwb*ngZ*HPzqhW7>uD-eK_#{97v!OJGu#?P4sgoXG7Ch$^wD;!=VYNfe15Iet=&xOt z9T#x+y_8Ys#^grlV{-ICz<5Xlk*lAzG5Hno8wW45a?sLG`L1vs^PUik!0|usL4=sB z9GO%0AK!(;f>D$vks)`FTEab53e1CCSzW#|gAk+jHXS1O=XmmXRPuOw@;He>BYB|u zA;MlQaecID&lXw#bKk@wW&Ak}&QmOE>F8GBe3CB!oJ+crA zu+f#qM~!N0HNZ)?0WbdE{UttDr0zExv$DUQFIYGyr|bRc5JJm5VbH659ndB{X#z>| zjSTR&UijNM2ki&UWWu8_KLBWe;%R=Ti>c|jSgQ-U7tcpSy7!(+WhhtSO$6btDhg;3 zP@+XvqeE`!9_<}lCFzY|vFWTN$^kH;piBdYJkC3_gd+Pd0(8o#QckY+qn@^dO`kEq zk|~juv@BD}6BZ{Nz8}V#-8gpTe5UqQs29mR8)OT}SBh{I<{&2)Zk*IRCaDUW8_@t3 zPK6wQ@#J?>PY+{u8Dn5|D0KXCll`|Zg7pXRH1F=08wQ*PCJAM8nG>1kthyNSd!ng) zxp`V_>5v@Q>HWIz`9wn~5y26TAt8SJ6UQ%_BRnGM**J*y+qr7qa_bfCjc5)m6Ou$G zp98*Ur|Tc-`rPJ){e{MACf77L_P%QveKK-#j zRrqPobkW6+e1xzz^v+bo@$ymQTcj&FfRWJ3JNsj{_|!qYhR7r(-c>5sEV0JNHis}j zYhu*UO@d&#%a+@aG0)@pAewW+opA9*3)Otc)2uN=L`6rB=||}~t`wq&V<~vxN?3&6 zF~m=F9HxRZ1cq_eN6z*dUedT>Y>LA|QF$)lX$JQ7nA|p22CtA{@WEoC?0-*2R@x)Z zi4{^ws53NP;k+RnaFhy}TuQp;<6;zWOJ!%#P92s%ULoZZFJWmZNCzA-U;typlCDnd zFt2Wqj>hvmeS8MvDEslsz2W$X`!;;^2o<;FiR9zXNbrxh>bwCtf+)7lZDd8Kk(V~H zj>yY{)%M=;uNnLm)A_z~fR!HkY=Ee?RgwoYFh4Me^gq|HocvYKq>WL90o#V)l3u4IvA|Y*YRAypt`H$->L9Y zAUq){U>}b$G|v)WT%LU&NOHJ3?8G6sVk1M@dF(*vbWP1~pA#H%jnnvc;T z|0XB+*MD^1FNLfGSZk_t-(Wp>wUws%0kni+;tA5Ct@nZf-r?ht_ZngjHQ8)dun{YZ z8TSJifYF}-0cNplzX>l#Z%w`Z^^s!M@9ON#?;gfzu~kaJvmxzhXwh3(u( zC$G0g(s5Jf?j}+bXt2IZ5x6j31LkM=v8UOQyf+LKPGmy^WVFlg>LGWC(4TJ-Aw{Sv z0V)l+ES1Q>j9_z9aRhs$sgLO>2|{*n@BUd}Zn3ha6Y5RV%DQgY21T5>LI2#>-&kA4 zOeheN21Yoq4knr~-BaVRN{O`iC8a0xFpEOK8ypU@7v|Imy?k2lvHANQGih(6-1jj^ zw0Ko0h>GOJDJbq^Qs(?FU2YWF6V6D1U?)(}JO)ldq);GMx zKkY$W51%$1g-CWgt4>l!?0|M7IW6d@bCSSUX{bigLtt3x1+Mqow5#=2I5?1_=Q4xQ zUN(L$0(HlTf*lu*m&7$>MXdrsnq0pqfUjRORa!yHf^}X3JPV8y?9KLv*}ThtkyuhU zumK5+1L$r&q_}>mLgxG5rPH>3De^l%znuPz6zmOw)JxB5(q3{NUiYaDoh>qCsDJ@|yBcujDz7l;3)ypFthkcf8|#K~(0J z@HUg`=-W&sDIULe9M!9_1zXMZkTh^VZa;E9E(n2MR?uY0bF3+El(^O?w;2n8#wW z+t!0{<|?7ATu&21!1xcgRVb+_NG+9u=zUN|@yC5Wh6;l`v))P|P{Jc-x%Re%deFDN z>1zv7+LfE7dAd;VMGyI(fPXHSxEC9|V2^b}A_*PyuLG zdplVD(DqFX@_7nESe_OJGKvTeVY7$Wx$Eh-tt%-m;5VPxLw!DME7Qu8uKu z#3D#ks!YKfjt5dHqso=VX3OQ zaQDL$$EyY zKNMoS&1k&N?%D56cxlDQv|#+GXYmyIBRnK@`FLqvV%?!3-X3eW7%&HQ+lG ziec%I7He8q`Wj;7YjpT&ETO&SGQ&3?Y@pUPXPDDyW61}CHH`2E!}DyQ(HvjF_m-D` z?FJhjqH0QZg^vEcVZlX5H5&sBB^l$RgS|1GeNB#wHdb|Xh?G?S+8h{;=iX8r$;Rz- zX;g=aA-pDPS-J1h0)4kwh*dIod*ft#_HD9C9G8QAtW>&m=jdx=SFdiAs9JS&8oLiH;bX8?J znh=arLAT&{xAWqHZb@bYfFT#+&AhWSRRnq>R0Q0)Z+RMGbtqWwO$|lLR3Y7`BQVA0RMQ&c1qx~6Rp<&CU`~G-EQQlcK z5mNLDtG%31zf2M@Aw zEf-Xw%FM*=R(F&52DeXN-=b|};Dze^tV{P7?E1Ch6CntJN5u4*kh%mY7O`$$;O`gN zP#6Vwb>?HvJx`eHBZI?#S!8;6gC20ewOF&SQfU6jXEh|B`MAz?9KDB~wT4oMgWV7D z_r^uVA=WmehUN5$x)=m&TxIDcNDl0|;V&4V#8H{vBNgV+pOPZb!Lvq%Y0rCGq-3oq zKh`2x67bOPc9TldIl!`Wa`=&5z?&pm#mNSIgF4z#-VIGpb9?rnDKx6Wb&JftH}T19 z;32vTNK`1p^~EYV0{S2*=YAi6vmRaL_Ixy+at7M0xAX_|e7znMDj4u7|D@P8p13-D zuEudE#3f9`SyNfrTgf$Xn~)hu0A7{eT6=e!~WJ>Jw!83pV|q`!{X7 zF%$$C@jA)@NxH2pARDP3;NM`_B0Rt6G zT83gkw0H+gl8FMd@?<{%oJ_EB3YjjvS{ih9V0ZQpuA!x_t1Cj8GLQ z3PoFFs=!F_UXm6qhCU_t(hT?W6AO6h(RV-U!4k2Y3j5+{C^-SAn2doQN@6z}tlje~ z6?Xp%jf!M zG)My_UsYE?#4guOqQ8!xQ`{b^DM@*PC&_&pd)G~PDjO*#fX^}j|p|6JDQVV~NA za;)816T1b=S3cW}7QI(3(a@gdwao8DwOY`a^Y0chJrl!4kbl==vHvVoRxIdO*g0Ac4bz3uOVt+X`A?jIOlD2hUchg=|N@d2Rga4e-T`dBe6G z3`XMoa)03F7Ib~(01cd=>Uog-`ET7L6d+D5(5I^+1;U`V!ta_*;v``5J;p-Og9i3xqq)=Hf6<6h!D%A)2BC z&~iVHpTIIg7eDwhr8!*oKeMvEppL<>$oO1l*Ty#5!p5yb#v&#b$f!|I_Y<2 zPTgp~U$^p|>Vw~bRqQ!nOt;IB&M@>bYcC_;v=Rp-04oYkg(g%{IVFCUl!A0h=+%=t zJ}D3(MfHNdVCF?mj%P~EYb=tI_Ftgd^Jev9B7ZYLxv)h$5tk*h#jemBq8ac)md&GN ztB;D*$-wI6^$Tvkjwhh+F4oM=@*YOMIGF!26FqgY5E1p}w}0{FU^$_8nrj zRA+pTch+$#5}Z}%1n^((lHfL5NA`As?5&uj-=?eX#Iu%XklWU^?t*$Kwt8}~&(#qN z&T-aWYm`p%FY`1xuBANkZ8+Zz1K6Yc-UwQ1`UDpt)s`Ep4gGx^cBPJ2^jC@*DV(+x z*r%%tv?DY-5X&L}sX%Al&V}!L-zU-lCDh>GeL+NQ5#3qXyHhGc3y6G#^N7JqS@@8=-Vk{{~|3GqhPIl)f zqi*U+^gQDZ%TcA5XFcO!J5u2K`!gThz8+=0u6mIG<+|I)=859=-{Wav5B91qK8j5M z@dHic(lA_xGz(QuW|AM2vQwXU&ULd}l?r*@yKq-M9UABvq@4C2z6>)jre&Du4{Q!S zZ;b~*0-J}B69^=~vb)6h6G)Jn#vNiC6!9eMM5wjbnQIlPx?>J9Djn^K5kM9unJY|= zOLaY!!(}oGN?vg5AfeMqIt--EjA#cSkg{(`Ob~@qodb|8Vp)3NWO&xeSE9V`FmL-> zoMIvHS#X zu0s46lwO`0?K~4S{2iH0I%qs_W2l@!7_jIE``L2^K#3OdKU)ByC|%)ajwTtJzl4f{w*iW*a5IL~8lR^n2XqTK>zMCK@>~N2l43VB- zfc`a)<1Q_hSi|Tnk>F>EY-{6v`bv=Hxe85>v9j)V{%`E;8nS*OB8)&}w7vM#CIkg;R6L}nLxW9?lK3IhmQGj-or;dCU*SrG>8s3Oee5pi^$oeX)J_Ye*f-&M}uQ1%NTJ@E|MA%16*;miC+KmetO(AHDeA2W^H|cdQO# z6CdD4EYd)f1o|A~xY=3=n613{s+3B!{`@Oo*cYkeKFGy6&HS$YzHlIvaQVhIK$=bG zzB@qN)Un8>LK{x1eU?x56+ktT3xY|Y>lgT`FnFJH4yfE4fEpFW@z80D8(*996UG?7HShmr=oAz30m0=- zxI@sdy57|doj_t<`R2=Jvi6psjH|R7NLOCiED@K-%BG|$j`|xbHTFlE%Vk)6gn5N0 zImF?-P-EN$gzypn}GmSuNXn8CT{{T#*LhI8gLLA{s{^^ z`UoO&z?ZndDaL2buMFbhaDUO&F%@=XUETMN80qK+EIRnA$O5&k+*>usOX`+#GL{#! z+*MLEruY%>mo4b%!Ax5nY1kT;vV&O0>RuA65lL`g0#Ab4U-%OjU)V%A(VzE}1SuCr zZhnd<)F1ASd}<23G!!-Sf?n|LgnRCzn-P>i?6-B31gzV3E^-?NzxKbAZ8r7jS6o2A zD1XjQI8&J~vaZAs#IITDSThfIx7o#Wn^G7^wRmIdXz)csZ_!aud>!!weYweeYg8R$ zNDz9ER^s{gG4h#SYrmssVK*HFLYw3l+W#z^_kskGFB3>XREcAa3_?*)<6l=?$)`&A z;A%k)b4I)P%Ear@YY4JzH$Rp-nu8S8@fR7i0wzIloAz=u4iQ+5xR+$i#L9_pKbu* zoGsg>6r{!X$tk$YQy(_ctz;K4fO=rJ|E|5a8H3*6hP3$CpaCef@Wqk@Mj7$2GJC=D zFJRP^i5wDBa=!&zlacORGuYC0f|{?M;e9kIgkWobCX7OXrWQm>us^9p_9-G+=t(-m zP>}5`_g-4|8899=!b)gC>F(bb*TJ?J{iMV$?nGW@4q9QL4D9_N_O;Lz7{*}AVehlf z*8jIcA{@{Kv6V)P6dq<8OT5K(6OQW-FlkM$zkXc-5w~#nNh$oDx1TsoS=0Fw=*~SMUJ#k_6=39S3KB-un+IgOPFAWT+!nuocvT{0+3qhjnG9bQ$Yn` zSoRuxI(mI}_my=(7?6GrUwx$N$_D{+RjRt{tR{?+7A-uB2A4uNMWKuIgtg&@EfeFd zV-Trf(&@7^lX3SG`(0K8BN^DlYW2)4K%(E_c*XNOAN-hZ|_{{!9pJxe+P} zp?zEv*_{e@kc|ZkIj$*IDm{uG-@vNNX~3Y}*5Y?o6P?S~ewl>4A)FQvj>zKNZn9cB zdbdpwnm3P=O827|(?eEa(iC0F$VB@IfvkwVg@PvIVlC)EFthf~YJ8y=-Df&z7fsDFmPJ*TlN^4n9Z( z4DpHIhXlVyxx5UgCvl?ZKXN)kbTXJ4guRIKA#F%jzh?~W8$-rOuyqe1xmqm7l`7ncNN$-%fd`ZX*Ejqx9Vwq{`Q6*6A#s7wIJ_TrUy}k zg#6{z>c^2lAk}<)ms|14KI)G2R?1NEJO@Grpr3n&8>z&1V9-!Ov(yb~^M)BzbI*I) z)4^aI?D`T~>>&xCSyF|^ihVJ%-3Px9;E?V2rxm8#CAa|Eeg)jaLBRj|(E##oW{*Mk zDE+^;h9c3$#Tmv>Du@Lnk9mq_z@?J?(=!UN|9Wh@!L`J3cQCwJ?~OFlq5?_dKQzh6 zx0GV9=vH!silrlJSwZx!dMM-*RWWsq7O6)dc`o?e<0N*qqbHyFO+}Ac^!m=Zng$G# zSRix!?I_zzZt!+$zQ`j3qTWi(JlH&_+L4!yQ8Bj)E$(8k9E2nY{qZZCyRd0~c?YkA zLCnxF@PShO{25cG#nIx@8KQ8GRWG$GN`s!QC?iqh`(q!#O*bs5yS^iwKcjF23K0H@ zGJZn}rsfzk2M!}1^=|&>xPh*Mt;CrhHV?>e^U23sPv27l%LNR`s8Rm&w=E8=1N^Dt zFM(i0Y58-=-8qA!r~Iz6AdlT)KrGVOgGP^~asyDOW@EMFg5jN(H}+aHy!|3{$ZzHc z{#IAhPx{elq-6K4fH#0sWzAaly}O9IM$0%A9pG-DI&qjHf_WzCV*J~{m7UHR=Z&c) zpQaqB_F7`6~ZDcU<;IJnanggh|MAD5BLW|q}HTyt| zm3ZszRp`uG_WE%db?fEB^{SF_`n;Q@mv0U^sR!I)pq+7e^u%5Wrj1gaV_?%!_m`WV z36iW&wodD+7uq?DDrs`+D$-L`P3*GFw(-c0iy6oNf(8+)hV9XRrHzG6uO6zSc51np zb5Y%6;dGp4fBer^e?IbdLS-nVjCuUd3;;dcFWF+q6F;&t(X{UVEJE~!Sy3I4%Os;= zZ74fF1Kd=MjA;xv3id51J<~{IpLh6^F zimV+-CsG5fCu$u$!65;95#h7Z;&UUPh{3QGYiYdu9!d@rEe8V9dTa!=$#M9yCiF>R z-?SnOUBJXapqmVGndj$!=6#Lo%#(p&Ar_bxste+vs_&Rv0C46!c~e1?N>2k6jJB7V zKk8kOt1Q=B=psR=wpWPm-*P$EZp!baz{C8lZ&Cy^qPPXZq}?<8GMW~I=>8jQfy2L- zR!R_^^<2O2zgGPCpE+!!So0k3>l#abL!8a$q7HGTQJ4uqQf^Ore@evgR2GnA-r0hs zyK}e@IAtLgf$un}qc7}mH#y_hzum%JTUGMP%myF-6zzloZ6N*o+)~h7jtXm=zQJ?a zY;BejUgU#p_3OJx`OzS$oz=;wrv=CCNeRwV+v-42dX?TVV*Si$E2M9CXVFg3?^hR6gyI7mKtFyhFvN6mE{WX1=q0f^(Qxie$;fy3{iM$z+ zCB+Pv15JFK1KN*&4Z{R`14c#T58bu1?{%iz7}*8yV34Pd2*KlT_Fs?kN-5~tY=~zB zW->;!3(cl6?y@jY6u1wm?W{+8OV}dKCb^IFcZQ8>o?{RU=Z;36=HPix^BTe)jAcr= z%wTchDMfneV-Ngm&U@eiLFM~2yKHIFPLu2U6?*A)*#9m%PVKTF^qnMiKRe({Flhah zD)%k5AhE-BEI`~+^WZo30Bs>=2jo{K4{n`Oocwvoo}C&4_lWxb+c;q4>R^L#)F&?E z(d^4_B+{Q;(;1r?J;#;J@D6DS4)V|HTnKwKWR{DjaWYLfQB_US`WR}~x$d&*z1uv3>Y7| z<-el}iM(1Ln@=dXhbE=*RPr~sh71X@%ONf~CW3PN55*M$#$w~1W>KR;1M(>Qe#FG6 zzA)YUV_Dsg+FMfBAZZu*aJ}81I=tfFdJPT1dz^0meQ$V*GdV&Q{KJ5|rX=v#dlJO_ zlWQ+VX(bws=WdUEHUn4Q^GlZ4xZ6L2 zHCC}Z^vzFUM%a5OwHVDl?F}INZ2?pRb28kyy(_1=82xHpQ69JDE~6u-g|RdVkmFm| zL{=N=`h_`CoGx@uKY!`V)~2x zP7qzc9nSV0PRZ`7<}HOX2R~Tlc`CSwUO?ASa=u^_3eC3aMebRyxvY>*nwW?|ZV> zVF4596Q91H5GD44@h1apWPa~a1iNn857i&a1g5wB=2vzbrD){2SNZMAO1P7;n$XTMa;C)Ae@?RYo$vkN32LJ6-8vBRVX!jM|LJ6^dr11Z$h%-74TiIJ_u*B;{lS1 zC!6D@sF?is=R)mO?(@QTG{#6FNs;|_{;S8C+f-0TGRxhun8$um2V@b(Wz?Mxo=JVm z519BSQkhp-f==sFYu}}c+a0m%gnT;M*2@r6r-@`@&`HnWVJILkyZf^0ngM6O&yzC! zm;0?sIYPD@J*if5B{hanr*l-5t~~GuV7g*1Rw#iVRq*eQ>f$|(nTTD2KTD&m57WJ?ZbpmyNw!ND5{90@wj(nJ@~FpH;&a*Xr~=HXLZ~qp7n<52XunW zG49wjwm+0BE!PuvwAU5VAR~P4d=u5qYLo;ymV?Q-`bFs1uk~^L_*7VTjuz&gVEaes z`~mj9>cGA0UR9-C8vFev{@)ije=V*X*Bv-bHs*D)ko}`T=s1=uPW+Px)!!vOx;S((WZZoyfEg$}Bk>6XDL(a^#|BLTnnFuCt!Ab9}qZy)*r56B07U zyq=F#AQ+6yrsSLb@Hs_Fij0-H(wuZ%e!IA{N||<)^!`n$1RtZCc7?NMp_ZYv>og$x z)au`gi$}P3-0jV7Wqi`EQ@dD>O&8ZXp6nif{))90M>3o4>iK))tmI|GG^kfyWSRZ{o|jfg7bI8IWMl8?c@}$# z$Q79^uJ!f0X|Tx|DbKC@j{S-yuq1Z*weH`sHT*074n!9_CdoLv$ztR-&um@b_gPf9 z?Qq&;rjz=Txq_95?OedAUKWkhzcu!szdCoyZ+CO>UVm)ZA=7G_#Dc}Wy~i{ei#ZHAKiT+TKNQz9Jvo)Xk$^!&&KvEH8&&0BGJ8xE{8ATuoY5&pY_l_K zv(D!2sjH>=8{9~+>EqU#YZC58zD$>1MaqS2aOE7aUwevCLRxOVl{~n@D%SCLj}Krl zgT>WqZ*^zqsy<^y#M+xupX6B1!w#>yhr(s;J{AV-_Dxrnuvq^i*a_ccwf!xZv*X6> z2sYpo^rhJA6vU6Q#%h-pZy+Lj7r0qFyB3Go*>P4PVUFIZhQDn>0odreL%pe6LV(cV zl`s46FW&fd>=YTkXsc5rZg8;eA0L7*DiDo|ygjb~&$2xE-F3e3`*FYBFGz4nN$Hgv z#l_EIPPmGVom#BOEdO&P^cjfYj?vC|#l%0zK&fhYKd*80E-{p6K?mYnX~Y?U0W}JE zF+bzNH0_{XcSaJNk*M0?{_|PY36(KWGBwBlGdZy!NoKYVL0Bc+bL_h}^=Y}ugXOsf zyAoNpwt%oHSZnZEtLWiL4zGSz1WD$feg9;0UD1tZMg2Iwr_|A`mdi6E4DHq(G4EWv z`gJQE5qLWjsn8yuT=@_6CrQ+xp~)DsTJHy~H=;Q>cQZ_&2pjEj@aA z34=*Vtx+^1=I)h#Fd-Yu!xLA>&FDd#!K9;l}n(5Ll zu!?{uiplS-uS!bjL-DE+vHZ7zixawl7fM@zNngwU*n$ImW=`;gTZSGqGoC75gvrbAB~fmtkoB zZn1%AlUsUfzrcFr-h#QABFz;hpuZXc@>KuB_s$l*kp(Zma^$x;DRwqxD9ts8$ClcB?)S--uk)_LR85T&CFg$?Iml1~(6ei+ zIa<8*_H0$sVhZ#R_iBmO>6&UBZKGbS>T3SbeF*t8Zm{03vR<7m0>`4`h zu$J@hy339x5$aQNW!;Y|;?G2^Xe#)m8VWDmwWdd~IR?b6%68cm(u**7EBd1+PMsa> z%zavx*I_F40*gEL=8O06UXS8Ext0Mes8osep~us$-_B_SgIeQx(+3ALYNGNwYL}Tj z0=aM{GaRZjslAj%mEu=G%`U!`>d4KFmrM=^(IXr$!i&u%wrJ;!Bja=Pr8$~0mKAcp zqfmt#-1SH#166E`mFJEzD2Qj8eWgb!^5z03Yv&gCaK|&G#mSgPou>?I;1S`MMtsC+0uW&mGZJZWlf$r#`vcuj4<-jFWPl9^`XQD%^bJ9h@LdYkpX5; z>XPFAWX5y5Ij)nS`iv(}5@YV(ZLCzShwPom&Ed{ugNi#ln;+BWi~dTJVY9)0VftG<(=f?dr8QgLsDUIyE?mmttLByJM>-V-5f_Vy^ zsFC3Dt6i&c=G9$*p%<1jVO%4*jM2G#N#zD{!?h5WK(GOpp`6d34s~@7-JvaN6H#jJZqq+SqI)z!LVP_zm|Mgjf772cRqTPi%Bi>-0L{N+|5`a2!({(aLcT^4 zdE@)p1wQ46WALm7+1{U7M|O85mWTQ0@4wMHxfUNC3kq*j!f&%9 zbBs9ff)g0FWZdm)2*2}E=i>Muq#2dm6F`VBGq7W{JLE-?|@6PMT0g zr=9hGVS1i~ljL%nq{#g~MvXawCa00!=yy&DAP&Ng%Pg;cyel5htGxLe?N4J^DrJf5 zOluRD3DH1T0zMdi8FMmPO?!E%S5CMjgTEY}<41{!OUP?-nu>lP-I}nokfC)XmNxS3wJ$=+o~?at&O-9q-suh)Y z3aHA+{7S00Z&3;Ul^<02Tg`_)s0?jX09QxqOrZiD$vJ8Js@!Nf!)4jmoETN_Bb=i9 z4_zujF|lAOzLiH>zScgoyO(WhGP@b>8Hxqe#z!t~f=cAOj>jQPu>9TrGD#LIf8P7qIpLTO<6&!5&n8yW z?h%4|UKUT)A(Xoq#OV=fdw+A*9~K48L$Fea*tN&P_j5a>)FR`>O&Og@$?AW>K4IhX zr68`HMSTtKUwatj5&Lti3u&oImZt>kV2M?y^3+=PY`*C|6HC2Q7fpUhs{K%1!au%y z_FSD|0o9(4+IbXVd#WVJn9VVwn55*5>qSs}oBC(^Bq9=fw8nOy@FZt+G%xJ%vmKk? zWN0fF?>(k{P;e7=cG?n~bI}H#z2AyqK5@L2dh5d>3{qreexd8^_SbM?qh7pOl&0nA zz5NGr)s+U?7dA69yn;ax9L?jSvEOq9g)(@^L77xFy7>ZsG+GDrpRx|>W@BukKl!|9 zy>Vll@INAA6fl&j4cfP6^SgyoAONY@d(mWb1BGHt2ZH}uDRjV8X znw>mt`>oBaj8_Ib7=)EXt#$gvSB)nssQiLMw9Zk@C3PQO&vp0jUFdGkyzQ^@j*02@ z?qJ{1AE~7K@TE61PEzV55Vh}HAD7cvf4cWd~{7G=@Y7`~+R^1GFP*GZ5%C+O%6QxnrYV;wxW7uga#k{=AyrtSL#Jx8915n;mAE#>s=nJ4JoNR?Kdbex=r zzg1jl-e*CNi_1Na<8IZ(X$7gkE&|-=;}@RyiKeG+5tVrGZwQQREQh;?c zBBs=J6=tP`>lah6U$YY~H>6H!TWcOq7U!Ry%YJQf-i@P_<8gJojgD8t^TAKg=o^8X1DkY-Ti5u!~FG+t` zqCC2?xyPZcd)WEB+pTN5%~LjWWgKxjD|0?9WD@5ZDarPq*9`7X*ezT3R&8(uz2qz`g`0L?_ei|<4HnL=;k`BCky6JYn-J?=*rzw+nrP@?&>lSlY8 zpR2W-1ML2rTg*VS{pmOTAtc~AVxMpGY4re(F0a69bdC6Mw#rcW2YLd2SjI>~*9Z># z$YZABv!@+8@bI(5@-J?ZR6K5VdlAr6r@E?oaFo|d0~_t~H+24u;hx18Fm>qf%Cqk7 z`k}hC%xSGwN3!H#QIWt8XJr>wE|m4{?SDf0??~_|1%}KhJ(WW#r)e={a176A?#UD7 z#BKRE=mt0{=ub*Z%jjrJhj3Pm%vq%)DHp+@z_0GFp;TqQ&AGQeDTn`eBwtN^a|7Z~ z_jru{UtC=L8}Dbu6W@eR9q0nw*K(E8d$cmiir{}F1$S}$KVd)eGJbYwj~UuR7Z|j4 zhNyMbHkSkw>AX*`j;mV3iU>b^gYl#57C)GU{xIins|;Ge$iZ*#V^plS;0=A9RyE@7 zgCj_E#&`FJ8(jsV)(3_O4hBb;l;Os|e(aB{e8tpqKK4Jcg~k@8n1$5lTK%O1`-NT> zay9imxcA@pE&jdOpTwmeQeS05<$|>6R8JRuNUeHA{c4}5+ar`V6m0_6C)%qq(kOAa zQN!=40~_+C1-%yg?Tn=M@ajk(E|%MUP^i4=4EfSHcH>8)5i+ zpNY+favUg&WsIWQC~Y{4@JVtkd0bCB19~+g)+Rsdkzi)ciWRk<`zHFl$Z<4ir5~p zE7sPIF$>pre5ppzPNdM4?2$INcx~^v)2Q|-p>RiJwyw4J(-gPV!}?2RIJ@(+=bh1` zs^$zXJEmZNBU6yTIDR$0()R#E0!#Dr!l=fR52@KvjS5b{W7OIkLIQ_zhs2_v9BX@9 zYmLt55K^fpifUqi?z=Uy;ScUoe;a)^Jww1y?a!ogh9>Tk3_|N|{y+}pw88%8Q9neOv^j-LKOWDov z#}yrb%NC@J8 z{C&xZv@fXr_hL#lYPE-^8-;KGrSm_xH7)s0zi%70^n1S6Z3=s78k&<-&5#2{_UNcZy&yN2OsTN4b{VxAl`ey>yA38n4&vU^f05| z7Oi_pkr^1vh_UOB+wx8gE1pK%Og12Fgw`19UjM(ku00;=td0MqL}YeTD7jQKS-DN) zI^>pU#%)U%m54PiqY`7*m>6VN$)(1~wV}(-Yg`)kGg9lCt%$+2ScDmDjj!yO#Wj+Lk%V%u(zx8#|fInv-Wrd8~ccEuP0AFkHi(x?Nk}Ry;}Q#uwzm;eb@F=Y^WTV${Cd;Jh8!ne=_Ps=O&OD%b`KHI;lAYroV`J{yHKHP{Cb=*LPT zvj6@dCmOsSN`h=J{P{s%!e6TvT|!Ba7&!J`na#2ic3|a*0$Q9{0YJo|-)?^s_qo-L z--~@thfS_+-NHhN8|MbP%_9xArkvajCB}c5o?y|!uQ14tM!-HPk1nlrF`SgobXOh# z09^>@D|z4WB1dwU1GuagE}tob4bZ7za8(Inkk5n)=e!$=p8$Gm@b=;vm+>uf+WFzE01<@tO?Ca+x#Q0X zzfHGa-$IsnT>zko?fV%?x|VCiGj4bdERS37Rs;ayyl0wGZ_mKq zNb9P0V5jW`PalJmv3{nN>v`C zB@SmzlK9G-48YAB49oD7vLjPh4udNnz)`>V3_UI@>1^%8cnd5<_yjpK#HR;kFY)6B zV9f%;;0nB6s5G?JRPt1%3`J)`(4G14OiT3?6ZRZuUt<;k-8Z$V5ZhmHV7Sg;JEP@Ef3Ohk9d#Q-;l>`AJdj zjwC1uLaT{Qs+)U?)-VV#8j$x6of7b#ry6uKx5&7;My_5|JrxWv zt;!J$Ny|f3LExFdKMM9jT<_?=<%Fr3mpY%i{I`AFMu^fwYyr5NE*E{eP;=F)I@{Je zs$>v%xLp4=rN>n>{rE@Y60#`*4$Ye#aag}1wav2uai6fJA}D1i_V&6brKPYx-|3$u z{Lp7Q08QA|6)*Xt>dM1h=EF!}CRc}8uJ#H$dIKo9Wj8%d0NMK=7QU)P5WqgxaCx>t z-WRx@q(T)5ENvxsx0Vc0O-&S(`$Pqw~7itU% z!)6t*QfD~GGQbL$8icHP$^|b)HG2E1BQ$`S@-ez-_ARefVc)|0y3GnHYI`AEA9F3z zX*yAwfY+dw13_H(g3kQ=O_j66H9tq{aHtB<<|fcaGQFmg@s0K3BKIdfGvLHoRtCg0 z^XKtiV-xz7;!+#j4yc0*;yPe{bR_o|6)gr?1p&$_=k|ZCiD|j{%zNEb(b$&$-;fF^ zCiBfEZq1Rj7GOn+7R~IjAkg*xkhSVymj6XVw?+Z=Yl+V!sl~rzy z|B*Mm5EKgNg$R-OM&;doR>^@bhcy^nsYH;=Z=J zNsKfWe=0r#rXRb{dQ&1eaN}1!&g#XJI(R5W!P#R)Buh9rc`(H3uq5;S6`)Z{$gac0 z?AWM0l&~^0&`3c3kj*z#h0qZ0yr0mYQbBDv)#s%b27s?Tc*NR7&I0Sq2N^SbE&%P| zp$UeGepWnQYhPneU%8V@d&AnK7X_r^eL2_!!D1ax`sPXXd9;HUJfQUN9lx~lDVslx zZ9hm0^i8>?3(DjocS8;){Bo*hBDbAQ%L;KjK7*39$ zmit=cQO0>)P%{xG_fcat2a*J_vr4&+t(QQL>Eczwh0CQ0?Tfbp$5>&T&H?^I_F&73~2^=+r6cXOMmXMsq*$vFiDUQ4!&?VqZgL z2IJrcQ0+J4xiKr#@0MOf_GzKKAA{z#kG6PiCyiX;dSk9GH;Kg4+*kwyY}7(Q_qtz% zQvz+Z&GRI&KC9`-yP$NL&_3oh8)|r!RMvxkdsQ!2XOw511pC8I>#0)R@PtyEai-XF z)+7TMXh@Rt^i)|v50PzgRhl}IGR3vkz12P`N9vPQ?CvdaYdfSMvm^M`a9 zakj)pQQS()v-@*#CNe=54HT7VsN-{RYhLEZ@wp;LQ?@ZO?>le_*wY~-Tj^^g{nRgn z?fg@&2E`HOt1qb~A{gIVRiLujF|5?_VE=Js$+2A^>H6Gd(MVA>UfGO2%u~O3Rt7h1 z(V2x^WPh&i24duhAY&Nq(|OxXW6cwDj*Yx~Bk5q(5eiAgw$jfA+m!bU}16 zvuK1FZdy9`e1%(fqV0(~sN`BfHAI_!p3N4&*3c@9o$5Lr!RO~Mv6I_RDnoRa29&C& znTe6f`E&fVN#hwpFxGsg>m^BZnK2l~A7Q9aqKBu#ml@zQUJ2UGA@|-fDoqXN5i2{( zIxO3y0z#2@j%6Dj%g8=lzX`v + + Tux + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/ubuntu.svg b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/ubuntu.svg new file mode 100644 index 0000000..f217bc8 --- /dev/null +++ b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/ubuntu.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/unknown.svg b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/unknown.svg new file mode 100644 index 0000000..51f3016 --- /dev/null +++ b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/unknown.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + OS + + + diff --git a/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/windows.svg b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/windows.svg new file mode 100644 index 0000000..2c7392e --- /dev/null +++ b/org.jdrupes.vmoperator.vmaccess/resources/org/jdrupes/vmoperator/vmaccess/osicons/windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-functions.ts b/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-functions.ts index 9d2e134..ec21fb5 100644 --- a/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-functions.ts +++ b/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-functions.ts @@ -87,6 +87,26 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement, const running = computed(() => previewApi.vmDefinition.running); const inUse = computed(() => previewApi.vmDefinition.usedBy != ''); const permissions = computed(() => previewApi.permissions); + const osicon = computed(() => { + if (!previewApi.vmDefinition.status?.osinfo?.id) { + return null; + } + switch(previewApi.vmDefinition.status.osinfo.id) { + case "almalinux": return "almalinux.svg"; + case "arch": return "arch.svg"; + case "debian": return "debian.svg"; + case "fedora": return "fedora.svg"; + case "mswindows": return "windows.svg"; + case "ubuntu": return "ubuntu.svg"; + default: { + if ((previewApi.vmDefinition.status.osinfo.name || "") + .toLowerCase().includes("linux")) { + return "tux.svg"; + } + return "unknown.svg"; + } + } + }); watch(previewApi, (api: Api) => { JGConsole.instance.updateConletTitle(conletId, @@ -101,7 +121,7 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement, return { localize, resourceBase, vmAction, poolName, vmName, configured, busy, startable, stoppable, running, inUse, - permissions }; + permissions, osicon }; }, template: `
{{ entry.status?.osinfo?.["pretty-name"] || "" }}
{{ localize("usedFrom") }} {{ entry.usedFrom }}
@@ -111,13 +131,16 @@ window.orgJDrupesVmOperatorVmAccess.initPreview = (previewDom: HTMLElement, style="position: absolute;" :class="{ busy: busy }" > + :src="resourceBase + 'computer.svg'"> diff --git a/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-style.scss b/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-style.scss index 86b4014..63ae299 100644 --- a/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-style.scss +++ b/org.jdrupes.vmoperator.vmaccess/src/org/jdrupes/vmoperator/vmaccess/browser/VmAccess-style.scss @@ -79,6 +79,20 @@ z-index: 100; pointer-events: none; } + + span.osicon { + width: 4.25em; + height: 3em; + padding: 0.25rem; + pointer-events: none; + + img { + display: block; + height: 1.75em; + margin: 0.2em auto 0; + pointer-events: none; + } + } } .jdrupes-vmoperator-vmaccess.jdrupes-vmoperator-vmaccess-edit {
{{ vmName }}