From b38b988da4bf88992ded0effcbb2430496560b90 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 20 Jun 2018 08:48:14 +1000 Subject: [PATCH] Frontend --- src/frontend/app-images/default-avatar.jpg | Bin 0 -> 2506 bytes .../favicons/android-chrome-192x192.png | Bin 0 -> 15638 bytes .../favicons/android-chrome-512x512.png | Bin 0 -> 35095 bytes .../app-images/favicons/apple-touch-icon.png | Bin 0 -> 11338 bytes .../app-images/favicons/browserconfig.xml | 9 + .../app-images/favicons/favicon-16x16.png | Bin 0 -> 958 bytes .../app-images/favicons/favicon-32x32.png | Bin 0 -> 1993 bytes src/frontend/app-images/favicons/favicon.ico | Bin 0 -> 15086 bytes .../app-images/favicons/manifest.json | 18 ++ .../app-images/favicons/mstile-150x150.png | Bin 0 -> 10368 bytes .../app-images/favicons/safari-pinned-tab.svg | 32 +++ src/frontend/fonts | 1 + src/frontend/images | 1 + src/frontend/js/app/api.js | 224 ++++++++++++++++++ src/frontend/js/app/cache.js | 10 + src/frontend/js/app/controller.js | 107 +++++++++ src/frontend/js/app/dashboard/main.ejs | 1 + src/frontend/js/app/dashboard/main.js | 10 + src/frontend/js/app/main.js | 167 +++++++++++++ src/frontend/js/app/profile/main.ejs | 33 +++ src/frontend/js/app/profile/main.js | 21 ++ src/frontend/js/app/router.js | 17 ++ src/frontend/js/app/tokens.js | 128 ++++++++++ src/frontend/js/app/ui/footer/main.ejs | 14 ++ src/frontend/js/app/ui/footer/main.js | 16 ++ src/frontend/js/app/ui/header/main.ejs | 28 +++ src/frontend/js/app/ui/header/main.js | 55 +++++ src/frontend/js/app/ui/main.ejs | 18 ++ src/frontend/js/app/ui/main.js | 44 ++++ src/frontend/js/app/ui/menu/main.ejs | 56 +++++ src/frontend/js/app/ui/menu/main.js | 10 + src/frontend/js/index.js | 112 +++++++++ src/frontend/js/lib/helpers.js | 36 +++ src/frontend/js/lib/marionette.js | 117 +++++++++ src/frontend/js/login.js | 5 + src/frontend/js/login/main.js | 17 ++ src/frontend/js/login/ui/login.ejs | 28 +++ src/frontend/js/login/ui/login.js | 42 ++++ src/frontend/js/models/user.js | 29 +++ src/frontend/scss/styles.scss | 13 + 40 files changed, 1419 insertions(+) create mode 100644 src/frontend/app-images/default-avatar.jpg create mode 100644 src/frontend/app-images/favicons/android-chrome-192x192.png create mode 100644 src/frontend/app-images/favicons/android-chrome-512x512.png create mode 100644 src/frontend/app-images/favicons/apple-touch-icon.png create mode 100644 src/frontend/app-images/favicons/browserconfig.xml create mode 100644 src/frontend/app-images/favicons/favicon-16x16.png create mode 100644 src/frontend/app-images/favicons/favicon-32x32.png create mode 100644 src/frontend/app-images/favicons/favicon.ico create mode 100644 src/frontend/app-images/favicons/manifest.json create mode 100644 src/frontend/app-images/favicons/mstile-150x150.png create mode 100644 src/frontend/app-images/favicons/safari-pinned-tab.svg create mode 120000 src/frontend/fonts create mode 120000 src/frontend/images create mode 100644 src/frontend/js/app/api.js create mode 100644 src/frontend/js/app/cache.js create mode 100644 src/frontend/js/app/controller.js create mode 100644 src/frontend/js/app/dashboard/main.ejs create mode 100644 src/frontend/js/app/dashboard/main.js create mode 100644 src/frontend/js/app/main.js create mode 100644 src/frontend/js/app/profile/main.ejs create mode 100644 src/frontend/js/app/profile/main.js create mode 100644 src/frontend/js/app/router.js create mode 100644 src/frontend/js/app/tokens.js create mode 100644 src/frontend/js/app/ui/footer/main.ejs create mode 100644 src/frontend/js/app/ui/footer/main.js create mode 100644 src/frontend/js/app/ui/header/main.ejs create mode 100644 src/frontend/js/app/ui/header/main.js create mode 100644 src/frontend/js/app/ui/main.ejs create mode 100644 src/frontend/js/app/ui/main.js create mode 100644 src/frontend/js/app/ui/menu/main.ejs create mode 100644 src/frontend/js/app/ui/menu/main.js create mode 100644 src/frontend/js/index.js create mode 100644 src/frontend/js/lib/helpers.js create mode 100644 src/frontend/js/lib/marionette.js create mode 100644 src/frontend/js/login.js create mode 100644 src/frontend/js/login/main.js create mode 100644 src/frontend/js/login/ui/login.ejs create mode 100644 src/frontend/js/login/ui/login.js create mode 100644 src/frontend/js/models/user.js create mode 100644 src/frontend/scss/styles.scss diff --git a/src/frontend/app-images/default-avatar.jpg b/src/frontend/app-images/default-avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a0e507681fce1e48acc015055ef117fae0fefdf GIT binary patch literal 2506 zcmc&#dpuNWA3tYq$Y4%l(HP#;c+1jybBm2^vXn7dZ?3syb}=;LmO?YK_HExu8&NUB z@OByPhg^!>Nzvp|vLj?_-NOorGR91HW*DXS{p0;_f9G?a-*bN7%kMeoJm+(kdzS|Q z%mI5xdjJFh02CjWz6U_^k~P6W2!KH#90He{ zfX(6p3@%yjS|Cwygd7MHgS#@QAh+_N|0P3p}*OpHX0vQ-K6)8%a5sB{;?~f zv2)*d0g>;Fgjyl44@q@l6ej8=*A8*y5s28?-;qTy#g9lXp$5K%rc(*6Z=Q!F{ z%(zz#8pUnHx~1ba+bfPTvW3x_dYrn}h8_2|l}wcJLti#0RSpk&Fi?lU6r)^J$U@|B z(E*_$WHSO;K53n*8loiW6Vu|j)3bAhi8WQnEJf(YxBL$NBnpXOK_Zu@(7n(4gB|yG zKl-vpKQN%v$GN^fW`6EN*b=P6tGiUK(9SN#E|MXaCzgQ5s;#k2ZXFoKVfU>( zV*;38W#cUp&oxZ3t`)IH!nv=8X-6z+p>GlX>%6K?xbD|5oAD?MZ@I}@_)ywfqEI3u zK-%n1`}|{dlh{cA_m_orGkoM$879ffs^p}mh)fow!?d)|LLcQ0HRCiU)lx3O|1P7c z{KA~PsQoK7x|R_5HQNH7{R7A1&i5Q^i>VNieuA}(Cdom(3H6?d0&Dd)hp{o;@q23+ zTzr#B)apw9NTy&8#aycc0D6hmd{W6U|9%zO#!=4xBZ6IuE%T0k#v4o{O+3=_{|Eqk z&Jif;$*!Km8@u-ma<1DeCOlMi~*7(zmChC;DZ36bGejdVM}~p-?_g z21+wB|IX$v-}0v+lqmzgd2XCd#$1BuDgz-$HowUozMDb}3yT>{>zsBw?TNHoCkJ4X zO&a4w)F_K&Uhul>%yr-DPADpK|1vx&C|5Dodr-HuX*NNsQ4>v;G3RF~*2w{jLc@y> z%S0n;R=x}6#it0x!hIro1X#7AKpi9Hl8k@!9Pk=R~yg+c+X zk^RwJGJ_js5pH(9`o&_qc?&g_`y}W(>{OH1k8}o5n0o)ikj!Z%1{G*)_tI@UiRMZE-|D z3swnwuT-}>yx^JDIS*%b4vi)^oK2@0=oBW-R7_R-pfs8jYb`{R9adiPyL9_x60o3> zQtcD3+03eqq4PKfZt=#t*Quv`^Qn)=-_CKu9<<@qQ+~7k^xj_dy>DOonQcrOO}6=B z=YgIDRze-wglTdm&cD%j+ENr4ziUkQiL3ztd=)a!G>tno(Bb>uiXISeJO*5y&I+VA zxLVwf;@F(YxKXggIYds73E=oFLWV1g`4tD7segu6JJt%u^G>|BR5A=FCGQ$e6H%yl zba^tiID@}dQF@~V-u^9EeU7%E`z0Q@Ja`fNrXn{x*1>qqzw(4%$UD0E ztqc3?`Em@iiO#rr&JFrKew;)Z0HbB+SomwZ*M6jdIeT z3hG~KQgB?CJ&XKidN1P9dJ}nSS1_GK%`Q(7f678yVEsHSeT9J*Xq)^|&OAOVJZMK8 z?T$|vzkDOq;RfkUC|BCd0WXBN@sL0MBQ$5F8I z^9K~BACc?uHAbixp2tpB~5PN-0L#7aCvi z!qQ3qdeyQ^b+#}v!8zeGt-Mjsz|2%x8}u-InIiZOJD2b~_m`7HMNrs6 zl*snKx|*D<_00%*<}SeZPmKv+KjJV_Z>NZ50GQD7I%!f#7M2y#bv5^X))QrT*h+Au T>qCf_b%}Bq^gt-am!JO$JAY2W literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/android-chrome-192x192.png b/src/frontend/app-images/favicons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..7bf92ccd886e5ed4e8e6eba9cd1c7dda571ff505 GIT binary patch literal 15638 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 P1YBV4>gTe~DWM4f=W3Oa literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/apple-touch-icon.png b/src/frontend/app-images/favicons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..31e8c9d17a520783d9174e4498c11896262b3740 GIT binary patch literal 11338 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 literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/browserconfig.xml b/src/frontend/app-images/favicons/browserconfig.xml new file mode 100644 index 0000000..e781b88 --- /dev/null +++ b/src/frontend/app-images/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #f0ad00 + + + diff --git a/src/frontend/app-images/favicons/favicon-16x16.png b/src/frontend/app-images/favicons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..0072a798abd7b4b32510b692ebcf28402945827f GIT binary patch literal 958 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>H!=zdB{9=oJVv6E(>XxlpbK}ODi(&Ou75l~(=f&pkj*h!DciN*T zSI(Y48kb#DRMT-QL70(Y)*K0tt`bjQSN4ZI z?CiV}uhQHX0)_5-x;TbNTux3%NJvRcO@96$Aw4-YF$Ks?dh+Cv7z=Zro}XJ9o132; zN4SSaz=Zb66C0;@bFP;!c*oV)ul^PQ=UE%75Nz<`J=H$HB~g!w3OFZ zH`lR_jnUCjHrBS5*;zSRI9SnJc1ICAEQ%n|m}4IT@;^cY@=3zmFxGMx&vg2B_( K&t;ucLK6T@8Eg#z literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/favicon-32x32.png b/src/frontend/app-images/favicons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..f8762e73e4560445351f6fc311b235f903b107a5 GIT binary patch literal 1993 zcmZ`)dpOe#8~*LU=6sk;UTMgibH++0hiP(12g@wd2LdH#DI*3rYBYRdY`005|Yd*KOk zM*Rj{QJ#}&v~O~Pp2Qu(0YF=^%CAU;-_2+*!XW^l{Rsf{3jnY#m+0>SAk`KC79#)v zTLAz_DyPeDpS+ccgOCv(82hw0ClN z8GC2PQ$MZbnGQX!6pXBmPsYnAIW?^~j^j@R0KtGqz|&xL@B=qKCIO@eIs*!#w=;?v zQ49S+qU`WJz_`#)@>7^BY$4dq)xq}xQ{XTN4WJ0{kFE$SWz{e(I#x0uT%|7-2A-;_ z0K#C|5K_-*#>zy^4Q`MN$0gISUPp+QtO`YLr*B9~r<~QZw!BA@I#w!YNwn^NR|r4w z7X0A4Sh_24$?R2~^#vf4QG(M}-P7#lzH3)ERFr)eI@LkMs07EijY~sS#uY=D{-6Y5 zDq{xHjMKh_QO1qrJRnl1k3*Rkh)tu37OxVcUTWs$4pjA^4Tr5z?$JuKYu@N&{t$UC>QT=) z1q22C`D1>fdY&5Zd>gL=O~J1-J{DU&%tJma3LUjF5FNLy0?UQ*4+9-B zvgv+}u(*berJ=vB`Dx_*y{7IZM%;?X!|642BP*Z4s}U`t0S+NOS!Vyk)dNJg6oV8XF5xUzL77o zJcyI(uB`EV*3+%DGgHJn>vN5}?>_#n%ZzY5g@kLhvhxp6(DBqj zn)>>B#=2m)UNdW_G`)JnwR>H!hP^*d5C(3$FQAZEf_EziL%jx~-{(WuYOQ!V-IpCU z;HAC1GOxk@W?vIyrm!$a?L}%d;J1_u0YQfEDO3v=n8ZHw4Tt$20SQboANW{r3F?4P zwd~|RRLoK_4(7IRBBp*V!QSPg=I-$__H#+vcM$2<_Bol^zE=`>C zvloSW%0HJ9CyO*&e9mkC%JVq0`fcTjQ+q|o{aE57T0|dk?rs+ys`u3@$3pLONIAPy z>iH>=FpD6$8uTA9(b3qAa7?=5PtKR#47nv!N54%jS?SSIrontDJ2*fG8IBRh1yUJ+ ziB$T>ev7;5bJVN)FU2xLQ{;Us12436;dbUK_EmArVBryCUUHv@7u!~w$u6vepFj(p zp!zM@bB%%eH!<5Xbz^-yFNfWDa&Rl>oci{Y4HeHxw|NSe33z^QA`!D39^OVG-<*Po zVgJ<9RjF~`8!5FeeiFQ3L|k#i=5w^Z+Qv-<+Gi}k%jN@>S+S%!$~In>)k&L>z0*+a za#??jssx0jmj@@BV3iM#*<4dqvi|_)b?zey44n~L><33J3vhM9D?77}V#HT9$62i` z#mf*RbS3vuXy&%7Dy2{sB{KZJu>8&!Ust|(u=hI5W_Ha?YxaeOy~XWcE%l{192zn3?6G~J!Do#IOzsuA%FAH2>ffLgZN#HHi`vcbbe!$)J)^anzJ(07&P|eo=)d2AY{Y zJ0g1SJx2f_CMq`3DhLzA!4e$^0)?kz9+My@Jb#qv@3FzY=Y0l!FcIC0>th2fBDGS! TxbMC$UjcY~9LBe}N96t&@*{Y+{EG*@r_6 zc7nWDe}Cm_-sb;&%hK-={*jPFI6=5Y_zmH|34eBj{FZm}P5NXLJ|cYYZSoQ>u6*;- z`K0|JA)nAr_#NR}0>T98D`VOSg5z)UEKBEyOMkBY^2`zMk=9QLy9h4`qTg_No~fVU z5L|-u@A51W@6DCJP~(XE2ZS|*mxP&kdDHiTyOi*QP`u3Mo722x9`Sul=py`y@TNSz zZoJUwBz!y@K7;k8Ex$uNR>Hf~`AqT%O)KXGBhu{uIQF}QJi@;dW}@+~-wSOg;k&bu zfO?a`BA(rN=HteB?qTuVBA8_M07_p=1(Xtl`*z{XQW`JagltPLmShSbYHY{f}ljbi1X_gHzn+FItVU4SWeML4BB0ocFH2b|BIXZ;Lne4!C z65nM#@zdEdq^zrj_23HymZ~SPu0IcZVlEP+79n%xXY5ZoEjHN9m&t#>TaFvFM>XtG zNt9zD(qk4v=13rZe|j@M3vZ>t_~-k-CwTdd7Y_3hWM|~Uw(lP7hhHGG;a_0cJA}Bc zJ+KuWK>W%Wq*2E*Z<8&1+`RX(6`(y#9k9mkfg{R_q^M8b@_OM9&{xQB!sAc8WpJ7! zWR2QIm{~X0D`D3uB z^&w;5ATmyjBVpeF9BCy;iC&BZ(_xq1J^Xvi(GN@9X~eH^aPBOgs|>=cY_ZiJf6E>) z3|yR}#e0x+{3VL&f6aI_1Z#XB(zXsDyLJ#M2YX>lErcyH9mcq8FdA>bWa^fbI$W2UzjJ~vVWI-Y*_r~IpUDGr4h>2;XJlEZ_h(Glp-IPn3I(~&i-=~l8tj`|54aL92j5Nj$ z74sP{v(vJu>v5Fd`~z|-A8~$Cr-X#e>!_%{it5H@9I3m6gU9+&QP~M+;YC=ijaa_C z9+8nPh>Pn`_DDCceG~qLXMJw^HWdFkrbzm$O|40?c4fpaL|RTcoK4TMo-(9wDLCZ11W_|a3e4ctd{ZNHLbMRYx)qAqg1D@VcH^)EcjIZn=V zA$*7WwlJocu5#X;f-SlPj>u>vMt+LK{0fw`eU0rMze8ru5PiK*f!)!I4O_3H@p1=l zjE-XL*)vSMc#hkI!G}*!dv+KZ+2>%3%H+BxYey*hA;0PKY6u*%E?KxPU8KzimH%3d zq|G>JXFN|S+JZy1+)yq z4mz^UU*JOLB>iSk#iY2n zUL++CpkwH<(*F>7y?OgK`tRMNPd#<`&8jwa!bKv3-WFWGAu=~ zn7cjviyY$fC(qtj`q%!^*8d2Hj^D-Bo#Qxg^bUp|K10vM1X|iV&~WJz&Rn^QYyATl zq5mj5P=?yN6I_#LwH}NAgfV}o<;%0IK!W+0j}64fD|U=APP$@@%P%C(=$P}IYrrGa zpKHPPjcZVzmx7JOxu~u=M86vK_|f3~`&hR+hc*a%U!-$-L3BOMv#))YAj^_X8+1_y zi9;?QPtP1g*DaTiXdCEOe7&17RCvx~oXO)}P}-uzSTvk%#JGDd+@=jqH=ah0X(rkM z?kG+u5~%pvW;@o!eMTL-PDN6%JXVn(mtW} z+B-gm4Fzd%if{9rCTF!m+j2BCp2GF9F|N^LDjtcf=UXo#-{vc85FYgYZ5I1ye;Z1y zki1YNAzDG9JsKxZ)L@)@9<6^ZLo0o#q9g~!$#E#la^U!{zC!!J08XB1!l~x7j8C^2 ze_xSU9?5z`eN*^Sc@LFO+>yU)q6wfe;4PF=lt%r!OZ!S+4Any zU*i0l3>%Ko*R{_Ge_95a=duqSdHmS34#;=uFJq}!e+bB*2>PK1{#tgB8mGExo zLva1O>vs`zs^4iDSUZy3^#67JRBE*!OYK(k@un^DNoHAJ@p@>|~BBFK4F`pUWKjqlF@Q||)?g9rX-F#M%F|K4EuzO-Ei!+dGW zd5=h15tje{EbUr@;ah2cZZL2d2O10<#(~rAlsvz0Fmy@F2=3pO%X`jJ|5jeV&A)O% z7KBchybr?Hl)NY3{_&vtx4e^Y(ntEfQG6FRi9_b2_~TcEXN3PD_{RzVXXzt- zs|Yf7R_nV~56At8u$3V9vHWR-`z&L(5PqcbhSReDM}%&|+lr$%i0dE1r2Px_eMksb z#&G)y&R>M$f!{@LMLV1M&h(f?tYP@(o}Ik&k{TVM>cm`!!=AX{{Ys#cVR6#f&{CbcVvWC6cQ}8D!*nio`pT81j*5n$c|&3foDMv4vi~# zOP`#+kH;}TBmCB9tThrz>zj~V`x~Slc>qfV?}ucqQ#F}%ljOQ@x^sNJtiQ4TAMHeH zOhmA=WiXzE*3ZNaY-NtiT=nfII!Mn&O3g!LeL0DQvH_$V8fJS4$wsTn$+_wUlIxYa zK{xMo96)Mp9Pxyuhbs4ds=p=eX94sTKg*C-R*jsKzeZBt7!nHmk-EPZj?&#QMwcOa zMJr-s+F7@~LHbg+7=**PpEg?ND~rEuuIHzuDYUZ)0dY<45M(p|lj|@dbAK1>`d`DA zGz3f1brkHljEX%qtVy+ERml~^C!S*+{5)b~J7F`mz-f_l=4*8zG<7`p&`VlOK9-C&(!2t$*PnCpLtu z&}<|Gi8WGBDOEPL#MQI5TZ)9#Qf#|0f{I-es{UiO_MvS34YaiNsT$(#mr}!hikjN1 zaK@X%$rB5Gp)G4yfc#F2k-6Wq%BHH8u(AcEYnxQPta!~WRY#K=RY6e?YfyLHc`&J8 zPNC)U1r#Qk!kG(t4fUfh{48NzPjn#jOlleZtmh50zV#GWZa#w4HKaZx-|EiYg~+e- zxsvy3Y;J87)~i&$K4gp*`a$%s`CEcaOFmga>K?g;(yW9~@mXjG@jp$UcPyDhPc+vk9f-cm){jtDR}S4( zbAN&EkztfOrMBX_!+2ot4%gj+=PyuGT@}g?gtqLNg6Lnr<2^$Q=c4%nOtga|eh^Kq zlhlFR-n4aFH{%2I$};Dr4zay_mC9R6zOTKv2dk5fGx>qg4jO-ac@iEHS?k}7B-3t` z7gs^*9ut&L@}z_J?vN()>dX&GE$Y~jgQ|wv$6Bn)xvwb*mNV7yCq(?!I*?Z4nfMZ0 zt_`~@%Fusj!jt3dWo@FVsZQ0CT07fOQML*VXX@4W>Z+Z=Y@p(=dkqL&e?r=X{7}{% z>xxvKTj%UW-*;ZU!o{l>U3vb)`#CRPp}zh^FdwWU4z)L>t^?QK*U{&9$(gRrjz`_e zBQ9Og9y0eOZ{612$-LE(g~7Um|~Wqi>U^I za;6Y87+&+I`$^m79zR7~f>Uq{4WT78g|_4=BrUOk;K@_GhI7jHK4BrDj_`^gIv0@N z@=m@n%M$p<_dg`;Cj1*g+swa}Z_?+7-WcSW(2`@0bgw5|7kP&=SKSNo%yDmAEgA>g zzaR)sq!e!FS)vk|R^IP6u7W*oH_ufL4^HXh1u{m)icH_g+LxWP8YxwGk-+_Ea>^Q* zjof#~@f@&nGx?EyfY11E++$Da_qpcf$ok?aQa9d1a!E7ti|UY`CHH2!kYfCt;{*0= zGWMlwJnyK)aX)U`h=R&CWET!$)Ak-*?jA?o`Eiu4+8S)Fm$B}-!?U}kgnRfdIP*Jr z2kr#}vDUnQH|Rf;#CKmyN0S9%_y@zMj{-MoY5OBc|3 zy%SfuI#^5e+4IPFH~*p=*Igm2=^FMQo=|k923(q9MMY^I?_@o|-W?mgd62Ol{b@a9 zo0CzLR*cJSSGmub#L?<~s5!Vt?f;v&_wZVCWvt77KDLpyVlm1xZD?#fiQxwi&^IxT zmJ4TD>uh)L>1cD;Sdag>=ug{N)|W!wE0H@KN4e){K7WRM>{jpCSNV_lF@G!n^&hXF zb?s6=v-S9|o(Ft4=lSD><9*_p)%^VMa@6uPs$k_h{oID(v literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/manifest.json b/src/frontend/app-images/favicons/manifest.json new file mode 100644 index 0000000..c0a1e16 --- /dev/null +++ b/src/frontend/app-images/favicons/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "", + "icons": [ + { + "src": "/images/favicon/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/images/favicon/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/src/frontend/app-images/favicons/mstile-150x150.png b/src/frontend/app-images/favicons/mstile-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..14687cce15c46bb9bf20f3143b4cff0576fa5a5e GIT binary patch literal 10368 zcmc(FWmFqq^lgw5C=SJg6m2OMG`JUs;u0hfqDp*oE9q-4erIIP@uR&@ZyC+ zq1emst+(D<@5B52Us;n`b7$_{bM~Bb?#$gg_Kl_z;ZxeD004kcMOi@y0Kg#nZ^Okw zxA-D&O3*uOJ6R1`005bUe`omwear+`*3kd}{MiA3&MvZPS)OpTMzFQ&Ipt{`dRT`6UhA0`yeTPz0g~fIvd(16!rf0075x6$M#6-=*VK zKZMzQ(8BN2)0Kers326AZlSU&^9x5RZm74Z>-#7D$W>N~pARU{QWdcT4bLTZ=Ot^g z-!o~c3_hv0ORUF&p9EZJIoP)s&I=djaH=w}btq-s)>V=LNU4sUwQ1=SEZkHkq~t~y z@Igx8nGow*yL8v>+G*a7&p1ZO2OZUy|D%5t+_7SSM!syQ58D%3**Q$QQ1Q^qZcZ4@ z8!SCc6($@=Wb{87N@Xm08EFIt1PcUAgo(ai(4AA&8d_T|(td=_-c9!-afMFr=9w2% zVe|;zUSMbd9{Hfor>AF`xA)C5MAX!j2^I(h-5W1!-vn-uO%i%q-IPZ*bM*|^T?KQ;{A<+l_!3(**4WTA z8yPS415#_N|NIzQ~@2wN(HbJj_10F539Py<27?E>1sViyf^B|OKq)=`R- z=&*In;1tAnPw*kI3+pbn@#-?MGoS%qw@}dSL)q?JlQ^3?wHB6IVWM5wB_JS7dU3w% zR(YnUjLv@Z1z78NMc+mtm}23HQ><7%uJHy_?U2ds5A+?P3W=1>8YeGq4=7DcmQTo2 zR=Yoj?r0B@gP%Lu2k9s@IW0acd_Jij$JYjy$SrNY!1z(+tb*6h`VGq|UM!!?>)G}D z$ZrR4*Ygs^GMDq3Ak1@h9`g>QH&4$_aYuV*w+}*-@VVCO?vXxi?P9<}qJ8huSQa_% zs&)q1?GQi#o2huA%MUCDfFI)GivHBg$16Wp?9+0(w?6Zk-XYHrQ^Aw2_ud+|hqwzN zj#c;VL}rh58w~@kds^f7Nbxh(xQ@iDaHIS6&3%7#!XkB*z^GoJ%7on@F0d?{0? zYvm(mc1!7NJbk=ui_ivtiiKkcMyqT|V?CyxB~-s3#b6-&?5;Kq=XE^K49SM<8efd_ zeo+`>Bw;7a6U~w)Is-7r{#f?uC|=FDDT#$MNH*4Un^RA}m50*e_h2wQtIf$_2r>q$ z?lHk(@)*8hQ@8PDsZao@n z%q$Llw2r@Xp9$kKSlY}4{)gm~8{Gy?7S)#lzW-+Hl^D6C65M3ai-40Fcs0Wv{#r|^1;{aVi6`dN4GS?vyd)X^jgC@9WTjZVDDh!2dUA$N-GUk82 zF;r3H4BHc>W{Gg^ll$ARxnCClO+1nb7vfzP-MQ|cl0S-7Z|?^}p4}u~t-31RFLB{y zL*9R?U0t^TSj32Byhv!|B6CaHQFB~ua1-@$HXZ+N}$igWLa|2nb$O}PFOb0rl2KCh z%!cFxMz5@Sge%S7@3n4U)PFs8Al;7cE>OD9P8WA}8`Ji1TxQ49Vq2#5;=<>~@*QOeu@_SAROhY5s1uB@<9mcxnF_EUq(CD zye#KUBh2m7rK@3&=&Nsq`X%+nX6d3NDN1-EG+o?Aj&7j7Ji3gF&(R|Fm(}U4DRfg7`(j7lX1xzxf41U<7RFfA}xf%)C_b*M#zSYiN{Trz~A#y zz4sc2D*Y1gU{_{j@a=31T@fZO=CQ^X1TdOj*Jmf6v!9@0NX2Euks|j3U7#T-w~miK zX%A02TKJwC`vLD`h6cNR6)BCIQ0HE<(LM83Q5E~Pw&QxF7x^G z4JGZ4>K#q;A+ClaCpCbDb&BhwU6sx8R~)I!pT{fpenN?GTcHI1PN%uO#e$AL{2K2x z&FFv=-~7I-_WAQg4#vV#POs_1?F*?^Q*DFMd4Pq}+TxYmkh$cxf2p9`SkxKrS!`Kf zChwey={&0EnC@mqDf_Kc8K#W zS(ehC20>ZTA~GTSYF!r&~dJksJpKYMPPcA3Jg2aSW7X;6DMa=3hsf z1om<8C~Q0YvtMOfnZ&X%e;#&`2_jDTXW-JOcfc|i5$Pj zy~pa?cK3U4^zKhF*a>5e`#{<=Wtwt`Aa;_VGEpqi3cu23_29LM^dm$AIuE$eO+xt* zC0y#8nx(j>H@<=9F=xeS{QC=QT@i^nrMkQ;nRU;ZMLi2=Po#Nk@9YR8&B%Z{wtC$2 zdk3F{I~lb{z%pp$t(_cZp9PN)nh_0wot8V^>zywxrXguloS}mcCxd&$n4<&vug2u1 z9PQT@4Ww&$&zXzN(zE=i7bv|*2A(}j9jv*ZBFf;c%9{0!QZ}+$o0lGo%n!!FnU0as z(0^~E=1^9xn)n!h3KX1Uzn%=I^SkJ_{|B5lVoHzqI4#|AGBt}ky@iY?^but_CB}XH zmld5(rok*hAVJr|{X#7pV!J!dRVb-|)_Za+8m^~4T_dGI~SEpdKfO-N>- z)RC%hwS15n+w!UTD4f`(u&arT&gdNz>|&6u(!}7$&MTJdu##$a6=!`+782|p!dQ6) zY7hwJe!6k3u1;Elds@YwXg~FH+up(68A^1=P|I~-RO(nK$aj@eKUY`W_1@yXguW8G z3OOwP8FbARa(VpGZ*P^HAj!Jgk3kw2T?ah2ub(959a-K?nH(H)(r6^{i;1Z+pXeCo z6?O?L@-r}qk|zz7m6vyYj)TXe_G3LfyWDWW7w17AS5_LfmRHU4+O~sfJ>%j3qyQ^4 zUL?(AB<%QD{k3`|ZxXb)rM#~7w#bK==TcSseGkb0*1N4v8M6_x^di%!Zg#sgH4Bav z71;CSgd(M-gTF?*2H)Cf795t8meNjm^l?u1icvXoGs}5t2o0)VH8p^w{ZdSW*N95j zBle|`(@h6JFVPCQ)Gn@InKsD0%A{MVgv(TT{BJAY3{7b>r>zf+tv zVSND-cVkHLyRBAJ^-XGQ=O@gDlzz6cgHoIs&A6?MiPcFsDH*8=J83_A2(0_?hH6{c zX-Vc7{`VcYKHGzWl!!rU6b0Y26I_GEBhK#YK~lkC0nL@qQcKm%S$O6!CX{A}>WC>FTfJtFx0qq!wbQ)=uC25|@{irt1%X@p zpZJpZ*7(pz4ZFvmfopr6Cu!K;PVgyk!9QXAUIcY_qnXNI{m|+iWdpV{YlO+9a6v%~yk) zv5kGbUY;_th~3lV#fwk0^{Y65i{qv*!*g#ti_837greufNQOa5*&1HuhsLT3jk2yqhx_%yqV( zc?_!2*f?YHb^8315aZpQjbdygpIQogV)-h@*lpR0-XM;`sikKWaYO&9QPQ%J z#f;Z^xpi)Ib4^JpKe&pyuG{3mQAc8?f!$PCdH_%b?rp{I^rVMGZTaKRYJ{DcAA*vI~~DgoPjFqWms zMaWiIzc7s+MrPn>$z80YqEpT)FW)yaTkLz+L+G^Lmb&&p`g%$fz^Q|tP*e3vH)A5I zVh#?TjaYAHkn;zVoLTK*@LUp+sOdf1VM?drjO{toDUF3Yq%ee$0CI+F6IaGkaY7Ht zmTTEf)A;jLwOL?2w)NQIeb-p>*ev#ELF$T6eHO{oZOEJ!%F#y_%0osaZgx-ud&va|zT`0*{IoX~r`{=Ksxk<@)3IFKve~ z)3GIBN{BDe=VA}AINhvXDAMy_E(-S=qg7yU#FLRN;O^PYs3Go@z0+qw)A;92E)+rq z_s=xVG$8xgG4-B{Iy{%AR-S_7!pgl)ZsB^IDqE?qt&mWy3R`~B;6G{2EmP9!T*k=c z@IvzRelC-j|3Rw!accd?i&w)xI%d9`oTugwMq`AuR4+VLS>=&5T|~ z#P#jQCyNK=hcR}A(*IEBwWsbWPF-=}5jyG_&595)GegX+zKao4aB~B4 zOtdv2`FL0>cGELs#SMYXj^J2GX`B1`Z$i>{Vb5{CJ?h@61x>ry8x4`@QTn9$z$5yG z#?Svs`zAQ?By9~8ftJx(@V8^-?PjKl_j6uKl8g-3JbU{aFLlmH70oWOmECiKhnBRl z6mrzFBTu8agrSiyI*PB@1(^Ss9_+bZb#|AGVo?v7?Q{OjF`f)CPed0f?#^VY3tMB8 zU_wVQg$nDg#=qx`e)VyGa?AvRmp*mjK(%x6fTWfP$d6_*^X*||JvAHw$FoLNC~(w& z`r84r-^^Tk=WI#`IJDC>38u$Dr$TG6a35|K3kUT-HxBPoabKDqi+W7*q#u5$#2CKp zk-F3NypEgk+eo(rYRv|$LUO{_^fKlDh2TSE+Z!X3e0K0?Qxt}~jL#Z^FQd)-NG>8! zi!prt@nui<8dB~dpoLj z`la5M&w1P%8zjVw<^~t8I_>d?0{ftTecFsBUtjUP?(NG^s4XfYcsTQOw;R_AF)ix0 zXpA2VUq*1gI+O4*utRpwW(@k|2Sx7XhtRDP_ZE|CDH_*?WmIv&5V!zeKM#z zdRicBf3u(zGTdvqe&)S~_%5;?I@HX1GkmpF(MKoSeWvEh_wVHMuV%sCABrQ+OEOLN z__EBJoD6+=HgSE_mN32_U8h}RaWx>^v?#YTtC1H5-!yI3PW@W{!(=O>;Mk9wYe79K zefkAAW5uWa`re+7&#zn|jJTrsDnsWplu5jS9i(442WxFQh8_1yL`p5TZMcQgS(h6k zS4uB8ATf=4dQnxGG`tN$Q65zOH_LYB+sF4xgO6hg97QJm-lHLsC`rR+^y-9z5^gln zuJblem`w%!Kv<5qEkr<<1ltp7Dtj=Ua#3^-MHvS(ygkTMYPcp48}u z`umPbwzrim7u~{lJE;6lVcfDZWo_-0Y~v~1`6-W+Rr$M}^>4kmUwAE_vK^aq8LNhW zCf~t5ZEbs^0n)9_fHM1(Zo6j6$T7YWOp zClG3!otdB2cmzFzpVb>zclc!1Kc10=4{5%O{7}AGN8RB&(j+)&3J|66A}K+CX+GmJ zC4=BN<<&SAgSSajjoiBjWPEET>3`v27t~g`|4^ET=na@jg&1Z;&r|EX`Uqox7D)K* z#^d!`?jY5dM(kOA>(g)1cT4G(n=f-2EY{y>q2FB_<`QDS*sUNeB~1jXWPV>95p%cy z!ipeKrjfM6_erAexoaz&a+w@$LiP5k)lB|kyZGwVk~hHDT`GSauvgh9y)BuMy4T_g z@i+6?cIYLE--4}ByG`tr6WTKvr?4Q4F>2UX7~UvQOGx$aeAJZZ5!q>p`sPlP1w3JU zTh?xylrYFf$V%2+U6tJHt0*P;edD+8pN=bG!?l2dl}SEkoR=UHvtMn3gXAEv&2Mk< z$)8c2&HAC2R3^NS!uFuC>7{Eh-n=eJzzIJF>C$4rqQzIlti2)gmiK1#=!qT&Sy z{*&ml8wdOU=5Ixz_>7;mac<#ZDMN_#^OdH&I8Kb!$5lt{2tUys#g>J?0HWt<%k?$M zaPBJSyzc0|t((3^+LulnEQxV$D|99Vlx6RqNHhj#+Kfo}GL+j~#*n*k`IfX7sJWWk zcGL*O{w#UPui=RMm5U&sSbE>>V}2O*DWl{>-A_`TK6z^(&d3#UGCei3wBWmfk$A>M z-=MlsgkNB7vO;HW_81xeWt$QrLE2_64tb{{*%OJ0X4;zWgXCDq|1tU;Ah2T!$)E{|*oS z(Rx?nl(F3#PcEg|h*Y1f+Q%|ek=M^d?Fdxn*>)iPpuJMUr;OB`_moPsqZjDN;6X=5 z8pRqJgun^9?+B#KKB$-IVx}Lj>xZ*u%rG-=N4e()JYZ&ZjU{Z+Ql6`P4oR19t3K;# z#;A5I(|~})g@%dOY1#6^ZmlvLSd6wy3tU;=*%7$Gl_^sr1qLdA7-1|^Ko9)F|8ied zp>e0O0neB{6h^t3ZHQ|sY2th`uWu-QHUr7?=f$}~11a%!- zI)2&|k4KdJl9AZe+3UGUGrV3Cx5wdsv)BE4&jHzv5K>{Kk9L?S0;jEiG9!V=#vOgZuyXa@{`lWoP(f% z%U~QO>${JnGzAR#Kdk)mLFa@taMhb1!g9W@vx4boom`TX<`nyw3}!KhPbg2SN6&}r zDlV0OM>(um$A=Y#o9-X=>~j3U;8LD~2^(6?u^~Xui6G2+YBE;7F#__U!3gG_R_&fR zd3mnEzO@%#!`Gg_tnsXbRr&yXn5fWGCKr`vku?nSII$S_LbG(5{kQ9@_?}$-ja+Z& z@voH}Un{p-QVsM=kP!-@0o@(**c&F^tI?qKVi?B~$|M~5?Ezb5`sDAKY2?bwF`jh( z@s<$Ci_eU%iar68e3oHm$ngfM-cGsTT(1@@!qNuegI4pQuCukT_ME@kjSIy>r}5oB zD`h0qA;py57>P61)G$k1iC(OT@&{#AsK|F$YHmpAg>xl@gz67SF=G#c=5nC z`nYyFxk|@Q=w|9*<#zI1_YV??MPPzjtQ?hHhsD2ar0-%Ev!U7_g@J!oe^+1JV6y50 z-d*^$M?_Lk_(`<{3OYfINamlLTMTrkkiHIJ*NYN+*D4TMUydmeeu;ILA~@oi&56f{ zDc%0!?uTq&q-76oj2mL>{9DZqtAj@rsONvnkTdQjllVj3&cRv8MaC+V)h2?pG${w!GT9XtU26|v2H6RTL+Mg}ILsL&CV zKt{x!n)L_{!m^ZHAR0}sBqBK_&v#Pai~lJ_i7OH0*%63JgUHIoj z3&qCSyB)UUA})3ar`|X?8ZDPu_~>?HFt}3|;$_ROPWv)mx2`JT73D)9y7b!U5L%ct z41m=rZ{IPkW90M9dEihA&u+JTk@EPlmT%STe$12uKbvAQ)VxZjQ{A$wi;0Z-N_|Pe zz~YB$C68ic5HY3cZ2SFjYY?%eqR=Z>%eK9!Z0j9uOci{J7a?+i@{YnXs9t>g!ZaWa zWbqSP*cHArfUZG?Y~oh>L?$La$ABAg*st6I0t9w`mt$yjhHY=w2Qa3lgz0E1!#JYU zC&E;!8yq$Gm0IX!68yf`hnSAYlffhXP%q{-Lbqz)+h14SKo zpsI2|`)sa#iKrqNire!2tt0LHfSECIvfSP-a3J$tUnPX?Lr16~I1ualJh$L`Z`1(@ zdgv~4!s^n)NH`&zDB63Tjs2@qi*q-*4;7$9C@pHa&)vYU@N|gSv^lNiiNo2VL|@}q z9O@n7aS+2N@VCNF^8;>#xr3&0i0k9(vW z7d`~PQR%-Np>%0{n5}@dW71&67@|e%1!#ftq4O}(Eb>sQ9|Iw6FJ%ub_1Yrjd)iST z(vj^0IgY+9&29>GMV9Z*ZwbT&`YMB(5BO2%mV8SZiu6$ z%J;nyFBaCk9J>TL@Qb0_NLNPp>l4zx8v$7eXK@L0L27Pl4g#GO9x82>w{x9;FL zLiB(x_=A_P=pN#x%_SC05XG#BMYQ~(|->^GE#E?4UABV#ZVE6%27tOAffDpYAwmfZc#B>Z>QtgqJm$cpUSoD94gs$m`t-K z(E=T5gI~Hd@#rvun~Q?})@uXnjNTfS#8ft}lcWH{LcqsEfJ?W6rxTmd7XGJ7|00@s zsM`V}!^VtcDYh&g>uHkz)cQtnCm}V0aH`JC&DjfH%JNmm9kQu*GQhj@cXr!#`e^ol=w^=9TMOiW>@`#Y1OqnvSFuTS{y6M04@#?MHP<=Kr@k7! zltc0>+n1E2@ow+)xvK5r$@Ar*kT~xYHUivcoEx#Z;1YOEK18|z&BN8BMF=bZ(eg2G z@FYUJb}!g~cgV=~(}i|B$N`K}Jcyh(6n)7N8X;a5)==Qj`OXFY?hvftZ$&}P`S)#r zKZG4JXbnPrPL6j6t7YD-yo^)zM0qHJ=}(2xZS-wJ2@-~uj(n1eHys)p$Y9AZX7j@7_m6R&w46&id zaBlDZ2Fwogo%#JCh5$8g7Sd7^@#;+n(MGMtOzHyok++qrG!G*0lzVLs6``B;FMJ3iHv-hlN)o8=|_Ps}-_H0Va+; zgLd16W|zUU?e4n;V2}O1hxEq*fB5#vN@9x$8XB4gdj8X~L=5q9HDSz1ZQN2m5R9s! z6)}f?)ohyNN;GInFM|a2y!T`@t()`vUj~y6f-^H1@5205;~~c6&i~fKH12*`HU+OJ zLT;xp1+}Gi4q0TVQ0gkt0}N<=Nl|)%&1-U{EJrjq$a}`uo7SxqDjxQKl{)GRy}m(c zZhWmQT&Mm^EIj4y+6^0vRYvIlkpN)|)}BEXcJZh_(PNtrF70ujQ%+8!g*|-DUkplQ z(1h3QN?V0avRAk8Mpy4C9qt$J3f$43PZ>3Yhr$_OW& z>UYa|yVm;Zc`bH|Pz2sK5lj*VIOP9CS;l&_+uvdW+Q%~PJsBiHJZX`E)cWPtKD(nv ze76b^Tk;x}`X7~N{f`a)efbfH#quZLCZv7}t)FA_R5bFmw)C_SgS*?HcL07qun0Gp zpPNrak55DlEFi`&_=*oK#>e+He58>h+jL7iNb0$NEA2z|m0UPpUkC11eI_b2*`O^bi0QMNNfjIoP}Z1@kzr&Hw-a literal 0 HcmV?d00001 diff --git a/src/frontend/app-images/favicons/safari-pinned-tab.svg b/src/frontend/app-images/favicons/safari-pinned-tab.svg new file mode 100644 index 0000000..8333c04 --- /dev/null +++ b/src/frontend/app-images/favicons/safari-pinned-tab.svg @@ -0,0 +1,32 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/src/frontend/fonts b/src/frontend/fonts new file mode 120000 index 0000000..84b6a8e --- /dev/null +++ b/src/frontend/fonts @@ -0,0 +1 @@ +../../node_modules/tabler-ui/dist/assets/fonts \ No newline at end of file diff --git a/src/frontend/images b/src/frontend/images new file mode 120000 index 0000000..6f1cb6a --- /dev/null +++ b/src/frontend/images @@ -0,0 +1 @@ +../../node_modules/tabler-ui/dist/assets/images \ No newline at end of file diff --git a/src/frontend/js/app/api.js b/src/frontend/js/app/api.js new file mode 100644 index 0000000..d6c4764 --- /dev/null +++ b/src/frontend/js/app/api.js @@ -0,0 +1,224 @@ +'use strict'; + +const $ = require('jquery'); +const _ = require('underscore'); +const Tokens = require('./tokens'); + +/** + * @param {String} message + * @param {*} debug + * @param {Integer} code + * @constructor + */ +const ApiError = function (message, debug, code) { + let temp = Error.call(this, message); + temp.name = this.name = 'ApiError'; + this.stack = temp.stack; + this.message = temp.message; + this.debug = debug; + this.code = code; +}; + +ApiError.prototype = Object.create(Error.prototype, { + constructor: { + value: ApiError, + writable: true, + configurable: true + } +}); + +/** + * + * @param {String} verb + * @param {String} path + * @param {Object} [data] + * @param {Object} [options] + * @returns {Promise} + */ +function fetch (verb, path, data, options) { + options = options || {}; + + return new Promise(function (resolve, reject) { + let api_url = '/api/'; + let url = api_url + path; + let token = Tokens.getTopToken(); + + $.ajax({ + url: url, + data: typeof data === 'object' ? JSON.stringify(data) : data, + type: verb, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + crossDomain: true, + timeout: (options.timeout ? options.timeout : 15000), + xhrFields: { + withCredentials: true + }, + + beforeSend: function (xhr) { + xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); + }, + + success: function (data, textStatus, response) { + let total = response.getResponseHeader('X-Dataset-Total'); + if (total !== null) { + resolve({ + data: data, + pagination: { + total: parseInt(total, 10), + offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10), + limit: parseInt(response.getResponseHeader('X-Dataset-Limit'), 10) + } + }); + } else { + resolve(response); + } + }, + + error: function (xhr, status, error_thrown) { + let code = 400; + + if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { + error_thrown = xhr.responseJSON.error.message; + code = xhr.responseJSON.error.code || 500; + } + + reject(new ApiError(error_thrown, xhr.responseText, code)); + } + }); + }); +} + +/** + * + * @param {Array} expand + * @returns {String} + */ +function makeExpansionString (expand) { + let items = []; + _.forEach(expand, function (exp) { + items.push(encodeURIComponent(exp)); + }); + + return items.join(','); +} + +module.exports = { + status: function () { + return fetch('get', ''); + }, + + Tokens: { + + /** + * @param {String} identity + * @param {String} secret + * @param {Boolean} [wipe] Will wipe the stack before adding to it again if login was successful + * @returns {Promise} + */ + login: function (identity, secret, wipe) { + return fetch('post', 'tokens', {identity: identity, secret: secret}) + .then(response => { + if (response.token) { + if (wipe) { + Tokens.clearTokens(); + } + + // Set storage token + Tokens.addToken(response.token); + return response.token; + } else { + Tokens.clearTokens(); + throw(new Error('No token returned')); + } + }); + }, + + /** + * @returns {Promise} + */ + refresh: function () { + return fetch('get', 'tokens') + .then(response => { + if (response.token) { + Tokens.setCurrentToken(response.token); + return response.token; + } else { + Tokens.clearTokens(); + throw(new Error('No token returned')); + } + }); + } + }, + + Users: { + + /** + * @param {Integer|String} user_id + * @param {Array} [expand] + * @returns {Promise} + */ + getById: function (user_id, expand) { + return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : '')); + }, + + /** + * @param {Integer} [offset] + * @param {Integer} [limit] + * @param {String} [sort] + * @param {Array} [expand] + * @param {String} [query] + * @returns {Promise} + */ + getAll: function (offset, limit, sort, expand, query) { + return fetch('get', 'users?offset=' + (offset ? offset : 0) + '&limit=' + (limit ? limit : 20) + (sort ? '&sort=' + sort : '') + + (typeof expand === 'object' && expand !== null && expand.length ? '&expand=' + makeExpansionString(expand) : '') + + (typeof query === 'string' ? '&query=' + query : '')); + }, + + /** + * @param {Object} data + * @returns {Promise} + */ + create: function (data) { + return fetch('post', 'users', data); + }, + + /** + * @param {Object} data + * @param {Integer} data.id + * @returns {Promise} + */ + update: function (data) { + let id = data.id; + delete data.id; + return fetch('put', 'users/' + id, data); + }, + + /** + * @param {Integer} id + * @returns {Promise} + */ + delete: function (id) { + return fetch('delete', 'users/' + id); + }, + + /** + * + * @param {Integer} id + * @param {Object} auth + * @returns {Promise} + */ + setPassword: function (id, auth) { + return fetch('put', 'users/' + id + '/auth', auth); + }, + + /** + * @param {Integer} id + * @returns {Promise} + */ + loginAs: function (id) { + return fetch('post', 'users/' + id + '/login'); + } + } +}; diff --git a/src/frontend/js/app/cache.js b/src/frontend/js/app/cache.js new file mode 100644 index 0000000..c34d674 --- /dev/null +++ b/src/frontend/js/app/cache.js @@ -0,0 +1,10 @@ +'use strict'; + +const UserModel = require('../models/user'); + +let cache = { + User: new UserModel.Model() +}; + +module.exports = cache; + diff --git a/src/frontend/js/app/controller.js b/src/frontend/js/app/controller.js new file mode 100644 index 0000000..e217d1d --- /dev/null +++ b/src/frontend/js/app/controller.js @@ -0,0 +1,107 @@ +'use strict'; + +const Backbone = require('backbone'); +const Cache = require('./cache'); +const Tokens = require('./tokens'); + +module.exports = { + + /** + * @param {String} route + * @param {Object} [options] + * @returns {Boolean} + */ + navigate: function (route, options) { + options = options || {}; + Backbone.history.navigate(route.toString(), options); + return true; + }, + + /** + * Login + */ + showLogin: function () { + window.location = '/login'; + }, + + /** + * Users + * + * @param {Number} [offset] + * @param {Number} [limit] + * @param {String} [sort] + */ + showUsers: function (offset, limit, sort) { + /* + let controller = this; + if (Cache.User.isAdmin()) { + require(['./main', './users/main'], (App, View) => { + controller.navigate('/users'); + App.UI.showMainLoading(); + let view = new View({ + sort: (typeof sort !== 'undefined' && sort ? sort : Cache.Session.Users.sort), + offset: (typeof offset !== 'undefined' ? offset : Cache.Session.Users.offset), + limit: (typeof limit !== 'undefined' && limit ? limit : Cache.Session.Users.limit) + }); + + view.on('loaded', function () { + App.UI.hideMainLoading(); + }); + + App.UI.showAppContent(view); + }); + } else { + this.showRules(); + } + */ + }, + + /** + * Error + * + * @param {Error} err + * @param {String} nice_msg + */ + /* + showError: function (err, nice_msg) { + require(['./main', './error/main'], (App, View) => { + App.UI.showAppContent(new View({ + err: err, + nice_msg: nice_msg + })); + }); + }, + */ + + /** + * Dashboard + */ + showDashboard: function () { + let controller = this; + + require(['./main', './dashboard/main'], (App, View) => { + controller.navigate('/'); + App.UI.showAppContent(new View()); + }); + }, + + /** + * Dashboard + */ + showProfile: function () { + let controller = this; + + require(['./main', './profile/main'], (App, View) => { + controller.navigate('/profile'); + App.UI.showAppContent(new View()); + }); + }, + + /** + * Logout + */ + logout: function () { + Tokens.dropTopToken(); + this.showLogin(); + } +}; diff --git a/src/frontend/js/app/dashboard/main.ejs b/src/frontend/js/app/dashboard/main.ejs new file mode 100644 index 0000000..40816a2 --- /dev/null +++ b/src/frontend/js/app/dashboard/main.ejs @@ -0,0 +1 @@ +Hi \ No newline at end of file diff --git a/src/frontend/js/app/dashboard/main.js b/src/frontend/js/app/dashboard/main.js new file mode 100644 index 0000000..6694281 --- /dev/null +++ b/src/frontend/js/app/dashboard/main.js @@ -0,0 +1,10 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + template: template, + id: 'dashboard' +}); + diff --git a/src/frontend/js/app/main.js b/src/frontend/js/app/main.js new file mode 100644 index 0000000..4ef25c6 --- /dev/null +++ b/src/frontend/js/app/main.js @@ -0,0 +1,167 @@ +'use strict'; + +const _ = require('underscore'); +const Backbone = require('backbone'); +const Mn = require('../lib/marionette'); +const Cache = require('./cache'); +const Controller = require('./controller'); +const Router = require('./router'); +const Api = require('./api'); +const Tokens = require('./tokens'); +const UI = require('./ui/main'); + +const App = Mn.Application.extend({ + + region: '#app', + Cache: Cache, + Api: Api, + UI: null, + Controller: Controller, + version: null, + + onStart: function (app, options) { + console.log('Welcome to Nginx Proxy Manager'); + + // Check if token is coming through + if (this.getParam('token')) { + Tokens.addToken(this.getParam('token')); + } + + // Check if we are still logged in by refreshing the token + Api.status() + .then(result => { + this.version = [result.version.major, result.version.minor, result.version.revision].join('.'); + }) + .then(Api.Tokens.refresh) + .then(this.bootstrap) + .then(() => { + console.info('You are logged in'); + this.bootstrapTimer(); + this.refreshTokenTimer(); + + this.UI = new UI(); + this.UI.on('render', () => { + new Router(options); + Backbone.history.start({pushState: true}); + }); + + this.getRegion().show(this.UI); + }) + .catch(err => { + console.warn('Not logged in:', err.message); + Controller.showLogin(); + }); + + }, + + History: { + replace: function (data) { + window.history.replaceState(_.extend(window.history.state || {}, data), document.title); + }, + + get: function (attr) { + return window.history.state ? window.history.state[attr] : undefined; + } + }, + + Error: function (code, message, debug) { + let temp = Error.call(this, message); + temp.name = this.name = 'AppError'; + this.stack = temp.stack; + this.message = temp.message; + this.code = code; + this.debug = debug; + }, + + showError: function () { + let ErrorView = Mn.View.extend({ + tagName: 'section', + id: 'error', + template: _.template('Error loading stuff. Please reload the app.') + }); + + this.getRegion().show(new ErrorView()); + }, + + getParam: function (name) { + name = name.replace(/[\[\]]/g, '\\$&'); + let url = window.location.href; + let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); + let results = regex.exec(url); + + if (!results) { + return null; + } + + if (!results[2]) { + return ''; + } + + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + }, + + /** + * Get user and other base info to start prime the cache and the application + * + * @returns {Promise} + */ + bootstrap: function () { + return Api.Users.getById('me') + .then(response => { + Cache.User.set(response); + Tokens.setCurrentName(response.nickname || response.name); + }); + }, + + /** + * Bootstraps the user from time to time + */ + bootstrapTimer: function () { + setTimeout(() => { + Api.status() + .then(result => { + let version = [result.version.major, result.version.minor, result.version.revision].join('.'); + if (version !== this.version) { + document.location.reload(); + } + }) + .then(this.bootstrap) + .then(() => { + this.bootstrapTimer(); + }) + .catch(err => { + if (err.message !== 'timeout' && err.code && err.code !== 400) { + console.log(err); + console.error(err.message); + console.info('Not logged in?'); + Controller.showLogin(); + } else { + this.bootstrapTimer(); + } + }); + }, 30 * 1000); // 30 seconds + }, + + refreshTokenTimer: function () { + setTimeout(() => { + return Api.Tokens.refresh() + .then(this.bootstrap) + .then(() => { + this.refreshTokenTimer(); + }) + .catch(err => { + if (err.message !== 'timeout' && err.code && err.code !== 400) { + console.log(err); + console.error(err.message); + console.info('Not logged in?'); + Controller.showLogin(); + } else { + this.refreshTokenTimer(); + } + }); + }, 10 * 60 * 1000); + } +}); + +const app = new App(); +module.exports = app; diff --git a/src/frontend/js/app/profile/main.ejs b/src/frontend/js/app/profile/main.ejs new file mode 100644 index 0000000..f1e5d5c --- /dev/null +++ b/src/frontend/js/app/profile/main.ejs @@ -0,0 +1,33 @@ +
+ +
+
+
+

