From 33309a109b296a5bacadf410ac1be14a0306a96e Mon Sep 17 00:00:00 2001 From: Josef Rokos Date: Mon, 28 Apr 2014 17:15:25 +0200 Subject: [PATCH] =?UTF-8?q?Zapomenut=C3=A9=20soubory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/info/bukova/isspst/Constants.java | 10 +++ .../info/bukova/isspst/DbInitListener.java | 74 ++++++++++++++++++ .../info/bukova/isspst/LoginFailHandler.java | 25 ++++++ .../java/info/bukova/isspst/data/User.java | 9 +++ .../info/bukova/isspst/ui/ListViewModel.java | 2 +- .../java/info/bukova/isspst/ui/UserForm.java | 14 ++++ .../java/info/bukova/isspst/ui/UsersList.java | 21 +++++ .../webapp/WEB-INF/spring/root-context.xml | 2 +- src/main/webapp/admin/userForm.zul | 6 ++ src/main/webapp/admin/users.zul | 29 +++++++ src/main/webapp/img/add.png | Bin 0 -> 3459 bytes src/main/webapp/img/delete.png | Bin 0 -> 3283 bytes src/main/webapp/img/edit.png | Bin 0 -> 3550 bytes src/main/webapp/img/funnel.png | Bin 0 -> 543 bytes 14 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 src/main/java/info/bukova/isspst/Constants.java create mode 100644 src/main/java/info/bukova/isspst/DbInitListener.java create mode 100644 src/main/java/info/bukova/isspst/LoginFailHandler.java create mode 100644 src/main/java/info/bukova/isspst/ui/UserForm.java create mode 100644 src/main/java/info/bukova/isspst/ui/UsersList.java create mode 100644 src/main/webapp/admin/userForm.zul create mode 100644 src/main/webapp/admin/users.zul create mode 100644 src/main/webapp/img/add.png create mode 100644 src/main/webapp/img/delete.png create mode 100644 src/main/webapp/img/edit.png create mode 100644 src/main/webapp/img/funnel.png diff --git a/src/main/java/info/bukova/isspst/Constants.java b/src/main/java/info/bukova/isspst/Constants.java new file mode 100644 index 00000000..2d1b3923 --- /dev/null +++ b/src/main/java/info/bukova/isspst/Constants.java @@ -0,0 +1,10 @@ +package info.bukova.isspst; + +public class Constants { + + public final static String DEF_ADMIN = "admin"; + public final static String DEF_ADMIN_PASSWD = "admin"; + public final static String ROLE_USER = "ROLE_USER"; + public final static String ROLE_ADMIN = "ROLE_ADMIN"; + public final static String ROLES[] = {ROLE_USER, ROLE_ADMIN}; +} diff --git a/src/main/java/info/bukova/isspst/DbInitListener.java b/src/main/java/info/bukova/isspst/DbInitListener.java new file mode 100644 index 00000000..ada4fa8a --- /dev/null +++ b/src/main/java/info/bukova/isspst/DbInitListener.java @@ -0,0 +1,74 @@ +package info.bukova.isspst; + +import info.bukova.isspst.data.Role; +import info.bukova.isspst.data.User; +import info.bukova.isspst.services.RoleService; +import info.bukova.isspst.services.UserService; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +public class DbInitListener implements ServletContextListener { + + private RoleService roleService; + private UserService userService; + + @Override + public void contextDestroyed(ServletContextEvent arg0) { + + } + + @Override + public void contextInitialized(ServletContextEvent evt) { + Logger logger = LoggerFactory.getLogger(DbInitListener.class); + logger.info("Initializing database"); + + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(evt.getServletContext()); + roleService = ctx.getBean(RoleService.class); + userService = ctx.getBean(UserService.class); + + checkRoles(); + checkUsers(); + } + + private void checkRoles() { + Role r = null; + + for (String auth : Constants.ROLES) + { + r = roleService.getRoleByAuthority(auth); + if (r == null) { + r = new Role(); + r.setAuthority(auth); + r.setDescription("---"); + roleService.add(r); + } + } + } + + private void checkUsers() { + boolean adminExists = false; + + for (User u : userService.getAll()) { + if (userService.hasRole(u, Constants.ROLE_ADMIN)) { + adminExists = true; + break; + } + } + + if (!adminExists) { + User u = new User(); + u.setUsername(Constants.DEF_ADMIN); + u.addAuthority(roleService.getRoleByAuthority(Constants.ROLE_ADMIN)); + u.setEnabled(true); + userService.setPassword(u, Constants.DEF_ADMIN_PASSWD); + userService.add(u); + } + } + +} diff --git a/src/main/java/info/bukova/isspst/LoginFailHandler.java b/src/main/java/info/bukova/isspst/LoginFailHandler.java new file mode 100644 index 00000000..8d2bb71e --- /dev/null +++ b/src/main/java/info/bukova/isspst/LoginFailHandler.java @@ -0,0 +1,25 @@ +package info.bukova.isspst; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +public class LoginFailHandler implements AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) + throws IOException, ServletException { + + Logger logger = LoggerFactory.getLogger(LoginFailHandler.class); + logger.info("Login failed with message: " + ex.getMessage()); + + } + +} diff --git a/src/main/java/info/bukova/isspst/data/User.java b/src/main/java/info/bukova/isspst/data/User.java index 9f5f0c4b..5091b7e9 100644 --- a/src/main/java/info/bukova/isspst/data/User.java +++ b/src/main/java/info/bukova/isspst/data/User.java @@ -36,6 +36,7 @@ public class User implements UserDetails, DataModel { private String password; @Column(name="ENABLED") private boolean enabled; + private String fullName; @OneToMany(fetch=FetchType.EAGER) @JoinTable(name="USER_ROLE") @MapKeyColumn(name="ROLE_ID") private List authorities; @@ -137,4 +138,12 @@ public class User implements UserDetails, DataModel { this.valid = valid; } + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + } diff --git a/src/main/java/info/bukova/isspst/ui/ListViewModel.java b/src/main/java/info/bukova/isspst/ui/ListViewModel.java index 1842cd89..bdc32a95 100644 --- a/src/main/java/info/bukova/isspst/ui/ListViewModel.java +++ b/src/main/java/info/bukova/isspst/ui/ListViewModel.java @@ -43,7 +43,7 @@ public class ListViewModel { public List getDataList() { if (dataList == null) { dataList = new ArrayList(); - loadFromDb(); + loadFromDbSync(); } return dataList; diff --git a/src/main/java/info/bukova/isspst/ui/UserForm.java b/src/main/java/info/bukova/isspst/ui/UserForm.java new file mode 100644 index 00000000..07116ed4 --- /dev/null +++ b/src/main/java/info/bukova/isspst/ui/UserForm.java @@ -0,0 +1,14 @@ +package info.bukova.isspst.ui; + +import org.zkoss.bind.annotation.Init; + +import info.bukova.isspst.data.User; + +public class UserForm extends FormViewModel { + + @Init(superclass = true) + public void init() { + + } + +} diff --git a/src/main/java/info/bukova/isspst/ui/UsersList.java b/src/main/java/info/bukova/isspst/ui/UsersList.java new file mode 100644 index 00000000..c5ca0c0f --- /dev/null +++ b/src/main/java/info/bukova/isspst/ui/UsersList.java @@ -0,0 +1,21 @@ +package info.bukova.isspst.ui; + +import org.zkoss.bind.annotation.Init; +import org.zkoss.zk.ui.select.annotation.WireVariable; + +import info.bukova.isspst.data.User; +import info.bukova.isspst.services.UserService; + +public class UsersList extends ListViewModel { + + @WireVariable + private UserService userService; + + @Init + public void init() { + service = userService; + dataClass = User.class; + formZul = "userForm.zul"; + } + +} diff --git a/src/main/webapp/WEB-INF/spring/root-context.xml b/src/main/webapp/WEB-INF/spring/root-context.xml index ca5f890c..39249fd2 100644 --- a/src/main/webapp/WEB-INF/spring/root-context.xml +++ b/src/main/webapp/WEB-INF/spring/root-context.xml @@ -53,7 +53,7 @@ - + diff --git a/src/main/webapp/admin/userForm.zul b/src/main/webapp/admin/userForm.zul new file mode 100644 index 00000000..635a212d --- /dev/null +++ b/src/main/webapp/admin/userForm.zul @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/webapp/admin/users.zul b/src/main/webapp/admin/users.zul new file mode 100644 index 00000000..b7f4f7e0 --- /dev/null +++ b/src/main/webapp/admin/users.zul @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/img/add.png b/src/main/webapp/img/add.png new file mode 100644 index 0000000000000000000000000000000000000000..a074acc3b4f39678e0c979c0e96fe3b63cf250f0 GIT binary patch literal 3459 zcmV-}4Se#6P)KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde00d`2O+f$vv5tKEQIh}w03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(` z>RI+y?e7jKeZ#YO-C0-#AmK~#9!oRv>VR8bVhe|Kg~O%be!Oe6@?i6}9IcA~2m zQL7d$bRo8DA?;eU4qAm!3uBTnqgB+VWn~6Jix!Dc)}}CU5wyv9^WMAn+|yzj$I*H7 z#tvNGJ>1LvzVn?w&&(GVlzp12VvHFB-cLMUN=3C+_sZ8fhqaEOp|b$2uC6TsCIuiVS7o5ey&wp1&M`Ol zsHvRg%CdO8m&A(E9iim&gXHsr+kRJ97lnt9Qvk%{rG5m-k^MUtUqfIIfLBFC5Rs~i zTAzJA6A^qP098f2N4&=vQ}bXNdjRn%5vccg5xhtZKr`&i5((OXNR33snC-`#VSj7+ ze0kFiD~q=k;OWFKvsa>B06rz`ia9aNPL5+GOG{RmdGaKLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde00d`2O+f$vv5tKEQIh}w03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(` z>RI+y?e7jKeZ#YO-C0q{vgK~#9!w3WYZ(@+$~zZ*=4xOjm9sVhpnpbDw1RUd>% zL>btbGWRV~nFvKlEC@0100yLrxv=B`3P{~boH*+F!(j5qshx8p#FZ@D_g>%McfNb> zu^T2M#g0u?;hZ}G@TJ@9$hNs|n2faB>v%6d9*^-UJqG~X4T2K@pS!(It;mLIF*cvi z%`4Ay5D|z7aooZz&r?-B9wsAa0#p^M3X$rIS(eX4grlP;Xtf?;l4Yr?9@hba1wdpo zN`-3K_r;xvaC1XAJbVlQxVZS90&oVPZm$Z^V%~H*1rcE|c;&TcL1BwPgljWK*FuTo zHsZLwe2=0C$=PcIzyjgw2< ziWBXt-p``7O|~-U{`u3&{%7}qtE8`HnyL!6iO)0O;Kf65MrLGp!T^_pUn`4<$h2lQ zQOJ)6kjtqFa0gAyCO=oDf{D$KvTLBxYy*3lIk(XakzIdV6g#dhb|s5`17AKLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde00d`2O+f$vv5tKEQIh}w03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(` z>RI+y?e7jKeZ#YO-C0{clsK~#9!l$Bp-(^nM7Ket4ujU;7OE&AZ1X&PHYk?Mml zrR-tg22REtd#OYYA4*+pEshmOe z%>JJjS3+POfKx?85RnfmYGwA7OhjH zB@(m-krIiFF~#8Zu&*T1<0F0r2n}Y<2Z_o6oJZQ_0IJH&%nZq7Qg`j>DguQDv!)>r z;EHaoV6BvV^Kw-snM`VXdponu!*Cx$ZCyM*;@2{X@+Z1p_Js__o4%nV+{m}>i)dWB zgsJdJ)_*bpDQNh3lf?>RZE;+W@40LVf3}3JT>%dL{f?>WX;!RQN#gqVga)&wq)b&E z3n0%=H~qA&UrNu`CPwdM=x7acbW(`@dXZQxW=g8aTDu@=_Kx|9g#CQc{VCUOXNZJ? z-2USq9;V-Lcwm>A*DAzH-N6U0Ev7Zx$XA~?bLCcsb!&s%x%-kw>3_NYW5iTwc2c$3 zmD>+j7t`7r0ZXl;-`@4Xpn^*&g8hJEZ@b)U}dJNbDm!>X1b zf8Bk~RQeUa?b~Ro-e?5?FW@`4(>m|V8W9UH||907*qoM6N<$f(lj9)c^nh literal 0 HcmV?d00001 diff --git a/src/main/webapp/img/funnel.png b/src/main/webapp/img/funnel.png new file mode 100644 index 0000000000000000000000000000000000000000..35f1d2596041194e48eb476b141bbd22ff247594 GIT binary patch literal 543 zcmV+)0^t3LP)$2syY3<$ zJPP75Pag6Tcqj;hJ%!@Eg}QX?5CsJZ-6iZ#=#tSX2>*u&B0`~yr$sNj<|o?B>gGqQ z1r5wT-kUeSH($$*MkDx-2=vi3ZMRmdMXS{+bUGd9TxE>Oe!m}r!Qeoa<*uSAU0gf3 z|0tKs_ul}NXr)pq4F&@n&~CS()#`#_d;-sV5Fi0Mf+PijfV#eSFc$$yCX>;N#bU`c zO>}O7X1S|A9b?X0RQghC-G6i$yYg83Bi>kjrgf;rvib&`&3Nl{C)Nk+MX@TvhCC*s?!g^Dj_&2LH?40 zBhHKue;s9xn*WQ0r-lWO_U^wY;0wLD7<()?q)hiy}*jE hQ`Hxh{7An97yzXKrVmzF7S8|x002ovPDHLkV1kLD?DYTu literal 0 HcmV?d00001