My Profile

+
+
+
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+ +
+
+
+
+ +
\ No newline at end of file diff --git a/src/frontend/js/app/profile/main.js b/src/frontend/js/app/profile/main.js new file mode 100644 index 0000000..5f4ea1a --- /dev/null +++ b/src/frontend/js/app/profile/main.js @@ -0,0 +1,21 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const Cache = require('../cache'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + template: template, + id: 'profile', + + templateContext: { + getUserField: function (field, default_val) { + return Cache.User.get(field) || default_val; + } + }, + + initialize: function () { + this.model = Cache.User; + } +}); + diff --git a/src/frontend/js/app/router.js b/src/frontend/js/app/router.js new file mode 100644 index 0000000..91825e7 --- /dev/null +++ b/src/frontend/js/app/router.js @@ -0,0 +1,17 @@ +'use strict'; + +const Mn = require('../lib/marionette'); +const Controller = require('./controller'); + +module.exports = Mn.AppRouter.extend({ + appRoutes: { + users: 'showUsers', + profile: 'showProfile', + logout: 'logout', + '*default': 'showDashboard' + }, + + initialize: function () { + this.controller = Controller; + } +}); diff --git a/src/frontend/js/app/tokens.js b/src/frontend/js/app/tokens.js new file mode 100644 index 0000000..fb85e9f --- /dev/null +++ b/src/frontend/js/app/tokens.js @@ -0,0 +1,128 @@ +'use strict'; + +const STORAGE_NAME = 'nginx-proxy-manager-tokens'; + +/** + * @returns {Array} + */ +const getStorageTokens = function () { + let json = window.localStorage.getItem(STORAGE_NAME); + if (json) { + try { + return JSON.parse(json); + } catch (err) { + return []; + } + } + + return []; +}; + +/** + * @param {Array} tokens + */ +const setStorageTokens = function (tokens) { + window.localStorage.setItem(STORAGE_NAME, JSON.stringify(tokens)); +}; + +const Tokens = { + + /** + * @returns {Integer} + */ + getTokenCount: () => { + return getStorageTokens().length; + }, + + /** + * @returns {Object} t,n + */ + getTopToken: () => { + let tokens = getStorageTokens(); + if (tokens && tokens.length) { + return tokens[0]; + } + + return null; + }, + + /** + * @returns {String} + */ + getNextTokenName: () => { + let tokens = getStorageTokens(); + if (tokens && tokens.length > 1 && typeof tokens[1] !== 'undefined' && typeof tokens[1].n !== 'undefined') { + return tokens[1].n; + } + + return null; + }, + + /** + * + * @param {String} token + * @param {String} [name] + * @returns {Integer} + */ + addToken: (token, name) => { + // Get top token and if it's the same, ignore this call + let top = Tokens.getTopToken(); + if (!top || top.t !== token) { + let tokens = getStorageTokens(); + tokens.unshift({t: token, n: name || null}); + setStorageTokens(tokens); + } + + return Tokens.getTokenCount(); + }, + + /** + * @param {String} token + * @returns {Boolean} + */ + setCurrentToken: token => { + let tokens = getStorageTokens(); + if (tokens.length) { + tokens[0].t = token; + setStorageTokens(tokens); + return true; + } + + return false; + }, + + /** + * @param {String} name + * @returns {Boolean} + */ + setCurrentName: name => { + let tokens = getStorageTokens(); + if (tokens.length) { + tokens[0].n = name; + setStorageTokens(tokens); + return true; + } + + return false; + }, + + /** + * @returns {Integer} + */ + dropTopToken: () => { + let tokens = getStorageTokens(); + tokens.shift(); + setStorageTokens(tokens); + return tokens.length; + }, + + /** + * + */ + clearTokens: () => { + window.localStorage.removeItem(STORAGE_NAME); + } + +}; + +module.exports = Tokens; diff --git a/src/frontend/js/app/ui/footer/main.ejs b/src/frontend/js/app/ui/footer/main.ejs new file mode 100644 index 0000000..eeea09e --- /dev/null +++ b/src/frontend/js/app/ui/footer/main.ejs @@ -0,0 +1,14 @@ +
+
+
+ +
+
+
+ v<%- getVersion() %> © 2018 jc21.com. Theme by Tabler +
+
diff --git a/src/frontend/js/app/ui/footer/main.js b/src/frontend/js/app/ui/footer/main.js new file mode 100644 index 0000000..531ad81 --- /dev/null +++ b/src/frontend/js/app/ui/footer/main.js @@ -0,0 +1,16 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); +const App = require('../../main'); + +module.exports = Mn.View.extend({ + className: 'container', + template: template, + + templateContext: { + getVersion: function () { + return App.version; + } + } +}); diff --git a/src/frontend/js/app/ui/header/main.ejs b/src/frontend/js/app/ui/header/main.ejs new file mode 100644 index 0000000..eba23e3 --- /dev/null +++ b/src/frontend/js/app/ui/header/main.ejs @@ -0,0 +1,28 @@ + diff --git a/src/frontend/js/app/ui/header/main.js b/src/frontend/js/app/ui/header/main.js new file mode 100644 index 0000000..74f492f --- /dev/null +++ b/src/frontend/js/app/ui/header/main.js @@ -0,0 +1,55 @@ +'use strict'; + +const $ = require('jquery'); +const Mn = require('backbone.marionette'); +const Cache = require('../../cache'); +const Controller = require('../../controller'); +const Tokens = require('../../tokens'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + id: 'header', + className: 'header', + template: template, + + ui: { + link: 'a' + }, + + events: { + 'click @ui.link': function (e) { + e.preventDefault(); + let href = $(e.currentTarget).attr('href'); + + switch (href) { + case '/': + Controller.showDashboard(); + break; + case '/profile': + Controller.showProfile(); + break; + case '/logout': + Controller.logout(); + break; + } + } + }, + + templateContext: { + getUserField: function (field, default_val) { + return Cache.User.get(field) || default_val; + }, + + getRole: function () { + return Cache.User.isAdmin() ? 'Administrator' : 'Apache Helicopter'; + }, + + getLogoutText: function () { + if (Tokens.getTokenCount() > 1) { + return 'Sign back in as ' + Tokens.getNextTokenName(); + } + + return 'Sign out'; + } + } +}); diff --git a/src/frontend/js/app/ui/main.ejs b/src/frontend/js/app/ui/main.ejs new file mode 100644 index 0000000..5d8b656 --- /dev/null +++ b/src/frontend/js/app/ui/main.ejs @@ -0,0 +1,18 @@ +
+ + + +
+
+ +
+
+
+ +
+ +
\ No newline at end of file diff --git a/src/frontend/js/app/ui/main.js b/src/frontend/js/app/ui/main.js new file mode 100644 index 0000000..b013cd4 --- /dev/null +++ b/src/frontend/js/app/ui/main.js @@ -0,0 +1,44 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); +const HeaderView = require('./header/main'); +const MenuView = require('./menu/main'); +const FooterView = require('./footer/main'); +const Cache = require('../cache'); + +module.exports = Mn.View.extend({ + className: 'page', + template: template, + + regions: { + header_region: { + el: '#header', + replaceElement: true + }, + menu_region: { + el: '#menu', + replaceElement: true + }, + footer_region: '.footer', + app_content_region: '#app-content' + }, + + showAppContent: function (view) { + this.showChildView('app_content_region', view); + }, + + onRender: function () { + this.showChildView('header_region', new HeaderView({ + model: Cache.User + })); + + this.showChildView('menu_region', new MenuView()); + this.showChildView('footer_region', new FooterView()); + }, + + reset: function () { + this.getRegion('header_region').reset(); + this.getRegion('footer_region').reset(); + } +}); diff --git a/src/frontend/js/app/ui/menu/main.ejs b/src/frontend/js/app/ui/menu/main.ejs new file mode 100644 index 0000000..d4b975c --- /dev/null +++ b/src/frontend/js/app/ui/menu/main.ejs @@ -0,0 +1,56 @@ + \ No newline at end of file diff --git a/src/frontend/js/app/ui/menu/main.js b/src/frontend/js/app/ui/menu/main.js new file mode 100644 index 0000000..b9fdf17 --- /dev/null +++ b/src/frontend/js/app/ui/menu/main.js @@ -0,0 +1,10 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const template = require('./main.ejs'); + +module.exports = Mn.View.extend({ + id: 'menu', + className: 'header collapse d-lg-flex p-0', + template: template +}); diff --git a/src/frontend/js/index.js b/src/frontend/js/index.js new file mode 100644 index 0000000..bfaa017 --- /dev/null +++ b/src/frontend/js/index.js @@ -0,0 +1,112 @@ +// This has to exist here so that Webpack picks it up +import '../scss/styles.scss'; + +window.tabler = { + colors: { + 'blue': '#467fcf', + 'blue-darkest': '#0e1929', + 'blue-darker': '#1c3353', + 'blue-dark': '#3866a6', + 'blue-light': '#7ea5dd', + 'blue-lighter': '#c8d9f1', + 'blue-lightest': '#edf2fa', + 'azure': '#45aaf2', + 'azure-darkest': '#0e2230', + 'azure-darker': '#1c4461', + 'azure-dark': '#3788c2', + 'azure-light': '#7dc4f6', + 'azure-lighter': '#c7e6fb', + 'azure-lightest': '#ecf7fe', + 'indigo': '#6574cd', + 'indigo-darkest': '#141729', + 'indigo-darker': '#282e52', + 'indigo-dark': '#515da4', + 'indigo-light': '#939edc', + 'indigo-lighter': '#d1d5f0', + 'indigo-lightest': '#f0f1fa', + 'purple': '#a55eea', + 'purple-darkest': '#21132f', + 'purple-darker': '#42265e', + 'purple-dark': '#844bbb', + 'purple-light': '#c08ef0', + 'purple-lighter': '#e4cff9', + 'purple-lightest': '#f6effd', + 'pink': '#f66d9b', + 'pink-darkest': '#31161f', + 'pink-darker': '#622c3e', + 'pink-dark': '#c5577c', + 'pink-light': '#f999b9', + 'pink-lighter': '#fcd3e1', + 'pink-lightest': '#fef0f5', + 'red': '#e74c3c', + 'red-darkest': '#2e0f0c', + 'red-darker': '#5c1e18', + 'red-dark': '#b93d30', + 'red-light': '#ee8277', + 'red-lighter': '#f8c9c5', + 'red-lightest': '#fdedec', + 'orange': '#fd9644', + 'orange-darkest': '#331e0e', + 'orange-darker': '#653c1b', + 'orange-dark': '#ca7836', + 'orange-light': '#feb67c', + 'orange-lighter': '#fee0c7', + 'orange-lightest': '#fff5ec', + 'yellow': '#f1c40f', + 'yellow-darkest': '#302703', + 'yellow-darker': '#604e06', + 'yellow-dark': '#c19d0c', + 'yellow-light': '#f5d657', + 'yellow-lighter': '#fbedb7', + 'yellow-lightest': '#fef9e7', + 'lime': '#7bd235', + 'lime-darkest': '#192a0b', + 'lime-darker': '#315415', + 'lime-dark': '#62a82a', + 'lime-light': '#a3e072', + 'lime-lighter': '#d7f2c2', + 'lime-lightest': '#f2fbeb', + 'green': '#5eba00', + 'green-darkest': '#132500', + 'green-darker': '#264a00', + 'green-dark': '#4b9500', + 'green-light': '#8ecf4d', + 'green-lighter': '#cfeab3', + 'green-lightest': '#eff8e6', + 'teal': '#2bcbba', + 'teal-darkest': '#092925', + 'teal-darker': '#11514a', + 'teal-dark': '#22a295', + 'teal-light': '#6bdbcf', + 'teal-lighter': '#bfefea', + 'teal-lightest': '#eafaf8', + 'cyan': '#17a2b8', + 'cyan-darkest': '#052025', + 'cyan-darker': '#09414a', + 'cyan-dark': '#128293', + 'cyan-light': '#5dbecd', + 'cyan-lighter': '#b9e3ea', + 'cyan-lightest': '#e8f6f8', + 'gray': '#868e96', + 'gray-darkest': '#1b1c1e', + 'gray-darker': '#36393c', + 'gray-light': '#aab0b6', + 'gray-lighter': '#dbdde0', + 'gray-lightest': '#f3f4f5', + 'gray-dark': '#343a40', + 'gray-dark-darkest': '#0a0c0d', + 'gray-dark-darker': '#15171a', + 'gray-dark-dark': '#2a2e33', + 'gray-dark-light': '#717579', + 'gray-dark-lighter': '#c2c4c6', + 'gray-dark-lightest': '#ebebec' + } +}; + +require('tabler-core'); + +const App = require('./app/main'); + +$(document).ready(() => { + App.start(); +}); diff --git a/src/frontend/js/lib/helpers.js b/src/frontend/js/lib/helpers.js new file mode 100644 index 0000000..984b513 --- /dev/null +++ b/src/frontend/js/lib/helpers.js @@ -0,0 +1,36 @@ +'use strict'; + +const numeral = require('numeral'); +const moment = require('moment'); + +module.exports = { + + /** + * @param {Integer} number + * @returns {String} + */ + niceNumber: function (number) { + return numeral(number).format('0,0'); + }, + + /** + * @param {String|Integer} date + * @returns {String} + */ + shortTime: function (date) { + let shorttime = ''; + + if (typeof date === 'number') { + shorttime = moment.unix(date).format('H:mm A'); + } else { + shorttime = moment(date).format('H:mm A'); + } + + return shorttime; + }, + + replaceSlackLinks: function (content) { + return content.replace(/<(http[^|>]+)\|([^>]+)>/gi, '$2'); + } + +}; diff --git a/src/frontend/js/lib/marionette.js b/src/frontend/js/lib/marionette.js new file mode 100644 index 0000000..810defe --- /dev/null +++ b/src/frontend/js/lib/marionette.js @@ -0,0 +1,117 @@ +'use strict'; + +const _ = require('underscore'); +const Mn = require('backbone.marionette'); +const moment = require('moment'); +const numeral = require('numeral'); + +let render = Mn.Renderer.render; + +Mn.Renderer.render = function (template, data, view) { + + data = _.clone(data); + + /** + * @param {Integer} number + * @returns {String} + */ + data.niceNumber = function (number) { + return numeral(number).format('0,0'); + }; + + /** + * @param {Integer} seconds + * @returns {String} + */ + data.secondsToTime = function (seconds) { + let sec_num = parseInt(seconds, 10); + let minutes = Math.floor(sec_num / 60); + let sec = sec_num - (minutes * 60); + + if (sec < 10) { + sec = '0' + sec; + } + + return minutes + ':' + sec; + }; + + /** + * @param {String} date + * @returns {String} + */ + data.shortDate = function (date) { + let shortdate = ''; + + if (typeof date === 'number') { + shortdate = moment.unix(date).format('YYYY-MM-DD'); + } else { + shortdate = moment(date).format('YYYY-MM-DD'); + } + + return moment().format('YYYY-MM-DD') === shortdate ? 'Today' : shortdate; + }; + + /** + * @param {String} date + * @returns {String} + */ + data.shortTime = function (date) { + let shorttime = ''; + + if (typeof date === 'number') { + shorttime = moment.unix(date).format('H:mm A'); + } else { + shorttime = moment(date).format('H:mm A'); + } + + return shorttime; + }; + + /** + * @param {String} string + * @returns {String} + */ + data.escape = function (string) { + let entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }; + + return String(string).replace(/[&<>"'\/]/g, function (s) { + return entityMap[s]; + }); + }; + + /** + * @param {String} string + * @param {Integer} length + * @returns {String} + */ + data.trim = function (string, length) { + if (string.length > length) { + let trimmedString = string.substr(0, length); + return trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(' '))) + '...'; + } + + return string; + }; + + /** + * @param {String} name + * @returns {String} + */ + data.niceVarName = function (name) { + return name.replace('_', ' ') + .replace(/^(.)|\s+(.)/g, function ($1) { + return $1.toUpperCase(); + }); + }; + + return render.call(this, template, data, view); +}; + +module.exports = Mn; diff --git a/src/frontend/js/login.js b/src/frontend/js/login.js new file mode 100644 index 0000000..0094e2a --- /dev/null +++ b/src/frontend/js/login.js @@ -0,0 +1,5 @@ +const App = require('./login/main'); + +$(document).ready(() => { + App.start(); +}); diff --git a/src/frontend/js/login/main.js b/src/frontend/js/login/main.js new file mode 100644 index 0000000..80d2866 --- /dev/null +++ b/src/frontend/js/login/main.js @@ -0,0 +1,17 @@ +'use strict'; + +const Mn = require('backbone.marionette'); +const LoginView = require('./ui/login'); + +const App = Mn.Application.extend({ + + region: '#login', + UI: null, + + onStart: function (/*app, options*/) { + this.getRegion().show(new LoginView()); + } +}); + +const app = new App(); +module.exports = app; diff --git a/src/frontend/js/login/ui/login.ejs b/src/frontend/js/login/ui/login.ejs new file mode 100644 index 0000000..991d42f --- /dev/null +++ b/src/frontend/js/login/ui/login.ejs @@ -0,0 +1,28 @@ +
+
+ +
+
diff --git a/src/frontend/js/login/ui/login.js b/src/frontend/js/login/ui/login.js new file mode 100644 index 0000000..07ee8eb --- /dev/null +++ b/src/frontend/js/login/ui/login.js @@ -0,0 +1,42 @@ +'use strict'; + +const $ = require('jquery'); +const Mn = require('backbone.marionette'); +const template = require('./login.ejs'); +const Api = require('../../app/api'); + +module.exports = Mn.View.extend({ + template: template, + className: 'page-single', + + ui: { + form: 'form', + identity: 'input[name="identity"]', + secret: 'input[name="secret"]', + error: '.secret-error', + button: 'button' + }, + + events: { + 'submit @ui.form': function (e) { + e.preventDefault(); + this.ui.button.addClass('btn-loading').prop('disabled', true); + this.ui.error.hide(); + + Api.Tokens.login(this.ui.identity.val(), this.ui.secret.val(), true) + .then(() => { + window.location = '/'; + }) + .catch(err => { + this.ui.error.text(err.message).show(); + this.ui.button.removeClass('btn-loading').prop('disabled', false); + }); + } + }, + + templateContext: { + getVersion: function () { + return $('#login').data('version'); + } + } +}); diff --git a/src/frontend/js/models/user.js b/src/frontend/js/models/user.js new file mode 100644 index 0000000..41250d3 --- /dev/null +++ b/src/frontend/js/models/user.js @@ -0,0 +1,29 @@ +'use strict'; + +const _ = require('underscore'); +const Backbone = require('backbone'); + +const model = Backbone.Model.extend({ + idAttribute: 'id', + + defaults: function () { + return { + name: '', + nickname: '', + email: '', + is_disabled: false, + roles: [] + }; + }, + + isAdmin: function () { + return _.indexOf(this.get('roles'), 'admin') !== -1; + } +}); + +module.exports = { + Model: model, + Collection: Backbone.Collection.extend({ + model: model + }) +}; diff --git a/src/frontend/scss/styles.scss b/src/frontend/scss/styles.scss new file mode 100644 index 0000000..ac3bf72 --- /dev/null +++ b/src/frontend/scss/styles.scss @@ -0,0 +1,13 @@ +@import "~tabler-ui/dist/assets/css/dashboard"; + +/* Before any JS content is loaded */ +#app > .loader, #login > .loader, .container > .loader { + position: absolute; + left: 49%; + top: 40%; + display: block; +} + +.no-js-warning { + margin-top: 100px; +} \ No newline at end of file