From 78558f9c8e327bb65ec606507778ac2632917ad6 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 20 Mar 2020 11:37:35 +1300 Subject: [PATCH 01/40] chore(version): bump version number --- api/portainer.go | 2 +- api/swagger.yaml | 4 ++-- api/swagger_config.json | 2 +- distribution/portainer.spec | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/portainer.go b/api/portainer.go index e97230a0e7599..23b149c82b280 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -917,7 +917,7 @@ type ( const ( // APIVersion is the version number of the Portainer API - APIVersion = "1.23.2" + APIVersion = "1.24.0-dev" // DBVersion is the version number of the Portainer database DBVersion = 22 // AssetsServerURL represents the URL of the Portainer asset server diff --git a/api/swagger.yaml b/api/swagger.yaml index dd980a700a2f8..0d0a60a8923f0 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -54,7 +54,7 @@ info: **NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8). - version: "1.23.2" + version: "1.24.0-dev" title: "Portainer API" contact: email: "info@portainer.io" @@ -3174,7 +3174,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.23.2" + example: "1.24.0-dev" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" diff --git a/api/swagger_config.json b/api/swagger_config.json index dc70a5211b3be..3bdb706885679 100644 --- a/api/swagger_config.json +++ b/api/swagger_config.json @@ -1,5 +1,5 @@ { "packageName": "portainer", - "packageVersion": "1.23.2", + "packageVersion": "1.24.0-dev", "projectName": "portainer" } diff --git a/distribution/portainer.spec b/distribution/portainer.spec index 93cad801f53cd..6b215cad74a9d 100644 --- a/distribution/portainer.spec +++ b/distribution/portainer.spec @@ -1,5 +1,5 @@ Name: portainer -Version: 1.23.2 +Version: 1.24.0-dev Release: 0 License: Zlib Summary: A lightweight docker management UI diff --git a/package.json b/package.json index f0f72ce8b7dee..9cc2882dae5b7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "1.23.2", + "version": "1.24.0-dev", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git" From ae7f46c8ef32c10e527fa8e88b3b5e304ca5ebff Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 26 Mar 2020 07:44:27 +0200 Subject: [PATCH 02/40] feat(endpoints): filter by endpoint type (#3646) --- api/http/handler/endpoints/endpoint_list.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/http/handler/endpoints/endpoint_list.go b/api/http/handler/endpoints/endpoint_list.go index b89899d167f76..403a3f66864a7 100644 --- a/api/http/handler/endpoints/endpoint_list.go +++ b/api/http/handler/endpoints/endpoint_list.go @@ -28,6 +28,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht groupID, _ := request.RetrieveNumericQueryParameter(r, "groupId", true) limit, _ := request.RetrieveNumericQueryParameter(r, "limit", true) + endpointType, _ := request.RetrieveNumericQueryParameter(r, "type", true) endpointGroups, err := handler.EndpointGroupService.EndpointGroups() if err != nil { @@ -54,6 +55,10 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, endpointGroups, search) } + if endpointType != 0 { + filteredEndpoints = filterEndpointsByType(filteredEndpoints, portainer.EndpointType(endpointType)) + } + filteredEndpointCount := len(filteredEndpoints) paginatedEndpoints := paginateEndpoints(filteredEndpoints, start, limit) @@ -156,3 +161,14 @@ func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGrou return false } + +func filterEndpointsByType(endpoints []portainer.Endpoint, endpointType portainer.EndpointType) []portainer.Endpoint { + filteredEndpoints := make([]portainer.Endpoint, 0) + + for _, endpoint := range endpoints { + if endpoint.Type == endpointType { + filteredEndpoints = append(filteredEndpoints, endpoint) + } + } + return filteredEndpoints +} From 00bef100eefffe5280de27eb5f9e602f34010962 Mon Sep 17 00:00:00 2001 From: Ben Brooks Date: Fri, 27 Mar 2020 03:49:20 +0000 Subject: [PATCH 03/40] chore(assets): double UI image resolutions for HiDPI displays (#3648) Fixes #3069 Prevents users seeing blurry logos and other images when using a hidpi display (like scaled 4k, or a Retina display). These images have been recreated manually with 2x the original resolution but should resemble the originals as much as possible. They have also been run through pngcrush for compression. --- assets/images/logo.png | Bin 1769 -> 5074 bytes assets/images/logo_small.png | Bin 1092 -> 3105 bytes assets/images/support_1.png | Bin 4733 -> 5200 bytes assets/images/support_2.png | Bin 5753 -> 6291 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/images/logo.png b/assets/images/logo.png index f0ba92c53e07910bbc3e0817016d2b3a10be51bf..2e46594f291f5e2e3541c3c8fdd240afb7b70217 100644 GIT binary patch literal 5074 zcmV;@6D{nCP)mtBEed0vA7j0;bsI*fDsW1wqiRCW6)lz@FjMG5fKUWV%r-(pcZaw z(2#o6!A%aHLpqFz1R51vHeN+O+?3#TpfOdr9{<430$htI7!gsi`CANb!BW`y3kSpC z9yFvDyTag7%z~X^I3C7dL`21QDn`T36kO2FJ+@>#iVbj6jW4YN6cH7hPwa*7Tg}nd z-~lA|VnKHYe2#Ls$-%Rlzlo^Wye%8AAP;Uz@gcVLz{!In(658}TO^F9h=__U1~+2~ z?EH;GJba`o0+(SH>4cx4? z=4ie;1izy_f7AY(NNDrs9PQ;+bF|HPtq($X?{?Ei zSPVP$7=hRE7T)cJ;nrn+jdy$Db-a(oXq^#ww;e`ALbce!H%3H4x7cz#s20V@v6P}3 zIUOj4n|$kX++$WbUSdQfbc-#^gO=b{WLciWL}Yc~X=}!IG_tI(lmn3EB}PQLLnjXw z;xZV^!x+=eU(nqX1`DiRI3ptB?_#?G#_|Zpc5~fh?^eb34~%#ge?gup4$qCQN{e z$MJXsmm&?uYa|(G<58T6B6n1!%^rpcP?uVJlQnq9YrBaOfk#n+ zhLqv1a4)u~t?Zq1kmX7rhHGbynOS=;*0%TBwr$&3UE8*8+qP}ryEX@3|M8@%>#a;q zaFTP*x7}6mA}2{B-B07!z<6+|(aM(2pb*t74zM|4Jo(4Sb7vKCSf3$`w&lEIDt=H(8htSsKEB8m3dDu;;`yg@;@52O~o&##zyZN)sWWJ zOmJ_wB7?0r5c8bYvh5t$9N1!Lw9!XK(>T#=|5W{R+M4NB(^!_c;fmfB?GXUBnGS3Y zYzfqb0yZdpznm~AZ6rHYmo(UhuqfS6^H+N@Aoq6nYKPHwuYjpY0*I9Soo#f2(VkrIkdH};yrTnM_`QryG&+mF_vU&WzgiMM zFOF@k3|Yb9H=7zAM!YJlajFe?ZX3Rf=kB!LxgPfo3{wcmu$_$7W?=smTK*F`Cw2&3 z-^OBnTgzyD{OT6qM>D>ucW>{&$)((jI$B_CL!5SEuGX34_Y(={7^C*UA;~uRt=Wim zTk`hl$QOH z`CQh}9tDgj_`w2*%d9wV$g_Qc^yiilXTFs&58!^D#iWl}pKJDeZ_5#mX81XoV-*+r zMUwe)_me+b8-Q7li1?1m3oSm^pj7?MP zkrQx1op0mo>Zbna86{otz zMjyeN0aWZo4YsW#_>(5smg?j}GSO3YWVGe{T_eA}OAQ+(;aiXn@8|GK-G|ihEkgJ) zUY^|)KfjUd)nu-Y)=tG$mUP3=D$!j5VW{MN-}}60IZ{8Ca3)1>U_00P?Z_YtofxGq z47hi&ZJ7t=r{I^wm4YkZopie*0(2Sx->)8jfSl&lm zG;W6{0NeT!&b>lb=4087r#eCz`-!l&OdD+H27v4K9L9s~iUe#+tT27d!7{&F*OpVe z{RV(uCSE^ztq5sV9U1Z*hvqc9=ZRmljD9wH55L~!|Fh)Gi1~CKTSNd`H7$|Z-v0^n zKh%nkctZdlaM8z70sKX{-nVpmKv(5rqbh;cbm00H@%)Hz%(XK+{%lPAJ$FP9*rYXh zK@ixic-46M2)7iN{6H9AqQ(3**OyCvo)Ti*t&9Two(&hi<-b&{uh4S;l8Q!W4m<_y zbB(>dVLkJn97evF^#6{yZJia^XgF*yWz5Tme^F__Ot`)yt(Qo8T}5jz9k9_txx-I1 zF^>=UeQ*(iZKgDd=TY+=%6Lm9wM{t%Xdg2CT!GUeLETb*J5mb!JTl*b#H#|oD(J%P zPo|$IANd&W3j;Q7;(tlka13K_I<8fT|EtIeU*~bX(o(ZJ;C+dNcP1@|ZRt`s!>=}k zW1`U)QlNh)yy<{V9BKSGUDNJ_u@T|y%Jo;}**RW*4A1}@o>YoI)2dvZ!rB;hX3Xja zqyOx@g5O=jg3Sg9K)am^d}ZQRKMSzo&!%=anD6`B!k~h ztrOL+o29j|Z%D9Bv(hb1u$?VwdO}Q#@x#P33$RU(u`K8bX^~deaSY{;wK&ppK-Bc0 zYdTLV&nCIRM*2|P>63d3@N zjXc@Cz+gK|D!OfA`d7i2oacTH%3BU>I)0R&{lNZ_cyyMg^b-mFdPLH!YY4C%oB`Oz zO09Zs!gxNK5!fci2-_Y48o$=?bu#oYk~EE@ZKMTzTnMmzK&4tJur(A*@b?jiLR3@4 zAhNato5;tMyjH>wJxOY&A<`VLku*r+*Rk?}$Ah~t}b3I+fly9(|6#%ZAC2`fA9I(5` zyaIu3v3K%S0_`nvSy!xEw%aYCUpvbCHVXl^jWPh+(E{Aj3FEnCMqt||M%cC!unpJN z3SFnP+!GTmgE6! z`$hoU?g{!7ZRZrg)=0osL--ctNiT!|+rvc$wo8f`Y-L!w zE>fY(j4loM{R+r^k&5dh3(^%$z60p#c1D-Q;lpHJS5iyWiPG&IT_j-BmSoboQ^%Dm z*w72o6}^`7kw}!GLrVZ`t!3)ZLu$#4sEx(X!}1BXb!yXf&tv7w+IVn5!mVOn67zps znbi5W#IZpFzn+#kJ)MgLY$F61mnKNlhFE+{8EjX`q*N-q6jX#@Bd@+Lmdw@(wn;iy zD)b5Y1l!#ygN;m*IoN*GZE+%KB;$iYQ!5^jiNGUc`ZY|9FOLX33JJERMxRKUc8D3z zainP}gKc9O@V|({zCwTvO_bq@-D6gE?To&W=~)%|0$V#~GN~vfv`R}AY%OKgz;vTg5es>L0}W zHAx9<^>l;Eq+lbhXUm2YonlPbK2-pDPCi#2tXZ3ld$vGeTf%&=@(7?gMr!3}{R%R` zd|5uu2@(7%SHD<1-@M4c)>*bGn#&TPCK2{4yO?K78f<7^+b_V=Jq$d6%qa5r3+Oq% zZN9P)H)>Ko6=C2D?>di=M!uPWCXTviBM+71LRGloM_dI z_3GHpxt&g3p*6c&fS+p_{U;!u62h|@T)tqHb z*U5R6B@H&>^|Vw-F9WP{Kfuvltz;?Hmr@zN%y0PwTNyoy?+NF0rgSyQ6WCVu%BsMP z9aMZL5jP_L?J1Mn7pUPRG1w@`S-8Kcxw8V>EOrp6ku73&V;V?%t2?kNnOaXCpB-|d z=fOS`lwV?fX)p1s2bRX?br)B$2o)RH%2>$0Kp=iAErPC83~d>M8^U7o&jl7g0Q1rW z8{#{DBC9g~pi&!28nh)%I?*$_NLndXbZh&iFviw_@Tl7`A=vc(zod+PN_iM4Y0%AR z6DB4v5d+K>fIqKbJ2eZcQNy>+5Nsc?1I151XP*87cpju1@SU_IstE5<5yml&r5394 zF<`uaUoY_g8u4oe(>=
O*}UCP-Khb7GKdJ6OZ>pE@=%-*I&I1TE@s@uaY002H?8;${aSDEKg&j$wRWB z8Z~wE48is(*Cx<9nnPv{a#hr*?=uDvr(k@BPkY|?T&l%a(M zT6olvnP+-?#^L|GC|iR^0Nb)Wf~_hS*wD)C>M~slZcutmCp~)djwoA$KN9kp&Q#zv z(&gJYpI}2{DPyy8l`-jNhBc8NFzLgJ_a8bS$2iVUR#+zm+}odnRJb?RI@-;w=YNlI`h0ziDUUJ;J_~p+Li4%y z2ls3n@vxvrtZ>~F;kn%b$P8N0m*%i$uOay>sKQlM@GW1)ui3=g%lC81Ctt8VQUA10 zsp>?;BRewiE0}5bqW``wR--#)fbeYfo30)%9k6NQ6TXv}F@6h$^Ej@z%Jad*>+|l5 zSZ{kVAlZPHOPpi<23x9#IT^U0^^Oh+B@Fv>?A-M%Y(Ed<%dnVp-g;Q!S~=pg{Q=bKA*{CL zlD}HT$)n6yJd$|dM1DAh<#e4hJ69BlL~#o!tUWwn@Iluiv-aY8JAgKeg!S7w;#UCJ zmuugo2sS4trxI1XgIq#7T2v#gb%f6XrfL~S_?Vi@8!KrXum^gDH3e9@dBxBBSKPMhOGhaBgHL8x__O*s_{{)VCTT$*^VB9jWgiiXbEzwk+!5 z8B*Rglt)N1Y+0>G%DaJzh&&|u=fN$cx+5rpkYw2MXon|Aamx^r3|k>u;}TNZ3p7GV zGHivajiq>q6t)r}$*}zj_0S8mumW?i5`S|0Q63@5u>DTkQOM3?AF^&Usv;!)ZicNo z4k6E;peagVBJSW@JB~j1A0bJ_nDbMq9*3YN8kBVI!7cB-)}BLeigQ*nkok zjWc+Q&vyg!Q45g_Nro+h(rAG}n2I52i%N*}NiuAbWY{Fhut}0(lVsQ=$*@V1VUr}o oCP{`(l3|l1!zM|FO_F}6_wZymPGo`c5C8xG07*qoM6N<$f|jAd*Z=?k literal 1769 zcmVN zc8UbEW}2EIEq*lkt3gpjRtGi5bykN2kx*FJAD{lXci(;Q-TPkdrZmj#`@A!E_nz~d z^Pcm4&-*;*dCqPKsicxhDygK>InmeIj!Zdp7TfU{opR_bwhM4o%As>B?Qt1-+zBa< z(rnsdd%S5j7I@QMOY}>*6o)p$+dguOvW#uh$1Ofq??T;DI)#yqv=+;KG1nzH&T_2t z<&;rAvH|YZ>;-2Q_N2zzsKtGb>gu5S7@EC?S!AaVR5>Xs8)`hxk9{Ae%1%2iGR#nA zT@~ASpG3xVRc!r@j;;yWpAW}Ar(?FaEQ{&2>mHsOwsw@uW1ksMT4zbh<&eQMF2WxU${2dvRq7~;cl39#>X5?L&qY3W zkXGAi&iP-OU8`QbI(Mj7Z_S~I?NGF3)nK;=!E2UUYppwzu<5SS$svT`DCe2t^95JO zSeF>7N35);O2b0X(;3Pgu2L;#sx%^|HPBR3Gq%PlDl_4nBUCy*t8u!D5cF2zGT$_$ z!@YZ{aEY(x?kZI{Jp|pHlD};-LS@c4dnk9Yu?L93^ReY+K{{eP41aX2by~Cgw>523 z3Lyk#?$VU4+3vfs7=#t3*@1<|#P)w{vl>IJ%a*iSZJB(QC90!?>NV{Nb_Nb zMfd1tzTMfHy>8Y$s%fxAg*Wh6-u&0#i0Ipe-p{sg_LCwTdehXZ^?)nXs&z+6*tU32 zs}<&&YbACW73EG|z)Nm*nIGGZyJOgP;1vsuaI{PP3Aeh`#1KL-#%`@1b(MJ@)2hL% z3D}yk#f>gAUwE<&oCY6h)L^l3$=EE#c1vBR%8Pg@am`t%MT-wT zphb%&B(c)#Ti*_+Y**d7Mn8rJ(pK^3#J!tHbIRVi%eIe#v3KQ*beuW zy(*)+QFe72+dMqgW_6YMr424EgiVDjBahpTZ3>?8*=P@V2OdnyLL|wGBG|V2z*(RDqnY<=kdGf z0miWDZ#Ql$uy1=1^=@#MSyp4K&lJX1o`|iwXq0fGS0c?9bHbzk9*%1E_(5)zP%I`j z(#uiHO@<^#35BtRpoi)xLfPS0?fyOoTQBe7>Vi?iJbR)s)Fwa+L3cYMwI=r)Yn-!8 zwZXIy4AN8>TL`)t?n>WxT0El5>I`y$8%>J;{YYC(8Y>yBGIs;2htth5$EhKN;0%+K zV$z&@RmStoj&`o=otZnTOiUVkgq4Xg=@BNn!3=|o$E5dXq3!bx3;ZW*x#sGtpXp{9 zsY-Q6Y+XH@Tjr_EOX>EUmvlq4Tx$(Xv2_G@j1}%x5oI(w%A+Z^j@bIR#!9ajJ=`0e zV#|d#L|qAIqSwVt`!&w(>d?CTM@dJ?>5*R!woxUXz|Txs#j(vP5!*v4t2nj`OT@N4 zKdsZXu?^N-BDj-NT5)hSB|&S<&9J4|dbzEH6}K!U)e+r5Q(WP>98htTucZIfkqqxB zuSTF6oaWnBd)Kq7^-1Xz12@3sR$ApI$E7qwT_9^G00000 LNkvXXu0mjf25yMr diff --git a/assets/images/logo_small.png b/assets/images/logo_small.png index 636f43ab9b15ccb74ca6dd92c6d6c1949b68f657..76d3a46b09e32632c0a74475028a6ac2add3fcb1 100644 GIT binary patch literal 3105 zcmV++4BqpJP)aaYhOC%4T@H{4bvpVfjEKEfh5IT zn69860zkepkPk)2D46b}9YR8Uk94xI1STT)IdBW_`-Fv;64_9;WCL47b!6S&tO`PTu5Rgo}+Xi-k*4q(t$MniY5R* z18Xr5P04}Hw?xa}`P#gs3XSg?>jxOhYC74sodDn23%D z;td0dkHR+DkqU)vvf#skY=YS6KwRrTgv5Uk2m2Et5E$YO0~rK+XR#di4r4v+ZS(7a z02^Vd08!37#p(xp$FKC ze(>71ZQHhOo7c8&+qP}nw(Wy&&*E#6-zHDy?as{Z% zvwY7toTev_k)9ib`}$dS6Wm1)_cDh08j;U8dGCIQV*;Nj6(da_S_F`A#9dc$a`s}zxeaog%4MTuCnr!yXK|%f#E~cBIIJ{8 z=?Rj zV9mYG+^fr!c(En#dHlbW`x!R6jXK;b)5`msm1;5t(!F~Z&mB~{88|m{?U2jOsc-ZA zE?~*93h0{QxUiPzPCTsF_ytH~DmbcD?B12vmg(R14hZo_FhIMJ(J*_#M&dPF!ZQJS z?Pqjx(L?s5L4+x+F-ilN5(YW#xSLTa!zyt?agc$aVQ(*eRa7KnDUhI|ecXO9A;{Q*A!`C6&F zP(7t0mTNVcuKXzUmd}9Xld*#gnb)wb{tO!a2l&UsG_0Gg#vR`-AkG>}H;OpUVc4Fj z6yZD1S<@rzuOOh6r7@f-M$I$$e=g7ds&png)zZ@~r(tdc%8SJNS1A1fumzNX7eK;5 z`zzDbGtk*epONPXF8j>?j2u8fL;IMKBAV8#Xcvbln~54j`fdSN5HQ}sE$!C1@DA_q zwF1Kb3n|Z?I7cpx=94^sj~yKXk}Ev{!f+(RMtVybR^YXgVX>cp5DqbiJ;hpq0B{FW zz%Y$S+7lm8IU(lUr)uKJrof_*%%bvjfP|Pg6Hh{3tY%^adXTAUh7@^y21p2=0QpV# z9m=n~I0h}JJ6RlL3sb&#^1Lps?@`Y|=KWegi02j=j}L&iUxFw!#AH|bOHwJ`D${S3p*2fVVc_|IxVbk=I3ed}fzc8his`o1Ps5kbgzT;s+p?EA8Q48M}5u`iqLG z+SPsnvOzO~EN4rF40HShM9w+wL<88!JAVr!_$b%jb`giPHCo01q`Jp-kT1ory&%GMy z1wyN+1bJ;C3x)F9G@k*P<+cz7zzv0K7CXkZJGcq&jEVtBQICL>7bC6Po^+H)K$_Ua zk1e;IXJ3$0L9P|x={w>m0v&+_BuG2B&SyZ13WrFxn}u9qBX&+05d7+&L`4E$k=K}p zyk@!fiqC+|;Qx3UQe*yqg8%cBZgLq%BiU)+LxPUhh4n$O`?GO7V#E3 z`2~n*@L3Lx@XAW4ASK&KL+m(6ss88LjE0C`Xt@9)^9 zm9jn!;h~BT5aQ2@6OgNgO;L#V?aK2vVY6JHC?I`30!$?4L1 zxLV@_gnDFZe}-E?P)o{l8=a#rjoG-VH}h=!MBTg?F`fb*1@C|e@5dHgjb}%DDhl#( z90N>S7(Ec{6@!W65DReU%Q&dOA;gg*Jzl;7g0P1+Qc(+8m^@RY=}cCR*O2Z&$^k*F zI`0+^lc!`_#bV<&u}k6B1OT~*{N~emPl^MOYk4<}GwBU$d14zc6AQ{WCJIOv29K9O zLHGj5Ft%z0gmK%H-fAdzQILvxPZt-9fRjX=3t0%Qsnn18Ylw9IYw!Mb1;|`GK+uao zU!asr9_vyzQ%O&Ntu1|z7a-_E+`vIPkYSX66MldrOg=UPdqx6)l#LKD_OFR&s|j}R z2GS9rL#vO*QTL>gJjM+OB0?_#1JpYpG=K|e&XF1;i zXR(jb17-?9f*8q(0MbDg01dP?Do2mSJ||bYgK1V{(p#0nw>^kM1NlJg^|I6RJbRtX zgFsKQTe&4^D#_GFo~HYO?e#jyK47Y8z4s9JXt*(T3e%Dg92PH6rS~l_>Yya{VYc0N zcRHVU99HyS!id(ba9>XP-g2n#EXtxJooY+U_DPXOZ-7uCK^+wkSppLOq?a1aYVD^~ z_Dhxof5P>PiKAHpfTT=DMvjXqI!_kRd=1Gvb@VWoz8Xl{0?z=YI`qO{Lw9-tVq^e8 z*JAX@(3a^#AYvc(>P8?MeHC25=?jRF0R-KTYitb$&aD`;UPJA)2gJw#B8=>(F|sO0 z{0Tt}P6J3<%K-8LE=wA#pTT|$w)&aLE@N{e-Nzh_{Hm0ZN*NhBKEUzJP$XG_ZSmGQ|p0%(K_^&TRhvzNH4oA`~+{q zLn9(&Jc-?Spm4@mhP&P9ki&j-L4*l@vEKndxrqopY)b%mNA)#2n+33Ei30EZ$w1+)gFCi(ml-abA*iT zUUiZR^?tCh6l5zlVvmn-XGb1M+aH^&3ImdU?_?{SQ5++l@K`$Qp;C}^=>1CZ7&eQ3Tu=%4hn zsYgmdp7wA$YsFuHjEcY&258XWBz@&N1sUZ?s=1t^WMO>Cc{Hi)AVyA;l`+lXhzKv+ zD5IBZ+pO#aq#hGxWSmnHBke&JYgVC`oYkI+XazCA5}6bv>q{@H(o2mdON#w5t^{Pa zTsx4fvCBStjY#~k3SVPo;Z!J$L|AHl(mK~~c5BvRK}ptUj*w{+a}DXd-Y!wurj8^T%iyGroLs105r(?3!@_C~wzxC#$X9mQZ%>;{ zMP0evvF@=zsT@(Ag4CA>#v&cdtYmy%tIPk(7&oluer=~9E6WRHc&8u-%j#7a z;e}2>c9h$@u!k{b+u07JKFU$#ys6peW*K6!`DKdHC0)`b?c`5F1d8URvP=m80000< KMNUMnLSTY2=M!51 diff --git a/assets/images/support_1.png b/assets/images/support_1.png index e33bc088b2d64a4e052323aed9b5d07e124f6937..9b92a10f755871f5325f53d01c6dd891703e972c 100644 GIT binary patch literal 5200 zcmV-W6tC-vP))P(NyWKwaw?EeYwxtC^GBbAosZ?#XYt~w|q9`OYb7v+z6+{ul zXK}5zf>stSh=_|71tgg}_s(R7gh%8}07*!~%kT*AydU5G&zZ}RkPHD6B{TE=f4{>e zbMM@__x|rW-}%mWzVj&(5)u*;5)u-R&BzjBse?aS$HD z|Hozd0^d?3`X&ZxEC0&j3FX zpC7Wbm;8lY3qI}e7PdlbyCpKqrHjF117hHq0x<|OBv)r*hxtq~+;8$RnK)*UbqsB@ z+#uP~aFE>qjSqn6U#X+!e_4_E?4YXV7i^y3t87}e#AX z7AuMliuF5c#I`*rL{)XI*nhZAlvbP+8+RWU`RhIrfBc|aO#R~)amQmt0>2YGhbzeD z972|&#GwW6q?b&Q=zTam`V5epkl2iz0Bhhcr$)tZ-`XkGmez<xej(`|us#?>2}h-q#{YJJ7c!{{J#4 zCKEOD^=;y|sgbzJhR};6#tC{`ITZQoV1MG0GVud#2_VhulWMHXg{ax zRU|HfVE3y2v4?NO+uD&6!t5AXV2uT_tXrOts;>Kq93{^H>z5Au|+OxRraH+#m=fz@iz9^MLS4S z6A;^LL^GL_=4=uNkJbyq;o}V=cXkQA0G$FU&T+-~zkViaKb}&65g6+5Xdl@DV)YpFb2~jmG}ZQ`kv!`V6tM_+Xr5c<}LS z;TO`aFH|JD1|H75_T*+kJ5{g2?JS{W=P@y2TqtgmFNdH>z;)^Dy%Y8xsujO@IKnTY zHK0+6{_^(}iH<<9{|59iThZorFpF<|SRnvzdw(`D%nuU#E~{i&BnQ@43qp9)VJ6<@ zj%%@zJ$?I?ibT6OvnLo-{gL=XB(vxFpO3hYd-OyDcUuN`*4)df$!tw_RGkz6N6;*% zi~Grlc#TpdVo*H=KStN0#nN8Z1pFpE#htBhM)z%bzZR+lK&j%h(rcHlJk zH_`6KLyq%T>=A^m7!{iM^2T-}KfTi~+uT;amkEuBFY#NO#GecIi|U$s@d@N+e1_lb zFuuI0?6^2n-%MR8-WMDB(#I?5j_K3I71T9CL?~|d@?P2?2(uUOj1!0l$Dzsgz(YmI zvXn=W5I$@db7m)qAG{^}H?)z@g=6V=;&Ez=Om0(u4Go=)pvJuAd#KOb={E_BgH`mM zPW}SnrKKO!J`fxg$PHVdq&rlX`4v2f%2>)xkhRC$aEP@9gY)r$g++l%kpMi3RJ`q| zc=i1<*0#H67PFRtFgE8m zh}iZ+mfW2yhvkWxXxxuGSV4JXN;+f2gw0pCC)fr}d-#>jq(yjWtc^@qZGDTlNbtZw z4Kxp-MUHyyJ6uQRp0%;Qv4uySdWv0pPl;iElPp7Ai^pSVkud7Xwefzjs_DbioZ$Rx zku6b;@MOI0`|*SEfdk&^CDo!Y;&t@ValP@j7=*LK^L$ETEZ20YCN;b5^T zo%zOgnAvjinpDc%Ft$v>8qH?fYm^Vo3;v^$ZlKW? zn--eo@R}7vT!wh?*>&fJ085YZLxVHKgYSJAaKuKwO*+Nrpvo<+&78v2Z#r0Qe!qA#NjLTNo#UzyW?EKnjJymjsq zAHXQnBQ|a2*U}`3F8F+G+%!niiQx4+;{}Hqhm<0?p|J&dTHO-N!m}nk`lJ*d)Hmj) zHNhNn*B7U*iPnNIr!&EvZtF1usXTJ@NV*e@Z7_u+SAm-_AfC1aTf?I7rg)G;KY##7 zS`us}6+Whu}ImZ>zY&|A3cXsnIuU%-;R`)a({hw_gF4Fm zGP+K0tA5I>3dZYckoKued`PHy>}$NFq%+j% z<`?X4^IlZXslzZL4gT{C{3HTTtGRJPC_e3UP)-o!7LoSu9;yYuqexJkI`|G}clZtD zAcw^vj0GnX9-X(n7uH8#fI-{6+35rB#3_*K^anqWNTtm2@FQJr}B=DkJ zDPswbc-5*N(0^q?(fhh=GapXkHjiDy%g$32K=GjJib7Zr)_rjrVqPP)wD ziwdXL;LOFoh#i$Dxd05B6KZ!k=W6(vz+Wp8-NQDf@ar(uyRtlC_NYtoF#8Jj9jfKN zE}~cLRB=&MZ>UCf75t|naq+O{1b*o7g%4%ALsTZA*GNY9i#>GYTtlj<90SLY-^Bc< zh542A8BcL(kK?BsMQGz8@zlanF$wdZUjFlLjw#CEt5JM(^A%9lGYP@&W8ZpGh zb+Q`mF8wY=qQ@j3v}uI`DL>**t14(<;!`<9m|@sm5tsP95*<0wz~-5Yt#~5>O~*kb5dKV&=siG#zlMJ10!sNAoNKVDeQkNUs5#kq2}$bB zs5x07roFZWvXw`CFwvZyrn&ExQ9I0St);O%Yk!HcSKP)UTsp7+pnAh0yDRYTio|CF zs$c_cu5$QDn{|5lT;sX&6V~WnbHo+}sxaffn6=#IjSAHlI)T{oBb2b9Nc38bhb#JL zFS(Mo=TCugc#S8KFtFe5i!=@z8*Cfq4{=@Sw~Z>Xt^5Slf95Hkjm@pBku9wq1)uRg z9K-E1+FH!^-^VipZ$#LU&Izm0gh7L2w6e=^skOaZ#{UQR%b7yfAIxFSCS9s`)@ z^DxXE`95auJ&jdZc4NJTx*_gRGZEwTg~XlHi_zw87B9Z@F-;2NDzydct4a9(OGEp_ z9CUXu3lMXX*J2DhgxP?)$Z(s@I9I*HWA25ecm>0K1G~DuVJN*w*GR*iio_)li$$(z zfh(s!yd1-Br`0^p-Ks|0=kA|vensh`2)F85aUIO(T}T6Z3*XrTb9x5OQ2h{hxM^^X z*@E%eR>Zg4GIDiJEP%8P&LJ^cGF+i%n-*!XqnbOBs(L~n4Anxp!zV|-)rCp9A|tQp zsw{8tHgfuKjrmw`>yCe$wCoyHl|)|%{pi8jG~*@^X~6UfXi_bkthRxcfG>b0DO}k$Bl0CLPDr ztMH~Vb3Svxm=FGvOytEZeMjv!Y2|Q`ILxR2169E3wj@9H6KVFmqU-Jc@N@X>BZ!fo zzX%r*%_*^XITzZ;7JN@?vju8bvuLp^Z+MrrKvnle%ec}y9c$J>iHdGo&-Vi$QUiIxm*3sRdEp z5SxhT2%AV+<_?*QFK43dwtJyLsJ}&;N53&(%qw3c(#WKD5PU}3Pef#7FS&}_{&C@N z!eJYUb3Kb1BAZd%yUpQCKx_iRy>NUQ-+>*4iCtP1gG*DyQ^S&NtF z4eGZ#ypenCuHXpO709AB3LRrV%*dkuyL2bo;{R5C=6HDGb5)NS0xA3P;D zny4FuHnYv<3T_xNzHrO5*S6Kpd9SP~8D#qW(z=n83%7&p^&qn?k>f=fDyh487QEIh zkNyHiV-kAAHZHGZ6t7r&M0~ve6lYzmDec>WW$(B!X>dags^f-=ZEoW*Jo+y05%*5_ zI431dwZrw{Pk3QR%}nSRA&au&t{Fw8zkj!!3wv7CX8W@6H9CC;D$$pT`=4G@fr4n2 zxR-=4F}!swho{?8(6KW9PxVDF==mbNSTWQWEVX$;@iM(hAl9{-G+4108d_x$oG=oH z?FvlDN}WiTEoaq0gccj{26sy^CPepK65LL>UZ)oL zO$wyE2Ij#$OM*L_X|6n*^f71L^i~ii3GOIX_%gVQMzNB2DyTd7&&Z%S1t&`qe7^8A zG3ste=9NmFTA@RYktGT4(oBbG5aOpvye6T3H-_OP!3iYxY|PP0vVzv`ixt2&x)suN zo2FMLJ;7SA3ionR_e{?QNd{AsxF)4OW!QvuXHa8zX5Gi(h+P~opMUo#l2kAJ=3#6 z^8ELebOKEDdQ2`$R)TkPGM_~15{yhvIWbKVED4qbOM)fAl3+=&Bv=wG36=y)f+fL{ zU1iK7v)wbJoiw!QHQ$WbgtPT-2oJ z(mgpwkHrC@ml7}MIxegxkr=9GjzT(V9n3Ten}l0o`g$Y@N-D6>z8;?+f*i6IEJ9g_ zl1VN_qC1Fwlq(E!^XHwKJ1ze+1?^IA$?svBJ>)Ob0e{=zfRN|3+0PR)zfJN?myv!X)iGunr? z7*beE-vPnLtaGfUD(|!~2bJenBrZS5F~YhG*V5WXSU)Kvwz!9Rufwa4ftF6RdxO*Q z+2gczc#VglwJzT03Es!Y`phh+dsTlrhg>E{LPA19LPFxx;{O2`2V4OErV`@-0000< KMNUMnLSTZgE+aGm literal 4733 zcma)AX*`tQ-@b>z@Iyk0&=`e?8ChcNTb67gB4WsBGBcJL%OHd#WDD8%Eh1TxJtUHS zA6pc%?^}rf^m~5K|MP#I&-3DWan8BF=X^ic_d3^g&i&#%H`Lc;V&Gx`0DwtbOWlYv z%KvE)T1t;RCc9DwdS@+j0st_7`qMyhBFq;7fQ40CUB%?#lhu19Pm??Gr>Wl7cXG>K z2DP@cLQrQ_GQ_cQHdId#uz?SKIuc{Z_kku<5Xlf2R61EmSV-Ypr-P$MZEu7ce^`*o z6>z{CVd3{eXU|gIg!1v-%3Z4W=sDgj9Ob%P^t)^4xZ!QX{?c4Mp=UkKyXR<DxHH)!KX^BKxQ()cm&&h*AZ=DgF-Q z8}mrJ(c*=3(zx8SrmxK_?m}v9O$?2~Zwn=y>ieGfuD0}wNn0XYDj?PXTek_VfX^>A z9#i%^gVR%@8sz5@O4!FPv*|aw42-hT>v1?dcg9pI-YGhBDl15{=g1O!uivaX&nqn^ z3HL6Q=wq@r@bz3pZdGM|bk$TwrCvzJ!3DXaB|R`>t9vq{g@?<`gl=}RTq^o0o&L9m z(uK~|gVuQRzFSzr@4c9&akaex*E$R0(bW8WK5MdWLn|bbv{c&e{r(jf@=D_~frp^0 zJmdO@T^iZr!NhTT9Q;zt%-PQb()3azFt2FP(?WS!;T*Nm!8P>{`Y_w-qPQ zyb3cmGa7g4cAD*35_xt;Yc`cmzjtXr5;YKQ0kzDq4hk}6rKc)fYHBNDtUGa&LQT5x zrgDq9t}m#{?oU2oUgm%c)*TM*@CG~Spi4ZQls7fOUN}mfXTA+v^1tm(iP_p=@leYh zSLCHhjE#E4KIg#fw>6pdw)|~B0lGbWm~7GhK*CSv8Ej=lpEl}3qK#&X8cdh1RQm}+ z&+v^}Z#!5na&akxD?P#meiBD8TZ>bO8QUoNMIGJ`a0^rrwRaM1a{=YCmZVw7a(aiZ zBNM>?e7*Ob1b*zv-hkJ{THBF}>YJBkKmD|pneW(QU1hkF{0$tyy1`!6@JwZU57HbS1FEQ`T)GJ$)O(io8D-Ti8D%UF>KAw1!L25 zZ~bnz&n8Oo^LUbPQca^}orfB)G*VCYD=|&i;C$VedOTw{u>zL9Xqq zpI<0*eR6R3^V{uW^1)q49fo%;BFluSp?5F6Ebc{uk;p4v8nN+P!CVW9SsN4&a}jLGdtvFLEJN=_Z*iDXiDYmh81srxL$d?IfraMR&wXjE^X_3 zH-?nrtpUGv`KN-XyEi8W6_~*`{i|0K5M?3_3g$1iLM=>&z%~itLiy?mEwssMY9>KE zxbdJ&y;$lb_g@*_KB_l7?;lVt*9k^RysL6?=(#XHGf;^E+(k}F^cfm)qf*iJ(;pEuSk;%nlGVukKSKid&nlBxkq|1?gn z!3G<2Ba0D3feBDhbWoyV$)z{C7GB=mF2j-HN$cNR$zmZt=u@pn6NRe(F2o4rboD{y zUI?T#?x1Oank?$hrE4E=tABt}g@p8Ny+cziY%$xvEXI0BjyeY-tF^Tq2U8+jrz-g! zrT8WQVO=y65C{QTMb8|_~I=9Dt zVL$n?G|=v~Wf*qNxj;pcvIEcS7#GSbIl=^R6W3p-9N5{x?gV@y5CyC?=@Mdl_ zUaJCL`wfw}ttfx{e&3R8=D*qi$xqHQ9V>XP3T_aS0mqHy*!vYUVFDW=ul(}qf zrN7rqvZ`;=)Hm6jv5Q+W)P9OIF=}$#4KCcjxO7xrc|7+{$M-Qo+k8Temv?5bjtY2u ziaRopbGR75d*){`@Athzc;i*2uFl=_F6WXWo7%6&ZBOt%t$i7uli~KT zZzo-($;OP`PpY)~T%`v+*G#Ia72ZC@Mkbn1ya}Jkx&EaxN~%jF;9_c1P7Q;@oVsUAPg+g?jEMsR!JrlJ*#Kkm=xwKT;1 zh?}cH4NGj3&?Tt(vFXh^e8Hhze&N>}p0|9m(d-8C zcyC2|`rIH(YNwqWo`2<7=m3L=p}lq+;~mzhqVT$3(h-Y8F?3NIlha4FGCa>WQG?Z~ z1Dt3!n2nH_!g7&<+xw|e=S~Nc)D3UC+@SjBT|cJ8N>_G)%#qIyPQI4_hn`bQtkp!- z7jm_E(12@ISQbaHFtFn4-FEiCoOSCwD{BWt*R^{4l|fU8(O_|0F&9@qJl# z%}0mX_bUULK_HrZ8S3fsFA6ZF;~y98^e+hkw6#^w#%<(vKFYIR=j&P}w}t<@$FV<2 z3$U3E^&N7~yx^VE6Yv@T;r(fkBUlJ%=?mOq@ML6)-~}u(ZefZUr%MH z`<4Nt0>s8{WBbMdzkfJp>O^NVm5~q=^&XE+vN6qLu@IK{Sv`^VGi)S0snYkROzp(4 zEKfbtWsc3YyQV4^e|~;_61ma~<9V{5i@sE(I|-JMgf-a|eL!>Rux0Vn%W5VbI$bBo zYl8a(!6(59BFeLL<$)@j3=7R40n-WNm(oog;N)Nx8x?=aSPTN1gHB&`^^OT039qU3 z)Qjg*I29Rt|G^=UpYlt+_|wAiyOy_2>fnl9Tqa$tZF0JblscB<6ZW+Xm6}Q-0}EI5 z0w2^{nI+@RcX{(Wp336v$u_3`C@ui39HY#RNX!nAlKY4tT0PpUwZ!)sWbD4@5_ z_HmM&i7wyQUMIZHqa1^X3iA2`;}N}F?5Zudq;#di{>lXvzNcDX#65cvOUvtp(~saL zne(?A!ltFZFjARcJg=Ssj~LmYJL*`ltSpIsebH!)m!kSn)wQKlozI<9 zd#kY5gmGuF;)0yJ^eAC!@HNi0y*lfI<6br;YjZun#B*+wnUg}z(VMA(_yTiPf=pcI zvTiB)ic4ir#*HxjnXlVOTd9B=ugSm?nfuG@=SR6#9T=ZJc{j8`Qj;M+aIQfRGs)Rx zNGGsyP~HEV>Z4osgiSMN`CecA)5%!J!hBJX$5msxYq_8K41P4z97fZUDw7-~dY^OR zQeG``(L&=e{{BY;u3H=&^$8>~g(`pdx|CnpxACQ}e0-|=5wOhGK-BlpkgKhTxJsU} ztV?d>{!QEDH|+G}G|noJe=qmQ+<;9pR)bw#GU(dc{FSek4R>w%@efssRZ6i>SFBvS zfp#b#$WRy%sz0ewD{4|Zv^_t|Yf6e=h2&Y+j4qnK$7z%N_?|y4%*=cXMvSAZRzLu5 zOrUClyP;Qcv_w}8Q~**#fQHvtRsilM$d6F$IFV)Z3Z}Vd8VHaBfW!{Sryx(PCvvgI zYVsf{%6Z~S&;m3k{#woo*IY&JidC(&m^dFcGm5L%Uzhw45zE3ABd4L{GVM|R-Hso7 zRkfo;wS7(X(Q3%2M#({wZ2r0*b2}YHR#lD$W|d3#{{o?yZ`1e`I^B)GKWrkQ7ZYzz zP~saHR8e0bWDnj?lQuLHwbcE<6tzr{(UIcwip z+)Y54-AEi;N!|gIvCT&1ARve(3B^hk8b^EH?2x#4^CkOb)w0+`RD$VSD;!%W1d?Dg zxnw2>nA|z;o#pT?r$4hYpa)W3&q*-Qj~`m#BCjSfwjM4O>@H)4mpz!1SS(W1*hWG* zpP+<7K3&*y(+fX@05u+q8~axWs6HyR@UsKD9Xez0Nc;PN2C1*LL3}Jmzqq;Qblw38 zT2a56(#ps~ia*ZNs#^xV=l9n79#)gN0xo6A0i3l&=^T~z^jEXbL~7$s$t^`X3sXA> zjXfnOV{xL+Nqj}hYD!vd>NDLhe#?gjciSzTKaj^NHL7^NZKkmfDFza)o<&I$R}$L& zEE)xC>X6Q1nKCrf7ca0o2sh9p&PlY3Ee?lI1jGSmR5DlSHCeNG(to?EK61k>p2_J1 zu+nlcp6R19q?BBlsLlZgKO0;oR<(nW~%7ntlEI!EJn+eCLUG3mP}2YVZ8^h^3x znrZMyEZ44oBh@qk=ztLKYPF33z5NeP`8JgvYmSTPB<~t z11DR1F;AQ`5ow4mhajIwjICqixQ9WZzW!S&ikK`7>df}oj{9zxGq&Hf=q%gfc? z*h}BU&dbqG_JN?1B7?jqk^+FUC!(O9I4quk^i&Z18y89W{xc01g#MjEbW{-3#-SYS zkw^_s4BEuW2@4fRh>0TvDIl&7&`2ZooBu#iA_c*RM4~ei4)^fz5c7}_b8>Zni_6N& z!V%Zu*RP9GGDHa^JQ3w7iYM?>5E!6;W2oB`>|8O*PjM5ENv9{sZm& zAJBNhKMGK^fKwLW;$jkT3KkS9Pl=E!Zp4R9u8QjRuJ(>%y7m~N-9!05Y5%=IDGC2m z<3DQt|9Ma}`}Y?A2MVJi`)rS=nCnV$(HfZ!|8oSiHT2a>RBZzO1vzB8{r~^~ diff --git a/assets/images/support_2.png b/assets/images/support_2.png index 89045ba20678595c929524d8689f02d203352922..8bac085d1cb1f66ec3ddc9daf322a6a27ba0d1e2 100644 GIT binary patch literal 6291 zcmV;E7;NW>P)IItFLrOI3hQJ$dQE01PE71$o=jAy?nGy+nof{k#wit@Ap#as_N>h?_ckI z?|bik?^ATrNhh6j(n%+sLaFbhRhJvB{+o<8|KCkkx6NSj%rICz&l+vsDF(auK9k*- zVl?}Iq39Gp)N^w7l?J=#E`ax*$>!N@w0de(M?j=xtUiZT=W2m-gvsVT4#0i*e?qdu z^-D#k_DS7r%YS6D`ZA0*UxDh#5-Acu01S3VWdJPX3qYTb;-y%C?USm_JHlkqlGKUb zs}!9sEU6al4wKEd1t0-bKLK@KY_K%4k-&ClGuYVTvqu2%5~J0diDMI0yY~-@PTw7+ zrY`!P(dK@}WY4OH)HVxbvRM<|$GJq;v6-S9xJSCi!1i`{MIWb^&*_PCy2*2Bn`{Bd zQo;dt6(rsTpa-cVW?!o4)OMt*-ir-Z_uH&mZPC>*0AsLv0?fM~42VguZxU~Qyh|+0 zIV9HaC==WEo)E<)72?3*N|9f9Qf%CHT&!C6m3Zf~J>sEvwupg`S z6nQ3_doaCZicYa7lU17zaH9g7G~UG$`1gmh#S0(o6l?R##Hor}5s4Zags8`#NK{o* zFShPEA!dEDTcl0TC8=jE0Ik758|{A0U~_j>bc!9R4sS9fs3wBs1_2oL^jh)h>Vx8B zMNJT_^2%DVq2QR9pIIcHoxek5yt+}0er6p3$2=0>alE`iJUM5(n7w?jShM}8I9~qs z_p1SRV9OCP;kAvd8SoNuUyXaIKtKI}F3qu_P5|-~G;Fw`#A@`OM^-biwRI$sESU_{ z6Yp;m{T~VhRW_Jj1iTQ$T9CVwI++UQ6%Bs2`*txjoxaZeH zOJoX+twlx~xe1gzT7ewO-JmdazNss&L^ZN2zB=b*Un*>wbz z>_fHlwy=|>(bXM1`0*UEP%9QEPS*r~WA3UV(F;aeQo4&&HXCB#Jfq$F5{77hqx)5K zIuC#?D?2wfz9-n%_M7Bq!$=ZKEP!R{!f`q;2F&VTYKd&|8MK7#w%ITnG%bf;1b_>x zJF9W1g{{fzIZBQc?0>Gu_*K^^Iz=PV3B!|HRhxgI1ec{M_0iR0XYr|EAN$;b9VDrd zfQ=>44CdrFH;F?>&j_N!$E!s8%shSp!W}6o6LRstb|@)h5r+T5e!=T3Fkh`M2(#8ej{Z=p!CeeZ4e`?(=;?1FN#1lhH|L=gEm9cJJS zmY@`SQm1XdMA0e4&un$ORcAn88#Jn{|MLZpagUy;;$cho2$FjNC7HF!j^dL7#2z$h zbn!SD9t=#OJe-hV1PS9r(-Dc;ywm*Pf(cj%E2Uo(4`X8dEjAX=KWpFu^#vFGaL#RYIS zcp8CjV?e{BQn$m{gC+@eqVWC~KinyZK6LL3N-;cL^}63$62gF=a^EGTSGBpt?t|q# z-bB9}bUDsmwpS2sMO5hiH#UYueqz%v8!U3%D*=`6OZ?U*@lnM6gNh`w24Ybr)kr~MKm-bkC0S*2n=ixL^Bue3<8LTW7lLTp;6(o z%;zW)+=n4PXEuVk;U)8DNF!eh=i+zxd}awI_o+i5p;O@0c<=MQH0I^s45x+eV*XCJ zy+HKpk}v5P0L~W34V$ZUbW+XUE6@-{jVU*7mLBhhQkE73=YxTT1+F3yg*b|oyX~lW z`_tVlZ9|{PWhnz-tj=%A@Un{&``BTEs1i9TxEpv2p+QI|$BPdhspODhGsKZ5^UOQ~ zHZ)|(!?`^N%cY9O^H|)4lsBd*9XcCw^W~uc8z5;9zO|X82#v>e@v$u+?s!uS`~^H{hY60||R7>!6)sAmnaWnz|SR?~LRo?aR5pLaN5DcWMN z`DU2x-ohScj~MgZy01fk`A50YU<`5ay*(2QF_u<;PpF$0Q0!By;Bx^(a<)ent2E%30=-McG}1|F}9vf zw^*|iJL|*XTzMTpEyF`648W%NL<-S$o1MO>or@zkt2cMJFwbA04m*~=)DuW6M z18{Xs9lJ1Z9^Ev+oEd8ORa3HF;!3bZn}Oj&LIGS=U57laW&vjASrb-masmhSJ+nGA z!0dC^7ACHWwz=Qv$N+P?t<~d7n1|dg!Tr#SE+aLSG9ia%>oJ(=GecdK2??;2UhQ*A za!?YT`q`HMK;`N;-ih`Ra)LF9w$558Rr)W;pb3nKt5U6mYJLW09@J6J}C zc8~0rI7{O3+1ofhr<g!*01x#~04^pH0Ff@G_oE$VzUW_zLi zr4>BZh4+e$DlUp@_m!#U%>Pn!YB?FlyZ&Ty_)B|Qd{ibOW*}qu#TGhpuE8Z#j)8s1 z^D+Nv-l`(n$5UKF>3Dgy@NFy=PtVI2lQIA4jgNM*rw!IJ0er?}cHgP!^lhwaw-F6k zMj+pgr1I=7t@Cc7PbUwPtZykfWR{_vnZPp~m*pPfLZukrGN>~#EoB`hscpur#(QV1 z5k1UYC#%|M*3uN6S`G3cgDp!S}q@F!iW|mxd-dsZzomx+ZbnTC5(9I_2YL+_8Cg7Qa=R*LA3z{J)0IB_2g1d%G zFt${RxIW}1I&z|l)iW1cu?GaYjzdTwyiL(5b_5B&8sp5ll=9O(-NUN(-Ou-kvXj;4 zk)&RWnv;d%k$1O%w{pY>6V2IZn)UH+>W8_nl@iOd_7{2fiT)hn(s=y`)$aEh&92`o zI0&kDPcj;DP}bu zF6x}R=n~q7Pk}MnJx?KF;DFH)sO~n--O$_VpJxW%3b!L&6I!DNL4#wovP&ObYGYf*|Bu5~U2d>s-P~(JjuQ@# zE(~-S^|l0V#LT^CunNmAthZ3v!{VzYU{V}DF?d=o`rOUpmBnAuq%f{ho4LM(nEzkm z+b`b4a0jyhF(-K~V$eR!2Gm57#aoMOoiSOx`=BY_Lb$JMr2FfQ(u;JD6z)`XIuBs6 z$Q3PcmE!b&j<8!f$5rkA67+qR&Z*ujln6z*Rr9iIp+3`)2J`{Gvlr_0G>oA$JuLp3 z?&H06h|ku;zul0Ou5n@kxS{)aVozO?*;i|@1*(jw=1!ohR^Mm6Y`*k9Q?h>Jwh<&YJf&Xrosxcd$Da{#2y6n)to z1|8>|QDIGE=6ugCV?X;LsmPWreMkK^No5~^*xRA~GgZK8wj@7}6KKYaoSTeJ|MU3m zBk+-jFTzDYb4n~;&UwahnK$UtEQ$KnWLoUX?k|uFR5f3;j4Q3vx!y`h(UN3qAQjn) z*OG)$!?sd_IuWRJElSd-!sOuhA>qgA{s3?j_vSgfaK|2lRlA#QhK@|XMNk~7&W>ek zDgacIphgEez$%iKxkKjS8$Ho?8|{!F)ZZe>qu=N<_O0&|Xspsh8T&~335cZBMVE2k zKf(VC7;OD;t>;ifWHX9;H<%njU|Ym^7WPl$yOZemJ1n>W;V}Y7HeRb(R*2^MT0eSJXBZ%gBw{I>#4l3$r?kpMdBD<8}}To8^JNWTrL}T z6NyeKwoDT&%~Kd^L^e`jMWagVmyos`Ow!7Z2c~<4QB^=dM_twyktfxx-O5H>qEcV1 zB?Mr%MjHm9&ulQ5-5dH&%-ZtEyW1+>{CIawJn*#H`IY^qWNio7>j7p%G{cKBR1$ab zELg3{R_!H3W1?Eb1}?AU$z8Vgi1_lrDbBiBli$7>%ieKeQul^3RLAw@Vrluqu;?Q` zBc7d}@y&!()h6?bzwpA2Hg8nJ2w9YcY14D^Ut79|3wz3Hv+bI3kG#~*%Zqo5QO~R? zL_xG7JWJG<7}h$L!_y22XjpmvPjzHBY5BsvSlG+q&No2`!cY zNA<*Ey9^Vu635eJ7{9VBT#HqBgPR2y6QuKHd53snzJI*L`;1Kk3}z{=g}sq5Qrro^ z&XRp^LIZ4U9$;qDWU#vDCPYwzDy(9xcn}Y*0~~TLtyGF&$Mp#Su=gI-$-^QY;1K3| zoi^9-1W0*xy$AKI0~|q3hgY5zg9iOt3>;_$zb*z0$i%j&-TR#J&|3kR4se9v2rTY` z1%szu1t8PG*#K39bNikOYH|Mx85E~rWa$7$7!%L9Z&r2PfI+1pfSv--b0EnUaBneu z_|Mz9-#N>qHiD%C9Ku7=mx`@vwxE;()YW3};K5?-*dNB(nuOX=gyD36L)biSzBn;x zI0Jih&`9y|h{xYn;+1IazQzLBo@SXO-C)y-;vQg|yBNO=K%X7@vSY75xc&ye9ULFzZal^1saORckq(sk)s zLZ5BwxH&^>1`Iw<;OjiDAPo7b8Yossr4{08=JsnDb(k1h-HO8S<0RWBA+!`-j7VzD1bLS2P3QH8}6z z$r%#qIIiOWd*%N((zy{CzoP>jBY+{TmjK*iut4Ju2;2V&ICQ8~1pZGY3gMiM^4z>A z051YREzdmx7Lgd|+@S*;BY@GEHrc3~gbjjY%KJXQj;L}`{7qz!!j(4p6b?OzElU@43a@EhQFo4EEIzIIyL zO*+6a0vHXtKuXQ%7J&N)oLh%&R@nZVWRUanB5MVT@ zU-rMo769y*>bz%|YF!LIEHW}Kiv}p>j;1znXp=|)3A8nXK zYFRe^T<~k5Oi`hh0*q_hNnJ-d_gVQ84c7r~8-T|}b9B)DXpFw~s+Izb|3AdOnUP$R zIJO^LNqxNza9aQjj}#A2&_}jaoE8$Cffph<-U^N8sm}NVSmiutZFg9(VU1MN{S}>p z>n3@4feR{X5({p=fJ&>`1g4h~FXuWctQOiwN;SRVLPv4P;%*lJqejOFq}ob6U&DA*;h8l$9u%WIm@+ZMGj!AYX!<)<$Z&KR81=JYEIQ124np z9Evc3OUTj&zpS(m5}h!adNjaYWrRdv2Ef`@F|r&Z6?|#jr|oU^^>L4=zmcih-Q!7m zBLFZgn6UxBaT@^Kt;q}y$vAIX`zv!eZP6v2Q(MQVT01II@;y(8vuWd6dwse`MN$*N z_VuAjcNYX?V0^sk4 zC=|$jp#-@tBpKK3e)cya@SSm8_Wx{AQK}M4C5EXId(TnXS0+0&vn+fP%3Qt>$Lv(J zl+LB^(04nhZoh=f6SoZjgF--F9?L#ij2`_{%B-vv)nQNbO3_n&AT z!e0Y)`mGpCa%o7aY+7rEP%_7BB>0E~vy}SVaCOZ&VDUs{ zsAvG31@_ZWsQIF$d~Sfg%h%w~HcthPZ8X}wPg5=GnD@!j@CuHTG2Cgf$4iDA0oqU+ z4ut@@lp;qF`(z{m#@oE7t{8(JNtw8=62R01$Gpgg*OtY)seWYdieM zRAj-@nj^+{RsRYl)ZudV`M5%WeBik;YL(A$ei{Q zZ`3Cv@UN)#JPzvhU8vk_T89DKGG55TnROac3Gi1}su|g5Qk>aUXITZ)K01cJ7@So> z-vPkK~_e6cZ+X^P6wY%$h&ukNfUB_nx))e*3(2*I75l=%Ef10}lfL0ARYWt7%N0 zmH!Sp8uC-=jqDSgl`TzjTU4I8KRh0QM0C17{zNWf~Tli3B;8W8nTyGmZ&j+E+ zepi~+sDllMrl#&@{Zy3ymaCqM`WY=wtN4~la;-A17H3kdR5PuLnZ)n0S6AM+em(r| z+Zz!M>Y9uZeW0Fa$tz1fM?ql=RGl~ORTD#0@wq;`D#6FOEk{f6st}vuM=kubVyW-$ zeE!g|N!r{Q%&1=Qg+|L$}+X{>2S+)4dBwk?v$N%;y_!QRh2pO4AW12h`g?1<&~oO=F3j8&>FI{?Oe zxKMhRv-lf)jSH5fM5;1Joz8XI0!P+cnua_Vf^=C@Oy?=znI4s^==?}*;k285i3}JC z*FzACiKheBd2mAiU@9q&aKM|VP~b_r#F3^La%I+i*qqqr%h=o1qa|sA7wVx|%(m6} zM6hA!f9h#t-GXs}`la!v$z9g7_|*@becu+_=~$llRW{yd$W)wN7e1pofpEvx`Fm_~ zR~&D>eOVQvh#z2U2$@$%AfB9u7}a8sp60s;a<>&iM8DqQUv%O!K)Bpd%}CE=O+Hez z=W~3xJlP%{I{m`e@P|5 z$v}N)cq`*X!)h}!a&9jvV#wysNr+{0L5f|OdXQYw)oa7ot~$`&egW#$jYPj3d}Y&8 zY07IZ47$|j7Uh>|C3c69(llbw(6JkA@G3N@&+auVH^E+{h+;uL1lp2ybjc+{UVh|e z_G`~ON&VZYpbLM*{i{5PlPcmr(AHJS!uA$|gpqln7VGX&<*|1I5R7uSc{k>*oNIZk ztEq^9nX+lnJF4%mKn1IkX7FEIeCsoXbMUAX@HH2mfz0dF+&~k)CG+KGR~nl?`jY4S zF9RROOMlc833*}1#(l*bKecW+o40ZB{LUe*xvv`tSy?$liI_yM{WCU z7il#Glp4nV@qk~uGoV5h?f?83&B`5}%RzB3Ta+pxACsaZHqRW~{IMl7iRH{Dg}3NI zxyAT876Og`?R0K3w9L1eqv&CozF@gI2cK&UlG{e`*6{XnqAg|?>KhjJ1M-UH6>mFqj!1CoUkIWYNZp51K$)btFXag`vB~ECEPE=Yw zs!eiMphGC?&bVZ^+~{G?dXcZ&uz4`^M<0L1sKN5m(%LQ>CVDR*g?iL+JviYt)NTAm znnQbc-;B$Jrr!+S(|jp93=e906UvJ1qk8m*=BKH@?hnqNJU`SrGuR^S_3D)Su9h}a zQj7Ov70vksmu>xvlWC?!w4NT0K#?h#j}UDc~tQ33z&1 z_ZmHuEn2QZ9ODpmpW9GAd-yA@@7S_j*4JYAH||h3%S5cC9RFr3h4S9%@do5jU~(cgMF`*x@awJobPS$R*OD;4%^o+=^01nsCBXqqW?LBOqB%P>F~@ zu4x4>0tupD(5`<%ceg5PCi^2S&#%ug-Y}lp*d(>k<0d#?7+(3&%)l;Dnu@PO`fVbc z)*~&pBAL5Vswdfb`>^n-x+}=rr_WcjgA}V6{n_5%xUN(MQ@(p|l?%^OkDr`cUsOA& zpoxv=0Msx5sCxmWnr%~AvUB6HA_1+wtj(Dsp}=Z5bnHZCv*_`RwxcG0x3(EnuEQ(7 z5$hO+t27@kG)5*o5(M>v;zo@;iu6{#yVXDksp)C}L&f9G00r~D&)T!imeIiY#!b(&jG zeMGFaHg^%wS^tJ=iNk80Szei_Et>z_JJ9vTx;7WLhl(+6J@y<~Ta61mY0IrMYw-Uy z-7%4FPca&@Kd{SHYt~XsoB-Cl2kgMrf}EaXo^*RvmprSjJ-Xeq`T?WUVqA`CbU7=C ztu0L3sN`Uvp+uc*Mx9@=K=XfA765QOZeC7NkE=U}_H&<^;7)}`UB~B+j<-kJluf+m z4bwN})IQtNjXqwW#fvBV_iT|yqnPh5_djTq5$d)6uwTw@S{YmWwvy3$#^i9&Hp+K( zIfTMvx%$eU%n+h zIG7Np^*!t%USk;wR9hhF>{jrf41VJRs2xvnpOdC5%WGNIsDbw&j1!ltkK>~9tS-&g zmb+JBt|%_~&3G=9tG&S)haG)>LOg87ot~LbZ^tJ2Tb5fL{pxu|?7U*>d6{`AFt`ia z%}wE~FGku)i&!p_SYG}6vq=-d+tM6ig`!xJu4}B67 zorZdBs62*9@im+TNhdiDyvL$eBo6tu`udx#xsje;r|JBW$uqTi z$hAQQlhAt{{$r~xSwz_B7v{4nUOSNf{rH7XkgHNPn~9+1p0;2>G@Ro>%0BmT!V)!mA0>ENpGr@ zApP(`i-=S0ufUH-0}=fO?sH$7J19mj3xlerA%}vw=i972}g<}%cC;aa(m_=Gb&iG8#%C_^LnA8aSy zR`}wb!S&)P#)F&TO$MRM{U;v5Y96R1mW0(|lWoz`$|JdImO?D_3Aa9Ten2Y=h^?+H zdQ-gTKwDjIj!Ek+GzXJp%9vXTWTkX^s>QdQ=}fzNX7@$f(+A$--xzN>?lrqm^GX8B zs6Qs7GCcC6talPu?9g)QM@3T8hZ{~q4YXZzGcZ?kpwk~@5UoAM~7omvK42XI1R zy=-(UcLQ5}ArjFOAoI0I-)kwUQPPQ%)UhHwXKW^XYxSkk81UQDX#<~wMz2nb2H9(N z{yXK9m}n+YLO>$N#%p2-33>X=;go7b{72VE7A19G#mP1cF@OTWnCp^-Y|>5k*keo|2;om{vg z2(8$qjh{GOa>&XhaJ|ncn~(R*y%-s->*v>9CyobImEkAlJ|K->`CYO`vL!nfuEs(j z^>mY-!QbG-D`r=XOVUN|8Kvq;uf=q7q-I^2B*BtStcxpjKHE9OJ$@`tGujb3a=ghu z@yXLBa-%we*cuIsc7lsl5AQ*KdpX%FoAWVUx_Pfcq^T?Q!5_i(#c{b%JjT?YUAJYz zqUn;OT%Wcqk3~wlz3M9KymY(H5h8A9@}~JE1@z%38a5$4i#oeQCPp2{m*DCps*Rs~ zllFX1A7mZdE9G_h6&3K4)Z=Dy{{(wbP7M~x|8A2)SO+}>0Ey8lH8DAA-k+?BGwD}F>N|_o=E@h{TwRK#{Zj~R~r)7vN;{BA9^`Fc=v=eXNK@-S55OVe&b zL+E-4%ac;Xy@?W?QyvD~sL_Tx)ALtA)LNfgjq=4)!B#arje;iGi>Gg1+>Izlj0_4JH6o7?wi3!Q(XfiaZcfs;a^&mTutD-ZFyF|>xwGcR0~Nu6 za$3>`B`$+4Ei0@2IQM`6PR{idz^?Mxk()LP%WVsv5K`^yXB*e2pUY=!cEJmC>xTdQ zJj9Ki*F^OXOH3HKU9Y~<+rU)MdqbnhvmFfist9-^ts!^u$pxFlzB7P{N%l#w72Y~R zuZ%8XV1S1e+Y0iUrra1Uz&g98Z?=b5hWEGgqgj&~O^js>x7x!MfC+4t7Q*vb^tUrD zKS|1eCT` zy?LXaPm7V%mMOOl^1htRlHVD12l+y>ci8OP(qNki4QjebUPdeud@7(l9-C`U%J%5VJ6<=!|OG-zslq=FOmz1;zsDXgG&iEuL^Q zoZ{jx`ATUS1|k={8iNaG;q2AR^l9u;THi8u;p_R+ACLtUS2eNJSjyAwG65?Z?aWHoObH%Q3wvAQD-vi&1%U2_aB>_Ob!_!7$zF^(jY^CL z3BHv(S~(k|Dw)%&({}&TTc&Mee0d1E{Ia7ogbsAm($YRhsGrS&11)AzttTTRIc;=< zqyEB%fzY@6ett=-964;rysRt>G?p(D8@9Um6K;yZ`gaZdf}aqhGK2aUZ*Wlb=mgB9;or04bx;Bwvw_|uSVun#ZbFV=>c5TZq7&R?bwehi@ z8bi;`0tgQtOs8{&-Q=r+)o@rG8xZUj_J2+?vU7xj=yiDVMPBbV?L3zee~@>aBREm~ z6sA#-OYs1?NX^4A1evmY+!0~;{atz@aLlNbNs4Kvg?q&MfhV`466rt9&U~Y{V$H;H=GO0TdBj!`SbWy1e$UL2i1Tq^d0f=%sqR-sojN1g?A3?nf+~|rC@LA8EWEg=-8d!*JS<=I&oQ65PM>kMrX;qZ8JpBV7UghL4 zu01&?N%RzbJ&S2C7}(zr`GsivsUZu#t3`dwHF2xjQG0zv?Od`WTN3uTxDZmDjj|fO zsu8cc*M8~b;yKH{v%gvciv9)wK*0txu|QcE=qo`H9ukhu2qzbb01qVj9+7;ls1|^9 zgu-1=U?&$>n5T;HZd02u80M@ZY%XUYWq{Ohaf9gwdAmS@9-2Ud;7~dByZgkCufNJ1bU(z z1H?Uj1jz^t;D2Fgy7)l7VMr7V;R*hW>F9*;MX3l2Gl2hrM*cTwPoIArAbTN6-jI}* zkdY)~fx*h;NJ-rn<%aN9)pYT8flKJSz)(;(<-ckFwLzwo{LAs*IRF1T$e#WC6aR;f z07oQQhtxkh$Z1L%-Y$+Pg!f;=R5ctu{T!7g|M&9$Ddj)akzFPGEBWs#k%NEN*~ODw YTyJs}$$g6C-(TRq) Date: Sun, 29 Mar 2020 00:25:50 +0000 Subject: [PATCH 04/40] fix(services): enforce minimum replica count of 0 (#3653) * fix(services): enforce minimum replica count of 0 Fixes #3652 Prevents replica count from being set below zero and causing an error. * fix(services): enforce replica count is an integer Prevents users entering decimals in the replica count --- .../datatables/services-datatable/servicesDatatable.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/docker/components/datatables/services-datatable/servicesDatatable.html b/app/docker/components/datatables/services-datatable/servicesDatatable.html index 7c092c0e45ea5..bcd92884b9159 100644 --- a/app/docker/components/datatables/services-datatable/servicesDatatable.html +++ b/app/docker/components/datatables/services-datatable/servicesDatatable.html @@ -141,7 +141,7 @@ - + From edd86f25061b4df7f795cb3fb1b8aea2056a267d Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sun, 29 Mar 2020 12:54:14 +0300 Subject: [PATCH 05/40] refactor(tags): refactor tag management (#3628) * refactor(tags): replace tags with tag ids * refactor(tags): revert tags to be strings and add tagids * refactor(tags): enable search by tag in home view * refactor(tags): show endpoint tags * refactor(endpoints): expect tagIds on create payload * refactor(endpoints): expect tagIds on update payload * refactor(endpoints): replace TagIds to TagIDs * refactor(endpoints): set endpoint group to get TagIDs * refactor(endpoints): refactor tag-selector to receive tag-ids * refactor(endpoints): show tags in multi-endpoint-selector * chore(tags): revert reformat * refactor(endpoints): remove unneeded bind * refactor(endpoints): change param tags to tagids in endpoint create * refactor(endpoints): remove console.log * refactor(tags): remove deleted tag from endpoint and endpoint group * fix(endpoints): show loading label while loading tags * chore(go): remove obsolete import labels * chore(db): add db version comment * fix(db): add tag service to migrator * refactor(db): add error checks in migrator * style(db): sort props in alphabetical order * style(tags): fix typo Co-Authored-By: Anthony Lapenna * refactor(endpoints): replace tagsMap with tag string representation * refactor(tags): rewrite tag delete to be more readable * refactor(home): rearange code to match former style * refactor(tags): guard against missing model in tag-selector * refactor(tags): rename vars in tag_delete * refactor(tags): allow any authenticated user to fetch tag list * refactor(endpoints): replace controller function with class * refactor(endpoints): replace function with helper * refactor(endpoints): replace controller with class * refactor(tags): revert tags-selector to use 1 way bindings * refactor(endpoints): load empty tag array instead of nil * refactor(endpoints): revert default tag ids * refactor(endpoints): use function in place * refactor(tags): use lodash * style(tags): use parens in arrow functions * fix(tags): remove tag from tag model Co-authored-by: Anthony Lapenna --- api/bolt/datastore.go | 1 + api/bolt/init.go | 2 +- api/bolt/migrator/migrate_dbversion22.go | 57 +++++++++++++++++++ api/bolt/migrator/migrator.go | 12 ++++ api/bolt/tag/tag.go | 13 +++++ api/cmd/portainer/main.go | 6 +- .../endpointgroups/endpointgroup_create.go | 8 +-- .../endpointgroups/endpointgroup_update.go | 6 +- api/http/handler/endpoints/endpoint_create.go | 22 +++---- api/http/handler/endpoints/endpoint_list.go | 39 +++++++++---- api/http/handler/endpoints/endpoint_update.go | 6 +- api/http/handler/endpoints/handler.go | 1 + api/http/handler/tags/handler.go | 6 +- api/http/handler/tags/tag_delete.go | 51 +++++++++++++++++ api/http/server.go | 4 +- api/portainer.go | 13 ++++- .../endpoint-item/endpoint-item-controller.js | 46 ++++++++++++--- .../endpoint-item/endpointItem.html | 8 +-- .../endpoint-item/endpointItem.js | 9 ++- .../endpoint-list/endpoint-list-controller.js | 12 ++-- .../components/endpoint-list/endpoint-list.js | 1 + .../endpoint-list/endpointList.html | 2 + .../forms/group-form/groupForm.html | 3 +- .../forms/schedule-form/schedule-form.js | 1 + .../forms/schedule-form/scheduleForm.html | 4 +- .../multi-endpoint-selector.js | 9 +-- .../multiEndpointSelector.html | 8 +-- .../multiEndpointSelectorController.js | 51 +++++++++-------- .../components/tag-selector/tag-selector.js | 4 +- .../components/tag-selector/tagSelector.html | 9 +-- .../tag-selector/tagSelectorController.js | 39 +++++++------ app/portainer/helpers/tagHelper.js | 9 +++ app/portainer/models/group.js | 8 +-- app/portainer/services/api/endpointService.js | 8 +-- app/portainer/services/fileUpload.js | 8 +-- .../create/createEndpointController.js | 28 ++++----- .../endpoints/create/createendpoint.html | 3 +- .../views/endpoints/edit/endpoint.html | 3 +- .../endpoints/edit/endpointController.js | 6 +- .../groups/create/createGroupController.js | 2 +- .../views/groups/edit/groupController.js | 2 +- app/portainer/views/home/home.html | 1 + app/portainer/views/home/homeController.js | 16 ++++-- .../create/createScheduleController.js | 9 +-- .../schedules/create/createschedule.html | 1 + .../views/schedules/edit/schedule.html | 1 + .../schedules/edit/scheduleController.js | 9 +-- 47 files changed, 400 insertions(+), 167 deletions(-) create mode 100644 api/bolt/migrator/migrate_dbversion22.go create mode 100644 app/portainer/helpers/tagHelper.js diff --git a/api/bolt/datastore.go b/api/bolt/datastore.go index a857375f3e706..c6ce7db61eef0 100644 --- a/api/bolt/datastore.go +++ b/api/bolt/datastore.go @@ -128,6 +128,7 @@ func (store *Store) MigrateData() error { ScheduleService: store.ScheduleService, SettingsService: store.SettingsService, StackService: store.StackService, + TagService: store.TagService, TeamMembershipService: store.TeamMembershipService, TemplateService: store.TemplateService, UserService: store.UserService, diff --git a/api/bolt/init.go b/api/bolt/init.go index bc6e39be50913..8e1a0661c4a55 100644 --- a/api/bolt/init.go +++ b/api/bolt/init.go @@ -16,7 +16,7 @@ func (store *Store) Init() error { Labels: []portainer.Pair{}, UserAccessPolicies: portainer.UserAccessPolicies{}, TeamAccessPolicies: portainer.TeamAccessPolicies{}, - Tags: []string{}, + TagIDs: []portainer.TagID{}, } err = store.EndpointGroupService.CreateEndpointGroup(unassignedGroup) diff --git a/api/bolt/migrator/migrate_dbversion22.go b/api/bolt/migrator/migrate_dbversion22.go new file mode 100644 index 0000000000000..f2e3c2b6c6043 --- /dev/null +++ b/api/bolt/migrator/migrate_dbversion22.go @@ -0,0 +1,57 @@ +package migrator + +import "github.com/portainer/portainer/api" + +func (m *Migrator) updateEndointsAndEndpointsGroupsToDBVersion23() error { + tags, err := m.tagService.Tags() + if err != nil { + return err + } + + tagsNameMap := make(map[string]portainer.TagID) + for _, tag := range tags { + tagsNameMap[tag.Name] = tag.ID + } + + endpoints, err := m.endpointService.Endpoints() + if err != nil { + return err + } + + for _, endpoint := range endpoints { + endpointTags := make([]portainer.TagID, 0) + for _, tagName := range endpoint.Tags { + tagID, ok := tagsNameMap[tagName] + if ok { + endpointTags = append(endpointTags, tagID) + } + } + endpoint.TagIDs = endpointTags + err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint) + if err != nil { + return err + } + } + + endpointGroups, err := m.endpointGroupService.EndpointGroups() + if err != nil { + return err + } + + for _, endpointGroup := range endpointGroups { + endpointGroupTags := make([]portainer.TagID, 0) + for _, tagName := range endpointGroup.Tags { + tagID, ok := tagsNameMap[tagName] + if ok { + endpointGroupTags = append(endpointGroupTags, tagID) + } + } + endpointGroup.TagIDs = endpointGroupTags + err = m.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) + if err != nil { + return err + } + } + + return nil +} diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go index 885cc9ab855ae..8f4aee0851a9a 100644 --- a/api/bolt/migrator/migrator.go +++ b/api/bolt/migrator/migrator.go @@ -12,6 +12,7 @@ import ( "github.com/portainer/portainer/api/bolt/schedule" "github.com/portainer/portainer/api/bolt/settings" "github.com/portainer/portainer/api/bolt/stack" + "github.com/portainer/portainer/api/bolt/tag" "github.com/portainer/portainer/api/bolt/teammembership" "github.com/portainer/portainer/api/bolt/template" "github.com/portainer/portainer/api/bolt/user" @@ -32,6 +33,7 @@ type ( scheduleService *schedule.Service settingsService *settings.Service stackService *stack.Service + tagService *tag.Service teamMembershipService *teammembership.Service templateService *template.Service userService *user.Service @@ -52,6 +54,7 @@ type ( ScheduleService *schedule.Service SettingsService *settings.Service StackService *stack.Service + TagService *tag.Service TeamMembershipService *teammembership.Service TemplateService *template.Service UserService *user.Service @@ -73,6 +76,7 @@ func NewMigrator(parameters *Parameters) *Migrator { roleService: parameters.RoleService, scheduleService: parameters.ScheduleService, settingsService: parameters.SettingsService, + tagService: parameters.TagService, teamMembershipService: parameters.TeamMembershipService, templateService: parameters.TemplateService, stackService: parameters.StackService, @@ -301,5 +305,13 @@ func (m *Migrator) Migrate() error { } } + // Portainer 1.24.0-dev + if m.currentDBVersion < 23 { + err := m.updateEndointsAndEndpointsGroupsToDBVersion23() + if err != nil { + return err + } + } + return m.versionService.StoreDBVersion(portainer.DBVersion) } diff --git a/api/bolt/tag/tag.go b/api/bolt/tag/tag.go index d54ee6b76eb7f..d4a5dc9de723a 100644 --- a/api/bolt/tag/tag.go +++ b/api/bolt/tag/tag.go @@ -52,6 +52,19 @@ func (service *Service) Tags() ([]portainer.Tag, error) { return tags, err } +// Tag returns a tag by ID. +func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) { + var tag portainer.Tag + identifier := internal.Itob(int(ID)) + + err := internal.GetObject(service.db, BucketName, identifier, &tag) + if err != nil { + return nil, err + } + + return &tag, nil +} + // CreateTag creates a new tag. func (service *Service) CreateTag(tag *portainer.Tag) error { return service.db.Update(func(tx *bolt.Tx) error { diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 236b64d35df24..46c5bb5ca4a02 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -259,7 +259,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL LogoURL: *flags.Logo, AuthenticationMethod: portainer.AuthenticationInternal, LDAPSettings: portainer.LDAPSettings{ - AnonymousMode: true, + AnonymousMode: true, AutoCreateUsers: true, TLSConfig: portainer.TLSConfiguration{}, SearchSettings: []portainer.LDAPSearchSettings{ @@ -397,7 +397,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, endpointService portain UserAccessPolicies: portainer.UserAccessPolicies{}, TeamAccessPolicies: portainer.TeamAccessPolicies{}, Extensions: []portainer.EndpointExtension{}, - Tags: []string{}, + TagIDs: []portainer.TagID{}, Status: portainer.EndpointStatusUp, Snapshots: []portainer.Snapshot{}, } @@ -440,7 +440,7 @@ func createUnsecuredEndpoint(endpointURL string, endpointService portainer.Endpo UserAccessPolicies: portainer.UserAccessPolicies{}, TeamAccessPolicies: portainer.TeamAccessPolicies{}, Extensions: []portainer.EndpointExtension{}, - Tags: []string{}, + TagIDs: []portainer.TagID{}, Status: portainer.EndpointStatusUp, Snapshots: []portainer.Snapshot{}, } diff --git a/api/http/handler/endpointgroups/endpointgroup_create.go b/api/http/handler/endpointgroups/endpointgroup_create.go index 32a617c9269c1..34bdbe754e51f 100644 --- a/api/http/handler/endpointgroups/endpointgroup_create.go +++ b/api/http/handler/endpointgroups/endpointgroup_create.go @@ -14,15 +14,15 @@ type endpointGroupCreatePayload struct { Name string Description string AssociatedEndpoints []portainer.EndpointID - Tags []string + TagIDs []portainer.TagID } func (payload *endpointGroupCreatePayload) Validate(r *http.Request) error { if govalidator.IsNull(payload.Name) { return portainer.Error("Invalid endpoint group name") } - if payload.Tags == nil { - payload.Tags = []string{} + if payload.TagIDs == nil { + payload.TagIDs = []portainer.TagID{} } return nil } @@ -40,7 +40,7 @@ func (handler *Handler) endpointGroupCreate(w http.ResponseWriter, r *http.Reque Description: payload.Description, UserAccessPolicies: portainer.UserAccessPolicies{}, TeamAccessPolicies: portainer.TeamAccessPolicies{}, - Tags: payload.Tags, + TagIDs: payload.TagIDs, } err = handler.EndpointGroupService.CreateEndpointGroup(endpointGroup) diff --git a/api/http/handler/endpointgroups/endpointgroup_update.go b/api/http/handler/endpointgroups/endpointgroup_update.go index 58ea605fccd0d..ef7b4edc47161 100644 --- a/api/http/handler/endpointgroups/endpointgroup_update.go +++ b/api/http/handler/endpointgroups/endpointgroup_update.go @@ -13,7 +13,7 @@ import ( type endpointGroupUpdatePayload struct { Name string Description string - Tags []string + TagIDs []portainer.TagID UserAccessPolicies portainer.UserAccessPolicies TeamAccessPolicies portainer.TeamAccessPolicies } @@ -50,8 +50,8 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque endpointGroup.Description = payload.Description } - if payload.Tags != nil { - endpointGroup.Tags = payload.Tags + if payload.TagIDs != nil { + endpointGroup.TagIDs = payload.TagIDs } updateAuthorizations := false diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go index 8659231491920..dfa0309270f91 100644 --- a/api/http/handler/endpoints/endpoint_create.go +++ b/api/http/handler/endpoints/endpoint_create.go @@ -32,7 +32,7 @@ type endpointCreatePayload struct { AzureApplicationID string AzureTenantID string AzureAuthenticationKey string - Tags []string + TagIDs []portainer.TagID } func (payload *endpointCreatePayload) Validate(r *http.Request) error { @@ -54,14 +54,14 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error { } payload.GroupID = groupID - var tags []string - err = request.RetrieveMultiPartFormJSONValue(r, "Tags", &tags, true) + var tagIDs []portainer.TagID + err = request.RetrieveMultiPartFormJSONValue(r, "TagIds", &tagIDs, true) if err != nil { - return portainer.Error("Invalid Tags parameter") + return portainer.Error("Invalid TagIds parameter") } - payload.Tags = tags - if payload.Tags == nil { - payload.Tags = make([]string, 0) + payload.TagIDs = tagIDs + if payload.TagIDs == nil { + payload.TagIDs = make([]portainer.TagID, 0) } useTLS, _ := request.RetrieveBooleanMultiPartFormValue(r, "TLS", true) @@ -187,7 +187,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po TeamAccessPolicies: portainer.TeamAccessPolicies{}, Extensions: []portainer.EndpointExtension{}, AzureCredentials: credentials, - Tags: payload.Tags, + TagIDs: payload.TagIDs, Status: portainer.EndpointStatusUp, Snapshots: []portainer.Snapshot{}, } @@ -232,7 +232,7 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) AuthorizedUsers: []portainer.UserID{}, AuthorizedTeams: []portainer.TeamID{}, Extensions: []portainer.EndpointExtension{}, - Tags: payload.Tags, + TagIDs: payload.TagIDs, Status: portainer.EndpointStatusUp, Snapshots: []portainer.Snapshot{}, EdgeKey: edgeKey, @@ -278,7 +278,7 @@ func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload) UserAccessPolicies: portainer.UserAccessPolicies{}, TeamAccessPolicies: portainer.TeamAccessPolicies{}, Extensions: []portainer.EndpointExtension{}, - Tags: payload.Tags, + TagIDs: payload.TagIDs, Status: portainer.EndpointStatusUp, Snapshots: []portainer.Snapshot{}, } @@ -322,7 +322,7 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload) UserAccessPolicies: portainer.UserAccessPolicies{}, TeamAccessPolicies: portainer.TeamAccessPolicies{}, Extensions: []portainer.EndpointExtension{}, - Tags: payload.Tags, + TagIDs: payload.TagIDs, Status: portainer.EndpointStatusUp, Snapshots: []portainer.Snapshot{}, } diff --git a/api/http/handler/endpoints/endpoint_list.go b/api/http/handler/endpoints/endpoint_list.go index 403a3f66864a7..1d98296bfb389 100644 --- a/api/http/handler/endpoints/endpoint_list.go +++ b/api/http/handler/endpoints/endpoint_list.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api" "github.com/portainer/libhttp/request" @@ -52,7 +52,15 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht } if search != "" { - filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, endpointGroups, search) + tags, err := handler.TagsService.Tags() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve tags from the database", err} + } + tagsMap := make(map[portainer.TagID]string) + for _, tag := range tags { + tagsMap[tag.ID] = tag.Name + } + filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, endpointGroups, tagsMap, search) } if endpointType != 0 { @@ -102,17 +110,17 @@ func filterEndpointsByGroupID(endpoints []portainer.Endpoint, endpointGroupID po return filteredEndpoints } -func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, searchCriteria string) []portainer.Endpoint { +func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) []portainer.Endpoint { filteredEndpoints := make([]portainer.Endpoint, 0) for _, endpoint := range endpoints { - - if endpointMatchSearchCriteria(&endpoint, searchCriteria) { + endpointTags := convertTagIDsToTags(tagsMap, endpoint.TagIDs) + if endpointMatchSearchCriteria(&endpoint, endpointTags, searchCriteria) { filteredEndpoints = append(filteredEndpoints, endpoint) continue } - if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, searchCriteria) { + if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, tagsMap, searchCriteria) { filteredEndpoints = append(filteredEndpoints, endpoint) } } @@ -120,7 +128,7 @@ func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGro return filteredEndpoints } -func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria string) bool { +func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, searchCriteria string) bool { if strings.Contains(strings.ToLower(endpoint.Name), searchCriteria) { return true } @@ -134,8 +142,7 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria st } else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" { return true } - - for _, tag := range endpoint.Tags { + for _, tag := range tags { if strings.Contains(strings.ToLower(tag), searchCriteria) { return true } @@ -144,14 +151,14 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria st return false } -func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, searchCriteria string) bool { +func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) bool { for _, group := range endpointGroups { if group.ID == endpoint.GroupID { if strings.Contains(strings.ToLower(group.Name), searchCriteria) { return true } - - for _, tag := range group.Tags { + tags := convertTagIDsToTags(tagsMap, group.TagIDs) + for _, tag := range tags { if strings.Contains(strings.ToLower(tag), searchCriteria) { return true } @@ -172,3 +179,11 @@ func filterEndpointsByType(endpoints []portainer.Endpoint, endpointType portaine } return filteredEndpoints } + +func convertTagIDsToTags(tagsMap map[portainer.TagID]string, tagIDs []portainer.TagID) []string { + tags := make([]string, 0) + for _, tagID := range tagIDs { + tags = append(tags, tagsMap[tagID]) + } + return tags +} diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go index dd87c22f20d5c..7c02f5f67f18f 100644 --- a/api/http/handler/endpoints/endpoint_update.go +++ b/api/http/handler/endpoints/endpoint_update.go @@ -24,7 +24,7 @@ type endpointUpdatePayload struct { AzureApplicationID *string AzureTenantID *string AzureAuthenticationKey *string - Tags []string + TagIDs []portainer.TagID UserAccessPolicies portainer.UserAccessPolicies TeamAccessPolicies portainer.TeamAccessPolicies } @@ -73,8 +73,8 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * endpoint.GroupID = portainer.EndpointGroupID(*payload.GroupID) } - if payload.Tags != nil { - endpoint.Tags = payload.Tags + if payload.TagIDs != nil { + endpoint.TagIDs = payload.TagIDs } updateAuthorizations := false diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index c655a0eef9578..6281a4a98d26f 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -37,6 +37,7 @@ type Handler struct { JobService portainer.JobService ReverseTunnelService portainer.ReverseTunnelService SettingsService portainer.SettingsService + TagsService portainer.TagService AuthorizationService *portainer.AuthorizationService } diff --git a/api/http/handler/tags/handler.go b/api/http/handler/tags/handler.go index 33cb59c9d1a9c..b5dac0274cab9 100644 --- a/api/http/handler/tags/handler.go +++ b/api/http/handler/tags/handler.go @@ -12,7 +12,9 @@ import ( // Handler is the HTTP handler used to handle tag operations. type Handler struct { *mux.Router - TagService portainer.TagService + TagService portainer.TagService + EndpointService portainer.EndpointService + EndpointGroupService portainer.EndpointGroupService } // NewHandler creates a handler to manage tag operations. @@ -23,7 +25,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { h.Handle("/tags", bouncer.AdminAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost) h.Handle("/tags", - bouncer.AdminAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet) + bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet) h.Handle("/tags/{id}", bouncer.AdminAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete) diff --git a/api/http/handler/tags/tag_delete.go b/api/http/handler/tags/tag_delete.go index 9c9e9d4e3074c..2467a38fd1249 100644 --- a/api/http/handler/tags/tag_delete.go +++ b/api/http/handler/tags/tag_delete.go @@ -15,6 +15,39 @@ func (handler *Handler) tagDelete(w http.ResponseWriter, r *http.Request) *httpe if err != nil { return &httperror.HandlerError{http.StatusBadRequest, "Invalid tag identifier route variable", err} } + tagID := portainer.TagID(id) + + endpoints, err := handler.EndpointService.Endpoints() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err} + } + + for _, endpoint := range endpoints { + tagIdx := findTagIndex(endpoint.TagIDs, tagID) + if tagIdx != -1 { + endpoint.TagIDs = removeElement(endpoint.TagIDs, tagIdx) + err = handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint", err} + } + } + } + + endpointGroups, err := handler.EndpointGroupService.EndpointGroups() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err} + } + + for _, endpointGroup := range endpointGroups { + tagIdx := findTagIndex(endpointGroup.TagIDs, tagID) + if tagIdx != -1 { + endpointGroup.TagIDs = removeElement(endpointGroup.TagIDs, tagIdx) + err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint group", err} + } + } + } err = handler.TagService.DeleteTag(portainer.TagID(id)) if err != nil { @@ -23,3 +56,21 @@ func (handler *Handler) tagDelete(w http.ResponseWriter, r *http.Request) *httpe return response.Empty(w) } + +func findTagIndex(tags []portainer.TagID, searchTagID portainer.TagID) int { + for idx, tagID := range tags { + if searchTagID == tagID { + return idx + } + } + return -1 +} + +func removeElement(arr []portainer.TagID, index int) []portainer.TagID { + if index < 0 { + return arr + } + lastTagIdx := len(arr) - 1 + arr[index] = arr[lastTagIdx] + return arr[:lastTagIdx] +} diff --git a/api/http/server.go b/api/http/server.go index 066bc7bef4b5a..eb47770714b12 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -7,7 +7,7 @@ import ( "github.com/portainer/portainer/api/http/handler/roles" - "github.com/portainer/portainer/api" + portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/docker" "github.com/portainer/portainer/api/http/handler" "github.com/portainer/portainer/api/http/handler/auth" @@ -222,6 +222,8 @@ func (server *Server) Start() error { var tagHandler = tags.NewHandler(requestBouncer) tagHandler.TagService = server.TagService + tagHandler.EndpointService = server.EndpointService + tagHandler.EndpointGroupService = server.EndpointGroupService var teamHandler = teams.NewHandler(requestBouncer) teamHandler.TeamService = server.TeamService diff --git a/api/portainer.go b/api/portainer.go index 23b149c82b280..f0339677df13c 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -260,7 +260,7 @@ type ( TLSConfig TLSConfiguration `json:"TLSConfig"` Extensions []EndpointExtension `json:"Extensions"` AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"` - Tags []string `json:"Tags"` + TagIDs []TagID `json:"TagIds"` Status EndpointStatus `json:"Status"` Snapshots []Snapshot `json:"Snapshots"` UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"` @@ -277,6 +277,9 @@ type ( // Deprecated in DBVersion == 18 AuthorizedUsers []UserID `json:"AuthorizedUsers"` AuthorizedTeams []TeamID `json:"AuthorizedTeams"` + + // Deprecated in DBVersion == 22 + Tags []string `json:"Tags"` } // Authorization represents an authorization associated to an operation @@ -426,7 +429,7 @@ type ( Description string `json:"Description"` UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"` TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"` - Tags []string `json:"Tags"` + TagIDs []TagID `json:"TagIds"` // Deprecated fields Labels []Pair `json:"Labels"` @@ -434,6 +437,9 @@ type ( // Deprecated in DBVersion == 18 AuthorizedUsers []UserID `json:"AuthorizedUsers"` AuthorizedTeams []TeamID `json:"AuthorizedTeams"` + + // Deprecated in DBVersion == 22 + Tags []string `json:"Tags"` } // EndpointExtension represents a deprecated form of Portainer extension @@ -775,6 +781,7 @@ type ( // TagService represents a service for managing tag data TagService interface { Tags() ([]Tag, error) + Tag(ID TagID) (*Tag, error) CreateTag(tag *Tag) error DeleteTag(ID TagID) error } @@ -919,7 +926,7 @@ const ( // APIVersion is the version number of the Portainer API APIVersion = "1.24.0-dev" // DBVersion is the version number of the Portainer database - DBVersion = 22 + DBVersion = 23 // AssetsServerURL represents the URL of the Portainer asset server AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com" // MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js b/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js index 62433ee25df8c..61be9faf0c2f2 100644 --- a/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js +++ b/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js @@ -1,12 +1,42 @@ -angular.module('portainer.app').controller('EndpointItemController', [ - function EndpointItemController() { - var ctrl = this; +import angular from 'angular'; +import _ from 'lodash-es'; +import PortainerEndpointTagHelper from 'Portainer/helpers/tagHelper'; - ctrl.editEndpoint = editEndpoint; +class EndpointItemController { + /* @ngInject */ + constructor() { + this.editEndpoint = this.editEndpoint.bind(this); + } + + editEndpoint(event) { + event.stopPropagation(); + this.onEdit(this.model.Id); + } + + joinTags() { + if (!this.tags) { + return 'Loading tags...'; + } + + if (!this.model.TagIds || !this.model.TagIds.length) { + return ''; + } + + const tagNames = PortainerEndpointTagHelper.idsToTagNames(this.tags, this.model.TagIds); + return _.join(tagNames, ',') + } - function editEndpoint(event) { - event.stopPropagation(); - ctrl.onEdit(ctrl.model.Id); + $onInit() { + this.endpointTags = this.joinTags(); + } + + $onChanges({ tags, model }) { + if ((!tags && !model) || (!tags.currentValue && !model.currentValue)) { + return; } + this.endpointTags = this.joinTags(); } -]); +} + +angular.module('portainer.app').controller('EndpointItemController', EndpointItemController); +export default EndpointItemController; diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html index 5060848a0f5bb..c8d9f4457f04c 100644 --- a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html +++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html @@ -85,14 +85,12 @@ - - + No tags - + - - {{ tag }}{{ $last? '' : ', ' }} - + {{ $ctrl.endpointTags }} diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js index ab12b75a6d3df..ec8f2b1fa1ad3 100644 --- a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js +++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js @@ -1,10 +1,15 @@ +import angular from 'angular'; + +import EndpointItemController from './endpoint-item-controller'; + angular.module('portainer.app').component('endpointItem', { templateUrl: './endpointItem.html', bindings: { model: '<', onSelect: '<', onEdit: '<', - isAdmin:'<' + isAdmin: '<', + tags: '<', }, - controller: 'EndpointItemController' + controller: EndpointItemController, }); diff --git a/app/portainer/components/endpoint-list/endpoint-list-controller.js b/app/portainer/components/endpoint-list/endpoint-list-controller.js index 0abf59e8e7b55..ca5fe52d9e8c0 100644 --- a/app/portainer/components/endpoint-list/endpoint-list-controller.js +++ b/app/portainer/components/endpoint-list/endpoint-list-controller.js @@ -29,12 +29,12 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable if (this.hasBackendPagination()) { this.paginationChangedAction(); } else { - this.state.filteredEndpoints = frontEndpointFilter(this.endpoints, filterValue); + this.state.filteredEndpoints = frontEndpointFilter(this.endpoints, this.tags, filterValue); this.state.loading = false; } } - function frontEndpointFilter(endpoints, filterValue) { + function frontEndpointFilter(endpoints, tags, filterValue) { if (!endpoints || !endpoints.length || !filterValue) { return endpoints; } @@ -47,8 +47,12 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable _.includes(endpoint.Name.toLowerCase(), lowerCaseKeyword) || _.includes(endpoint.GroupName.toLowerCase(), lowerCaseKeyword) || _.includes(endpoint.URL.toLowerCase(), lowerCaseKeyword) || - _.some(endpoint.Tags, function(tag) { - return _.includes(tag.toLowerCase(), lowerCaseKeyword); + _.some(endpoint.TagIds, tagId => { + const tag = tags.find(t => t.Id === tagId); + if (!tag) { + return false; + } + return _.includes(tag.Name.toLowerCase(), lowerCaseKeyword); }) || _.includes(statusString, keyword) ); diff --git a/app/portainer/components/endpoint-list/endpoint-list.js b/app/portainer/components/endpoint-list/endpoint-list.js index 06835c9e0e6e4..1bf1847492a71 100644 --- a/app/portainer/components/endpoint-list/endpoint-list.js +++ b/app/portainer/components/endpoint-list/endpoint-list.js @@ -5,6 +5,7 @@ angular.module('portainer.app').component('endpointList', { titleText: '@', titleIcon: '@', endpoints: '<', + tags: '<', tableKey: '@', dashboardAction: '<', snapshotAction: '<', diff --git a/app/portainer/components/endpoint-list/endpointList.html b/app/portainer/components/endpoint-list/endpointList.html index 028ccc122f69a..d6fd453a33bd2 100644 --- a/app/portainer/components/endpoint-list/endpointList.html +++ b/app/portainer/components/endpoint-list/endpointList.html @@ -33,6 +33,7 @@ on-select="$ctrl.dashboardAction" on-edit="$ctrl.editAction" is-admin="$ctrl.isAdmin" + tags="$ctrl.tags" >
Loading... diff --git a/app/portainer/components/forms/group-form/groupForm.html b/app/portainer/components/forms/group-form/groupForm.html index f16d3bd2cbf38..addd43a0186ca 100644 --- a/app/portainer/components/forms/group-form/groupForm.html +++ b/app/portainer/components/forms/group-form/groupForm.html @@ -28,8 +28,9 @@
diff --git a/app/portainer/components/forms/schedule-form/schedule-form.js b/app/portainer/components/forms/schedule-form/schedule-form.js index 1ca8de98bf041..557331f24c891 100644 --- a/app/portainer/components/forms/schedule-form/schedule-form.js +++ b/app/portainer/components/forms/schedule-form/schedule-form.js @@ -69,6 +69,7 @@ angular.module('portainer.app').component('scheduleForm', { model: '=', endpoints: '<', groups: '<', + tags: '<', addLabelAction: '<', removeLabelAction: '<', formAction: '<', diff --git a/app/portainer/components/forms/schedule-form/scheduleForm.html b/app/portainer/components/forms/schedule-form/scheduleForm.html index 83772022da9b6..86e8bdc00efbd 100644 --- a/app/portainer/components/forms/schedule-form/scheduleForm.html +++ b/app/portainer/components/forms/schedule-form/scheduleForm.html @@ -265,9 +265,9 @@
diff --git a/app/portainer/components/multi-endpoint-selector/multi-endpoint-selector.js b/app/portainer/components/multi-endpoint-selector/multi-endpoint-selector.js index 63e0683c95582..1862307f0b5a4 100644 --- a/app/portainer/components/multi-endpoint-selector/multi-endpoint-selector.js +++ b/app/portainer/components/multi-endpoint-selector/multi-endpoint-selector.js @@ -2,8 +2,9 @@ angular.module('portainer.app').component('multiEndpointSelector', { templateUrl: './multiEndpointSelector.html', controller: 'MultiEndpointSelectorController', bindings: { - 'model': '=', - 'endpoints': '<', - 'groups': '<' - } + model: '=', + endpoints: '<', + groups: '<', + tags: '<', + }, }); diff --git a/app/portainer/components/multi-endpoint-selector/multiEndpointSelector.html b/app/portainer/components/multi-endpoint-selector/multiEndpointSelector.html index 5acf2149d30b6..534a3c91c0403 100644 --- a/app/portainer/components/multi-endpoint-selector/multiEndpointSelector.html +++ b/app/portainer/components/multi-endpoint-selector/multiEndpointSelector.html @@ -2,13 +2,13 @@ {{ $item.Name }} - ({{ $item.Tags | arraytostr }}) - + ({{ $ctrl.tagIdsToTagNames($item.TagIds) | arraytostr }}) +
{{ endpoint.Name }} - ({{ endpoint.Tags | arraytostr }}) - + ({{ $ctrl.tagIdsToTagNames(endpoint.TagIds) | arraytostr }}) +
diff --git a/app/portainer/components/multi-endpoint-selector/multiEndpointSelectorController.js b/app/portainer/components/multi-endpoint-selector/multiEndpointSelectorController.js index 3b67fd6be8ca7..4f38858257969 100644 --- a/app/portainer/components/multi-endpoint-selector/multiEndpointSelectorController.js +++ b/app/portainer/components/multi-endpoint-selector/multiEndpointSelectorController.js @@ -1,37 +1,40 @@ import _ from 'lodash-es'; +import PortainerEndpointTagHelper from 'Portainer/helpers/tagHelper'; -angular.module('portainer.app') -.controller('MultiEndpointSelectorController', function () { - var ctrl = this; +import angular from 'angular'; - this.sortGroups = function(groups) { +class MultiEndpointSelectorController { + /* @ngInject */ + constructor() { + this.sortGroups = this.sortGroups.bind(this); + this.groupEndpoints = this.groupEndpoints.bind(this); + this.tagIdsToTagNames = this.tagIdsToTagNames.bind(this); + } + + sortGroups(groups) { return _.sortBy(groups, ['name']); - }; + } - this.groupEndpoints = function(endpoint) { - for (var i = 0; i < ctrl.availableGroups.length; i++) { - var group = ctrl.availableGroups[i]; + groupEndpoints(endpoint) { + for (var i = 0; i < this.availableGroups.length; i++) { + var group = this.availableGroups[i]; if (endpoint.GroupId === group.Id) { return group.Name; } } - }; - - this.$onInit = function() { - this.availableGroups = filterEmptyGroups(this.groups, this.endpoints); - }; + } - function filterEmptyGroups(groups, endpoints) { - return groups.filter(function f(group) { - for (var i = 0; i < endpoints.length; i++) { + tagIdsToTagNames(tagIds) { + return PortainerEndpointTagHelper.idsToTagNames(this.tags, tagIds); + } - var endpoint = endpoints[i]; - if (endpoint.GroupId === group.Id) { - return true; - } - } - return false; - }); + $onInit() { + this.availableGroups = _.filter(this.groups, group => + _.some(this.endpoints, endpoint => endpoint.GroupId == group.Id) + ); } -}); +} + +export default MultiEndpointSelectorController; +angular.module('portainer.app').controller('MultiEndpointSelectorController', MultiEndpointSelectorController); diff --git a/app/portainer/components/tag-selector/tag-selector.js b/app/portainer/components/tag-selector/tag-selector.js index a29a5eeaf794f..540b1690235a2 100644 --- a/app/portainer/components/tag-selector/tag-selector.js +++ b/app/portainer/components/tag-selector/tag-selector.js @@ -3,6 +3,6 @@ angular.module('portainer.app').component('tagSelector', { controller: 'TagSelectorController', bindings: { tags: '<', - model: '=' - } + model: '=', + }, }); diff --git a/app/portainer/components/tag-selector/tagSelector.html b/app/portainer/components/tag-selector/tagSelector.html index be92e1aa4f570..d453f1accd03a 100644 --- a/app/portainer/components/tag-selector/tagSelector.html +++ b/app/portainer/components/tag-selector/tagSelector.html @@ -3,8 +3,8 @@ Selected tags
- - {{ tag }} + + {{ tag.Name }} @@ -20,10 +20,11 @@ type="text" ng-model="$ctrl.state.selectedValue" id="tags" class="form-control" placeholder="Select tags..." - uib-typeahead="tag for tag in $ctrl.tags | filter:$viewValue | limitTo:7" + uib-typeahead="tag.Id as tag.Name for tag in $ctrl.tags | filter: $ctrl.filterSelected | filter:$viewValue | limitTo:7" typeahead-on-select="$ctrl.selectTag($item, $model, $label)" typeahead-no-results="$ctrl.state.noResult" - typeahead-show-hint="true" typeahead-min-length="0" /> + typeahead-show-hint="true" typeahead-min-length="0" + />
diff --git a/app/portainer/components/tag-selector/tagSelectorController.js b/app/portainer/components/tag-selector/tagSelectorController.js index d575f8ab2acc5..3d64485e39117 100644 --- a/app/portainer/components/tag-selector/tagSelectorController.js +++ b/app/portainer/components/tag-selector/tagSelectorController.js @@ -1,32 +1,35 @@ import _ from 'lodash-es'; -angular.module('portainer.app') -.controller('TagSelectorController', function () { - - this.$onChanges = function(changes) { - if(angular.isDefined(changes.tags.currentValue)) { - this.tags = _.difference(changes.tags.currentValue, this.model); - } +angular.module('portainer.app').controller('TagSelectorController', function() { + this.$onInit = function() { + this.state.selectedTags = _.map(this.model, (id) => _.find(this.tags, (t) => t.Id === id)); }; this.state = { selectedValue: '', - noResult: false + selectedTags: [], + noResult: false, }; this.selectTag = function($item) { this.state.selectedValue = ''; - this.model.push($item); - this.tags = _.remove(this.tags, function(item) { - return item !== $item; - }); + this.model.push($item.Id); + this.state.selectedTags.push($item); }; - this.removeTag = function(tag) { - var idx = this.model.indexOf(tag); - if (idx > -1) { - this.model.splice(idx, 1); - this.tags.push(tag); - } + this.removeTag = function removeTag(tag) { + _.remove(this.state.selectedTags, { Id: tag.Id }); + _.remove(this.model, (id) => id === tag.Id); }; + + this.filterSelected = filterSelected.bind(this); + + function filterSelected($item) { + if (!this.model) { + return true; + } + return !_.includes(this.model, $item.Id); + } + window._remove = _.remove; }); + diff --git a/app/portainer/helpers/tagHelper.js b/app/portainer/helpers/tagHelper.js new file mode 100644 index 0000000000000..e07ba6c361495 --- /dev/null +++ b/app/portainer/helpers/tagHelper.js @@ -0,0 +1,9 @@ +import _ from 'lodash'; + +export default class PortainerEndpointTagHelper { + static idsToTagNames(tags, ids) { + const filteredTags = _.filter(tags, tag => _.includes(ids, tag.Id)); + const tagNames = _.map(filteredTags, 'Name'); + return tagNames; + } +} diff --git a/app/portainer/models/group.js b/app/portainer/models/group.js index 79b9b3e53bca4..ca806f5823ebe 100644 --- a/app/portainer/models/group.js +++ b/app/portainer/models/group.js @@ -1,14 +1,14 @@ export function EndpointGroupDefaultModel() { this.Name = ''; this.Description = ''; - this.Tags = []; + this.TagIds = []; } export function EndpointGroupModel(data) { this.Id = data.Id; this.Name = data.Name; this.Description = data.Description; - this.Tags = data.Tags; + this.TagIds = data.TagIds; this.AuthorizedUsers = data.AuthorizedUsers; this.AuthorizedTeams = data.AuthorizedTeams; this.UserAccessPolicies = data.UserAccessPolicies; @@ -18,7 +18,7 @@ export function EndpointGroupModel(data) { export function EndpointGroupCreateRequest(model, endpoints) { this.Name = model.Name; this.Description = model.Description; - this.Tags = model.Tags; + this.TagIds = model.TagIds; this.AssociatedEndpoints = endpoints; } @@ -26,7 +26,7 @@ export function EndpointGroupUpdateRequest(model, endpoints) { this.id = model.Id; this.Name = model.Name; this.Description = model.Description; - this.Tags = model.Tags; + this.TagIds = model.TagIds; this.AssociatedEndpoints = endpoints; this.UserAccessPolicies = model.UserAccessPolicies; this.TeamAccessPolicies = model.TeamAccessPolicies; diff --git a/app/portainer/services/api/endpointService.js b/app/portainer/services/api/endpointService.js index 7a43ee5b2840c..6de03711d63c6 100644 --- a/app/portainer/services/api/endpointService.js +++ b/app/portainer/services/api/endpointService.js @@ -63,7 +63,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) { return deferred.promise; }; - service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { + service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { var deferred = $q.defer(); var endpointURL = URL; @@ -71,7 +71,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) { endpointURL = 'tcp://' + URL; } - FileUploadService.createEndpoint(name, type, endpointURL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) + FileUploadService.createEndpoint(name, type, endpointURL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) .then(function success(response) { deferred.resolve(response.data); }) @@ -82,10 +82,10 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) { return deferred.promise; }; - service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tags) { + service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tagIds) { var deferred = $q.defer(); - FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags) + FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) .then(function success(response) { deferred.resolve(response.data); }) diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js index 1f17916bc7c17..07baed73f941d 100644 --- a/app/portainer/services/fileUpload.js +++ b/app/portainer/services/fileUpload.js @@ -100,7 +100,7 @@ angular.module('portainer.app') }); }; - service.createEndpoint = function(name, type, URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { + service.createEndpoint = function(name, type, URL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { return Upload.upload({ url: 'api/endpoints', data: { @@ -109,7 +109,7 @@ angular.module('portainer.app') URL: URL, PublicURL: PublicURL, GroupID: groupID, - Tags: Upload.json(tags), + TagIds: Upload.json(tagIds), TLS: TLS, TLSSkipVerify: TLSSkipVerify, TLSSkipClientVerify: TLSSkipClientVerify, @@ -121,14 +121,14 @@ angular.module('portainer.app') }); }; - service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tags) { + service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tagIds) { return Upload.upload({ url: 'api/endpoints', data: { Name: name, EndpointType: 3, GroupID: groupId, - Tags: Upload.json(tags), + TagIds: Upload.json(tagIds), AzureApplicationID: applicationId, AzureTenantID: tenantId, AzureAuthenticationKey: authenticationKey diff --git a/app/portainer/views/endpoints/create/createEndpointController.js b/app/portainer/views/endpoints/create/createEndpointController.js index 1cdf544096cf2..968aa041ba5cc 100644 --- a/app/portainer/views/endpoints/create/createEndpointController.js +++ b/app/portainer/views/endpoints/create/createEndpointController.js @@ -18,7 +18,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, AzureApplicationId: '', AzureTenantId: '', AzureAuthenticationKey: '', - Tags: [] + TagIds: [] }; $scope.copyAgentCommand = function() { @@ -40,7 +40,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, var URL = $filter('stripprotocol')($scope.formValues.URL); var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL; var groupId = $scope.formValues.GroupId; - var tags = $scope.formValues.Tags; + var tagIds = $scope.formValues.TagIds; var securityData = $scope.formValues.SecurityFormData; var TLS = securityData.TLS; @@ -51,7 +51,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert; var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey; - addEndpoint(name, 1, URL, publicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile); + addEndpoint(name, 1, URL, publicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile); }; $scope.addAgentEndpoint = function() { @@ -59,18 +59,18 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, var URL = $filter('stripprotocol')($scope.formValues.URL); var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL; var groupId = $scope.formValues.GroupId; - var tags = $scope.formValues.Tags; + var tagIds = $scope.formValues.TagIds; - addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null); + addEndpoint(name, 2, URL, publicURL, groupId, tagIds, true, true, true, null, null, null); }; $scope.addEdgeAgentEndpoint = function() { var name = $scope.formValues.Name; var groupId = $scope.formValues.GroupId; - var tags = $scope.formValues.Tags; + var tagIds = $scope.formValues.TagIds; var URL = $scope.formValues.URL; - addEndpoint(name, 4, URL, "", groupId, tags, false, false, false, null, null, null); + addEndpoint(name, 4, URL, "", groupId, tagIds, false, false, false, null, null, null); }; $scope.addAzureEndpoint = function() { @@ -79,14 +79,14 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, var tenantId = $scope.formValues.AzureTenantId; var authenticationKey = $scope.formValues.AzureAuthenticationKey; var groupId = $scope.formValues.GroupId; - var tags = $scope.formValues.Tags; + var tagIds = $scope.formValues.TagIds; - createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags); + createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds); }; - function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags) { + function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) { $scope.state.actionInProgress = true; - EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags) + EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) .then(function success() { Notifications.success('Endpoint created', name); $state.go('portainer.endpoints', {}, {reload: true}); @@ -99,9 +99,9 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, }); } - function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { + function addEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { $scope.state.actionInProgress = true; - EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) + EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) .then(function success(data) { Notifications.success('Endpoint created', name); if (type === 4) { @@ -121,7 +121,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, function initView() { $q.all({ groups: GroupService.groups(), - tags: TagService.tagNames() + tags: TagService.tags() }) .then(function success(data) { $scope.groups = data.groups; diff --git a/app/portainer/views/endpoints/create/createendpoint.html b/app/portainer/views/endpoints/create/createendpoint.html index b930e25189bd2..89308e1791ffe 100644 --- a/app/portainer/views/endpoints/create/createendpoint.html +++ b/app/portainer/views/endpoints/create/createendpoint.html @@ -259,8 +259,9 @@
diff --git a/app/portainer/views/endpoints/edit/endpoint.html b/app/portainer/views/endpoints/edit/endpoint.html index 81f840a1cdf0e..d33e8bd7ac798 100644 --- a/app/portainer/views/endpoints/edit/endpoint.html +++ b/app/portainer/views/endpoints/edit/endpoint.html @@ -165,8 +165,9 @@
diff --git a/app/portainer/views/endpoints/edit/endpointController.js b/app/portainer/views/endpoints/edit/endpointController.js index aa023d5099d50..b01a5f1adfa34 100644 --- a/app/portainer/views/endpoints/edit/endpointController.js +++ b/app/portainer/views/endpoints/edit/endpointController.js @@ -41,12 +41,12 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, var TLSMode = securityData.TLSMode; var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only'); var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only'); - + var payload = { Name: endpoint.Name, PublicURL: endpoint.PublicURL, GroupID: endpoint.GroupId, - Tags: endpoint.Tags, + TagIds: endpoint.TagIds, TLS: TLS, TLSSkipVerify: TLSSkipVerify, TLSSkipClientVerify: TLSSkipClientVerify, @@ -96,7 +96,7 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, $q.all({ endpoint: EndpointService.endpoint($transition$.params().id), groups: GroupService.groups(), - tags: TagService.tagNames() + tags: TagService.tags() }) .then(function success(data) { var endpoint = data.endpoint; diff --git a/app/portainer/views/groups/create/createGroupController.js b/app/portainer/views/groups/create/createGroupController.js index 0f1e86ad3015e..109e70ca18458 100644 --- a/app/portainer/views/groups/create/createGroupController.js +++ b/app/portainer/views/groups/create/createGroupController.js @@ -32,7 +32,7 @@ function ($q, $scope, $state, GroupService, EndpointService, TagService, Notific }; function initView() { - TagService.tagNames() + TagService.tags() .then((tags) => { $scope.availableTags = tags; $scope.associatedEndpoints = []; diff --git a/app/portainer/views/groups/edit/groupController.js b/app/portainer/views/groups/edit/groupController.js index ed3967828b505..d173e40b1ff65 100644 --- a/app/portainer/views/groups/edit/groupController.js +++ b/app/portainer/views/groups/edit/groupController.js @@ -28,7 +28,7 @@ function ($q, $scope, $state, $transition$, GroupService, TagService, Notificati $q.all({ group: GroupService.group(groupId), - tags: TagService.tagNames() + tags: TagService.tags() }) .then(function success(data) { $scope.group = data.group; diff --git a/app/portainer/views/home/home.html b/app/portainer/views/home/home.html index dea3dff6c9c7b..043cfa1f05f22 100644 --- a/app/portainer/views/home/home.html +++ b/app/portainer/views/home/home.html @@ -34,6 +34,7 @@ Date: Mon, 6 Apr 2020 00:06:59 +0300 Subject: [PATCH 06/40] chore(yarn): change start:client to start webpack dev server (#3595) * chore(yarn): change start:client to start webpack dev server * Update package.json Co-authored-by: Anthony Lapenna --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9cc2882dae5b7..050de62d35e88 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "clean": "grunt clean:all", "start": "grunt clean:all && grunt start", "start:server": "grunt clean:server && grunt start:server", - "start:client": "grunt clean:client && grunt start:client", + "dev:client": "grunt clean:client && webpack-dev-server --config=./webpack/webpack.develop.js", "build:server:offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' && mv -f portainer ../../../dist/portainer", "clean:all": "grunt clean:all" }, From db8b3d6e5a26aac3a32de1aa27b3a32e7b721ae6 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 8 Apr 2020 10:56:24 +0300 Subject: [PATCH 07/40] create tag from tag selector (#3640) * feat(tags): add button to save tag when doesn't exist * feat(endpoints): allow the creating of tags in endpoint edit * feat(groups): allow user to create tags in create group * feat(groups): allow user to create tags in edit group * feat(endpoint): allow user to create tags from endpoint create * feat(tags): allow the creation of a new tag from dropdown * feat(tag): replace "add" with "create" * feat(tags): show tags input when not tags * feat(tags): hide create message when not allowed * refactor(tags): replace component controller with class * refactor(tags): replace native methods with lodash * refactor(tags): remove unused onChangeTags function * refactor(tags): remove on-change binding * style(tags): remove white space * refactor(endpoint-groups): move controller to separate file * fix(groups): allow admin to create tag in group form * refactor(endpoints): wrap async function with try catch and $async * style(tags): wrap arrow function args with parenthesis * refactor(endpoints): return $async functions * refactor(tags): throw error in the format Notification expects --- .../components/forms/group-form/group-form.js | 92 +----------------- .../forms/group-form/groupForm.html | 4 +- .../forms/group-form/groupFormController.js | 94 +++++++++++++++++++ .../components/tag-selector/tag-selector.js | 2 + .../components/tag-selector/tagSelector.html | 8 +- .../tag-selector/tagSelectorController.js | 73 +++++++++----- app/portainer/services/api/tagService.js | 10 +- .../create/createEndpointController.js | 24 ++++- .../endpoints/create/createendpoint.html | 6 +- .../views/endpoints/edit/endpoint.html | 4 +- .../endpoints/edit/endpointController.js | 25 ++++- .../groups/create/createGroupController.js | 19 +++- .../views/groups/create/creategroup.html | 1 + app/portainer/views/groups/edit/group.html | 1 + .../views/groups/edit/groupController.js | 21 ++++- 15 files changed, 245 insertions(+), 139 deletions(-) create mode 100644 app/portainer/components/forms/group-form/groupFormController.js diff --git a/app/portainer/components/forms/group-form/group-form.js b/app/portainer/components/forms/group-form/group-form.js index 10e12f62c114b..db298082aea2c 100644 --- a/app/portainer/components/forms/group-form/group-form.js +++ b/app/portainer/components/forms/group-form/group-form.js @@ -1,92 +1,5 @@ -import _ from 'lodash-es'; import angular from 'angular'; - -class GroupFormController { - /* @ngInject */ - constructor($q, EndpointService, GroupService, Notifications) { - this.$q = $q; - this.EndpointService = EndpointService; - this.GroupService = GroupService; - this.Notifications = Notifications; - - this.associateEndpoint = this.associateEndpoint.bind(this); - this.dissociateEndpoint = this.dissociateEndpoint.bind(this); - this.getPaginatedEndpointsByGroup = this.getPaginatedEndpointsByGroup.bind(this); - } - - $onInit() { - this.state = { - available: { - limit: '10', - filter: '', - pageNumber: 1, - totalCount: 0 - }, - associated: { - limit: '10', - filter: '', - pageNumber: 1, - totalCount: 0 - } - }; - } - associateEndpoint(endpoint) { - if (this.pageType === 'create' && !_.includes(this.associatedEndpoints, endpoint)) { - this.associatedEndpoints.push(endpoint); - } else if (this.pageType === 'edit') { - this.GroupService.addEndpoint(this.model.Id, endpoint) - .then(() => { - this.Notifications.success('Success', 'Endpoint successfully added to group'); - this.reloadTablesContent(); - }) - .catch((err) => this.Notifications.error('Error', err, 'Unable to add endpoint to group')); - } - } - - dissociateEndpoint(endpoint) { - if (this.pageType === 'create') { - _.remove(this.associatedEndpoints, (item) => item.Id === endpoint.Id); - } else if (this.pageType === 'edit') { - this.GroupService.removeEndpoint(this.model.Id, endpoint.Id) - .then(() => { - this.Notifications.success('Success', 'Endpoint successfully removed from group'); - this.reloadTablesContent(); - }) - .catch((err) => this.Notifications.error('Error', err, 'Unable to remove endpoint from group')); - } - } - - reloadTablesContent() { - this.getPaginatedEndpointsByGroup(this.pageType, 'available'); - this.getPaginatedEndpointsByGroup(this.pageType, 'associated'); - this.GroupService.group(this.model.Id) - .then((data) => { - this.model = data; - }) - } - - getPaginatedEndpointsByGroup(pageType, tableType) { - if (tableType === 'available') { - const context = this.state.available; - const start = (context.pageNumber - 1) * context.limit + 1; - this.EndpointService.endpointsByGroup(start, context.limit, context.filter, 1) - .then((data) => { - this.availableEndpoints = data.value; - this.state.available.totalCount = data.totalCount; - }); - } else if (tableType === 'associated' && pageType === 'edit') { - const groupId = this.model.Id ? this.model.Id : 1; - const context = this.state.associated; - const start = (context.pageNumber - 1) * context.limit + 1; - this.EndpointService.endpointsByGroup(start, context.limit, context.filter, groupId) - .then((data) => { - this.associatedEndpoints = data.value; - this.state.associated.totalCount = data.totalCount; - }); - } - // ignore (associated + create) group as there is no backend pagination for this table - } -} +import GroupFormController from './groupFormController'; angular.module('portainer.app').component('groupForm', { templateUrl: './groupForm.html', @@ -102,6 +15,7 @@ angular.module('portainer.app').component('groupForm', { removeLabelAction: '<', formAction: '<', formActionLabel: '@', - actionInProgress: '<' + actionInProgress: '<', + onCreateTag: '<' } }); diff --git a/app/portainer/components/forms/group-form/groupForm.html b/app/portainer/components/forms/group-form/groupForm.html index addd43a0186ca..5a0526b0229f3 100644 --- a/app/portainer/components/forms/group-form/groupForm.html +++ b/app/portainer/components/forms/group-form/groupForm.html @@ -28,9 +28,11 @@
diff --git a/app/portainer/components/forms/group-form/groupFormController.js b/app/portainer/components/forms/group-form/groupFormController.js new file mode 100644 index 0000000000000..95db59e603a27 --- /dev/null +++ b/app/portainer/components/forms/group-form/groupFormController.js @@ -0,0 +1,94 @@ +import _ from 'lodash-es'; +import angular from 'angular'; + +class GroupFormController { + /* @ngInject */ + constructor($q, EndpointService, GroupService, Notifications, Authentication) { + this.$q = $q; + this.EndpointService = EndpointService; + this.GroupService = GroupService; + this.Notifications = Notifications; + this.Authentication = Authentication; + + this.associateEndpoint = this.associateEndpoint.bind(this); + this.dissociateEndpoint = this.dissociateEndpoint.bind(this); + this.getPaginatedEndpointsByGroup = this.getPaginatedEndpointsByGroup.bind(this); + } + + $onInit() { + this.state = { + available: { + limit: '10', + filter: '', + pageNumber: 1, + totalCount: 0 + }, + associated: { + limit: '10', + filter: '', + pageNumber: 1, + totalCount: 0 + }, + allowCreateTag: this.Authentication.isAdmin() + }; + } + associateEndpoint(endpoint) { + if (this.pageType === 'create' && !_.includes(this.associatedEndpoints, endpoint)) { + this.associatedEndpoints.push(endpoint); + } else if (this.pageType === 'edit') { + this.GroupService.addEndpoint(this.model.Id, endpoint) + .then(() => { + this.Notifications.success('Success', 'Endpoint successfully added to group'); + this.reloadTablesContent(); + }) + .catch((err) => this.Notifications.error('Error', err, 'Unable to add endpoint to group')); + } + } + + dissociateEndpoint(endpoint) { + if (this.pageType === 'create') { + _.remove(this.associatedEndpoints, (item) => item.Id === endpoint.Id); + } else if (this.pageType === 'edit') { + this.GroupService.removeEndpoint(this.model.Id, endpoint.Id) + .then(() => { + this.Notifications.success('Success', 'Endpoint successfully removed from group'); + this.reloadTablesContent(); + }) + .catch((err) => this.Notifications.error('Error', err, 'Unable to remove endpoint from group')); + } + } + + reloadTablesContent() { + this.getPaginatedEndpointsByGroup(this.pageType, 'available'); + this.getPaginatedEndpointsByGroup(this.pageType, 'associated'); + this.GroupService.group(this.model.Id) + .then((data) => { + this.model = data; + }) + } + + getPaginatedEndpointsByGroup(pageType, tableType) { + if (tableType === 'available') { + const context = this.state.available; + const start = (context.pageNumber - 1) * context.limit + 1; + this.EndpointService.endpointsByGroup(start, context.limit, context.filter, 1) + .then((data) => { + this.availableEndpoints = data.value; + this.state.available.totalCount = data.totalCount; + }); + } else if (tableType === 'associated' && pageType === 'edit') { + const groupId = this.model.Id ? this.model.Id : 1; + const context = this.state.associated; + const start = (context.pageNumber - 1) * context.limit + 1; + this.EndpointService.endpointsByGroup(start, context.limit, context.filter, groupId) + .then((data) => { + this.associatedEndpoints = data.value; + this.state.associated.totalCount = data.totalCount; + }); + } + // ignore (associated + create) group as there is no backend pagination for this table + } +} + +angular.module('portainer.app').controller('GroupFormController', GroupFormController); +export default GroupFormController; \ No newline at end of file diff --git a/app/portainer/components/tag-selector/tag-selector.js b/app/portainer/components/tag-selector/tag-selector.js index 540b1690235a2..fc05755cfe10b 100644 --- a/app/portainer/components/tag-selector/tag-selector.js +++ b/app/portainer/components/tag-selector/tag-selector.js @@ -4,5 +4,7 @@ angular.module('portainer.app').component('tagSelector', { bindings: { tags: '<', model: '=', + onCreate: '<', + allowCreate: '<' }, }); diff --git a/app/portainer/components/tag-selector/tagSelector.html b/app/portainer/components/tag-selector/tagSelector.html index d453f1accd03a..144f046249439 100644 --- a/app/portainer/components/tag-selector/tagSelector.html +++ b/app/portainer/components/tag-selector/tagSelector.html @@ -15,24 +15,24 @@ -
+
-
+
No tags available.
-
+
No tags matching your filter. diff --git a/app/portainer/components/tag-selector/tagSelectorController.js b/app/portainer/components/tag-selector/tagSelectorController.js index 3d64485e39117..40b99d3e5df89 100644 --- a/app/portainer/components/tag-selector/tagSelectorController.js +++ b/app/portainer/components/tag-selector/tagSelectorController.js @@ -1,35 +1,62 @@ +import angular from 'angular'; import _ from 'lodash-es'; -angular.module('portainer.app').controller('TagSelectorController', function() { - this.$onInit = function() { - this.state.selectedTags = _.map(this.model, (id) => _.find(this.tags, (t) => t.Id === id)); - }; +class TagSelectorController { + /* @ngInject */ + constructor() { + this.state = { + selectedValue: '', + selectedTags: [], + noResult: false, + }; + } - this.state = { - selectedValue: '', - selectedTags: [], - noResult: false, - }; + removeTag(tag) { + _.remove(this.model, (id) => tag.Id === id); + _.remove(this.state.selectedTags, { Id: tag.Id }); + } - this.selectTag = function($item) { + selectTag($item) { this.state.selectedValue = ''; - this.model.push($item.Id); + if ($item.create && this.allowCreate) { + this.onCreate($item.value); + return; + } this.state.selectedTags.push($item); - }; + this.model.push($item.Id); + } - this.removeTag = function removeTag(tag) { - _.remove(this.state.selectedTags, { Id: tag.Id }); - _.remove(this.model, (id) => id === tag.Id); - }; + filterTags(searchValue) { + let filteredTags = _.filter(this.tags, (tag) => !_.includes(this.model, tag.Id)); + if (!searchValue) { + return filteredTags.slice(0, 7); + } + + const exactTag = _.find(this.tags, (tag) => tag.Name === searchValue); + filteredTags = _.filter(filteredTags, (tag) => _.includes(tag.Name.toLowerCase(), searchValue.toLowerCase())); + if (exactTag || !this.allowCreate) { + return filteredTags.slice(0, 7); + } + + return filteredTags.slice(0, 6).concat({ Name: `Create "${searchValue}"`, create: true, value: searchValue }); + } + + generateSelectedTags(model, tags) { + this.state.selectedTags = _.map(model, (id) => _.find(tags, (t) => t.Id === id)); + } - this.filterSelected = filterSelected.bind(this); + $onInit() { + this.generateSelectedTags(this.model, this.tags); + } - function filterSelected($item) { - if (!this.model) { - return true; + $onChanges({ tags, model }) { + const tagsValue = tags && tags.currentValue ? tags.currentValue : this.tags; + const modelValue = model && model.currentValue ? model.currentValue : this.model; + if (modelValue && tagsValue) { + this.generateSelectedTags(modelValue, tagsValue); } - return !_.includes(this.model, $item.Id); } - window._remove = _.remove; -}); +} +export default TagSelectorController; +angular.module('portainer.app').controller('TagSelectorController', TagSelectorController); diff --git a/app/portainer/services/api/tagService.js b/app/portainer/services/api/tagService.js index b8c6e3918a4c1..d7c952dd2d489 100644 --- a/app/portainer/services/api/tagService.js +++ b/app/portainer/services/api/tagService.js @@ -35,12 +35,16 @@ angular.module('portainer.app') return deferred.promise; }; - service.createTag = function(name) { + service.createTag = async function(name) { var payload = { Name: name }; - - return Tags.create({}, payload).$promise; + try { + const tag = await Tags.create({}, payload).$promise; + return new TagViewModel(tag); + } catch(err) { + throw { msg: 'Unable to create tag', err }; + } }; service.deleteTag = function(id) { diff --git a/app/portainer/views/endpoints/create/createEndpointController.js b/app/portainer/views/endpoints/create/createEndpointController.js index 968aa041ba5cc..3bb7a989fc53f 100644 --- a/app/portainer/views/endpoints/create/createEndpointController.js +++ b/app/portainer/views/endpoints/create/createEndpointController.js @@ -1,12 +1,12 @@ import {EndpointSecurityFormData} from '../../../components/endpointSecurity/porEndpointSecurityModel'; -angular.module('portainer.app') -.controller('CreateEndpointController', ['$q', '$scope', '$state', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'Notifications', -function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, TagService, Notifications) { +angular.module('portainer.app').controller('CreateEndpointController', +function CreateEndpointController($async, $q, $scope, $state, $filter, clipboard, EndpointService, GroupService, TagService, Notifications, Authentication) { $scope.state = { EnvironmentType: 'agent', - actionInProgress: false + actionInProgress: false, + allowCreateTag: Authentication.isAdmin() }; $scope.formValues = { @@ -84,6 +84,20 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.formValues.TagIds = $scope.formValues.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) { $scope.state.actionInProgress = true; EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) @@ -133,4 +147,4 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, } initView(); -}]); +}); diff --git a/app/portainer/views/endpoints/create/createendpoint.html b/app/portainer/views/endpoints/create/createendpoint.html index 89308e1791ffe..9554534642e52 100644 --- a/app/portainer/views/endpoints/create/createendpoint.html +++ b/app/portainer/views/endpoints/create/createendpoint.html @@ -259,10 +259,12 @@
+ allow-create="state.allowCreateTag" + on-create="onCreateTag" + >
diff --git a/app/portainer/views/endpoints/edit/endpoint.html b/app/portainer/views/endpoints/edit/endpoint.html index d33e8bd7ac798..827a9aa97d355 100644 --- a/app/portainer/views/endpoints/edit/endpoint.html +++ b/app/portainer/views/endpoints/edit/endpoint.html @@ -165,9 +165,11 @@
diff --git a/app/portainer/views/endpoints/edit/endpointController.js b/app/portainer/views/endpoints/edit/endpointController.js index b01a5f1adfa34..451b6955f3fb3 100644 --- a/app/portainer/views/endpoints/edit/endpointController.js +++ b/app/portainer/views/endpoints/edit/endpointController.js @@ -3,8 +3,8 @@ import uuidv4 from 'uuid/v4'; import {EndpointSecurityFormData} from '../../../components/endpointSecurity/porEndpointSecurityModel'; angular.module('portainer.app') -.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications', -function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications) { +.controller('EndpointController', +function EndpointController($async, $q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications, Authentication) { if (!$scope.applicationState.application.endpointManagement) { $state.go('portainer.endpoints'); @@ -13,13 +13,16 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, $scope.state = { uploadInProgress: false, actionInProgress: false, - deploymentTab: 0 + deploymentTab: 0, + allowCreate: Authentication.isAdmin() }; $scope.formValues = { SecurityFormData: new EndpointSecurityFormData() }; + + $scope.copyEdgeAgentDeploymentCommand = function() { if ($scope.state.deploymentTab === 0) { clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID + ' -e EDGE_KEY=' + $scope.endpoint.EdgeKey +' -e CAP_HOST_MANAGEMENT=1 -v portainer_agent_data:/data --name portainer_edge_agent portainer/agent'); @@ -34,6 +37,20 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, $('#copyNotificationEdgeKey').show().fadeOut(2500); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.endpoint.TagIds = $scope.endpoint.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + $scope.updateEndpoint = function() { var endpoint = $scope.endpoint; var securityData = $scope.formValues.SecurityFormData; @@ -120,4 +137,4 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, } initView(); -}]); +}); diff --git a/app/portainer/views/groups/create/createGroupController.js b/app/portainer/views/groups/create/createGroupController.js index 109e70ca18458..9308c93b09e9c 100644 --- a/app/portainer/views/groups/create/createGroupController.js +++ b/app/portainer/views/groups/create/createGroupController.js @@ -1,8 +1,7 @@ import {EndpointGroupDefaultModel} from '../../../models/group'; angular.module('portainer.app') -.controller('CreateGroupController', ['$q', '$scope', '$state', 'GroupService', 'EndpointService', 'TagService', 'Notifications', -function ($q, $scope, $state, GroupService, EndpointService, TagService, Notifications) { +.controller('CreateGroupController', function CreateGroupController($async, $scope, $state, GroupService, TagService, Notifications) { $scope.state = { actionInProgress: false @@ -31,6 +30,20 @@ function ($q, $scope, $state, GroupService, EndpointService, TagService, Notific }); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.model.TagIds = $scope.model.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + function initView() { TagService.tags() .then((tags) => { @@ -45,4 +58,4 @@ function ($q, $scope, $state, GroupService, EndpointService, TagService, Notific } initView(); -}]); +}); diff --git a/app/portainer/views/groups/create/creategroup.html b/app/portainer/views/groups/create/creategroup.html index 480d04f81c693..e89b01e088674 100644 --- a/app/portainer/views/groups/create/creategroup.html +++ b/app/portainer/views/groups/create/creategroup.html @@ -21,6 +21,7 @@ form-action="create" form-action-label="Create the group" action-in-progress="state.actionInProgress" + on-create-tag="onCreateTag" > diff --git a/app/portainer/views/groups/edit/group.html b/app/portainer/views/groups/edit/group.html index 7dca1c8078797..25b8f19a08eb6 100644 --- a/app/portainer/views/groups/edit/group.html +++ b/app/portainer/views/groups/edit/group.html @@ -21,6 +21,7 @@ form-action="update" form-action-label="Update the group" action-in-progress="state.actionInProgress" + on-create-tag="onCreateTag" > diff --git a/app/portainer/views/groups/edit/groupController.js b/app/portainer/views/groups/edit/groupController.js index d173e40b1ff65..a9bcf1812b1d4 100644 --- a/app/portainer/views/groups/edit/groupController.js +++ b/app/portainer/views/groups/edit/groupController.js @@ -1,6 +1,5 @@ -angular.module('portainer.app') -.controller('GroupController', ['$q', '$scope', '$state', '$transition$', 'GroupService', 'TagService', 'Notifications', -function ($q, $scope, $state, $transition$, GroupService, TagService, Notifications) { +angular.module('portainer.app').controller('GroupController', +function GroupController($q, $async, $scope, $state, $transition$, GroupService, TagService, Notifications) { $scope.state = { actionInProgress: false @@ -23,6 +22,20 @@ function ($q, $scope, $state, $transition$, GroupService, TagService, Notificati }); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.group.TagIds = $scope.group.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + function initView() { var groupId = $transition$.params().id; @@ -41,4 +54,4 @@ function ($q, $scope, $state, $transition$, GroupService, TagService, Notificati } initView(); -}]); +}); From df13f3b4ccab69bb8b6c7579cc27907c52556933 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 8 Apr 2020 12:03:52 +0300 Subject: [PATCH 08/40] chore(yarn): add start:client script back (#3691) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 050de62d35e88..f8cb84f4d784a 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "clean": "grunt clean:all", "start": "grunt clean:all && grunt start", "start:server": "grunt clean:server && grunt start:server", + "start:client": "grunt clean:client && grunt start:client", "dev:client": "grunt clean:client && webpack-dev-server --config=./webpack/webpack.develop.js", "build:server:offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' && mv -f portainer ../../../dist/portainer", "clean:all": "grunt clean:all" From 2542d30a09f5e9403d9a1f8e175dc0f0e3358d2c Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 8 Apr 2020 12:14:50 +0300 Subject: [PATCH 09/40] feat(endpoints): filter by ids and/or tag ids (#3690) * feat(endpoints): add filter by tagIds * refactor(endpoints): change endpoints service to query by tagIds * fix(endpoints): filter by tags * feat(endpoints): filter by endpoint groups tags * feat(endpoints): filter by ids --- api/http/handler/endpoints/endpoint_list.go | 69 +++++++++++++++++++ app/portainer/services/api/endpointService.js | 4 +- .../views/endpoints/endpointsController.js | 4 +- app/portainer/views/home/homeController.js | 4 +- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/api/http/handler/endpoints/endpoint_list.go b/api/http/handler/endpoints/endpoint_list.go index 1d98296bfb389..c68f8d9ec7678 100644 --- a/api/http/handler/endpoints/endpoint_list.go +++ b/api/http/handler/endpoints/endpoint_list.go @@ -30,6 +30,12 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht limit, _ := request.RetrieveNumericQueryParameter(r, "limit", true) endpointType, _ := request.RetrieveNumericQueryParameter(r, "type", true) + var tagIDs []portainer.TagID + request.RetrieveJSONQueryParameter(r, "tagIds", &tagIDs, true) + + var endpointIDs []portainer.EndpointID + request.RetrieveJSONQueryParameter(r, "endpointIds", &endpointIDs, true) + endpointGroups, err := handler.EndpointGroupService.EndpointGroups() if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoint groups from the database", err} @@ -47,6 +53,10 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext) + if endpointIDs != nil { + filteredEndpoints = filteredEndpointsByIds(filteredEndpoints, endpointIDs) + } + if groupID != 0 { filteredEndpoints = filterEndpointsByGroupID(filteredEndpoints, portainer.EndpointGroupID(groupID)) } @@ -67,6 +77,10 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht filteredEndpoints = filterEndpointsByType(filteredEndpoints, portainer.EndpointType(endpointType)) } + if tagIDs != nil { + filteredEndpoints = filteredEndpointsByTags(filteredEndpoints, tagIDs, endpointGroups) + } + filteredEndpointCount := len(filteredEndpoints) paginatedEndpoints := paginateEndpoints(filteredEndpoints, start, limit) @@ -187,3 +201,58 @@ func convertTagIDsToTags(tagsMap map[portainer.TagID]string, tagIDs []portainer. } return tags } + +func filteredEndpointsByTags(endpoints []portainer.Endpoint, tagIDs []portainer.TagID, endpointGroups []portainer.EndpointGroup) []portainer.Endpoint { + filteredEndpoints := make([]portainer.Endpoint, 0) + + for _, endpoint := range endpoints { + missingTags := make(map[portainer.TagID]bool) + for _, tagID := range tagIDs { + missingTags[tagID] = true + } + for _, tagID := range endpoint.TagIDs { + if missingTags[tagID] { + delete(missingTags, tagID) + } + } + missingTags = endpointGroupHasTags(endpoint.GroupID, endpointGroups, missingTags) + if len(missingTags) == 0 { + filteredEndpoints = append(filteredEndpoints, endpoint) + } + } + return filteredEndpoints +} + +func endpointGroupHasTags(groupID portainer.EndpointGroupID, groups []portainer.EndpointGroup, missingTags map[portainer.TagID]bool) map[portainer.TagID]bool { + var endpointGroup portainer.EndpointGroup + for _, group := range groups { + if group.ID == groupID { + endpointGroup = group + break + } + } + for _, tagID := range endpointGroup.TagIDs { + if missingTags[tagID] { + delete(missingTags, tagID) + } + } + return missingTags +} + +func filteredEndpointsByIds(endpoints []portainer.Endpoint, ids []portainer.EndpointID) []portainer.Endpoint { + filteredEndpoints := make([]portainer.Endpoint, 0) + + idsSet := make(map[portainer.EndpointID]bool) + for _, id := range ids { + idsSet[id] = true + } + + for _, endpoint := range endpoints { + if idsSet[endpoint.ID] { + filteredEndpoints = append(filteredEndpoints, endpoint) + } + } + + return filteredEndpoints + +} diff --git a/app/portainer/services/api/endpointService.js b/app/portainer/services/api/endpointService.js index 6de03711d63c6..7c60887fc8223 100644 --- a/app/portainer/services/api/endpointService.js +++ b/app/portainer/services/api/endpointService.js @@ -8,8 +8,8 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) { return Endpoints.get({id: endpointID}).$promise; }; - service.endpoints = function(start, limit, search) { - return Endpoints.query({start, limit, search}).$promise; + service.endpoints = function(start, limit, { search, type, tagIds, endpointIds } = {}) { + return Endpoints.query({ start, limit, search, type, tagIds, endpointIds }).$promise; }; service.snapshotEndpoints = function() { diff --git a/app/portainer/views/endpoints/endpointsController.js b/app/portainer/views/endpoints/endpointsController.js index 3b2313700f745..2f4f1d2684673 100644 --- a/app/portainer/views/endpoints/endpointsController.js +++ b/app/portainer/views/endpoints/endpointsController.js @@ -22,10 +22,10 @@ function ($q, $scope, $state, EndpointService, GroupService, EndpointHelper, Not }; $scope.getPaginatedEndpoints = getPaginatedEndpoints; - function getPaginatedEndpoints(lastId, limit, filter) { + function getPaginatedEndpoints(lastId, limit, search) { const deferred = $q.defer(); $q.all({ - endpoints: EndpointService.endpoints(lastId, limit, filter), + endpoints: EndpointService.endpoints(lastId, limit, { search }), groups: GroupService.groups() }) .then(function success(data) { diff --git a/app/portainer/views/home/homeController.js b/app/portainer/views/home/homeController.js index 255c7114675a3..319fd5a083c91 100644 --- a/app/portainer/views/home/homeController.js +++ b/app/portainer/views/home/homeController.js @@ -146,10 +146,10 @@ angular.module('portainer.app') } $scope.getPaginatedEndpoints = getPaginatedEndpoints; - function getPaginatedEndpoints(lastId, limit, filter) { + function getPaginatedEndpoints(lastId, limit, search) { const deferred = $q.defer(); $q.all({ - endpoints: EndpointService.endpoints(lastId, limit, filter), + endpoints: EndpointService.endpoints(lastId, limit, {search}), groups: GroupService.groups() }) .then(function success(data) { From 6da38d466b02632a66e3496fe135f0ce9c3d6393 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 9 Apr 2020 00:26:11 +0300 Subject: [PATCH 10/40] refactor(project): sort portainer types and interface definitions (#3694) * refactor(portainer): sort types * style(portainer): add comment about role service * refactor(portainer): sort interface types * refactor(portainer): sort enums --- api/portainer.go | 1246 +++++++++++++++++++++++----------------------- 1 file changed, 624 insertions(+), 622 deletions(-) diff --git a/api/portainer.go b/api/portainer.go index f0339677df13c..acf16961b0a96 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -3,10 +3,33 @@ package portainer import "time" type ( - // Pair defines a key/value string pair - Pair struct { - Name string `json:"name"` - Value string `json:"value"` + // AccessPolicy represent a policy that can be associated to a user or team + AccessPolicy struct { + RoleID RoleID `json:"RoleId"` + } + + // APIOperationAuthorizationRequest represent an request for the authorization to execute an API operation + APIOperationAuthorizationRequest struct { + Path string + Method string + Authorizations Authorizations + } + + // AuthenticationMethod represents the authentication method used to authenticate a user + AuthenticationMethod int + + // Authorization represents an authorization associated to an operation + Authorization string + + // Authorizations represents a set of authorizations associated to a role + Authorizations map[Authorization]bool + + // AzureCredentials represents the credentials used to connect to an Azure + // environment. + AzureCredentials struct { + ApplicationID string `json:"ApplicationID"` + TenantID string `json:"TenantID"` + AuthenticationKey string `json:"AuthenticationKey"` } // CLIFlags represents the available flags on the CLI @@ -39,165 +62,209 @@ type ( SnapshotInterval *string } - // Status represents the application status - Status struct { - Authentication bool `json:"Authentication"` - EndpointManagement bool `json:"EndpointManagement"` - Snapshot bool `json:"Snapshot"` - Analytics bool `json:"Analytics"` - Version string `json:"Version"` + // CLIService represents a service for managing CLI + CLIService interface { + ParseFlags(version string) (*CLIFlags, error) + ValidateFlags(flags *CLIFlags) error } - // LDAPSettings represents the settings used to connect to a LDAP server - LDAPSettings struct { - AnonymousMode bool `json:"AnonymousMode"` - ReaderDN string `json:"ReaderDN"` - Password string `json:"Password,omitempty"` - URL string `json:"URL"` - TLSConfig TLSConfiguration `json:"TLSConfig"` - StartTLS bool `json:"StartTLS"` - SearchSettings []LDAPSearchSettings `json:"SearchSettings"` - GroupSearchSettings []LDAPGroupSearchSettings `json:"GroupSearchSettings"` - AutoCreateUsers bool `json:"AutoCreateUsers"` + // DataStore defines the interface to manage the data + DataStore interface { + Open() error + Init() error + Close() error + MigrateData() error } - // OAuthSettings represents the settings used to authorize with an authorization server - OAuthSettings struct { - ClientID string `json:"ClientID"` - ClientSecret string `json:"ClientSecret,omitempty"` - AccessTokenURI string `json:"AccessTokenURI"` - AuthorizationURI string `json:"AuthorizationURI"` - ResourceURI string `json:"ResourceURI"` - RedirectURI string `json:"RedirectURI"` - UserIdentifier string `json:"UserIdentifier"` - Scopes string `json:"Scopes"` - OAuthAutoCreateUsers bool `json:"OAuthAutoCreateUsers"` - DefaultTeamID TeamID `json:"DefaultTeamID"` + // DockerHub represents all the required information to connect and use the + // Docker Hub + DockerHub struct { + Authentication bool `json:"Authentication"` + Username string `json:"Username"` + Password string `json:"Password,omitempty"` } - // TLSConfiguration represents a TLS configuration - TLSConfiguration struct { - TLS bool `json:"TLS"` - TLSSkipVerify bool `json:"TLSSkipVerify"` + // EdgeSchedule represents a scheduled job that can run on Edge environments. + EdgeSchedule struct { + ID ScheduleID `json:"Id"` + CronExpression string `json:"CronExpression"` + Script string `json:"Script"` + Version int `json:"Version"` + Endpoints []EndpointID `json:"Endpoints"` + } + + // Endpoint represents a Docker endpoint with all the info required + // to connect to it + Endpoint struct { + ID EndpointID `json:"Id"` + Name string `json:"Name"` + Type EndpointType `json:"Type"` + URL string `json:"URL"` + GroupID EndpointGroupID `json:"GroupId"` + PublicURL string `json:"PublicURL"` + TLSConfig TLSConfiguration `json:"TLSConfig"` + Extensions []EndpointExtension `json:"Extensions"` + AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"` + TagIDs []TagID `json:"TagIds"` + Status EndpointStatus `json:"Status"` + Snapshots []Snapshot `json:"Snapshots"` + UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"` + TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"` + EdgeID string `json:"EdgeID,omitempty"` + EdgeKey string `json:"EdgeKey"` + // Deprecated fields + // Deprecated in DBVersion == 4 + TLS bool `json:"TLS,omitempty"` TLSCACertPath string `json:"TLSCACert,omitempty"` TLSCertPath string `json:"TLSCert,omitempty"` TLSKeyPath string `json:"TLSKey,omitempty"` - } - // LDAPSearchSettings represents settings used to search for users in a LDAP server - LDAPSearchSettings struct { - BaseDN string `json:"BaseDN"` - Filter string `json:"Filter"` - UserNameAttribute string `json:"UserNameAttribute"` + // Deprecated in DBVersion == 18 + AuthorizedUsers []UserID `json:"AuthorizedUsers"` + AuthorizedTeams []TeamID `json:"AuthorizedTeams"` + + // Deprecated in DBVersion == 22 + Tags []string `json:"Tags"` } - // LDAPGroupSearchSettings represents settings used to search for groups in a LDAP server - LDAPGroupSearchSettings struct { - GroupBaseDN string `json:"GroupBaseDN"` - GroupFilter string `json:"GroupFilter"` - GroupAttribute string `json:"GroupAttribute"` + // EndpointAuthorizations represents the authorizations associated to a set of endpoints + EndpointAuthorizations map[EndpointID]Authorizations + + // EndpointExtension represents a deprecated form of Portainer extension + // TODO: legacy extension management + EndpointExtension struct { + Type EndpointExtensionType `json:"Type"` + URL string `json:"URL"` } - // Settings represents the application settings - Settings struct { - LogoURL string `json:"LogoURL"` - BlackListedLabels []Pair `json:"BlackListedLabels"` - AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"` - LDAPSettings LDAPSettings `json:"LDAPSettings"` - OAuthSettings OAuthSettings `json:"OAuthSettings"` - AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"` - AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"` - AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"` - SnapshotInterval string `json:"SnapshotInterval"` - TemplatesURL string `json:"TemplatesURL"` - EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"` - EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"` + // EndpointExtensionType represents the type of an endpoint extension. Only + // one extension of each type can be associated to an endpoint + EndpointExtensionType int + + // EndpointGroup represents a group of endpoints + EndpointGroup struct { + ID EndpointGroupID `json:"Id"` + Name string `json:"Name"` + Description string `json:"Description"` + UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"` + TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"` + TagIDs []TagID `json:"TagIds"` // Deprecated fields - DisplayDonationHeader bool - DisplayExternalContributors bool - } + Labels []Pair `json:"Labels"` - // User represents a user account - User struct { - ID UserID `json:"Id"` - Username string `json:"Username"` - Password string `json:"Password,omitempty"` - Role UserRole `json:"Role"` - PortainerAuthorizations Authorizations `json:"PortainerAuthorizations"` - EndpointAuthorizations EndpointAuthorizations `json:"EndpointAuthorizations"` + // Deprecated in DBVersion == 18 + AuthorizedUsers []UserID `json:"AuthorizedUsers"` + AuthorizedTeams []TeamID `json:"AuthorizedTeams"` + + // Deprecated in DBVersion == 22 + Tags []string `json:"Tags"` } - // UserID represents a user identifier - UserID int + // EndpointGroupID represents an endpoint group identifier + EndpointGroupID int - // UserRole represents the role of a user. It can be either an administrator - // or a regular user - UserRole int + // EndpointID represents an endpoint identifier + EndpointID int - // AuthenticationMethod represents the authentication method used to authenticate a user - AuthenticationMethod int + // EndpointStatus represents the status of an endpoint + EndpointStatus int - // Team represents a list of user accounts - Team struct { - ID TeamID `json:"Id"` - Name string `json:"Name"` - } + // EndpointSyncJob represents a scheduled job that synchronize endpoints based on an external file + EndpointSyncJob struct{} - // TeamID represents a team identifier - TeamID int + // EndpointType represents the type of an endpoint + EndpointType int - // TeamMembership represents a membership association between a user and a team - TeamMembership struct { - ID TeamMembershipID `json:"Id"` - UserID UserID `json:"UserID"` - TeamID TeamID `json:"TeamID"` - Role MembershipRole `json:"Role"` + // Extension represents a Portainer extension + Extension struct { + ID ExtensionID `json:"Id"` + Enabled bool `json:"Enabled"` + Name string `json:"Name,omitempty"` + ShortDescription string `json:"ShortDescription,omitempty"` + Description string `json:"Description,omitempty"` + DescriptionURL string `json:"DescriptionURL,omitempty"` + Price string `json:"Price,omitempty"` + PriceDescription string `json:"PriceDescription,omitempty"` + Deal bool `json:"Deal,omitempty"` + Available bool `json:"Available,omitempty"` + License LicenseInformation `json:"License,omitempty"` + Version string `json:"Version"` + UpdateAvailable bool `json:"UpdateAvailable"` + ShopURL string `json:"ShopURL,omitempty"` + Images []string `json:"Images,omitempty"` + Logo string `json:"Logo,omitempty"` } - // TeamMembershipID represents a team membership identifier - TeamMembershipID int + // ExtensionID represents a extension identifier + ExtensionID int - // MembershipRole represents the role of a user within a team - MembershipRole int + // GitlabRegistryData represents data required for gitlab registry to work + GitlabRegistryData struct { + ProjectID int `json:"ProjectId"` + InstanceURL string `json:"InstanceURL"` + ProjectPath string `json:"ProjectPath"` + } - // TokenData represents the data embedded in a JWT token - TokenData struct { - ID UserID - Username string - Role UserRole + // JobType represents a job type + JobType int + + // LDAPGroupSearchSettings represents settings used to search for groups in a LDAP server + LDAPGroupSearchSettings struct { + GroupBaseDN string `json:"GroupBaseDN"` + GroupFilter string `json:"GroupFilter"` + GroupAttribute string `json:"GroupAttribute"` } - // StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier) - StackID int + // LDAPSearchSettings represents settings used to search for users in a LDAP server + LDAPSearchSettings struct { + BaseDN string `json:"BaseDN"` + Filter string `json:"Filter"` + UserNameAttribute string `json:"UserNameAttribute"` + } - // StackType represents the type of the stack (compose v2, stack deploy v3) - StackType int + // LDAPSettings represents the settings used to connect to a LDAP server + LDAPSettings struct { + AnonymousMode bool `json:"AnonymousMode"` + ReaderDN string `json:"ReaderDN"` + Password string `json:"Password,omitempty"` + URL string `json:"URL"` + TLSConfig TLSConfiguration `json:"TLSConfig"` + StartTLS bool `json:"StartTLS"` + SearchSettings []LDAPSearchSettings `json:"SearchSettings"` + GroupSearchSettings []LDAPGroupSearchSettings `json:"GroupSearchSettings"` + AutoCreateUsers bool `json:"AutoCreateUsers"` + } - // Stack represents a Docker stack created via docker stack deploy - Stack struct { - ID StackID `json:"Id"` - Name string `json:"Name"` - Type StackType `json:"Type"` - EndpointID EndpointID `json:"EndpointId"` - SwarmID string `json:"SwarmId"` - EntryPoint string `json:"EntryPoint"` - Env []Pair `json:"Env"` - ResourceControl *ResourceControl `json:"ResourceControl"` - ProjectPath string + // LicenseInformation represents information about an extension license + LicenseInformation struct { + LicenseKey string `json:"LicenseKey,omitempty"` + Company string `json:"Company,omitempty"` + Expiration string `json:"Expiration,omitempty"` + Valid bool `json:"Valid,omitempty"` } - // RegistryID represents a registry identifier - RegistryID int + // MembershipRole represents the role of a user within a team + MembershipRole int - // RegistryType represents a type of registry - RegistryType int + // OAuthSettings represents the settings used to authorize with an authorization server + OAuthSettings struct { + ClientID string `json:"ClientID"` + ClientSecret string `json:"ClientSecret,omitempty"` + AccessTokenURI string `json:"AccessTokenURI"` + AuthorizationURI string `json:"AuthorizationURI"` + ResourceURI string `json:"ResourceURI"` + RedirectURI string `json:"RedirectURI"` + UserIdentifier string `json:"UserIdentifier"` + Scopes string `json:"Scopes"` + OAuthAutoCreateUsers bool `json:"OAuthAutoCreateUsers"` + DefaultTeamID TeamID `json:"DefaultTeamID"` + } - // GitlabRegistryData represents data required for gitlab registry to work - GitlabRegistryData struct { - ProjectID int `json:"ProjectId"` - InstanceURL string `json:"InstanceURL"` - ProjectPath string `json:"ProjectPath"` + // Pair defines a key/value string pair + Pair struct { + Name string `json:"name"` + Value string `json:"value"` } // Registry represents a Docker registry with all the info required @@ -221,6 +288,9 @@ type ( AuthorizedTeams []TeamID `json:"AuthorizedTeams"` } + // RegistryID represents a registry identifier + RegistryID int + // RegistryManagementConfiguration represents a configuration that can be used to query // the registry API via the registry management extension. RegistryManagementConfiguration struct { @@ -231,75 +301,35 @@ type ( TLSConfig TLSConfiguration `json:"TLSConfig"` } - // DockerHub represents all the required information to connect and use the - // Docker Hub - DockerHub struct { - Authentication bool `json:"Authentication"` - Username string `json:"Username"` - Password string `json:"Password,omitempty"` - } - - // EndpointID represents an endpoint identifier - EndpointID int + // RegistryType represents a type of registry + RegistryType int - // EndpointType represents the type of an endpoint - EndpointType int + // ResourceAccessLevel represents the level of control associated to a resource + ResourceAccessLevel int - // EndpointStatus represents the status of an endpoint - EndpointStatus int + // ResourceControl represent a reference to a Docker resource with specific access controls + ResourceControl struct { + ID ResourceControlID `json:"Id"` + ResourceID string `json:"ResourceId"` + SubResourceIDs []string `json:"SubResourceIds"` + Type ResourceControlType `json:"Type"` + UserAccesses []UserResourceAccess `json:"UserAccesses"` + TeamAccesses []TeamResourceAccess `json:"TeamAccesses"` + Public bool `json:"Public"` + AdministratorsOnly bool `json:"AdministratorsOnly"` + System bool `json:"System"` - // Endpoint represents a Docker endpoint with all the info required - // to connect to it - Endpoint struct { - ID EndpointID `json:"Id"` - Name string `json:"Name"` - Type EndpointType `json:"Type"` - URL string `json:"URL"` - GroupID EndpointGroupID `json:"GroupId"` - PublicURL string `json:"PublicURL"` - TLSConfig TLSConfiguration `json:"TLSConfig"` - Extensions []EndpointExtension `json:"Extensions"` - AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"` - TagIDs []TagID `json:"TagIds"` - Status EndpointStatus `json:"Status"` - Snapshots []Snapshot `json:"Snapshots"` - UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"` - TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"` - EdgeID string `json:"EdgeID,omitempty"` - EdgeKey string `json:"EdgeKey"` // Deprecated fields - // Deprecated in DBVersion == 4 - TLS bool `json:"TLS,omitempty"` - TLSCACertPath string `json:"TLSCACert,omitempty"` - TLSCertPath string `json:"TLSCert,omitempty"` - TLSKeyPath string `json:"TLSKey,omitempty"` - - // Deprecated in DBVersion == 18 - AuthorizedUsers []UserID `json:"AuthorizedUsers"` - AuthorizedTeams []TeamID `json:"AuthorizedTeams"` - - // Deprecated in DBVersion == 22 - Tags []string `json:"Tags"` + // Deprecated in DBVersion == 2 + OwnerID UserID `json:"OwnerId,omitempty"` + AccessLevel ResourceAccessLevel `json:"AccessLevel,omitempty"` } - // Authorization represents an authorization associated to an operation - Authorization string - - // Authorizations represents a set of authorizations associated to a role - Authorizations map[Authorization]bool - - // EndpointAuthorizations represents the authorizations associated to a set of endpoints - EndpointAuthorizations map[EndpointID]Authorizations - - // APIOperationAuthorizationRequest represent an request for the authorization to execute an API operation - APIOperationAuthorizationRequest struct { - Path string - Method string - Authorizations Authorizations - } + // ResourceControlID represents a resource control identifier + ResourceControlID int - // RoleID represents a role identifier - RoleID int + // ResourceControlType represents the type of resource associated to the resource control (volume, container, service...) + ResourceControlType int // Role represents a set of authorizations that can be associated to a user or // to a team. @@ -311,36 +341,8 @@ type ( Priority int `json:"Priority"` } - // AccessPolicy represent a policy that can be associated to a user or team - AccessPolicy struct { - RoleID RoleID `json:"RoleId"` - } - - // UserAccessPolicies represent the association of an access policy and a user - UserAccessPolicies map[UserID]AccessPolicy - // TeamAccessPolicies represent the association of an access policy and a team - TeamAccessPolicies map[TeamID]AccessPolicy - - // ScheduleID represents a schedule identifier. - ScheduleID int - - // JobType represents a job type - JobType int - - // ScriptExecutionJob represents a scheduled job that can execute a script via a privileged container - ScriptExecutionJob struct { - Endpoints []EndpointID - Image string - ScriptPath string - RetryCount int - RetryInterval int - } - - // SnapshotJob represents a scheduled job that can create endpoint snapshots - SnapshotJob struct{} - - // EndpointSyncJob represents a scheduled job that synchronize endpoints based on an external file - EndpointSyncJob struct{} + // RoleID represents a role identifier + RoleID int // Schedule represents a scheduled job. // It only contains a pointer to one of the JobRunner implementations @@ -359,36 +361,36 @@ type ( EndpointSyncJob *EndpointSyncJob } - // EdgeSchedule represents a scheduled job that can run on Edge environments. - EdgeSchedule struct { - ID ScheduleID `json:"Id"` - CronExpression string `json:"CronExpression"` - Script string `json:"Script"` - Version int `json:"Version"` - Endpoints []EndpointID `json:"Endpoints"` - } - - // WebhookID represents a webhook identifier. - WebhookID int - - // WebhookType represents the type of resource a webhook is related to - WebhookType int + // ScheduleID represents a schedule identifier. + ScheduleID int - // Webhook represents a url webhook that can be used to update a service - Webhook struct { - ID WebhookID `json:"Id"` - Token string `json:"Token"` - ResourceID string `json:"ResourceId"` - EndpointID EndpointID `json:"EndpointId"` - WebhookType WebhookType `json:"Type"` + // ScriptExecutionJob represents a scheduled job that can execute a script via a privileged container + ScriptExecutionJob struct { + Endpoints []EndpointID + Image string + ScriptPath string + RetryCount int + RetryInterval int } - // AzureCredentials represents the credentials used to connect to an Azure - // environment. - AzureCredentials struct { - ApplicationID string `json:"ApplicationID"` - TenantID string `json:"TenantID"` - AuthenticationKey string `json:"AuthenticationKey"` + // Settings represents the application settings + Settings struct { + LogoURL string `json:"LogoURL"` + BlackListedLabels []Pair `json:"BlackListedLabels"` + AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"` + LDAPSettings LDAPSettings `json:"LDAPSettings"` + OAuthSettings OAuthSettings `json:"OAuthSettings"` + AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"` + AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"` + AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"` + SnapshotInterval string `json:"SnapshotInterval"` + TemplatesURL string `json:"TemplatesURL"` + EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"` + EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"` + + // Deprecated fields + DisplayDonationHeader bool + DisplayExternalContributors bool } // Snapshot represents a snapshot of a specific endpoint at a specific time @@ -409,6 +411,9 @@ type ( SnapshotRaw SnapshotRaw `json:"SnapshotRaw"` } + // SnapshotJob represents a scheduled job that can create endpoint snapshots + SnapshotJob struct{} + // SnapshotRaw represents all the information related to a snapshot as returned by the Docker API SnapshotRaw struct { Containers interface{} `json:"Containers"` @@ -419,91 +424,72 @@ type ( Version interface{} `json:"Version"` } - // EndpointGroupID represents an endpoint group identifier - EndpointGroupID int - - // EndpointGroup represents a group of endpoints - EndpointGroup struct { - ID EndpointGroupID `json:"Id"` - Name string `json:"Name"` - Description string `json:"Description"` - UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"` - TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"` - TagIDs []TagID `json:"TagIds"` + // Stack represents a Docker stack created via docker stack deploy + Stack struct { + ID StackID `json:"Id"` + Name string `json:"Name"` + Type StackType `json:"Type"` + EndpointID EndpointID `json:"EndpointId"` + SwarmID string `json:"SwarmId"` + EntryPoint string `json:"EntryPoint"` + Env []Pair `json:"Env"` + ResourceControl *ResourceControl `json:"ResourceControl"` + ProjectPath string + } - // Deprecated fields - Labels []Pair `json:"Labels"` + // StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier) + StackID int - // Deprecated in DBVersion == 18 - AuthorizedUsers []UserID `json:"AuthorizedUsers"` - AuthorizedTeams []TeamID `json:"AuthorizedTeams"` + // StackType represents the type of the stack (compose v2, stack deploy v3) + StackType int - // Deprecated in DBVersion == 22 - Tags []string `json:"Tags"` + // Status represents the application status + Status struct { + Authentication bool `json:"Authentication"` + EndpointManagement bool `json:"EndpointManagement"` + Snapshot bool `json:"Snapshot"` + Analytics bool `json:"Analytics"` + Version string `json:"Version"` } - // EndpointExtension represents a deprecated form of Portainer extension - // TODO: legacy extension management - EndpointExtension struct { - Type EndpointExtensionType `json:"Type"` - URL string `json:"URL"` + // Tag represents a tag that can be associated to a resource + Tag struct { + ID TagID + Name string `json:"Name"` } - // EndpointExtensionType represents the type of an endpoint extension. Only - // one extension of each type can be associated to an endpoint - EndpointExtensionType int - - // ResourceControlID represents a resource control identifier - ResourceControlID int - - // ResourceControl represent a reference to a Docker resource with specific access controls - ResourceControl struct { - ID ResourceControlID `json:"Id"` - ResourceID string `json:"ResourceId"` - SubResourceIDs []string `json:"SubResourceIds"` - Type ResourceControlType `json:"Type"` - UserAccesses []UserResourceAccess `json:"UserAccesses"` - TeamAccesses []TeamResourceAccess `json:"TeamAccesses"` - Public bool `json:"Public"` - AdministratorsOnly bool `json:"AdministratorsOnly"` - System bool `json:"System"` + // TagID represents a tag identifier + TagID int - // Deprecated fields - // Deprecated in DBVersion == 2 - OwnerID UserID `json:"OwnerId,omitempty"` - AccessLevel ResourceAccessLevel `json:"AccessLevel,omitempty"` + // Team represents a list of user accounts + Team struct { + ID TeamID `json:"Id"` + Name string `json:"Name"` } - // ResourceControlType represents the type of resource associated to the resource control (volume, container, service...) - ResourceControlType int + // TeamAccessPolicies represent the association of an access policy and a team + TeamAccessPolicies map[TeamID]AccessPolicy - // UserResourceAccess represents the level of control on a resource for a specific user - UserResourceAccess struct { - UserID UserID `json:"UserId"` - AccessLevel ResourceAccessLevel `json:"AccessLevel"` + // TeamID represents a team identifier + TeamID int + + // TeamMembership represents a membership association between a user and a team + TeamMembership struct { + ID TeamMembershipID `json:"Id"` + UserID UserID `json:"UserID"` + TeamID TeamID `json:"TeamID"` + Role MembershipRole `json:"Role"` } + // TeamMembershipID represents a team membership identifier + TeamMembershipID int + // TeamResourceAccess represents the level of control on a resource for a specific team TeamResourceAccess struct { TeamID TeamID `json:"TeamId"` AccessLevel ResourceAccessLevel `json:"AccessLevel"` } - // TagID represents a tag identifier - TagID int - - // Tag represents a tag that can be associated to a resource - Tag struct { - ID TagID - Name string `json:"Name"` - } - - // TemplateID represents a template identifier - TemplateID int - - // TemplateType represents the type of a template - TemplateType int - // Template represents an application template Template struct { // Mandatory container/stack fields @@ -550,63 +536,51 @@ type ( Select []TemplateEnvSelect `json:"select,omitempty"` } - // TemplateVolume represents a template volume configuration - TemplateVolume struct { - Container string `json:"container"` - Bind string `json:"bind,omitempty"` - ReadOnly bool `json:"readonly,omitempty"` + // TemplateEnvSelect represents text/value pair that will be displayed as a choice for the + // template user + TemplateEnvSelect struct { + Text string `json:"text"` + Value string `json:"value"` + Default bool `json:"default"` } + // TemplateID represents a template identifier + TemplateID int + // TemplateRepository represents the git repository configuration for a template TemplateRepository struct { URL string `json:"url"` StackFile string `json:"stackfile"` } - // TemplateEnvSelect represents text/value pair that will be displayed as a choice for the - // template user - TemplateEnvSelect struct { - Text string `json:"text"` - Value string `json:"value"` - Default bool `json:"default"` + // TemplateType represents the type of a template + TemplateType int + + // TemplateVolume represents a template volume configuration + TemplateVolume struct { + Container string `json:"container"` + Bind string `json:"bind,omitempty"` + ReadOnly bool `json:"readonly,omitempty"` } - // ResourceAccessLevel represents the level of control associated to a resource - ResourceAccessLevel int + // TLSConfiguration represents a TLS configuration + TLSConfiguration struct { + TLS bool `json:"TLS"` + TLSSkipVerify bool `json:"TLSSkipVerify"` + TLSCACertPath string `json:"TLSCACert,omitempty"` + TLSCertPath string `json:"TLSCert,omitempty"` + TLSKeyPath string `json:"TLSKey,omitempty"` + } // TLSFileType represents a type of TLS file required to connect to a Docker endpoint. // It can be either a TLS CA file, a TLS certificate file or a TLS key file TLSFileType int - // ExtensionID represents a extension identifier - ExtensionID int - - // Extension represents a Portainer extension - Extension struct { - ID ExtensionID `json:"Id"` - Enabled bool `json:"Enabled"` - Name string `json:"Name,omitempty"` - ShortDescription string `json:"ShortDescription,omitempty"` - Description string `json:"Description,omitempty"` - DescriptionURL string `json:"DescriptionURL,omitempty"` - Price string `json:"Price,omitempty"` - PriceDescription string `json:"PriceDescription,omitempty"` - Deal bool `json:"Deal,omitempty"` - Available bool `json:"Available,omitempty"` - License LicenseInformation `json:"License,omitempty"` - Version string `json:"Version"` - UpdateAvailable bool `json:"UpdateAvailable"` - ShopURL string `json:"ShopURL,omitempty"` - Images []string `json:"Images,omitempty"` - Logo string `json:"Logo,omitempty"` - } - - // LicenseInformation represents information about an extension license - LicenseInformation struct { - LicenseKey string `json:"LicenseKey,omitempty"` - Company string `json:"Company,omitempty"` - Expiration string `json:"Expiration,omitempty"` - Valid bool `json:"Valid,omitempty"` + // TokenData represents the data embedded in a JWT token + TokenData struct { + ID UserID + Username string + Role UserRole } // TunnelDetails represents information associated to a tunnel @@ -623,64 +597,72 @@ type ( PrivateKeySeed string `json:"PrivateKeySeed"` } - // CLIService represents a service for managing CLI - CLIService interface { - ParseFlags(version string) (*CLIFlags, error) - ValidateFlags(flags *CLIFlags) error + // User represents a user account + User struct { + ID UserID `json:"Id"` + Username string `json:"Username"` + Password string `json:"Password,omitempty"` + Role UserRole `json:"Role"` + PortainerAuthorizations Authorizations `json:"PortainerAuthorizations"` + EndpointAuthorizations EndpointAuthorizations `json:"EndpointAuthorizations"` } - // DataStore defines the interface to manage the data - DataStore interface { - Open() error - Init() error - Close() error - MigrateData() error - } + // UserAccessPolicies represent the association of an access policy and a user + UserAccessPolicies map[UserID]AccessPolicy - // Server defines the interface to serve the API - Server interface { - Start() error + // UserID represents a user identifier + UserID int + + // UserResourceAccess represents the level of control on a resource for a specific user + UserResourceAccess struct { + UserID UserID `json:"UserId"` + AccessLevel ResourceAccessLevel `json:"AccessLevel"` } - // UserService represents a service for managing user data - UserService interface { - User(ID UserID) (*User, error) - UserByUsername(username string) (*User, error) - Users() ([]User, error) - UsersByRole(role UserRole) ([]User, error) - CreateUser(user *User) error - UpdateUser(ID UserID, user *User) error - DeleteUser(ID UserID) error + // UserRole represents the role of a user. It can be either an administrator + // or a regular user + UserRole int + + // Webhook represents a url webhook that can be used to update a service + Webhook struct { + ID WebhookID `json:"Id"` + Token string `json:"Token"` + ResourceID string `json:"ResourceId"` + EndpointID EndpointID `json:"EndpointId"` + WebhookType WebhookType `json:"Type"` } - RoleService interface { - Role(ID RoleID) (*Role, error) - Roles() ([]Role, error) - CreateRole(role *Role) error - UpdateRole(ID RoleID, role *Role) error + // WebhookID represents a webhook identifier. + WebhookID int + + // WebhookType represents the type of resource a webhook is related to + WebhookType int + + // ComposeStackManager represents a service to manage Compose stacks + ComposeStackManager interface { + Up(stack *Stack, endpoint *Endpoint) error + Down(stack *Stack, endpoint *Endpoint) error } - // TeamService represents a service for managing user data - TeamService interface { - Team(ID TeamID) (*Team, error) - TeamByName(name string) (*Team, error) - Teams() ([]Team, error) - CreateTeam(team *Team) error - UpdateTeam(ID TeamID, team *Team) error - DeleteTeam(ID TeamID) error + // CryptoService represents a service for encrypting/hashing data + CryptoService interface { + Hash(data string) (string, error) + CompareHashAndData(hash string, data string) error } - // TeamMembershipService represents a service for managing team membership data - TeamMembershipService interface { - TeamMembership(ID TeamMembershipID) (*TeamMembership, error) - TeamMemberships() ([]TeamMembership, error) - TeamMembershipsByUserID(userID UserID) ([]TeamMembership, error) - TeamMembershipsByTeamID(teamID TeamID) ([]TeamMembership, error) - CreateTeamMembership(membership *TeamMembership) error - UpdateTeamMembership(ID TeamMembershipID, membership *TeamMembership) error - DeleteTeamMembership(ID TeamMembershipID) error - DeleteTeamMembershipByUserID(userID UserID) error - DeleteTeamMembershipByTeamID(teamID TeamID) error + // DigitalSignatureService represents a service to manage digital signatures + DigitalSignatureService interface { + ParseKeyPair(private, public []byte) error + GenerateKeyPair() ([]byte, []byte, error) + EncodedPublicKey() string + PEMHeaders() (string, string) + CreateSignature(message string) (string, error) + } + + // DockerHubService represents a service for managing the DockerHub object + DockerHubService interface { + DockerHub() (*DockerHub, error) + UpdateDockerHub(registry *DockerHub) error } // EndpointService represents a service for managing endpoint data @@ -703,96 +685,14 @@ type ( DeleteEndpointGroup(ID EndpointGroupID) error } - // RegistryService represents a service for managing registry data - RegistryService interface { - Registry(ID RegistryID) (*Registry, error) - Registries() ([]Registry, error) - CreateRegistry(registry *Registry) error - UpdateRegistry(ID RegistryID, registry *Registry) error - DeleteRegistry(ID RegistryID) error - } - - // StackService represents a service for managing stack data - StackService interface { - Stack(ID StackID) (*Stack, error) - StackByName(name string) (*Stack, error) - Stacks() ([]Stack, error) - CreateStack(stack *Stack) error - UpdateStack(ID StackID, stack *Stack) error - DeleteStack(ID StackID) error - GetNextIdentifier() int - } - - // DockerHubService represents a service for managing the DockerHub object - DockerHubService interface { - DockerHub() (*DockerHub, error) - UpdateDockerHub(registry *DockerHub) error - } - - // SettingsService represents a service for managing application settings - SettingsService interface { - Settings() (*Settings, error) - UpdateSettings(settings *Settings) error - } - - // VersionService represents a service for managing version data - VersionService interface { - DBVersion() (int, error) - StoreDBVersion(version int) error - } - - // TunnelServerService represents a service for managing data associated to the tunnel server - TunnelServerService interface { - Info() (*TunnelServerInfo, error) - UpdateInfo(info *TunnelServerInfo) error - } - - // WebhookService represents a service for managing webhook data. - WebhookService interface { - Webhooks() ([]Webhook, error) - Webhook(ID WebhookID) (*Webhook, error) - CreateWebhook(portainer *Webhook) error - WebhookByResourceID(resourceID string) (*Webhook, error) - WebhookByToken(token string) (*Webhook, error) - DeleteWebhook(serviceID WebhookID) error - } - - // ResourceControlService represents a service for managing resource control data - ResourceControlService interface { - ResourceControl(ID ResourceControlID) (*ResourceControl, error) - ResourceControlByResourceIDAndType(resourceID string, resourceType ResourceControlType) (*ResourceControl, error) - ResourceControls() ([]ResourceControl, error) - CreateResourceControl(rc *ResourceControl) error - UpdateResourceControl(ID ResourceControlID, resourceControl *ResourceControl) error - DeleteResourceControl(ID ResourceControlID) error - } - - // ScheduleService represents a service for managing schedule data - ScheduleService interface { - Schedule(ID ScheduleID) (*Schedule, error) - Schedules() ([]Schedule, error) - SchedulesByJobType(jobType JobType) ([]Schedule, error) - CreateSchedule(schedule *Schedule) error - UpdateSchedule(ID ScheduleID, schedule *Schedule) error - DeleteSchedule(ID ScheduleID) error - GetNextIdentifier() int - } - - // TagService represents a service for managing tag data - TagService interface { - Tags() ([]Tag, error) - Tag(ID TagID) (*Tag, error) - CreateTag(tag *Tag) error - DeleteTag(ID TagID) error - } - - // TemplateService represents a service for managing template data - TemplateService interface { - Templates() ([]Template, error) - Template(ID TemplateID) (*Template, error) - CreateTemplate(template *Template) error - UpdateTemplate(ID TemplateID, template *Template) error - DeleteTemplate(ID TemplateID) error + // ExtensionManager represents a service used to manage extensions + ExtensionManager interface { + FetchExtensionDefinitions() ([]Extension, error) + InstallExtension(extension *Extension, licenseKey string, archiveFileName string, extensionArchive []byte) error + EnableExtension(extension *Extension, licenseKey string) error + DisableExtension(extension *Extension) error + UpdateExtension(extension *Extension, version string) error + StartExtensions() error } // ExtensionService represents a service for managing extension data @@ -803,27 +703,6 @@ type ( DeleteExtension(ID ExtensionID) error } - // CryptoService represents a service for encrypting/hashing data - CryptoService interface { - Hash(data string) (string, error) - CompareHashAndData(hash string, data string) error - } - - // DigitalSignatureService represents a service to manage digital signatures - DigitalSignatureService interface { - ParseKeyPair(private, public []byte) error - GenerateKeyPair() ([]byte, []byte, error) - EncodedPublicKey() string - PEMHeaders() (string, string) - CreateSignature(message string) (string, error) - } - - // JWTService represents a service for managing JWT tokens - JWTService interface { - GenerateToken(data *TokenData) (string, error) - ParseAndVerifyToken(token string) (*TokenData, error) - } - // FileService represents a service for managing files FileService interface { GetFileContent(filePath string) ([]byte, error) @@ -853,6 +732,12 @@ type ( ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password string) error } + // JobRunner represents a service that can be used to run a job + JobRunner interface { + Run() + GetSchedule() *Schedule + } + // JobScheduler represents a service to run jobs on a periodic basis JobScheduler interface { ScheduleJob(runner JobRunner) error @@ -862,15 +747,15 @@ type ( Start() } - // JobRunner represents a service that can be used to run a job - JobRunner interface { - Run() - GetSchedule() *Schedule + // JobService represents a service to manage job execution on hosts + JobService interface { + ExecuteScript(endpoint *Endpoint, nodeName, image string, script []byte, schedule *Schedule) error } - // Snapshotter represents a service used to create endpoint snapshots - Snapshotter interface { - CreateSnapshot(endpoint *Endpoint) (*Snapshot, error) + // JWTService represents a service for managing JWT tokens + JWTService interface { + GenerateToken(data *TokenData) (string, error) + ParseAndVerifyToken(token string) (*TokenData, error) } // LDAPService represents a service used to authenticate users against a LDAP/AD @@ -880,6 +765,83 @@ type ( GetUserGroups(username string, settings *LDAPSettings) ([]string, error) } + // RegistryService represents a service for managing registry data + RegistryService interface { + Registry(ID RegistryID) (*Registry, error) + Registries() ([]Registry, error) + CreateRegistry(registry *Registry) error + UpdateRegistry(ID RegistryID, registry *Registry) error + DeleteRegistry(ID RegistryID) error + } + + // ResourceControlService represents a service for managing resource control data + ResourceControlService interface { + ResourceControl(ID ResourceControlID) (*ResourceControl, error) + ResourceControlByResourceIDAndType(resourceID string, resourceType ResourceControlType) (*ResourceControl, error) + ResourceControls() ([]ResourceControl, error) + CreateResourceControl(rc *ResourceControl) error + UpdateResourceControl(ID ResourceControlID, resourceControl *ResourceControl) error + DeleteResourceControl(ID ResourceControlID) error + } + + // ReverseTunnelService represensts a service used to manage reverse tunnel connections. + ReverseTunnelService interface { + StartTunnelServer(addr, port string, snapshotter Snapshotter) error + GenerateEdgeKey(url, host string, endpointIdentifier int) string + SetTunnelStatusToActive(endpointID EndpointID) + SetTunnelStatusToRequired(endpointID EndpointID) error + SetTunnelStatusToIdle(endpointID EndpointID) + GetTunnelDetails(endpointID EndpointID) *TunnelDetails + AddSchedule(endpointID EndpointID, schedule *EdgeSchedule) + RemoveSchedule(scheduleID ScheduleID) + } + + // RoleService represents a service for managing user roles + RoleService interface { + Role(ID RoleID) (*Role, error) + Roles() ([]Role, error) + CreateRole(role *Role) error + UpdateRole(ID RoleID, role *Role) error + } + + // ScheduleService represents a service for managing schedule data + ScheduleService interface { + Schedule(ID ScheduleID) (*Schedule, error) + Schedules() ([]Schedule, error) + SchedulesByJobType(jobType JobType) ([]Schedule, error) + CreateSchedule(schedule *Schedule) error + UpdateSchedule(ID ScheduleID, schedule *Schedule) error + DeleteSchedule(ID ScheduleID) error + GetNextIdentifier() int + } + + // SettingsService represents a service for managing application settings + SettingsService interface { + Settings() (*Settings, error) + UpdateSettings(settings *Settings) error + } + + // Server defines the interface to serve the API + Server interface { + Start() error + } + + // Snapshotter represents a service used to create endpoint snapshots + Snapshotter interface { + CreateSnapshot(endpoint *Endpoint) (*Snapshot, error) + } + + // StackService represents a service for managing stack data + StackService interface { + Stack(ID StackID) (*Stack, error) + StackByName(name string) (*Stack, error) + Stacks() ([]Stack, error) + CreateStack(stack *Stack) error + UpdateStack(ID StackID, stack *Stack) error + DeleteStack(ID StackID) error + GetNextIdentifier() int + } + // SwarmStackManager represents a service to manage Swarm stacks SwarmStackManager interface { Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) @@ -888,37 +850,77 @@ type ( Remove(stack *Stack, endpoint *Endpoint) error } - // ComposeStackManager represents a service to manage Compose stacks - ComposeStackManager interface { - Up(stack *Stack, endpoint *Endpoint) error - Down(stack *Stack, endpoint *Endpoint) error + // TagService represents a service for managing tag data + TagService interface { + Tags() ([]Tag, error) + Tag(ID TagID) (*Tag, error) + CreateTag(tag *Tag) error + DeleteTag(ID TagID) error } - // JobService represents a service to manage job execution on hosts - JobService interface { - ExecuteScript(endpoint *Endpoint, nodeName, image string, script []byte, schedule *Schedule) error + // TeamService represents a service for managing user data + TeamService interface { + Team(ID TeamID) (*Team, error) + TeamByName(name string) (*Team, error) + Teams() ([]Team, error) + CreateTeam(team *Team) error + UpdateTeam(ID TeamID, team *Team) error + DeleteTeam(ID TeamID) error } - // ExtensionManager represents a service used to manage extensions - ExtensionManager interface { - FetchExtensionDefinitions() ([]Extension, error) - InstallExtension(extension *Extension, licenseKey string, archiveFileName string, extensionArchive []byte) error - EnableExtension(extension *Extension, licenseKey string) error - DisableExtension(extension *Extension) error - UpdateExtension(extension *Extension, version string) error - StartExtensions() error + // TeamMembershipService represents a service for managing team membership data + TeamMembershipService interface { + TeamMembership(ID TeamMembershipID) (*TeamMembership, error) + TeamMemberships() ([]TeamMembership, error) + TeamMembershipsByUserID(userID UserID) ([]TeamMembership, error) + TeamMembershipsByTeamID(teamID TeamID) ([]TeamMembership, error) + CreateTeamMembership(membership *TeamMembership) error + UpdateTeamMembership(ID TeamMembershipID, membership *TeamMembership) error + DeleteTeamMembership(ID TeamMembershipID) error + DeleteTeamMembershipByUserID(userID UserID) error + DeleteTeamMembershipByTeamID(teamID TeamID) error } - // ReverseTunnelService represensts a service used to manage reverse tunnel connections. - ReverseTunnelService interface { - StartTunnelServer(addr, port string, snapshotter Snapshotter) error - GenerateEdgeKey(url, host string, endpointIdentifier int) string - SetTunnelStatusToActive(endpointID EndpointID) - SetTunnelStatusToRequired(endpointID EndpointID) error - SetTunnelStatusToIdle(endpointID EndpointID) - GetTunnelDetails(endpointID EndpointID) *TunnelDetails - AddSchedule(endpointID EndpointID, schedule *EdgeSchedule) - RemoveSchedule(scheduleID ScheduleID) + // TemplateService represents a service for managing template data + TemplateService interface { + Templates() ([]Template, error) + Template(ID TemplateID) (*Template, error) + CreateTemplate(template *Template) error + UpdateTemplate(ID TemplateID, template *Template) error + DeleteTemplate(ID TemplateID) error + } + + // TunnelServerService represents a service for managing data associated to the tunnel server + TunnelServerService interface { + Info() (*TunnelServerInfo, error) + UpdateInfo(info *TunnelServerInfo) error + } + + // UserService represents a service for managing user data + UserService interface { + User(ID UserID) (*User, error) + UserByUsername(username string) (*User, error) + Users() ([]User, error) + UsersByRole(role UserRole) ([]User, error) + CreateUser(user *User) error + UpdateUser(ID UserID, user *User) error + DeleteUser(ID UserID) error + } + + // VersionService represents a service for managing version data + VersionService interface { + DBVersion() (int, error) + StoreDBVersion(version int) error + } + + // WebhookService represents a service for managing webhook data. + WebhookService interface { + Webhooks() ([]Webhook, error) + Webhook(ID WebhookID) (*Webhook, error) + CreateWebhook(portainer *Webhook) error + WebhookByResourceID(resourceID string) (*Webhook, error) + WebhookByToken(token string) (*Webhook, error) + DeleteWebhook(serviceID WebhookID) error } ) @@ -959,12 +961,61 @@ const ( ) const ( - // TLSFileCA represents a TLS CA certificate file - TLSFileCA TLSFileType = iota - // TLSFileCert represents a TLS certificate file - TLSFileCert - // TLSFileKey represents a TLS key file - TLSFileKey + _ AuthenticationMethod = iota + // AuthenticationInternal represents the internal authentication method (authentication against Portainer API) + AuthenticationInternal + // AuthenticationLDAP represents the LDAP authentication method (authentication against a LDAP server) + AuthenticationLDAP + //AuthenticationOAuth represents the OAuth authentication method (authentication against a authorization server) + AuthenticationOAuth +) + +const ( + _ EndpointExtensionType = iota + // StoridgeEndpointExtension represents the Storidge extension + StoridgeEndpointExtension +) + +const ( + _ EndpointStatus = iota + // EndpointStatusUp is used to represent an available endpoint + EndpointStatusUp + // EndpointStatusDown is used to represent an unavailable endpoint + EndpointStatusDown +) + +const ( + _ EndpointType = iota + // DockerEnvironment represents an endpoint connected to a Docker environment + DockerEnvironment + // AgentOnDockerEnvironment represents an endpoint connected to a Portainer agent deployed on a Docker environment + AgentOnDockerEnvironment + // AzureEnvironment represents an endpoint connected to an Azure environment + AzureEnvironment + // EdgeAgentEnvironment represents an endpoint connected to an Edge agent + EdgeAgentEnvironment +) + +const ( + _ ExtensionID = iota + // RegistryManagementExtension represents the registry management extension + RegistryManagementExtension + // OAuthAuthenticationExtension represents the OAuth authentication extension + OAuthAuthenticationExtension + // RBACExtension represents the RBAC extension + RBACExtension +) + +const ( + _ JobType = iota + // ScriptExecutionJobType is a non-system job used to execute a script against a list of + // endpoints via privileged containers + ScriptExecutionJobType + // SnapshotJobType is a system job used to create endpoint snapshots + SnapshotJobType + // EndpointSyncJobType is a system job used to synchronize endpoints from + // an external definition store + EndpointSyncJobType ) const ( @@ -976,21 +1027,15 @@ const ( ) const ( - _ UserRole = iota - // AdministratorRole represents an administrator user role - AdministratorRole - // StandardUserRole represents a regular user role - StandardUserRole -) - -const ( - _ AuthenticationMethod = iota - // AuthenticationInternal represents the internal authentication method (authentication against Portainer API) - AuthenticationInternal - // AuthenticationLDAP represents the LDAP authentication method (authentication against a LDAP server) - AuthenticationLDAP - //AuthenticationOAuth represents the OAuth authentication method (authentication against a authorization server) - AuthenticationOAuth + _ RegistryType = iota + // QuayRegistry represents a Quay.io registry + QuayRegistry + // AzureRegistry represents an ACR registry + AzureRegistry + // CustomRegistry represents a custom registry + CustomRegistry + // GitlabRegistry represents a gitlab registry + GitlabRegistry ) const ( @@ -1017,24 +1062,6 @@ const ( ConfigResourceControl ) -const ( - _ EndpointExtensionType = iota - // StoridgeEndpointExtension represents the Storidge extension - StoridgeEndpointExtension -) - -const ( - _ EndpointType = iota - // DockerEnvironment represents an endpoint connected to a Docker environment - DockerEnvironment - // AgentOnDockerEnvironment represents an endpoint connected to a Portainer agent deployed on a Docker environment - AgentOnDockerEnvironment - // AzureEnvironment represents an endpoint connected to an Azure environment - AzureEnvironment - // EdgeAgentEnvironment represents an endpoint connected to an Edge agent - EdgeAgentEnvironment -) - const ( _ StackType = iota // DockerSwarmStack represents a stack managed via docker stack @@ -1054,51 +1081,26 @@ const ( ) const ( - _ EndpointStatus = iota - // EndpointStatusUp is used to represent an available endpoint - EndpointStatusUp - // EndpointStatusDown is used to represent an unavailable endpoint - EndpointStatusDown -) - -const ( - _ WebhookType = iota - // ServiceWebhook is a webhook for restarting a docker service - ServiceWebhook -) - -const ( - _ ExtensionID = iota - // RegistryManagementExtension represents the registry management extension - RegistryManagementExtension - // OAuthAuthenticationExtension represents the OAuth authentication extension - OAuthAuthenticationExtension - // RBACExtension represents the RBAC extension - RBACExtension + // TLSFileCA represents a TLS CA certificate file + TLSFileCA TLSFileType = iota + // TLSFileCert represents a TLS certificate file + TLSFileCert + // TLSFileKey represents a TLS key file + TLSFileKey ) const ( - _ JobType = iota - // ScriptExecutionJobType is a non-system job used to execute a script against a list of - // endpoints via privileged containers - ScriptExecutionJobType - // SnapshotJobType is a system job used to create endpoint snapshots - SnapshotJobType - // EndpointSyncJobType is a system job used to synchronize endpoints from - // an external definition store - EndpointSyncJobType + _ UserRole = iota + // AdministratorRole represents an administrator user role + AdministratorRole + // StandardUserRole represents a regular user role + StandardUserRole ) const ( - _ RegistryType = iota - // QuayRegistry represents a Quay.io registry - QuayRegistry - // AzureRegistry represents an ACR registry - AzureRegistry - // CustomRegistry represents a custom registry - CustomRegistry - // GitlabRegistry represents a gitlab registry - GitlabRegistry + _ WebhookType = iota + // ServiceWebhook is a webhook for restarting a docker service + ServiceWebhook ) const ( From aeea88be36d67bfcc03bd9dab65bb59a27e80d6c Mon Sep 17 00:00:00 2001 From: Neil Cresswell Date: Thu, 9 Apr 2020 09:59:14 +1200 Subject: [PATCH 11/40] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 86363fab268f2..3c87a220ed071 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,10 @@ Unlike the public demo, the playground sessions are deleted after 4 hours. Apart ## Getting help -**NOTE**: You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/ +For FORMAL Support, please purchase a support subscription from here: https://www.portainer.io/products-services/portainer-business-support/ + +For community support: You can find more information about Portainer's community support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/ + * Issues: https://github.com/portainer/portainer/issues * FAQ: https://portainer.readthedocs.io/en/latest/faq.html From 18a38d597af77f6637bd86bdf9ea8e8f03c88eea Mon Sep 17 00:00:00 2001 From: Neil Cresswell Date: Thu, 9 Apr 2020 10:00:29 +1200 Subject: [PATCH 12/40] Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 3c87a220ed071..a66d4985da1dc 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,7 @@ For community support: You can find more information about Portainer's community **_Portainer_** has full support for the following Docker versions: -* Docker 1.10 to the latest version -* Standalone Docker Swarm >= 1.2.3 _(**NOTE:** Use of Standalone Docker Swarm is being discouraged since the introduction of built-in Swarm Mode in Docker. While older versions of Portainer had support for Standalone Docker Swarm, Portainer 1.17.0 and newer **do not** support it. However, the built-in Swarm Mode of Docker is fully supported.)_ - -Partial support for the following Docker versions (some features may not be available): - -* Docker 1.9 +Portainer supports N-2 Docker Versions. ## Licensing From 6663073be165ca90f430aa4b7b391857bf95d0de Mon Sep 17 00:00:00 2001 From: Neil Cresswell Date: Thu, 9 Apr 2020 10:01:20 +1200 Subject: [PATCH 13/40] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index a66d4985da1dc..6149b69c3ee76 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,7 @@ For community support: You can find more information about Portainer's community ## Limitations -**_Portainer_** has full support for the following Docker versions: - -Portainer supports N-2 Docker Versions. +Portainer supports "Current - 2 docker versions only. Prior versions may operate, however these are not supported. ## Licensing From cf5056d9c03b62d91a25c3b9127caac838695f98 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sat, 11 Apr 2020 00:54:53 +0300 Subject: [PATCH 14/40] chore(project): add prettier for code format (#3645) * chore(project): install prettier and lint-staged * chore(project): apply prettier to html too * chore(project): git ignore eslintcache * chore(project): add a comment about format script * chore(prettier): update printWidth * chore(prettier): remove useTabs option * chore(prettier): add HTML validation * refactor(prettier): fix closing tags * feat(prettier): define angular parser for html templates * style(prettier): run prettier on codebase Co-authored-by: Anthony Lapenna --- .gitignore | 3 +- .prettierrc | 13 + app/__module.js | 4 +- .../file-uploader/file-uploader-controller.js | 4 +- .../file-uploader/file-uploader.html | 7 +- .../components/file-uploader/file-uploader.js | 4 +- .../files-datatable/files-datatable.html | 45 +- .../files-datatable/files-datatable.js | 6 +- .../host-browser/host-browser-controller.js | 26 +- .../components/host-browser/host-browser.html | 10 +- .../components/host-browser/host-browser.js | 2 +- .../components/node-selector/node-selector.js | 4 +- .../node-selector/nodeSelector.html | 4 +- .../node-selector/nodeSelectorController.js | 36 +- .../volume-browser/volume-browser.js | 4 +- .../volume-browser/volumeBrowser.html | 9 +- .../volume-browser/volumeBrowserController.js | 250 +- app/agent/models/agent.js | 4 +- app/agent/rest/agent.js | 27 +- app/agent/rest/browse.js | 58 +- app/agent/rest/host.js | 11 +- app/agent/rest/ping.js | 20 +- app/agent/rest/v1/agent.js | 25 +- app/agent/rest/v1/browse.js | 56 +- app/agent/services/agentService.js | 16 +- app/agent/services/hostBrowserService.js | 21 +- app/agent/services/pingService.js | 2 +- app/agent/services/volumeBrowserService.js | 35 +- app/app.js | 93 +- app/azure/_module.js | 90 +- .../azure-endpoint-config.js | 4 +- .../azureEndpointConfig.html | 13 +- .../azure-sidebar-content.js | 2 +- .../containerGroupsDatatable.html | 32 +- .../containerGroupsDatatable.js | 4 +- app/azure/models/container_group.js | 24 +- app/azure/models/provider.js | 2 +- app/azure/rest/azure.js | 31 +- app/azure/rest/container_group.js | 74 +- app/azure/rest/location.js | 28 +- app/azure/rest/provider.js | 28 +- app/azure/rest/resource_group.js | 28 +- app/azure/rest/subscription.js | 28 +- app/azure/services/azureService.js | 112 +- app/azure/services/containerGroupService.js | 62 +- app/azure/services/locationService.js | 43 +- app/azure/services/providerService.js | 39 +- app/azure/services/resourceGroupService.js | 43 +- app/azure/services/subscriptionService.js | 43 +- .../containerInstancesController.js | 79 +- .../containerinstances.html | 10 +- .../createContainerInstanceController.js | 176 +- .../create/createcontainerinstance.html | 35 +- .../views/dashboard/dashboardController.js | 42 +- app/config.js | 61 +- app/constants.js | 51 +- app/docker/__module.js | 1054 +-- .../container-capabilities.js | 4 +- .../containerCapabilities.html | 9 +- .../containerQuickActions.html | 27 +- .../containerQuickActions.js | 4 +- .../container-restart-policy-controller.js | 37 +- .../container-restart-policy.js | 11 +- .../dashboard-cluster-agent-info.js | 2 +- .../dashboardClusterAgentInfoController.js | 31 +- .../configs-datatable/configsDatatable.html | 38 +- .../configs-datatable/configsDatatable.js | 4 +- .../containerNetworksDatatable.html | 26 +- .../containerNetworksDatatable.js | 4 +- .../containerProcessesDatatable.html | 6 +- .../containerProcessesDatatable.js | 4 +- .../actions/containersDatatableActions.html | 68 +- .../actions/containersDatatableActions.js | 4 +- .../containersDatatableActionsController.js | 182 +- .../containersDatatable.html | 210 +- .../containersDatatable.js | 4 +- .../containersDatatableController.js | 395 +- .../events-datatable/eventsDatatable.html | 19 +- .../events-datatable/eventsDatatable.js | 4 +- .../host-jobs-datatable/jobsDatatable.html | 38 +- .../host-jobs-datatable/jobsDatatable.js | 6 +- .../jobsDatatableController.js | 259 +- .../images-datatable/imagesDatatable.html | 67 +- .../images-datatable/imagesDatatable.js | 4 +- .../imagesDatatableController.js | 123 +- .../macvlanNodesDatatable.html | 22 +- .../macvlanNodesDatatable.js | 4 +- .../networkRowContent.html | 18 +- .../network-row-content/networkRowContent.js | 37 +- .../networks-datatable/networksDatatable.html | 46 +- .../networks-datatable/networksDatatable.js | 4 +- .../networksDatatableController.js | 130 +- .../nodeTasksDatatable.html | 27 +- .../nodeTasksDatatable.js | 4 +- .../nodes-datatable/nodesDatatable.html | 29 +- .../nodes-datatable/nodesDatatable.js | 4 +- .../secrets-datatable/secretsDatatable.html | 36 +- .../secrets-datatable/secretsDatatable.js | 4 +- .../serviceTasksDatatable.html | 30 +- .../serviceTasksDatatable.js | 4 +- .../serviceTasksDatatableController.js | 155 +- .../actions/servicesDatatableActions.html | 19 +- .../actions/servicesDatatableActions.js | 4 +- .../servicesDatatableActionsController.js | 187 +- .../services-datatable/servicesDatatable.html | 55 +- .../services-datatable/servicesDatatable.js | 4 +- .../servicesDatatableController.js | 175 +- .../tasks-datatable/tasksDatatable.html | 46 +- .../tasks-datatable/tasksDatatable.js | 4 +- .../tasksDatatableController.js | 84 +- .../volumes-datatable/volumesDatatable.html | 46 +- .../volumes-datatable/volumesDatatable.js | 4 +- .../volumesDatatableController.js | 123 +- .../docker-sidebar-content.js | 4 +- .../dockerSidebarContent.html | 2 +- .../host-overview/host-overview.html | 13 +- .../components/host-overview/host-overview.js | 4 +- .../devices-panel/devices-panel.html | 6 +- .../devices-panel/devices-panel.js | 4 +- .../disks-panel/disks-panel.html | 6 +- .../disks-panel/disks-panel.js | 4 +- .../engine-details-panel.html | 8 +- .../engine-details-panel.js | 4 +- .../host-details-panel.html | 7 +- .../host-details-panel/host-details-panel.js | 4 +- .../node-availability-select-controller.js | 16 +- .../node-availability-select.html | 5 +- .../node-availability-select.js | 4 +- .../node-labels-table-controller.js | 7 +- .../node-labels-table/node-labels-table.html | 8 +- .../node-labels-table/node-labels-table.js | 4 +- .../swarm-node-details-panel-controller.js | 155 +- .../swarm-node-details-panel.html | 28 +- .../swarm-node-details-panel.js | 4 +- .../imageRegistry/por-image-registry.js | 14 +- .../imageRegistry/porImageRegistry.html | 33 +- .../porImageRegistryController.js | 6 +- .../components/log-viewer/log-viewer.js | 4 +- .../components/log-viewer/logViewer.html | 42 +- .../log-viewer/logViewerController.js | 82 +- .../network-macvlan-form.js | 6 +- .../networkMacvlanForm.html | 53 +- .../networkMacvlanFormController.js | 91 +- .../networkMacvlanFormModel.js | 4 +- .../volumesNFSForm/volumes-nfs-form.js | 4 +- .../volumesNFSForm/volumesNFSFormModel.js | 2 +- .../volumesNFSForm/volumesnfsForm.html | 31 +- app/docker/filters/filters.js | 561 +- app/docker/helpers/configHelper.js | 69 +- app/docker/helpers/constraintsHelper.js | 22 +- app/docker/helpers/containerHelper.js | 308 +- app/docker/helpers/imageHelper.js | 113 +- app/docker/helpers/infoHelper.js | 59 +- app/docker/helpers/labelHelper.js | 51 +- app/docker/helpers/logHelper.js | 36 +- app/docker/helpers/nodeHelper.js | 29 +- app/docker/helpers/secretHelper.js | 69 +- app/docker/helpers/serviceHelper.js | 469 +- app/docker/helpers/taskHelper.js | 29 +- app/docker/helpers/volumeHelper.js | 49 +- .../interceptors/containersInterceptor.js | 9 +- app/docker/interceptors/imagesInterceptor.js | 9 +- app/docker/interceptors/infoInterceptor.js | 9 +- .../interceptors/networksInterceptor.js | 9 +- app/docker/interceptors/versionInterceptor.js | 9 +- app/docker/interceptors/volumesInterceptor.js | 9 +- app/docker/models/config.js | 11 +- app/docker/models/container.js | 6 +- app/docker/models/containerCapabilities.js | 166 +- app/docker/models/event.js | 268 +- app/docker/models/network.js | 2 +- app/docker/models/node.js | 4 +- app/docker/models/porImageRegistry.js | 8 +- app/docker/models/service.js | 12 +- app/docker/rest/build.js | 45 +- app/docker/rest/commit.js | 25 +- app/docker/rest/config.js | 32 +- app/docker/rest/container.js | 193 +- app/docker/rest/exec.js | 34 +- app/docker/rest/image.js | 95 +- app/docker/rest/network.js | 75 +- app/docker/rest/node.js | 31 +- app/docker/rest/plugin.js | 26 +- app/docker/rest/response/handlers.js | 11 +- app/docker/rest/response/image.js | 9 +- app/docker/rest/secret.js | 32 +- app/docker/rest/service.js | 71 +- app/docker/rest/swarm.js | 25 +- app/docker/rest/system.js | 53 +- app/docker/rest/systemEndpoint.js | 28 +- app/docker/rest/task.js | 39 +- app/docker/rest/volume.js | 40 +- app/docker/services/buildService.js | 110 +- app/docker/services/configService.js | 125 +- app/docker/services/containerService.js | 412 +- app/docker/services/execService.js | 50 +- app/docker/services/imageService.js | 389 +- app/docker/services/networkService.js | 145 +- app/docker/services/nodeService.js | 10 +- app/docker/services/pluginService.js | 118 +- app/docker/services/secretService.js | 125 +- app/docker/services/serviceService.js | 181 +- app/docker/services/swarmService.js | 39 +- app/docker/services/systemService.js | 114 +- app/docker/services/taskService.js | 109 +- app/docker/services/volumeService.js | 178 +- app/docker/views/configs/configs.html | 14 +- app/docker/views/configs/configsController.js | 1 - .../configs/create/createConfigController.js | 43 +- .../views/configs/create/createconfig.html | 12 +- app/docker/views/configs/edit/config.html | 21 +- .../views/configs/edit/configController.js | 54 +- .../views/containers/console/attach.html | 11 +- .../console/containerConsoleController.js | 378 +- app/docker/views/containers/console/exec.html | 49 +- app/docker/views/containers/containers.html | 6 +- .../views/containers/containersController.js | 47 +- .../create/createContainerController.js | 1512 ++-- .../containers/create/createcontainer.html | 153 +- .../views/containers/edit/container.html | 561 +- .../containers/edit/containerController.js | 667 +- .../inspect/containerInspectController.js | 44 +- .../containers/inspect/containerinspect.html | 8 +- .../logs/containerLogsController.js | 138 +- .../views/containers/logs/containerlogs.html | 9 +- .../stats/containerStatsController.js | 273 +- .../containers/stats/containerstats.html | 25 +- app/docker/views/dashboard/dashboard.html | 27 +- .../views/dashboard/dashboardController.js | 102 +- app/docker/views/events/events.html | 7 +- app/docker/views/events/eventsController.js | 36 +- .../host-browser-view-controller.js | 17 +- .../host-browser-view/host-browser-view.html | 6 +- .../host-browser-view/host-browser-view.js | 2 +- .../host/host-job/host-job-controller.js | 17 +- app/docker/views/host/host-job/host-job.js | 2 +- app/docker/views/host/host-view-controller.js | 57 +- app/docker/views/host/host-view.js | 2 +- .../images/build/buildImageController.js | 166 +- app/docker/views/images/build/buildimage.html | 60 +- app/docker/views/images/edit/image.html | 70 +- .../views/images/edit/imageController.js | 313 +- app/docker/views/images/images.html | 44 +- app/docker/views/images/imagesController.js | 258 +- .../images/import/importImageController.js | 60 +- .../views/images/import/importimage.html | 22 +- .../create/createNetworkController.js | 397 +- .../views/networks/create/createnetwork.html | 60 +- app/docker/views/networks/edit/network.html | 16 +- .../views/networks/edit/networkController.js | 186 +- app/docker/views/networks/networks.html | 18 +- .../views/networks/networksController.js | 141 +- .../node-browser/node-browser-controller.js | 21 +- .../nodes/node-browser/node-browser.html | 6 +- .../views/nodes/node-browser/node-browser.js | 2 +- .../node-details-view-controller.js | 38 +- .../nodes/node-details/node-details-view.html | 5 +- .../nodes/node-details/node-details-view.js | 2 +- .../nodes/node-job/node-job-controller.js | 21 +- app/docker/views/nodes/node-job/node-job.html | 6 +- app/docker/views/nodes/node-job/node-job.js | 2 +- .../secrets/create/createSecretController.js | 154 +- .../views/secrets/create/createsecret.html | 26 +- app/docker/views/secrets/edit/secret.html | 10 +- .../views/secrets/edit/secretController.js | 54 +- app/docker/views/secrets/secrets.html | 14 +- app/docker/views/secrets/secretsController.js | 81 +- .../create/createServiceController.js | 1029 +-- .../views/services/create/createservice.html | 92 +- .../services/create/includes/config.html | 4 +- .../create/includes/resources-placement.html | 14 +- .../services/create/includes/secret.html | 14 +- .../create/includes/update-restart.html | 28 +- .../views/services/edit/includes/configs.html | 22 +- .../services/edit/includes/constraints.html | 31 +- .../edit/includes/container-specs.html | 8 +- .../edit/includes/containerlabels.html | 26 +- .../edit/includes/environmentvariables.html | 14 +- .../views/services/edit/includes/hosts.html | 22 +- .../views/services/edit/includes/image.html | 9 +- .../views/services/edit/includes/logging.html | 135 +- .../views/services/edit/includes/mounts.html | 32 +- .../services/edit/includes/networks.html | 2 +- .../edit/includes/placementPreferences.html | 22 +- .../views/services/edit/includes/ports.html | 38 +- .../services/edit/includes/resources.html | 69 +- .../views/services/edit/includes/restart.html | 44 +- .../views/services/edit/includes/secrets.html | 17 +- .../services/edit/includes/servicelabels.html | 20 +- .../views/services/edit/includes/tasks.html | 10 +- .../services/edit/includes/updateconfig.html | 64 +- app/docker/views/services/edit/service.html | 113 +- .../views/services/edit/serviceController.js | 1208 +-- .../services/logs/serviceLogsController.js | 126 +- .../views/services/logs/servicelogs.html | 7 +- app/docker/views/services/services.html | 24 +- .../views/services/servicesController.js | 90 +- app/docker/views/swarm/swarm.html | 16 +- app/docker/views/swarm/swarmController.js | 185 +- .../visualizer/swarmVisualizerController.js | 272 +- .../swarm/visualizer/swarmvisualizer.html | 26 +- app/docker/views/tasks/edit/task.html | 18 +- app/docker/views/tasks/edit/taskController.js | 46 +- .../views/tasks/logs/taskLogsController.js | 138 +- app/docker/views/tasks/logs/tasklogs.html | 10 +- .../volumes/browse/browseVolumeController.js | 24 +- .../views/volumes/browse/browsevolume.html | 7 +- .../volumes/create/createVolumeController.js | 213 +- .../views/volumes/create/createvolume.html | 29 +- app/docker/views/volumes/edit/volume.html | 29 +- .../views/volumes/edit/volumeController.js | 198 +- app/docker/views/volumes/volumes.html | 6 +- app/docker/views/volumes/volumesController.js | 144 +- app/extensions/_module.js | 6 +- app/extensions/oauth/__module.js | 3 +- .../oauth-provider-selector-controller.js | 111 +- .../oauth-providers-selector.html | 10 +- .../oauth-providers-selector.js | 4 +- .../oauth-settings-controller.js | 111 +- .../oauth-settings/oauth-settings.html | 121 +- .../oauth-settings/oauth-settings.js | 4 +- app/extensions/oauth/services/rest/oauth.js | 31 +- app/extensions/rbac/__module.js | 36 +- .../access-viewer/accessViewer.html | 76 +- .../components/access-viewer/accessViewer.js | 2 +- .../access-viewer/accessViewerController.js | 36 +- .../datatable/accessViewerDatatable.html | 145 +- .../datatable/accessViewerDatatable.js | 4 +- .../roles-datatable/rolesDatatable.html | 26 +- .../roles-datatable/rolesDatatable.js | 4 +- .../rbac/directives/authorization.js | 19 +- .../rbac/directives/disable-authorization.js | 17 +- app/extensions/rbac/models/access.js | 4 +- app/extensions/rbac/rest/role.js | 29 +- app/extensions/rbac/services/roleService.js | 84 +- app/extensions/rbac/views/roles/roles.html | 26 +- .../rbac/views/roles/rolesController.js | 1 - app/extensions/registry-management/_module.js | 100 +- .../registryRepositoriesDatatable.html | 32 +- .../registryRepositoriesDatatable.js | 4 +- ...registryRepositoriesDatatableController.js | 36 +- .../registriesRepositoryTagsDatatable.html | 38 +- .../registriesRepositoryTagsDatatable.js | 4 +- ...stryRepositoriesTagsDatatableController.js | 22 +- .../helpers/localRegistryHelper.js | 9 +- .../models/registryImageLayer.js | 2 +- .../models/registryRepository.js | 2 +- .../models/registryTypes.js | 10 +- .../models/repositoryTag.js | 28 +- .../registry-management/rest/catalog.js | 57 +- .../registry-management/rest/gitlab.js | 65 +- .../rest/manifestJquery.js | 147 +- .../registry-management/rest/tags.js | 29 +- .../rest/transform/gitlabResponseGetLink.js | 2 +- .../rest/transform/linkGetResponse.js | 2 +- .../services/registryGitlabService.js | 131 +- .../services/registryServiceSelector.js | 130 +- .../services/registryV2Service.js | 520 +- .../configure/configureRegistryController.js | 121 +- .../views/configure/configureregistry.html | 41 +- .../progression-modal/progressionModal.html | 6 +- .../progression-modal/progressionModal.js | 4 +- .../repositories/edit/registryRepository.html | 63 +- .../edit/registryRepositoryController.js | 723 +- .../repositories/registryRepositories.html | 24 +- .../registryRepositoriesController.js | 116 +- .../tag/registryRepositoryTag.html | 36 +- .../tag/registryRepositoryTagController.js | 19 +- app/index.html | 112 +- app/integrations/_module.js | 4 +- app/integrations/storidge/_module.js | 228 +- .../storidgeClusterEventsDatatable.html | 19 +- .../storidgeClusterEventsDatatable.js | 4 +- .../storidgeDrivesDatatable.html | 39 +- .../storidgeDrivesDatatable.js | 4 +- .../storidgeDrivesDatatableController.js | 76 +- .../storidgeNodesDatatable.html | 25 +- .../nodes-datatable/storidgeNodesDatatable.js | 4 +- .../storidgeNodesDatatableController.js | 118 +- .../storidgeProfileSelector.js | 4 +- .../storidgeProfileSelectorController.js | 32 +- .../storidgeProfilesDatatable.html | 28 +- .../storidgeProfilesDatatable.js | 4 +- .../storidgeSnapshotCreation.html | 14 +- .../storidgeSnapshotCreation.js | 4 +- .../storidgeSnapshotCreationController.js | 50 +- .../storidgeSnapshotsDatatable.html | 27 +- .../storidgeSnapshotsDatatable.js | 4 +- .../volumeStoridgeInfo.html | 32 +- .../volumeStoridgeInfo.js | 4 +- .../volumeStoridgeInfoController.js | 184 +- app/integrations/storidge/filters/filters.js | 100 +- app/integrations/storidge/models/node.js | 2 +- app/integrations/storidge/models/profile.js | 12 +- app/integrations/storidge/models/volume.js | 4 +- app/integrations/storidge/rest/storidge.js | 86 +- .../storidge/services/chartService.js | 334 +- .../storidge/services/clusterService.js | 103 +- .../storidge/services/driveService.js | 156 +- .../storidge/services/nodeService.js | 125 +- .../storidge/services/profileService.js | 117 +- .../storidge/services/snapshotService.js | 137 +- .../storidge/services/volumeService.js | 65 +- .../storidge/views/cluster/cluster.html | 9 +- .../views/cluster/clusterController.js | 158 +- .../storidge/views/drives/drives.html | 20 +- .../storidge/views/drives/drivesController.js | 91 +- .../storidge/views/drives/inspect/drive.html | 6 +- .../views/drives/inspect/driveController.js | 92 +- .../storidge/views/monitor/monitor.html | 19 +- .../views/monitor/monitorController.js | 175 +- .../storidge/views/nodes/inspect/node.html | 14 +- .../views/nodes/inspect/nodeController.js | 208 +- .../create/createProfileController.js | 192 +- .../views/profiles/create/createprofile.html | 67 +- .../storidge/views/profiles/edit/profile.html | 291 +- .../views/profiles/edit/profileController.js | 265 +- .../storidge/views/profiles/profiles.html | 32 +- .../views/profiles/profilesController.js | 128 +- .../views/snapshots/inspect/snapshot.html | 9 +- .../snapshots/inspect/snapshotController.js | 84 +- app/portainer/__module.js | 1132 +-- .../access-datatable/accessDatatable.html | 78 +- .../access-datatable/accessDatatable.js | 4 +- .../accessDatatableController.js | 82 +- .../components/access-table/access-table.js | 10 +- .../components/access-table/accessTable.html | 22 +- .../por-access-control-form.js | 4 +- .../porAccessControlForm.html | 52 +- .../porAccessControlFormController.js | 126 +- .../por-access-control-panel.js | 4 +- .../porAccessControlPanel.html | 74 +- .../porAccessControlPanelController.js | 246 +- .../accessManagement/por-access-management.js | 4 +- .../accessManagement/porAccessManagement.html | 54 +- .../porAccessManagementController.js | 32 +- app/portainer/components/autofocus.js | 26 +- app/portainer/components/buttonSpinner.js | 7 +- .../components/code-editor/code-editor.js | 4 +- .../code-editor/codeEditorController.js | 36 +- .../components/datatables/datatable.css | 33 +- .../endpointsDatatable.html | 36 +- .../endpoints-datatable/endpointsDatatable.js | 4 +- .../endpointsDatatableController.js | 135 +- .../datatables/genericDatatableController.js | 371 +- .../groups-datatable/groupsDatatable.html | 36 +- .../groups-datatable/groupsDatatable.js | 4 +- .../registriesDatatable.html | 47 +- .../registriesDatatable.js | 4 +- .../scheduleTasksDatatable.html | 14 +- .../scheduleTasksDatatable.js | 4 +- .../schedulesDatatable.html | 24 +- .../schedules-datatable/schedulesDatatable.js | 4 +- .../schedulesDatatableController.js | 81 +- .../stacks-datatable/stacksDatatable.html | 45 +- .../stacks-datatable/stacksDatatable.js | 4 +- .../stacksDatatableController.js | 95 +- .../tags-datatable/tagsDatatable.html | 27 +- .../tags-datatable/tagsDatatable.js | 4 +- .../teams-datatable/teamsDatatable.html | 27 +- .../teams-datatable/teamsDatatable.js | 4 +- .../users-datatable/usersDatatable.html | 29 +- .../users-datatable/usersDatatable.js | 4 +- .../endpoint-item/endpoint-item-controller.js | 2 +- .../endpoint-item/endpointItem.html | 37 +- .../endpoint-list/endpoint-list-controller.js | 49 +- .../components/endpoint-list/endpoint-list.js | 6 +- .../endpoint-list/endpointList.html | 27 +- .../endpoint-selector/endpoint-selector.js | 8 +- .../endpointSelectorController.js | 10 +- .../endpointSecurity/por-endpoint-security.js | 4 +- .../endpointSecurity/porEndpointSecurity.html | 17 +- .../porEndpointSecurityController.js | 53 +- .../extension-item/extension-item.js | 4 +- .../extension-item/extensionItemController.js | 22 +- .../extension-list/extension-list.js | 4 +- .../extension-list/extensionList.html | 11 +- .../execute-job-form-controller.js | 119 +- .../execute-job-form/execute-job-form.html | 23 +- .../execute-job-form/execute-job-form.js | 4 +- .../components/forms/group-form/group-form.js | 4 +- .../forms/group-form/groupForm.html | 18 +- .../forms/group-form/groupFormController.js | 39 +- .../registry-form-azure.html | 8 +- .../registry-form-azure.js | 4 +- .../registry-form-custom.html | 16 +- .../registry-form-custom.js | 4 +- .../gitlabProjectsDatatable.html | 17 +- .../gitlabProjectsDatatable.js | 4 +- .../gitlabProjectsDatatableController.js | 90 +- .../registry-form-gitlab.html | 38 +- .../registry-form-gitlab.js | 4 +- .../registry-form-quay.html | 4 +- .../registry-form-quay/registry-form-quay.js | 4 +- .../forms/schedule-form/schedule-form.js | 34 +- .../forms/schedule-form/scheduleForm.html | 90 +- .../forms/template-form/template-form.js | 4 +- .../forms/template-form/templateForm.html | 128 +- .../template-form/templateFormController.js | 109 +- .../group-association-table.js | 30 +- .../groupAssociationTable.html | 19 +- app/portainer/components/header-content.js | 29 +- app/portainer/components/header-title.js | 35 +- app/portainer/components/header.js | 7 +- .../informationPanelOffline.js | 2 +- .../informationPanelOfflineController.js | 62 +- .../information-panel/information-panel.js | 4 +- app/portainer/components/loading.js | 5 +- .../components/motd-panel/motd-panel.js | 4 +- .../components/motd-panel/motdPanel.html | 2 +- .../multiEndpointSelector.html | 2 +- .../multiEndpointSelectorController.js | 4 +- app/portainer/components/onEnterKey.js | 35 +- .../product-list/product-item/product-item.js | 4 +- .../product-item/productItem.html | 6 +- .../components/product-list/product-list.js | 4 +- .../components/product-list/productList.html | 11 +- app/portainer/components/slider/slider.js | 4 +- .../components/slider/sliderController.js | 9 +- .../stack-duplication-form-controller.js | 38 +- .../stack-duplication-form.html | 31 +- .../stack-duplication-form.js | 4 +- .../components/tag-selector/tag-selector.js | 2 +- .../components/tag-selector/tagSelector.html | 11 +- .../template-item/template-item.js | 4 +- .../template-list/template-list-controller.js | 19 +- .../components/template-list/template-list.js | 4 +- .../template-list/templateList.html | 24 +- app/portainer/components/template-widget.js | 7 +- app/portainer/components/tooltip.js | 26 +- app/portainer/components/widget-body.js | 7 +- .../components/widget-custom-header.js | 10 +- app/portainer/components/widget-footer.js | 5 +- app/portainer/components/widget-header.js | 10 +- app/portainer/components/widget-taskbar.js | 7 +- app/portainer/components/widget.js | 7 +- app/portainer/filters/filters.js | 324 +- app/portainer/helpers/endpointHelper.js | 39 +- app/portainer/helpers/formHelper.js | 29 +- .../helpers/resourceControlHelper.js | 3 +- app/portainer/helpers/stackHelper.js | 53 +- app/portainer/helpers/tagHelper.js | 2 +- app/portainer/helpers/templateHelper.js | 210 +- app/portainer/helpers/urlHelper.js | 52 +- app/portainer/helpers/userHelper.js | 27 +- app/portainer/helpers/webhookHelper.js | 27 +- .../interceptors/endpointStatusInterceptor.js | 26 +- app/portainer/models/access.js | 4 +- app/portainer/models/registry.js | 8 +- .../resourceControlOwnership.js | 4 +- .../resourceControlOwnershipParameters.js | 4 +- .../resourceControl/resourceControlTypes.js | 12 +- .../resourceControlUpdatePayload.js | 8 +- app/portainer/models/schedule.js | 2 +- app/portainer/models/settings.js | 2 +- app/portainer/models/status.js | 2 +- app/portainer/models/template.js | 8 +- app/portainer/rest/auth.js | 26 +- app/portainer/rest/dockerhub.js | 23 +- app/portainer/rest/endpoint.js | 45 +- app/portainer/rest/extension.js | 30 +- app/portainer/rest/group.js | 35 +- app/portainer/rest/legacyExtension.js | 27 +- app/portainer/rest/motd.js | 27 +- app/portainer/rest/registry.js | 33 +- app/portainer/rest/resourceControl.js | 27 +- app/portainer/rest/schedule.js | 34 +- app/portainer/rest/settings.js | 27 +- app/portainer/rest/stack.js | 35 +- app/portainer/rest/status.js | 23 +- app/portainer/rest/support.js | 21 +- app/portainer/rest/tag.js | 25 +- app/portainer/rest/team.js | 29 +- app/portainer/rest/teamMembership.js | 27 +- app/portainer/rest/template.js | 27 +- .../rest/transform/getEndpointsTotalCount.js | 2 +- app/portainer/rest/user.js | 37 +- app/portainer/rest/webhooks.js | 26 +- app/portainer/services/api/accessService.js | 172 +- .../services/api/dockerhubService.js | 45 +- app/portainer/services/api/endpointService.js | 223 +- .../services/api/extensionService.js | 171 +- app/portainer/services/api/groupService.js | 112 +- .../services/api/legacyExtensionService.js | 34 +- app/portainer/services/api/motdService.js | 39 +- app/portainer/services/api/registryService.js | 247 +- .../services/api/resourceControlService.js | 297 +- app/portainer/services/api/scheduleService.js | 163 +- app/portainer/services/api/settingsService.js | 97 +- app/portainer/services/api/stackService.js | 616 +- app/portainer/services/api/statusService.js | 81 +- app/portainer/services/api/supportService.js | 37 +- app/portainer/services/api/tagService.js | 97 +- .../services/api/teamMembershipService.js | 77 +- app/portainer/services/api/teamService.js | 143 +- app/portainer/services/api/templateService.js | 200 +- app/portainer/services/api/userService.js | 299 +- app/portainer/services/api/webhookService.js | 57 +- app/portainer/services/async.js | 13 +- app/portainer/services/authentication.js | 201 +- app/portainer/services/chartService.js | 64 +- app/portainer/services/codeMirror.js | 13 +- app/portainer/services/datatableService.js | 105 +- app/portainer/services/endpointProvider.js | 176 +- app/portainer/services/fileUpload.js | 360 +- app/portainer/services/formValidator.js | 38 +- app/portainer/services/httpRequestHelper.js | 105 +- .../services/legacyExtensionManager.js | 137 +- app/portainer/services/localStorage.js | 272 +- app/portainer/services/modalService.js | 439 +- app/portainer/services/notifications.js | 80 +- app/portainer/services/pagination.js | 38 +- app/portainer/services/stateManager.js | 402 +- app/portainer/views/about/about.html | 137 +- app/portainer/views/account/account.html | 25 +- .../views/account/accountController.js | 66 +- app/portainer/views/auth/auth.html | 36 +- app/portainer/views/auth/authController.js | 35 +- .../endpoints/access/endpointAccess.html | 15 +- .../access/endpointAccessController.js | 18 +- .../create/createEndpointController.js | 300 +- .../endpoints/create/createendpoint.html | 142 +- .../views/endpoints/edit/endpoint.html | 130 +- .../endpoints/edit/endpointController.js | 280 +- app/portainer/views/endpoints/endpoints.html | 22 +- .../views/endpoints/endpointsController.js | 86 +- .../views/extensions/extensions.html | 46 +- .../views/extensions/extensionsController.js | 110 +- .../views/extensions/inspect/extension.html | 94 +- .../extensions/inspect/extensionController.js | 156 +- .../views/groups/access/groupAccess.html | 6 +- .../groups/access/groupAccessController.js | 62 +- .../groups/create/createGroupController.js | 54 +- .../views/groups/create/creategroup.html | 6 +- app/portainer/views/groups/edit/group.html | 6 +- .../views/groups/edit/groupController.js | 52 +- app/portainer/views/groups/groups.html | 12 +- .../views/groups/groupsController.js | 74 +- app/portainer/views/home/home.html | 23 +- app/portainer/views/home/homeController.js | 350 +- app/portainer/views/init/admin/initAdmin.html | 27 +- .../views/init/admin/initAdminController.js | 134 +- .../views/init/endpoint/initEndpoint.html | 113 +- .../init/endpoint/initEndpointController.js | 186 +- app/portainer/views/main/mainController.js | 64 +- .../registries/access/registryAccess.html | 3 +- .../access/registryAccessController.js | 60 +- .../create/createRegistryController.js | 182 +- .../registries/create/createregistry.html | 26 +- .../views/registries/edit/registry.html | 20 +- .../registries/edit/registryController.js | 81 +- .../views/registries/registries.html | 35 +- .../views/registries/registriesController.js | 163 +- .../create/createScheduleController.js | 108 +- .../schedules/create/createschedule.html | 4 +- .../views/schedules/edit/schedule.html | 21 +- .../schedules/edit/scheduleController.js | 177 +- app/portainer/views/schedules/schedules.html | 7 +- .../views/schedules/schedulesController.js | 91 +- .../settingsAuthentication.html | 115 +- .../settingsAuthenticationController.js | 311 +- app/portainer/views/settings/settings.html | 68 +- .../views/settings/settingsController.js | 200 +- app/portainer/views/sidebar/sidebar.html | 277 +- .../views/sidebar/sidebarController.js | 68 +- .../stacks/create/createStackController.js | 288 +- .../views/stacks/create/createstack.html | 59 +- app/portainer/views/stacks/edit/stack.html | 64 +- .../views/stacks/edit/stackController.js | 561 +- app/portainer/views/stacks/stacks.html | 16 +- .../views/stacks/stacksController.js | 113 +- .../views/support/product/product.html | 30 +- .../support/product/productController.js | 76 +- app/portainer/views/support/support.html | 9 +- .../views/support/supportController.js | 30 +- app/portainer/views/tags/tags.html | 30 +- app/portainer/views/tags/tagsController.js | 125 +- app/portainer/views/teams/edit/team.html | 34 +- .../views/teams/edit/teamController.js | 397 +- app/portainer/views/teams/teams.html | 61 +- app/portainer/views/teams/teamsController.js | 179 +- .../create/createTemplateController.js | 92 +- .../templates/create/createtemplate.html | 4 +- .../views/templates/edit/template.html | 4 +- .../templates/edit/templateController.js | 111 +- app/portainer/views/templates/templates.html | 81 +- .../views/templates/templatesController.js | 529 +- .../views/update-password/updatePassword.html | 109 +- .../updatePasswordController.js | 70 +- app/portainer/views/users/edit/user.html | 30 +- .../views/users/edit/userController.js | 147 +- app/portainer/views/users/users.html | 82 +- app/portainer/views/users/usersController.js | 229 +- app/vendors.js | 2 +- assets/css/app.css | 149 +- gruntfile.js | 152 +- package.json | 18 +- postcss.config.js | 4 +- test/e2e/cypress/integration/spec.js | 29 +- test/e2e/cypress/plugins/index.js | 2 +- test/e2e/cypress/support/index.js | 2 +- .../components/containerController.spec.js | 90 +- .../components/containerTopController.spec.js | 58 +- .../app/components/networkController.spec.js | 158 +- .../app/components/networksController.spec.js | 200 +- .../startContainerController.spec.js | 492 +- .../app/components/statsController.spec.js | 56 +- .../app/components/volumesController.spec.js | 115 +- test/unit/app/shared/filters.spec.js | 215 +- test/unit/karma.conf.js | 10 +- webpack/webpack.common.js | 54 +- webpack/webpack.develop.js | 14 +- webpack/webpack.production.js | 18 +- yarn.lock | 7309 ++++++++--------- 714 files changed, 31455 insertions(+), 28532 deletions(-) create mode 100644 .prettierrc diff --git a/.gitignore b/.gitignore index 43b170ab09776..d9c64e8bc5020 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist portainer-checksum.txt api/cmd/portainer/portainer* .tmp -.vscode \ No newline at end of file +.vscode +.eslintcache \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000000..3217d14eea6d5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,13 @@ +{ + "printWidth": 180, + "singleQuote": true, + "htmlWhitespaceSensitivity": "strict", + "overrides": [ + { + "files": ["*.html"], + "options": { + "parser": "angular" + } + } + ] +} diff --git a/app/__module.js b/app/__module.js index 881453c9a71a9..e5db84c05aab2 100644 --- a/app/__module.js +++ b/app/__module.js @@ -32,12 +32,12 @@ angular.module('portainer', [ 'portainer.extensions', 'portainer.integrations', 'rzModule', - 'moment-picker' + 'moment-picker', ]); if (require) { var req = require.context('./', true, /^(.*\.(js$))[^.]*$/im); - req.keys().forEach(function(key) { + req.keys().forEach(function (key) { req(key); }); } diff --git a/app/agent/components/file-uploader/file-uploader-controller.js b/app/agent/components/file-uploader/file-uploader-controller.js index e6516c67cc4e8..d0c5ad798b05f 100644 --- a/app/agent/components/file-uploader/file-uploader-controller.js +++ b/app/agent/components/file-uploader/file-uploader-controller.js @@ -4,7 +4,7 @@ angular.module('portainer.agent').controller('FileUploaderController', [ var ctrl = this; ctrl.state = { - uploadInProgress: false + uploadInProgress: false, }; ctrl.onFileSelected = onFileSelected; @@ -19,5 +19,5 @@ angular.module('portainer.agent').controller('FileUploaderController', [ ctrl.state.uploadInProgress = false; }); } - } + }, ]); diff --git a/app/agent/components/file-uploader/file-uploader.html b/app/agent/components/file-uploader/file-uploader.html index e092ce5d6ec19..5354b5c2f4cf0 100644 --- a/app/agent/components/file-uploader/file-uploader.html +++ b/app/agent/components/file-uploader/file-uploader.html @@ -1,6 +1,3 @@ - diff --git a/app/agent/components/file-uploader/file-uploader.js b/app/agent/components/file-uploader/file-uploader.js index 6427bae4a3df7..6232c7f8d6344 100644 --- a/app/agent/components/file-uploader/file-uploader.js +++ b/app/agent/components/file-uploader/file-uploader.js @@ -2,6 +2,6 @@ angular.module('portainer.agent').component('fileUploader', { templateUrl: './file-uploader.html', controller: 'FileUploaderController', bindings: { - uploadFile: ' - - - + +
@@ -43,36 +49,33 @@ - - + + - - + + @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/app/docker/components/host-view-panels/devices-panel/devices-panel.js b/app/docker/components/host-view-panels/devices-panel/devices-panel.js index 8049add850e15..a7d19bcae01ca 100644 --- a/app/docker/components/host-view-panels/devices-panel/devices-panel.js +++ b/app/docker/components/host-view-panels/devices-panel/devices-panel.js @@ -1,6 +1,6 @@ angular.module('portainer.docker').component('devicesPanel', { templateUrl: './devices-panel.html', bindings: { - devices: '<' - } + devices: '<', + }, }); diff --git a/app/docker/components/host-view-panels/disks-panel/disks-panel.html b/app/docker/components/host-view-panels/disks-panel/disks-panel.html index 632f07151d996..f2f59ec2c67a9 100644 --- a/app/docker/components/host-view-panels/disks-panel/disks-panel.html +++ b/app/docker/components/host-view-panels/disks-panel/disks-panel.html @@ -12,8 +12,8 @@ - - + + @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/app/docker/components/host-view-panels/disks-panel/disks-panel.js b/app/docker/components/host-view-panels/disks-panel/disks-panel.js index 3997a89b30778..4b755a6077ba8 100644 --- a/app/docker/components/host-view-panels/disks-panel/disks-panel.js +++ b/app/docker/components/host-view-panels/disks-panel/disks-panel.js @@ -1,6 +1,6 @@ angular.module('portainer.docker').component('disksPanel', { templateUrl: './disks-panel.html', bindings: { - disks: '<' - } + disks: '<', + }, }); diff --git a/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.html b/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.html index 036e4d7b7ca45..5b25d0ea261c9 100644 --- a/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.html +++ b/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.html @@ -7,7 +7,9 @@ - + @@ -31,11 +33,11 @@ - +
- Go - to parent + Go to parent
- + - + - {{ item.Name }} - - - {{ - item.Name }} + {{ item.Name }} + {{ item.Name }} {{ item.Size | humansize }} {{ item.ModTime | getisodatefromtimestamp }} - + Download @@ -94,4 +97,4 @@ - \ No newline at end of file + diff --git a/app/agent/components/files-datatable/files-datatable.js b/app/agent/components/files-datatable/files-datatable.js index d1735cf48f2b5..d75d0f970d406 100644 --- a/app/agent/components/files-datatable/files-datatable.js +++ b/app/agent/components/files-datatable/files-datatable.js @@ -15,8 +15,8 @@ angular.module('portainer.agent').component('filesDatatable', { rename: '&', download: '&', delete: '&', - + isUploadAllowed: '<', - onFileSelectedForUpload: '<' - } + onFileSelectedForUpload: '<', + }, }); diff --git a/app/agent/components/host-browser/host-browser-controller.js b/app/agent/components/host-browser/host-browser-controller.js index 797f5c6ac1d75..c9c1e5e0f4152 100644 --- a/app/agent/components/host-browser/host-browser-controller.js +++ b/app/agent/components/host-browser/host-browser-controller.js @@ -1,12 +1,15 @@ import _ from 'lodash-es'; angular.module('portainer.agent').controller('HostBrowserController', [ - 'HostBrowserService', 'Notifications', 'FileSaver', 'ModalService', + 'HostBrowserService', + 'Notifications', + 'FileSaver', + 'ModalService', function HostBrowserController(HostBrowserService, Notifications, FileSaver, ModalService) { var ctrl = this; var ROOT_PATH = '/host'; ctrl.state = { - path: ROOT_PATH + path: ROOT_PATH, }; ctrl.goToParent = goToParent; @@ -21,7 +24,7 @@ angular.module('portainer.agent').controller('HostBrowserController', [ function getRelativePath(path) { path = path || ctrl.state.path; - var rootPathRegex = new RegExp('^' + ROOT_PATH + '\/?'); + var rootPathRegex = new RegExp('^' + ROOT_PATH + '/?'); var relativePath = path.replace(rootPathRegex, '/'); return relativePath; } @@ -71,7 +74,7 @@ angular.module('portainer.agent').controller('HostBrowserController', [ HostBrowserService.get(filePath) .then(function onFileReceived(data) { var downloadData = new Blob([data.file], { - type: 'text/plain;charset=utf-8' + type: 'text/plain;charset=utf-8', }); FileSaver.saveAs(downloadData, file); }) @@ -83,15 +86,12 @@ angular.module('portainer.agent').controller('HostBrowserController', [ function confirmDeleteFile(name) { var filePath = buildPath(ctrl.state.path, name); - ModalService.confirmDeletion( - 'Are you sure that you want to delete ' + getRelativePath(filePath) + ' ?', - function onConfirm(confirmed) { - if (!confirmed) { - return; - } - return deleteFile(filePath); + ModalService.confirmDeletion('Are you sure that you want to delete ' + getRelativePath(filePath) + ' ?', function onConfirm(confirmed) { + if (!confirmed) { + return; } - ); + return deleteFile(filePath); + }); } function deleteFile(path) { @@ -145,5 +145,5 @@ angular.module('portainer.agent').controller('HostBrowserController', [ function refreshList() { getFilesForPath(ctrl.state.path); } - } + }, ]); diff --git a/app/agent/components/host-browser/host-browser.html b/app/agent/components/host-browser/host-browser.html index cf7b593064106..b81ecc7449341 100644 --- a/app/agent/components/host-browser/host-browser.html +++ b/app/agent/components/host-browser/host-browser.html @@ -1,6 +1,8 @@ - diff --git a/app/agent/components/host-browser/host-browser.js b/app/agent/components/host-browser/host-browser.js index d702255eb8e10..9b16b5a01227b 100644 --- a/app/agent/components/host-browser/host-browser.js +++ b/app/agent/components/host-browser/host-browser.js @@ -1,5 +1,5 @@ angular.module('portainer.agent').component('hostBrowser', { controller: 'HostBrowserController', templateUrl: './host-browser.html', - bindings: {} + bindings: {}, }); diff --git a/app/agent/components/node-selector/node-selector.js b/app/agent/components/node-selector/node-selector.js index 0d9c93ed3d56c..2f7e14e8216fb 100644 --- a/app/agent/components/node-selector/node-selector.js +++ b/app/agent/components/node-selector/node-selector.js @@ -2,6 +2,6 @@ angular.module('portainer.agent').component('nodeSelector', { templateUrl: './nodeSelector.html', controller: 'NodeSelectorController', bindings: { - model: '=' - } + model: '=', + }, }); diff --git a/app/agent/components/node-selector/nodeSelector.html b/app/agent/components/node-selector/nodeSelector.html index 1a910396cfc6a..4ab9e75bb799f 100644 --- a/app/agent/components/node-selector/nodeSelector.html +++ b/app/agent/components/node-selector/nodeSelector.html @@ -1,8 +1,6 @@
- +
diff --git a/app/agent/components/node-selector/nodeSelectorController.js b/app/agent/components/node-selector/nodeSelectorController.js index be2f6ce9261c2..ce44f85688c08 100644 --- a/app/agent/components/node-selector/nodeSelectorController.js +++ b/app/agent/components/node-selector/nodeSelectorController.js @@ -1,18 +1,20 @@ -angular.module('portainer.agent') -.controller('NodeSelectorController', ['AgentService', 'Notifications', function (AgentService, Notifications) { - var ctrl = this; +angular.module('portainer.agent').controller('NodeSelectorController', [ + 'AgentService', + 'Notifications', + function (AgentService, Notifications) { + var ctrl = this; - this.$onInit = function() { - AgentService.agents() - .then(function success(data) { - ctrl.agents = data; - if (!ctrl.model) { - ctrl.model = data[0].NodeName; - } - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to load agents'); - }); - }; - -}]); + this.$onInit = function () { + AgentService.agents() + .then(function success(data) { + ctrl.agents = data; + if (!ctrl.model) { + ctrl.model = data[0].NodeName; + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to load agents'); + }); + }; + }, +]); diff --git a/app/agent/components/volume-browser/volume-browser.js b/app/agent/components/volume-browser/volume-browser.js index bea11e61aaf24..5c2b2b78dbb96 100644 --- a/app/agent/components/volume-browser/volume-browser.js +++ b/app/agent/components/volume-browser/volume-browser.js @@ -4,6 +4,6 @@ angular.module('portainer.agent').component('volumeBrowser', { bindings: { volumeId: '<', nodeName: '<', - isUploadEnabled: '<' - } + isUploadEnabled: '<', + }, }); diff --git a/app/agent/components/volume-browser/volumeBrowser.html b/app/agent/components/volume-browser/volumeBrowser.html index 97c8a4da62686..5b85cfd94c387 100644 --- a/app/agent/components/volume-browser/volumeBrowser.html +++ b/app/agent/components/volume-browser/volumeBrowser.html @@ -1,6 +1,8 @@ diff --git a/app/agent/components/volume-browser/volumeBrowserController.js b/app/agent/components/volume-browser/volumeBrowserController.js index 0f8e99d862008..fccde7753a9a4 100644 --- a/app/agent/components/volume-browser/volumeBrowserController.js +++ b/app/agent/components/volume-browser/volumeBrowserController.js @@ -1,137 +1,137 @@ import _ from 'lodash-es'; -angular.module('portainer.agent') -.controller('VolumeBrowserController', ['HttpRequestHelper', 'VolumeBrowserService', 'FileSaver', 'Blob', 'ModalService', 'Notifications', -function (HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, ModalService, Notifications) { - var ctrl = this; - - this.state = { - path: '/' - }; - - this.rename = function(file, newName) { - var filePath = this.state.path === '/' ? file : this.state.path + '/' + file; - var newFilePath = this.state.path === '/' ? newName : this.state.path + '/' + newName; - - VolumeBrowserService.rename(this.volumeId, filePath, newFilePath) - .then(function success() { - Notifications.success('File successfully renamed', newFilePath); - return VolumeBrowserService.ls(ctrl.volumeId, ctrl.state.path); - }) - .then(function success(data) { - ctrl.files = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to rename file'); - }); - }; - - this.delete = function(file) { - var filePath = this.state.path === '/' ? file : this.state.path + '/' + file; - - ModalService.confirmDeletion( - 'Are you sure that you want to delete ' + filePath + ' ?', - function onConfirm(confirmed) { - if(!confirmed) { return; } +angular.module('portainer.agent').controller('VolumeBrowserController', [ + 'HttpRequestHelper', + 'VolumeBrowserService', + 'FileSaver', + 'Blob', + 'ModalService', + 'Notifications', + function (HttpRequestHelper, VolumeBrowserService, FileSaver, Blob, ModalService, Notifications) { + var ctrl = this; + + this.state = { + path: '/', + }; + + this.rename = function (file, newName) { + var filePath = this.state.path === '/' ? file : this.state.path + '/' + file; + var newFilePath = this.state.path === '/' ? newName : this.state.path + '/' + newName; + + VolumeBrowserService.rename(this.volumeId, filePath, newFilePath) + .then(function success() { + Notifications.success('File successfully renamed', newFilePath); + return VolumeBrowserService.ls(ctrl.volumeId, ctrl.state.path); + }) + .then(function success(data) { + ctrl.files = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to rename file'); + }); + }; + + this.delete = function (file) { + var filePath = this.state.path === '/' ? file : this.state.path + '/' + file; + + ModalService.confirmDeletion('Are you sure that you want to delete ' + filePath + ' ?', function onConfirm(confirmed) { + if (!confirmed) { + return; + } deleteFile(filePath); - } - ); - }; - - this.download = function(file) { - var filePath = this.state.path === '/' ? file : this.state.path + '/' + file; - VolumeBrowserService.get(this.volumeId, filePath) - .then(function success(data) { - var downloadData = new Blob([data.file]); - FileSaver.saveAs(downloadData, file); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to download file'); - }); - }; - - this.up = function() { - var parentFolder = parentPath(this.state.path); - browse(parentFolder); - }; - - this.browse = function(folder) { - var path = buildPath(this.state.path, folder); - browse(path); - }; - - function deleteFile(file) { - VolumeBrowserService.delete(ctrl.volumeId, file) - .then(function success() { - Notifications.success('File successfully deleted', file); - return VolumeBrowserService.ls(ctrl.volumeId, ctrl.state.path); - }) - .then(function success(data) { - ctrl.files = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to delete file'); - }); - } - - - function browse(path) { - VolumeBrowserService.ls(ctrl.volumeId, path) - .then(function success(data) { - ctrl.state.path = path; - ctrl.files = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to browse volume'); - }); - } - - this.onFileSelectedForUpload = function onFileSelectedForUpload(file) { - VolumeBrowserService.upload(ctrl.state.path, file, ctrl.volumeId) - .then(function onFileUpload() { - onFileUploaded(); - }) - .catch(function onFileUpload(err) { - Notifications.error('Failure', err, 'Unable to upload file'); }); - }; - - function parentPath(path) { - if (path.lastIndexOf('/') === 0) { - return '/'; + }; + + this.download = function (file) { + var filePath = this.state.path === '/' ? file : this.state.path + '/' + file; + VolumeBrowserService.get(this.volumeId, filePath) + .then(function success(data) { + var downloadData = new Blob([data.file]); + FileSaver.saveAs(downloadData, file); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to download file'); + }); + }; + + this.up = function () { + var parentFolder = parentPath(this.state.path); + browse(parentFolder); + }; + + this.browse = function (folder) { + var path = buildPath(this.state.path, folder); + browse(path); + }; + + function deleteFile(file) { + VolumeBrowserService.delete(ctrl.volumeId, file) + .then(function success() { + Notifications.success('File successfully deleted', file); + return VolumeBrowserService.ls(ctrl.volumeId, ctrl.state.path); + }) + .then(function success(data) { + ctrl.files = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to delete file'); + }); } - var split = _.split(path, '/'); - return _.join(_.slice(split, 0, split.length - 1), '/'); - } - - function buildPath(parent, file) { - if (parent === '/') { - return parent + file; + function browse(path) { + VolumeBrowserService.ls(ctrl.volumeId, path) + .then(function success(data) { + ctrl.state.path = path; + ctrl.files = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to browse volume'); + }); } - return parent + '/' + file; - } + this.onFileSelectedForUpload = function onFileSelectedForUpload(file) { + VolumeBrowserService.upload(ctrl.state.path, file, ctrl.volumeId) + .then(function onFileUpload() { + onFileUploaded(); + }) + .catch(function onFileUpload(err) { + Notifications.error('Failure', err, 'Unable to upload file'); + }); + }; + + function parentPath(path) { + if (path.lastIndexOf('/') === 0) { + return '/'; + } - this.$onInit = function() { - HttpRequestHelper.setPortainerAgentTargetHeader(this.nodeName); - VolumeBrowserService.ls(this.volumeId, this.state.path) - .then(function success(data) { - ctrl.files = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to browse volume'); - }); - }; - - function onFileUploaded() { - refreshList(); - } + var split = _.split(path, '/'); + return _.join(_.slice(split, 0, split.length - 1), '/'); + } - function refreshList() { - browse(ctrl.state.path); - } + function buildPath(parent, file) { + if (parent === '/') { + return parent + file; + } + return parent + '/' + file; + } - + this.$onInit = function () { + HttpRequestHelper.setPortainerAgentTargetHeader(this.nodeName); + VolumeBrowserService.ls(this.volumeId, this.state.path) + .then(function success(data) { + ctrl.files = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to browse volume'); + }); + }; + + function onFileUploaded() { + refreshList(); + } -}]); + function refreshList() { + browse(ctrl.state.path); + } + }, +]); diff --git a/app/agent/models/agent.js b/app/agent/models/agent.js index 3e171ff07feb2..54866ef90c0c8 100644 --- a/app/agent/models/agent.js +++ b/app/agent/models/agent.js @@ -1,5 +1,5 @@ export function AgentViewModel(data) { this.IPAddress = data.IPAddress; - this.NodeName = data.NodeName; - this.NodeRole = data.NodeRole; + this.NodeName = data.NodeName; + this.NodeRole = data.NodeRole; } diff --git a/app/agent/rest/agent.js b/app/agent/rest/agent.js index 522296c3a82c4..d7d0c6a2ffc8c 100644 --- a/app/agent/rest/agent.js +++ b/app/agent/rest/agent.js @@ -1,12 +1,19 @@ -angular.module('portainer.agent') -.factory('Agent', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'StateManager', +angular.module('portainer.agent').factory('Agent', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'StateManager', function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, StateManager) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/agents', { - endpointId: EndpointProvider.endpointID, - version: StateManager.getAgentApiVersion + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/agents', + { + endpointId: EndpointProvider.endpointID, + version: StateManager.getAgentApiVersion, + }, + { + query: { method: 'GET', isArray: true }, + } + ); }, - { - query: { method: 'GET', isArray: true } - }); -}]); +]); diff --git a/app/agent/rest/browse.js b/app/agent/rest/browse.js index 82e5d4b21b9bb..852ee053519bc 100644 --- a/app/agent/rest/browse.js +++ b/app/agent/rest/browse.js @@ -1,27 +1,39 @@ import { browseGetResponse } from './response/browse'; -angular.module('portainer.agent') -.factory('Browse', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'StateManager', +angular.module('portainer.agent').factory('Browse', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'StateManager', function BrowseFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, StateManager) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/browse/:action', { - endpointId: EndpointProvider.endpointID, - version: StateManager.getAgentApiVersion + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/browse/:action', + { + endpointId: EndpointProvider.endpointID, + version: StateManager.getAgentApiVersion, + }, + { + ls: { + method: 'GET', + isArray: true, + params: { action: 'ls' }, + }, + get: { + method: 'GET', + params: { action: 'get' }, + transformResponse: browseGetResponse, + responseType: 'arraybuffer', + }, + delete: { + method: 'DELETE', + params: { action: 'delete' }, + }, + rename: { + method: 'PUT', + params: { action: 'rename' }, + }, + } + ); }, - { - ls: { - method: 'GET', isArray: true, params: { action: 'ls' } - }, - get: { - method: 'GET', params: { action: 'get' }, - transformResponse: browseGetResponse, - responseType: 'arraybuffer' - }, - delete: { - method: 'DELETE', params: { action: 'delete' } - }, - rename: { - method: 'PUT', params: { action: 'rename' } - } - }); -}]); +]); diff --git a/app/agent/rest/host.js b/app/agent/rest/host.js index f184d2544bd55..a5fb198aed108 100644 --- a/app/agent/rest/host.js +++ b/app/agent/rest/host.js @@ -1,16 +1,19 @@ angular.module('portainer.agent').factory('Host', [ - '$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'StateManager', + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'StateManager', function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, StateManager) { 'use strict'; return $resource( API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/v:version/host/:action', { endpointId: EndpointProvider.endpointID, - version: StateManager.getAgentApiVersion + version: StateManager.getAgentApiVersion, }, { - info: { method: 'GET', params: { action: 'info' } } + info: { method: 'GET', params: { action: 'info' } }, } ); - } + }, ]); diff --git a/app/agent/rest/ping.js b/app/agent/rest/ping.js index 7eeb93f2e3c4b..b6527dfb41a32 100644 --- a/app/agent/rest/ping.js +++ b/app/agent/rest/ping.js @@ -1,11 +1,14 @@ angular.module('portainer.agent').factory('AgentPing', [ - '$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q', + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + '$q', function AgentPingFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q) { 'use strict'; return $resource( API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/ping', - { - endpointId: EndpointProvider.endpointID + { + endpointId: EndpointProvider.endpointID, }, { ping: { @@ -13,8 +16,7 @@ angular.module('portainer.agent').factory('AgentPing', [ interceptor: { response: function versionInterceptor(response) { var instance = response.resource; - var version = - response.headers('Portainer-Agent-Api-Version') || 1; + var version = response.headers('Portainer-Agent-Api-Version') || 1; instance.version = Number(version); return instance; }, @@ -24,10 +26,10 @@ angular.module('portainer.agent').factory('AgentPing', [ return { version: 1 }; } return $q.reject(error); - } - } - } + }, + }, + }, } ); - } + }, ]); diff --git a/app/agent/rest/v1/agent.js b/app/agent/rest/v1/agent.js index a78755b3545d7..3d9e8d606eff4 100644 --- a/app/agent/rest/v1/agent.js +++ b/app/agent/rest/v1/agent.js @@ -1,10 +1,17 @@ -angular.module('portainer.agent') -.factory('AgentVersion1', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/agents', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.agent').factory('AgentVersion1', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function AgentFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/agents', + { + endpointId: EndpointProvider.endpointID, + }, + { + query: { method: 'GET', isArray: true }, + } + ); }, - { - query: { method: 'GET', isArray: true } - }); -}]); +]); diff --git a/app/agent/rest/v1/browse.js b/app/agent/rest/v1/browse.js index 62a198743ebda..89c18b384149c 100644 --- a/app/agent/rest/v1/browse.js +++ b/app/agent/rest/v1/browse.js @@ -1,25 +1,37 @@ import { browseGetResponse } from '../response/browse'; -angular.module('portainer.agent') -.factory('BrowseVersion1', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function BrowseFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/browse/:volumeID/:action', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.agent').factory('BrowseVersion1', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function BrowseFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/browse/:volumeID/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + ls: { + method: 'GET', + isArray: true, + params: { action: 'ls' }, + }, + get: { + method: 'GET', + params: { action: 'get' }, + transformResponse: browseGetResponse, + responseType: 'arraybuffer', + }, + delete: { + method: 'DELETE', + params: { action: 'delete' }, + }, + rename: { + method: 'PUT', + params: { action: 'rename' }, + }, + } + ); }, - { - ls: { - method: 'GET', isArray: true, params: { action: 'ls' } - }, - get: { - method: 'GET', params: { action: 'get' }, - transformResponse: browseGetResponse, - responseType: 'arraybuffer' - }, - delete: { - method: 'DELETE', params: { action: 'delete' } - }, - rename: { - method: 'PUT', params: { action: 'rename' } - } - }); -}]); +]); diff --git a/app/agent/services/agentService.js b/app/agent/services/agentService.js index af471da0966b5..b9d938d949a38 100644 --- a/app/agent/services/agentService.js +++ b/app/agent/services/agentService.js @@ -1,7 +1,12 @@ import { AgentViewModel } from '../models/agent'; angular.module('portainer.agent').factory('AgentService', [ - '$q', 'Agent', 'AgentVersion1', 'HttpRequestHelper', 'Host', 'StateManager', + '$q', + 'Agent', + 'AgentVersion1', + 'HttpRequestHelper', + 'Host', + 'StateManager', function AgentServiceFactory($q, Agent, AgentVersion1, HttpRequestHelper, Host, StateManager) { 'use strict'; var service = {}; @@ -24,10 +29,11 @@ angular.module('portainer.agent').factory('AgentService', [ var agentVersion = getAgentApiVersion(); var service = agentVersion > 1 ? Agent : AgentVersion1; - - service.query({ version: agentVersion }) + + service + .query({ version: agentVersion }) .$promise.then(function success(data) { - var agents = data.map(function(item) { + var agents = data.map(function (item) { return new AgentViewModel(item); }); deferred.resolve(agents); @@ -40,5 +46,5 @@ angular.module('portainer.agent').factory('AgentService', [ } return service; - } + }, ]); diff --git a/app/agent/services/hostBrowserService.js b/app/agent/services/hostBrowserService.js index 6f292c36aa686..8de01f815a446 100644 --- a/app/agent/services/hostBrowserService.js +++ b/app/agent/services/hostBrowserService.js @@ -1,5 +1,10 @@ angular.module('portainer.agent').factory('HostBrowserService', [ - 'Browse', 'Upload', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', '$q', 'StateManager', + 'Browse', + 'Upload', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + '$q', + 'StateManager', function HostBrowserServiceFactory(Browse, Upload, API_ENDPOINT_ENDPOINTS, EndpointProvider, $q, StateManager) { var service = {}; @@ -24,7 +29,7 @@ angular.module('portainer.agent').factory('HostBrowserService', [ function rename(path, newPath) { var payload = { CurrentFilePath: path, - NewFilePath: newPath + NewFilePath: newPath, }; return Browse.rename({}, payload).$promise; } @@ -32,21 +37,15 @@ angular.module('portainer.agent').factory('HostBrowserService', [ function upload(path, file, onProgress) { var deferred = $q.defer(); var agentVersion = StateManager.getAgentApiVersion(); - var url = - API_ENDPOINT_ENDPOINTS + - '/' + - EndpointProvider.endpointID() + - '/docker' + - (agentVersion > 1 ? '/v' + agentVersion : '') + - '/browse/put'; + var url = API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/docker' + (agentVersion > 1 ? '/v' + agentVersion : '') + '/browse/put'; Upload.upload({ url: url, - data: { file: file, Path: path } + data: { file: file, Path: path }, }).then(deferred.resolve, deferred.reject, onProgress); return deferred.promise; } return service; - } + }, ]); diff --git a/app/agent/services/pingService.js b/app/agent/services/pingService.js index 765d47a5f56b8..cc3133f220fec 100644 --- a/app/agent/services/pingService.js +++ b/app/agent/services/pingService.js @@ -10,5 +10,5 @@ angular.module('portainer.agent').service('AgentPingService', [ } return service; - } + }, ]); diff --git a/app/agent/services/volumeBrowserService.js b/app/agent/services/volumeBrowserService.js index a4ffdbf9d4c26..002edb08bb173 100644 --- a/app/agent/services/volumeBrowserService.js +++ b/app/agent/services/volumeBrowserService.js @@ -1,5 +1,11 @@ angular.module('portainer.agent').factory('VolumeBrowserService', [ - 'StateManager', 'Browse', 'BrowseVersion1', '$q', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'Upload', + 'StateManager', + 'Browse', + 'BrowseVersion1', + '$q', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'Upload', function VolumeBrowserServiceFactory(StateManager, Browse, BrowseVersion1, $q, API_ENDPOINT_ENDPOINTS, EndpointProvider, Upload) { 'use strict'; var service = {}; @@ -14,22 +20,22 @@ angular.module('portainer.agent').factory('VolumeBrowserService', [ return agentVersion > 1 ? Browse : BrowseVersion1; } - service.ls = function(volumeId, path) { + service.ls = function (volumeId, path) { return getBrowseService().ls({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise; }; - service.get = function(volumeId, path) { + service.get = function (volumeId, path) { return getBrowseService().get({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise; }; - service.delete = function(volumeId, path) { + service.delete = function (volumeId, path) { return getBrowseService().delete({ volumeID: volumeId, path: path, version: getAgentApiVersion() }).$promise; }; - service.rename = function(volumeId, path, newPath) { + service.rename = function (volumeId, path, newPath) { var payload = { - CurrentFilePath: path, - NewFilePath: newPath + CurrentFilePath: path, + NewFilePath: newPath, }; return getBrowseService().rename({ volumeID: volumeId, version: getAgentApiVersion() }, payload).$promise; }; @@ -37,26 +43,19 @@ angular.module('portainer.agent').factory('VolumeBrowserService', [ service.upload = function upload(path, file, volumeId, onProgress) { var deferred = $q.defer(); var agentVersion = StateManager.getAgentApiVersion(); - if (agentVersion <2) { + if (agentVersion < 2) { deferred.reject('upload is not supported on this agent version'); return; } - var url = - API_ENDPOINT_ENDPOINTS + - '/' + - EndpointProvider.endpointID() + - '/docker' + - '/v' + agentVersion + - '/browse/put?volumeID=' + - volumeId; + var url = API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/docker' + '/v' + agentVersion + '/browse/put?volumeID=' + volumeId; Upload.upload({ url: url, - data: { file: file, Path: path } + data: { file: file, Path: path }, }).then(deferred.resolve, deferred.reject, onProgress); return deferred.promise; }; return service; - } + }, ]); diff --git a/app/app.js b/app/app.js index 95cf796bf6f96..6cec23d304716 100644 --- a/app/app.js +++ b/app/app.js @@ -1,46 +1,55 @@ import $ from 'jquery'; -import '@babel/polyfill' - -angular.module('portainer') -.run(['$rootScope', '$state', '$interval', 'LocalStorage', 'EndpointProvider', 'SystemService', 'cfpLoadingBar', '$transitions', 'HttpRequestHelper', -function ($rootScope, $state, $interval, LocalStorage, EndpointProvider, SystemService, cfpLoadingBar, $transitions, HttpRequestHelper) { - 'use strict'; - - EndpointProvider.initialize(); - - $rootScope.$state = $state; - - // Workaround to prevent the loading bar from going backward - // https://github.com/chieffancypants/angular-loading-bar/issues/273 - var originalSet = cfpLoadingBar.set; - cfpLoadingBar.set = function overrideSet(n) { - if (n > cfpLoadingBar.status()) { - originalSet.apply(cfpLoadingBar, arguments); - } - }; - - $transitions.onBefore({}, function() { - HttpRequestHelper.resetAgentHeaders(); - }); - - $state.defaultErrorHandler(function() { - // Do not log transitionTo errors - }); - - // Keep-alive Edge endpoints by sending a ping request every minute - $interval(function() { - ping(EndpointProvider, SystemService); - }, 60 * 1000) - - $(document).ajaxSend(function (event, jqXhr, jqOpts) { - const type = jqOpts.type === 'POST' || jqOpts.type === 'PUT' || jqOpts.type === 'PATCH'; - const hasNoContentType = jqOpts.contentType !== 'application/json' && jqOpts.headers && !jqOpts.headers['Content-Type']; - if (type && hasNoContentType) { - jqXhr.setRequestHeader('Content-Type', 'application/json'); - } - jqXhr.setRequestHeader('Authorization', 'Bearer ' + LocalStorage.getJWT()); - }); -}]); +import '@babel/polyfill'; + +angular.module('portainer').run([ + '$rootScope', + '$state', + '$interval', + 'LocalStorage', + 'EndpointProvider', + 'SystemService', + 'cfpLoadingBar', + '$transitions', + 'HttpRequestHelper', + function ($rootScope, $state, $interval, LocalStorage, EndpointProvider, SystemService, cfpLoadingBar, $transitions, HttpRequestHelper) { + 'use strict'; + + EndpointProvider.initialize(); + + $rootScope.$state = $state; + + // Workaround to prevent the loading bar from going backward + // https://github.com/chieffancypants/angular-loading-bar/issues/273 + var originalSet = cfpLoadingBar.set; + cfpLoadingBar.set = function overrideSet(n) { + if (n > cfpLoadingBar.status()) { + originalSet.apply(cfpLoadingBar, arguments); + } + }; + + $transitions.onBefore({}, function () { + HttpRequestHelper.resetAgentHeaders(); + }); + + $state.defaultErrorHandler(function () { + // Do not log transitionTo errors + }); + + // Keep-alive Edge endpoints by sending a ping request every minute + $interval(function () { + ping(EndpointProvider, SystemService); + }, 60 * 1000); + + $(document).ajaxSend(function (event, jqXhr, jqOpts) { + const type = jqOpts.type === 'POST' || jqOpts.type === 'PUT' || jqOpts.type === 'PATCH'; + const hasNoContentType = jqOpts.contentType !== 'application/json' && jqOpts.headers && !jqOpts.headers['Content-Type']; + if (type && hasNoContentType) { + jqXhr.setRequestHeader('Content-Type', 'application/json'); + } + jqXhr.setRequestHeader('Authorization', 'Bearer ' + LocalStorage.getJWT()); + }); + }, +]); function ping(EndpointProvider, SystemService) { let endpoint = EndpointProvider.currentEndpoint(); diff --git a/app/azure/_module.js b/app/azure/_module.js index 663f2c5112d36..a11a5aa5eb8ca 100644 --- a/app/azure/_module.js +++ b/app/azure/_module.js @@ -1,49 +1,51 @@ -angular.module('portainer.azure', ['portainer.app']) -.config(['$stateRegistryProvider', function ($stateRegistryProvider) { - 'use strict'; +angular.module('portainer.azure', ['portainer.app']).config([ + '$stateRegistryProvider', + function ($stateRegistryProvider) { + 'use strict'; - var azure = { - name: 'azure', - url: '/azure', - parent: 'root', - abstract: true - }; + var azure = { + name: 'azure', + url: '/azure', + parent: 'root', + abstract: true, + }; - var containerInstances = { - name: 'azure.containerinstances', - url: '/containerinstances', - views: { - 'content@': { - templateUrl: './views/containerinstances/containerinstances.html', - controller: 'AzureContainerInstancesController' - } - } - }; + var containerInstances = { + name: 'azure.containerinstances', + url: '/containerinstances', + views: { + 'content@': { + templateUrl: './views/containerinstances/containerinstances.html', + controller: 'AzureContainerInstancesController', + }, + }, + }; - var containerInstanceCreation = { - name: 'azure.containerinstances.new', - url: '/new/', - views: { - 'content@': { - templateUrl: './views/containerinstances/create/createcontainerinstance.html', - controller: 'AzureCreateContainerInstanceController' - } - } - }; + var containerInstanceCreation = { + name: 'azure.containerinstances.new', + url: '/new/', + views: { + 'content@': { + templateUrl: './views/containerinstances/create/createcontainerinstance.html', + controller: 'AzureCreateContainerInstanceController', + }, + }, + }; - var dashboard = { - name: 'azure.dashboard', - url: '/dashboard', - views: { - 'content@': { - templateUrl: './views/dashboard/dashboard.html', - controller: 'AzureDashboardController' - } - } - }; + var dashboard = { + name: 'azure.dashboard', + url: '/dashboard', + views: { + 'content@': { + templateUrl: './views/dashboard/dashboard.html', + controller: 'AzureDashboardController', + }, + }, + }; - $stateRegistryProvider.register(azure); - $stateRegistryProvider.register(containerInstances); - $stateRegistryProvider.register(containerInstanceCreation); - $stateRegistryProvider.register(dashboard); -}]); + $stateRegistryProvider.register(azure); + $stateRegistryProvider.register(containerInstances); + $stateRegistryProvider.register(containerInstanceCreation); + $stateRegistryProvider.register(dashboard); + }, +]); diff --git a/app/azure/components/azure-endpoint-config/azure-endpoint-config.js b/app/azure/components/azure-endpoint-config/azure-endpoint-config.js index 2909d38533a96..ff09f09087ad6 100644 --- a/app/azure/components/azure-endpoint-config/azure-endpoint-config.js +++ b/app/azure/components/azure-endpoint-config/azure-endpoint-config.js @@ -2,7 +2,7 @@ angular.module('portainer.azure').component('azureEndpointConfig', { bindings: { applicationId: '=', tenantId: '=', - authenticationKey: '=' + authenticationKey: '=', }, - templateUrl: './azureEndpointConfig.html' + templateUrl: './azureEndpointConfig.html', }); diff --git a/app/azure/components/azure-endpoint-config/azureEndpointConfig.html b/app/azure/components/azure-endpoint-config/azureEndpointConfig.html index c0d839102ca90..efc8bd79f2977 100644 --- a/app/azure/components/azure-endpoint-config/azureEndpointConfig.html +++ b/app/azure/components/azure-endpoint-config/azureEndpointConfig.html @@ -6,7 +6,7 @@
- +
@@ -14,7 +14,7 @@
- +
@@ -22,7 +22,14 @@
- +
diff --git a/app/azure/components/azure-sidebar-content/azure-sidebar-content.js b/app/azure/components/azure-sidebar-content/azure-sidebar-content.js index 68401cc1e778d..daec3ef12b2b0 100644 --- a/app/azure/components/azure-sidebar-content/azure-sidebar-content.js +++ b/app/azure/components/azure-sidebar-content/azure-sidebar-content.js @@ -1,3 +1,3 @@ angular.module('portainer.azure').component('azureSidebarContent', { - templateUrl: './azureSidebarContent.html' + templateUrl: './azureSidebarContent.html', }); diff --git a/app/azure/components/datatables/containergroups-datatable/containerGroupsDatatable.html b/app/azure/components/datatables/containergroups-datatable/containerGroupsDatatable.html index 54361570cdbb5..f9936d78b0329 100644 --- a/app/azure/components/datatables/containergroups-datatable/containerGroupsDatatable.html +++ b/app/azure/components/datatables/containergroups-datatable/containerGroupsDatatable.html @@ -2,13 +2,10 @@
-
- {{ $ctrl.titleText }} -
+
{{ $ctrl.titleText }}
-
@@ -47,20 +52,23 @@ - + @@ -73,9 +81,7 @@
- + - {{ item.Name | truncate:50 }} + {{ item.Name | truncate: 50 }} {{ item.Location }} :{{ p.port }} - - + -
- -
-
+
+
- \ No newline at end of file + diff --git a/app/docker/components/container-quick-actions/containerQuickActions.html b/app/docker/components/container-quick-actions/containerQuickActions.html index 65ee139497f0d..d4bbc55d107a0 100644 --- a/app/docker/components/container-quick-actions/containerQuickActions.html +++ b/app/docker/components/container-quick-actions/containerQuickActions.html @@ -1,10 +1,11 @@ - diff --git a/app/docker/components/container-quick-actions/containerQuickActions.js b/app/docker/components/container-quick-actions/containerQuickActions.js index 619cf7efa74ff..6f8631df738b2 100644 --- a/app/docker/components/container-quick-actions/containerQuickActions.js +++ b/app/docker/components/container-quick-actions/containerQuickActions.js @@ -5,6 +5,6 @@ angular.module('portainer.docker').component('containerQuickActions', { nodeName: '<', status: '<', state: '<', - taskId: '<' - } + taskId: '<', + }, }); diff --git a/app/docker/components/container-restart-policy/container-restart-policy-controller.js b/app/docker/components/container-restart-policy/container-restart-policy-controller.js index 64a8604fcde7a..159b8a2f5a226 100644 --- a/app/docker/components/container-restart-policy/container-restart-policy-controller.js +++ b/app/docker/components/container-restart-policy/container-restart-policy-controller.js @@ -1,26 +1,25 @@ -angular -.module('portainer.docker') -.controller('ContainerRestartPolicyController', [function ContainerRestartPolicyController() { - var ctrl = this; +angular.module('portainer.docker').controller('ContainerRestartPolicyController', [ + function ContainerRestartPolicyController() { + var ctrl = this; - this.state = { - editModel : {} - }; + this.state = { + editModel: {}, + }; - ctrl.save = save; + ctrl.save = save; - function save() { - if (ctrl.state.editModel.name === ctrl.name && ctrl.state.editModel.maximumRetryCount === ctrl.maximumRetryCount) { - return; + function save() { + if (ctrl.state.editModel.name === ctrl.name && ctrl.state.editModel.maximumRetryCount === ctrl.maximumRetryCount) { + return; + } + ctrl.updateRestartPolicy(ctrl.state.editModel); } - ctrl.updateRestartPolicy(ctrl.state.editModel); - } - this.$onInit = function() { - ctrl.state.editModel = { - name: ctrl.name ? ctrl.name : 'no', - maximumRetryCount: ctrl.maximumRetryCount + this.$onInit = function () { + ctrl.state.editModel = { + name: ctrl.name ? ctrl.name : 'no', + maximumRetryCount: ctrl.maximumRetryCount, + }; }; - }; -} + }, ]); diff --git a/app/docker/components/container-restart-policy/container-restart-policy.js b/app/docker/components/container-restart-policy/container-restart-policy.js index 60e4f0c4b6ea4..bc4d02709b685 100644 --- a/app/docker/components/container-restart-policy/container-restart-policy.js +++ b/app/docker/components/container-restart-policy/container-restart-policy.js @@ -1,10 +1,9 @@ -angular.module('portainer.docker') -.component('containerRestartPolicy', { +angular.module('portainer.docker').component('containerRestartPolicy', { templateUrl: './container-restart-policy.html', controller: 'ContainerRestartPolicyController', bindings: { - 'name': '<', - 'maximumRetryCount': '<', - 'updateRestartPolicy': '&' - } + name: '<', + maximumRetryCount: '<', + updateRestartPolicy: '&', + }, }); diff --git a/app/docker/components/dashboard-cluster-agent-info/dashboard-cluster-agent-info.js b/app/docker/components/dashboard-cluster-agent-info/dashboard-cluster-agent-info.js index 88aae5cec26a7..6498c2edcb5aa 100644 --- a/app/docker/components/dashboard-cluster-agent-info/dashboard-cluster-agent-info.js +++ b/app/docker/components/dashboard-cluster-agent-info/dashboard-cluster-agent-info.js @@ -1,4 +1,4 @@ angular.module('portainer.docker').component('dashboardClusterAgentInfo', { templateUrl: './dashboardClusterAgentInfo.html', - controller: 'DashboardClusterAgentInfoController' + controller: 'DashboardClusterAgentInfoController', }); diff --git a/app/docker/components/dashboard-cluster-agent-info/dashboardClusterAgentInfoController.js b/app/docker/components/dashboard-cluster-agent-info/dashboardClusterAgentInfoController.js index 7ba6f7f08578d..2b421729b7e10 100644 --- a/app/docker/components/dashboard-cluster-agent-info/dashboardClusterAgentInfoController.js +++ b/app/docker/components/dashboard-cluster-agent-info/dashboardClusterAgentInfoController.js @@ -1,16 +1,17 @@ -angular.module('portainer.docker') -.controller('DashboardClusterAgentInfoController', ['AgentService', 'Notifications', -function (AgentService, Notifications) { - var ctrl = this; +angular.module('portainer.docker').controller('DashboardClusterAgentInfoController', [ + 'AgentService', + 'Notifications', + function (AgentService, Notifications) { + var ctrl = this; - this.$onInit = function() { - AgentService.agents() - .then(function success(data) { - ctrl.agentCount = data.length; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve agent information'); - }); - }; - -}]); + this.$onInit = function () { + AgentService.agents() + .then(function success(data) { + ctrl.agentCount = data.length; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve agent information'); + }); + }; + }, +]); diff --git a/app/docker/components/datatables/configs-datatable/configsDatatable.html b/app/docker/components/datatables/configs-datatable/configsDatatable.html index d6a1e386ddfc5..a6b8aa6a75403 100644 --- a/app/docker/components/datatables/configs-datatable/configsDatatable.html +++ b/app/docker/components/datatables/configs-datatable/configsDatatable.html @@ -2,9 +2,7 @@
-
- {{ $ctrl.titleText }} -
+
{{ $ctrl.titleText }}
Settings @@ -16,7 +14,7 @@
{{ key }}
{{ key }} {{ value.IPAddress || '-' }} {{ value.Gateway || '-' }} {{ value.MacAddress || '-' }} - diff --git a/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.js b/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.js index 7ac1b55801177..501f86c463dc9 100644 --- a/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.js +++ b/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.js @@ -12,6 +12,6 @@ angular.module('portainer.docker').component('containerNetworksDatatable', { joinNetworkActionInProgress: '<', leaveNetworkActionInProgress: '<', leaveNetworkAction: '<', - nodeName: '<' - } + nodeName: '<', + }, }); diff --git a/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.html b/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.html index 37f3152b55bbd..326deef73badd 100644 --- a/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.html +++ b/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.html @@ -2,13 +2,11 @@
-
- {{ $ctrl.titleText }} -
+
{{ $ctrl.titleText }}
diff --git a/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.js b/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.js index 1a0abdc3a21ea..b89a4d63bbcfc 100644 --- a/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.js +++ b/app/docker/components/datatables/container-processes-datatable/containerProcessesDatatable.js @@ -8,6 +8,6 @@ angular.module('portainer.docker').component('containerProcessesDatatable', { headerset: '<', tableKey: '@', orderBy: '@', - reverseOrder: '<' - } + reverseOrder: '<', + }, }); diff --git a/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.html b/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.html index 873657caf20cc..9d9f8d9f82caa 100644 --- a/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.html +++ b/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.html @@ -1,31 +1,69 @@ -
+
- - - - - - -
diff --git a/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.js b/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.js index 2f82916a16a17..b6f83f273672f 100644 --- a/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.js +++ b/app/docker/components/datatables/containers-datatable/actions/containersDatatableActions.js @@ -7,6 +7,6 @@ angular.module('portainer.docker').component('containersDatatableActions', { noStoppedItemsSelected: '=', noRunningItemsSelected: '=', noPausedItemsSelected: '=', - showAddAction: '<' - } + showAddAction: '<', + }, }); diff --git a/app/docker/components/datatables/containers-datatable/actions/containersDatatableActionsController.js b/app/docker/components/datatables/containers-datatable/actions/containersDatatableActionsController.js index efacb89e725d5..432df50f8602e 100644 --- a/app/docker/components/datatables/containers-datatable/actions/containersDatatableActionsController.js +++ b/app/docker/components/datatables/containers-datatable/actions/containersDatatableActionsController.js @@ -1,106 +1,112 @@ -angular.module('portainer.docker') -.controller('ContainersDatatableActionsController', ['$state', 'ContainerService', 'ModalService', 'Notifications', 'HttpRequestHelper', -function ($state, ContainerService, ModalService, Notifications, HttpRequestHelper) { - this.startAction = function(selectedItems) { - var successMessage = 'Container successfully started'; - var errorMessage = 'Unable to start container'; - executeActionOnContainerList(selectedItems, ContainerService.startContainer, successMessage, errorMessage); - }; +angular.module('portainer.docker').controller('ContainersDatatableActionsController', [ + '$state', + 'ContainerService', + 'ModalService', + 'Notifications', + 'HttpRequestHelper', + function ($state, ContainerService, ModalService, Notifications, HttpRequestHelper) { + this.startAction = function (selectedItems) { + var successMessage = 'Container successfully started'; + var errorMessage = 'Unable to start container'; + executeActionOnContainerList(selectedItems, ContainerService.startContainer, successMessage, errorMessage); + }; - this.stopAction = function(selectedItems) { - var successMessage = 'Container successfully stopped'; - var errorMessage = 'Unable to stop container'; - executeActionOnContainerList(selectedItems, ContainerService.stopContainer, successMessage, errorMessage); - }; + this.stopAction = function (selectedItems) { + var successMessage = 'Container successfully stopped'; + var errorMessage = 'Unable to stop container'; + executeActionOnContainerList(selectedItems, ContainerService.stopContainer, successMessage, errorMessage); + }; - this.restartAction = function(selectedItems) { - var successMessage = 'Container successfully restarted'; - var errorMessage = 'Unable to restart container'; - executeActionOnContainerList(selectedItems, ContainerService.restartContainer, successMessage, errorMessage); - }; + this.restartAction = function (selectedItems) { + var successMessage = 'Container successfully restarted'; + var errorMessage = 'Unable to restart container'; + executeActionOnContainerList(selectedItems, ContainerService.restartContainer, successMessage, errorMessage); + }; - this.killAction = function(selectedItems) { - var successMessage = 'Container successfully killed'; - var errorMessage = 'Unable to kill container'; - executeActionOnContainerList(selectedItems, ContainerService.killContainer, successMessage, errorMessage); - }; + this.killAction = function (selectedItems) { + var successMessage = 'Container successfully killed'; + var errorMessage = 'Unable to kill container'; + executeActionOnContainerList(selectedItems, ContainerService.killContainer, successMessage, errorMessage); + }; - this.pauseAction = function(selectedItems) { - var successMessage = 'Container successfully paused'; - var errorMessage = 'Unable to pause container'; - executeActionOnContainerList(selectedItems, ContainerService.pauseContainer, successMessage, errorMessage); - }; + this.pauseAction = function (selectedItems) { + var successMessage = 'Container successfully paused'; + var errorMessage = 'Unable to pause container'; + executeActionOnContainerList(selectedItems, ContainerService.pauseContainer, successMessage, errorMessage); + }; - this.resumeAction = function(selectedItems) { - var successMessage = 'Container successfully resumed'; - var errorMessage = 'Unable to resume container'; - executeActionOnContainerList(selectedItems, ContainerService.resumeContainer, successMessage, errorMessage); - }; + this.resumeAction = function (selectedItems) { + var successMessage = 'Container successfully resumed'; + var errorMessage = 'Unable to resume container'; + executeActionOnContainerList(selectedItems, ContainerService.resumeContainer, successMessage, errorMessage); + }; - this.removeAction = function(selectedItems) { - var isOneContainerRunning = false; - for (var i = 0; i < selectedItems.length; i++) { - var container = selectedItems[i]; - if (container.State === 'running') { - isOneContainerRunning = true; - break; + this.removeAction = function (selectedItems) { + var isOneContainerRunning = false; + for (var i = 0; i < selectedItems.length; i++) { + var container = selectedItems[i]; + if (container.State === 'running') { + isOneContainerRunning = true; + break; + } } - } - var title = 'You are about to remove one or more container.'; - if (isOneContainerRunning) { - title = 'You are about to remove one or more running container.'; - } + var title = 'You are about to remove one or more container.'; + if (isOneContainerRunning) { + title = 'You are about to remove one or more running container.'; + } - ModalService.confirmContainerDeletion(title, function (result) { - if(!result) { return; } + ModalService.confirmContainerDeletion(title, function (result) { + if (!result) { + return; + } var cleanVolumes = false; if (result[0]) { cleanVolumes = true; } removeSelectedContainers(selectedItems, cleanVolumes); - } - ); - }; + }); + }; - function executeActionOnContainerList(containers, action, successMessage, errorMessage) { - var actionCount = containers.length; - angular.forEach(containers, function (container) { - HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName); - action(container.Id) - .then(function success() { - Notifications.success(successMessage, container.Names[0]); - }) - .catch(function error(err) { - errorMessage = errorMessage + ':' + container.Names[0]; - Notifications.error('Failure', err, errorMessage); - }) - .finally(function final() { - --actionCount; - if (actionCount === 0) { - $state.reload(); - } + function executeActionOnContainerList(containers, action, successMessage, errorMessage) { + var actionCount = containers.length; + angular.forEach(containers, function (container) { + HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName); + action(container.Id) + .then(function success() { + Notifications.success(successMessage, container.Names[0]); + }) + .catch(function error(err) { + errorMessage = errorMessage + ':' + container.Names[0]; + Notifications.error('Failure', err, errorMessage); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); - }); - } + } - function removeSelectedContainers(containers, cleanVolumes) { - var actionCount = containers.length; - angular.forEach(containers, function (container) { - HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName); - ContainerService.remove(container, cleanVolumes) - .then(function success() { - Notifications.success('Container successfully removed', container.Names[0]); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove container'); - }) - .finally(function final() { - --actionCount; - if (actionCount === 0) { - $state.reload(); - } + function removeSelectedContainers(containers, cleanVolumes) { + var actionCount = containers.length; + angular.forEach(containers, function (container) { + HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName); + ContainerService.remove(container, cleanVolumes) + .then(function success() { + Notifications.success('Container successfully removed', container.Names[0]); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove container'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); - }); - } -}]); + } + }, +]); diff --git a/app/docker/components/datatables/containers-datatable/containersDatatable.html b/app/docker/components/datatables/containers-datatable/containersDatatable.html index b8bdc3a4b555b..638b45e72d13e 100644 --- a/app/docker/components/datatables/containers-datatable/containersDatatable.html +++ b/app/docker/components/datatables/containers-datatable/containersDatatable.html @@ -2,60 +2,65 @@
-
- {{ $ctrl.titleText }} -
+
{{ $ctrl.titleText }}
- - Columns -
- @@ -235,39 +263,69 @@ - + - - +
- + @@ -170,7 +188,7 @@ @@ -180,7 +198,17 @@ + Quick actions @@ -199,8 +227,8 @@ - - + + Created
- - + + - {{ item | containername | truncate: $ctrl.settings.containerNameTruncateSize }} + {{ + item | containername | truncate: $ctrl.settings.containerNameTruncateSize + }} {{ item | containername | truncate: $ctrl.settings.containerNameTruncateSize }} - {{ item.Status }} - {{ item.Status }} + {{ item.Status }} + {{ item.Status }} + - {{ item.StackName ? item.StackName : '-' }} {{ item.Image | trimshasum }} {{ item.Image | trimshasum }} - {{item.Created | getisodatefromtimestamp}} + {{ item.Created | getisodatefromtimestamp }} {{ item.IP ? item.IP : '-' }} {{ item.NodeName ? item.NodeName : '-' }} - + {{ p.public }}:{{ p.private }} - - + - @@ -286,9 +344,7 @@
{{device.Name}}{{device.Vendor}}{{ device.Name }}{{ device.Vendor }}
Loading...
{{disk.Vendor}}{{disk.Size | humansize}}{{ disk.Vendor }}{{ disk.Size | humansize }}
Loading...
Version{{ $ctrl.engine.releaseVersion }} (API: {{ $ctrl.engine.apiVersion }}){{ $ctrl.engine.releaseVersion }} (API: {{ $ctrl.engine.apiVersion }})
Root directory
Engine Labels{{ $ctrl.engine.engineLabels | labelsToStr:', ' }}{{ $ctrl.engine.engineLabels | labelsToStr: ', ' }}
-
\ No newline at end of file +
diff --git a/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.js b/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.js index 12bb2fb1e9a3d..543a5909ed221 100644 --- a/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.js +++ b/app/docker/components/host-view-panels/engine-details-panel/engine-details-panel.js @@ -1,6 +1,6 @@ angular.module('portainer.docker').component('engineDetailsPanel', { templateUrl: './engine-details-panel.html', bindings: { - engine: '<' - } + engine: '<', + }, }); diff --git a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html index 9b5b006d7280a..24ba17e706b56 100644 --- a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html +++ b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.html @@ -11,8 +11,7 @@ OS Information - {{ $ctrl.host.os.type }} {{$ctrl.host.os.arch}} - {{$ctrl.host.os.name}} + {{ $ctrl.host.os.type }} {{ $ctrl.host.os.arch }} {{ $ctrl.host.os.name }} Kernel Version @@ -28,10 +27,10 @@ - - diff --git a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js index 5d25a626e3c07..693a022b7612a 100644 --- a/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js +++ b/app/docker/components/host-view-panels/host-details-panel/host-details-panel.js @@ -5,6 +5,6 @@ angular.module('portainer.docker').component('hostDetailsPanel', { isJobEnabled: '<', isBrowseEnabled: '<', browseUrl: '@', - jobUrl: '@' - } + jobUrl: '@', + }, }); diff --git a/app/docker/components/host-view-panels/node-availability-select/node-availability-select-controller.js b/app/docker/components/host-view-panels/node-availability-select/node-availability-select-controller.js index 52df40cd6eff5..2ebd29ee61c66 100644 --- a/app/docker/components/host-view-panels/node-availability-select/node-availability-select-controller.js +++ b/app/docker/components/host-view-panels/node-availability-select/node-availability-select-controller.js @@ -1,11 +1,9 @@ -angular - .module('portainer.docker') - .controller('NodeAvailabilitySelectController', [ - function NodeAvailabilitySelectController() { - this.onChange = onChange; +angular.module('portainer.docker').controller('NodeAvailabilitySelectController', [ + function NodeAvailabilitySelectController() { + this.onChange = onChange; - function onChange() { - this.onSave({ availability: this.availability }); - } + function onChange() { + this.onSave({ availability: this.availability }); } - ]); + }, +]); diff --git a/app/docker/components/host-view-panels/node-availability-select/node-availability-select.html b/app/docker/components/host-view-panels/node-availability-select/node-availability-select.html index 94e0861270290..b37f7d9d6c356 100644 --- a/app/docker/components/host-view-panels/node-availability-select/node-availability-select.html +++ b/app/docker/components/host-view-panels/node-availability-select/node-availability-select.html @@ -1,8 +1,7 @@
- -
\ No newline at end of file +
diff --git a/app/docker/components/host-view-panels/node-availability-select/node-availability-select.js b/app/docker/components/host-view-panels/node-availability-select/node-availability-select.js index b396d65f70c95..b9af163be5097 100644 --- a/app/docker/components/host-view-panels/node-availability-select/node-availability-select.js +++ b/app/docker/components/host-view-panels/node-availability-select/node-availability-select.js @@ -4,6 +4,6 @@ angular.module('portainer.docker').component('nodeAvailabilitySelect', { bindings: { availability: '<', originalValue: '<', - onSave: '&' - } + onSave: '&', + }, }); diff --git a/app/docker/components/host-view-panels/node-labels-table/node-labels-table-controller.js b/app/docker/components/host-view-panels/node-labels-table/node-labels-table-controller.js index a9ad8ab582617..4296d4bc00cfa 100644 --- a/app/docker/components/host-view-panels/node-labels-table/node-labels-table-controller.js +++ b/app/docker/components/host-view-panels/node-labels-table/node-labels-table-controller.js @@ -12,12 +12,9 @@ angular.module('portainer.docker').controller('NodeLabelsTableController', [ } function updateLabel(label) { - if ( - label.value !== label.originalValue || - label.key !== label.originalKey - ) { + if (label.value !== label.originalValue || label.key !== label.originalKey) { ctrl.onChangedLabels({ labels: ctrl.labels }); } } - } + }, ]); diff --git a/app/docker/components/host-view-panels/node-labels-table/node-labels-table.html b/app/docker/components/host-view-panels/node-labels-table/node-labels-table.html index 86eee9356d75f..e26ec168cbba4 100644 --- a/app/docker/components/host-view-panels/node-labels-table/node-labels-table.html +++ b/app/docker/components/host-view-panels/node-labels-table/node-labels-table.html @@ -14,15 +14,13 @@
Name - +
Value - + -
-
\ No newline at end of file +
diff --git a/app/docker/components/host-view-panels/swarm-node-details-panel/swarm-node-details-panel.js b/app/docker/components/host-view-panels/swarm-node-details-panel/swarm-node-details-panel.js index ad5bf7a7a50f9..751e4aea4aad1 100644 --- a/app/docker/components/host-view-panels/swarm-node-details-panel/swarm-node-details-panel.js +++ b/app/docker/components/host-view-panels/swarm-node-details-panel/swarm-node-details-panel.js @@ -3,6 +3,6 @@ angular.module('portainer.docker').component('swarmNodeDetailsPanel', { controller: 'SwarmNodeDetailsPanelController', bindings: { details: '<', - originalNode: '<' - } + originalNode: '<', + }, }); diff --git a/app/docker/components/imageRegistry/por-image-registry.js b/app/docker/components/imageRegistry/por-image-registry.js index 39d6a591976fd..4c2f7b4bd4974 100644 --- a/app/docker/components/imageRegistry/por-image-registry.js +++ b/app/docker/components/imageRegistry/por-image-registry.js @@ -2,13 +2,13 @@ angular.module('portainer.docker').component('porImageRegistry', { templateUrl: './porImageRegistry.html', controller: 'porImageRegistryController', bindings: { - 'model': '=', // must be of type PorImageRegistryModel - 'pullWarning': '<', - 'autoComplete': '<', - 'labelClass': '@', - 'inputClass': '@' + model: '=', // must be of type PorImageRegistryModel + pullWarning: '<', + autoComplete: '<', + labelClass: '@', + inputClass: '@', }, require: { - form: '^form' - } + form: '^form', + }, }); diff --git a/app/docker/components/imageRegistry/porImageRegistry.html b/app/docker/components/imageRegistry/porImageRegistry.html index 05bc7ea07c728..feee8ba1b6d45 100644 --- a/app/docker/components/imageRegistry/porImageRegistry.html +++ b/app/docker/components/imageRegistry/porImageRegistry.html @@ -5,15 +5,28 @@ Registry
- +
- {{$ctrl.displayedRegistryURL()}} - + {{ $ctrl.displayedRegistryURL() }} +
@@ -22,11 +35,12 @@
-
@@ -35,7 +49,10 @@
-

Image name is required. Tag must be specified otherwise Portainer will pull all tags associated to the image.

+

Image name is required. + Tag must be specified otherwise Portainer will pull all tags associated to the image.

diff --git a/app/docker/components/imageRegistry/porImageRegistryController.js b/app/docker/components/imageRegistry/porImageRegistryController.js index 7f4e9cfbc0594..f16902c9db88e 100644 --- a/app/docker/components/imageRegistry/porImageRegistryController.js +++ b/app/docker/components/imageRegistry/porImageRegistryController.js @@ -38,7 +38,7 @@ class porImageRegistryController { if (this.isKnownRegistry(registry)) { const url = this.getRegistryURL(registry); const registryImages = _.filter(this.images, (image) => _.includes(image, url)); - images = _.map(registryImages, (image) => _.replace(image, new RegExp(url + '\/?'), '')); + images = _.map(registryImages, (image) => _.replace(image, new RegExp(url + '/?'), '')); } else { const registries = _.filter(this.availableRegistries, (reg) => this.isKnownRegistry(reg)); const registryImages = _.flatMap(registries, (registry) => _.filter(this.images, (image) => _.includes(image, registry.URL))); @@ -64,7 +64,7 @@ class porImageRegistryController { const [registries, dockerhub, images] = await Promise.all([ this.RegistryService.registries(), this.DockerHubService.dockerhub(), - this.autoComplete ? this.ImageService.images() : [] + this.autoComplete ? this.ImageService.images() : [], ]); this.images = this.ImageService.getUniqueTagListFromImages(images); this.availableRegistries = _.concat(dockerhub, registries); @@ -73,7 +73,7 @@ class porImageRegistryController { if (!id) { this.model.Registry = dockerhub; } else { - this.model.Registry = _.find(this.availableRegistries, { 'Id': id }); + this.model.Registry = _.find(this.availableRegistries, { Id: id }); } } catch (err) { this.Notifications.error('Failure', err, 'Unable to retrieve registries'); diff --git a/app/docker/components/log-viewer/log-viewer.js b/app/docker/components/log-viewer/log-viewer.js index 0be45bc3ccdcb..7521916ea77f2 100644 --- a/app/docker/components/log-viewer/log-viewer.js +++ b/app/docker/components/log-viewer/log-viewer.js @@ -6,6 +6,6 @@ angular.module('portainer.docker').component('logViewer', { displayTimestamps: '=', logCollectionChange: '<', sinceTimestamp: '=', - lineCount: '=' - } + lineCount: '=', + }, }); diff --git a/app/docker/components/log-viewer/logViewer.html b/app/docker/components/log-viewer/logViewer.html index 44c1c5d7e095f..ac9095ebc2ad2 100644 --- a/app/docker/components/log-viewer/logViewer.html +++ b/app/docker/components/log-viewer/logViewer.html @@ -11,7 +11,11 @@ @@ -20,9 +24,7 @@ - +
@@ -30,9 +32,7 @@ - +
@@ -51,7 +51,7 @@ Search
- +
@@ -59,7 +59,7 @@ Lines
- +
@@ -67,9 +67,21 @@ Actions
- - - + + + @@ -81,9 +93,9 @@
-
-
-
+
+
+
       

{{ line }}

No log line matching the '{{ $ctrl.state.search }}' filter

No logs available

diff --git a/app/docker/components/log-viewer/logViewerController.js b/app/docker/components/log-viewer/logViewerController.js index 9fdc626637f16..d55fe555ea677 100644 --- a/app/docker/components/log-viewer/logViewerController.js +++ b/app/docker/components/log-viewer/logViewerController.js @@ -1,47 +1,47 @@ import moment from 'moment'; -angular.module('portainer.docker') -.controller('LogViewerController', ['clipboard', -function (clipboard) { +angular.module('portainer.docker').controller('LogViewerController', [ + 'clipboard', + function (clipboard) { + this.state = { + availableSinceDatetime: [ + { desc: 'Last day', value: moment().subtract(1, 'days').format() }, + { desc: 'Last 4 hours', value: moment().subtract(4, 'hours').format() }, + { desc: 'Last hour', value: moment().subtract(1, 'hours').format() }, + { desc: 'Last 10 minutes', value: moment().subtract(10, 'minutes').format() }, + ], + copySupported: clipboard.supported, + logCollection: true, + autoScroll: true, + wrapLines: true, + search: '', + filteredLogs: [], + selectedLines: [], + }; - this.state = { - availableSinceDatetime: [ - { desc: 'Last day', value: moment().subtract(1, 'days').format() }, - { desc: 'Last 4 hours', value: moment().subtract(4, 'hours').format() }, - { desc: 'Last hour', value: moment().subtract(1, 'hours').format() }, - { desc: 'Last 10 minutes', value: moment().subtract(10, 'minutes').format() } - ], - copySupported: clipboard.supported, - logCollection: true, - autoScroll: true, - wrapLines: true, - search: '', - filteredLogs: [], - selectedLines: [] - }; + this.copy = function () { + clipboard.copyText(this.state.filteredLogs); + $('#refreshRateChange').show(); + $('#refreshRateChange').fadeOut(2000); + }; - this.copy = function() { - clipboard.copyText(this.state.filteredLogs); - $('#refreshRateChange').show(); - $('#refreshRateChange').fadeOut(2000); - }; + this.copySelection = function () { + clipboard.copyText(this.state.selectedLines); + $('#refreshRateChange').show(); + $('#refreshRateChange').fadeOut(2000); + }; - this.copySelection = function() { - clipboard.copyText(this.state.selectedLines); - $('#refreshRateChange').show(); - $('#refreshRateChange').fadeOut(2000); - }; + this.clearSelection = function () { + this.state.selectedLines = []; + }; - this.clearSelection = function() { - this.state.selectedLines = []; - }; - - this.selectLine = function(line) { - var idx = this.state.selectedLines.indexOf(line); - if (idx === -1) { - this.state.selectedLines.push(line); - } else { - this.state.selectedLines.splice(idx, 1); - } - }; -}]); + this.selectLine = function (line) { + var idx = this.state.selectedLines.indexOf(line); + if (idx === -1) { + this.state.selectedLines.push(line); + } else { + this.state.selectedLines.splice(idx, 1); + } + }; + }, +]); diff --git a/app/docker/components/network-macvlan-form/network-macvlan-form.js b/app/docker/components/network-macvlan-form/network-macvlan-form.js index d9f07d4c8bf1d..3ae345cfb3917 100644 --- a/app/docker/components/network-macvlan-form/network-macvlan-form.js +++ b/app/docker/components/network-macvlan-form/network-macvlan-form.js @@ -3,6 +3,6 @@ angular.module('portainer.docker').component('networkMacvlanForm', { controller: 'NetworkMacvlanFormController', bindings: { data: '=', - applicationState: '<' - } -}); \ No newline at end of file + applicationState: '<', + }, +}); diff --git a/app/docker/components/network-macvlan-form/networkMacvlanForm.html b/app/docker/components/network-macvlan-form/networkMacvlanForm.html index b104bac9dccf5..fca929aa5ef83 100644 --- a/app/docker/components/network-macvlan-form/networkMacvlanForm.html +++ b/app/docker/components/network-macvlan-form/networkMacvlanForm.html @@ -10,10 +10,10 @@
-
+
- +
-
\ No newline at end of file +
diff --git a/app/docker/filters/filters.js b/app/docker/filters/filters.js index 46bdd65f02688..a7ad2310cf6e0 100644 --- a/app/docker/filters/filters.js +++ b/app/docker/filters/filters.js @@ -1,7 +1,7 @@ import _ from 'lodash-es'; function includeString(text, values) { - return values.some(function(val){ + return values.some(function (val) { return text.indexOf(val) !== -1; }); } @@ -16,290 +16,293 @@ function strToHash(str) { function hashToHexColor(hash) { var color = '#'; - for (var i = 0; i < 3;) { - color += ('00' + ((hash >> i++ * 8) & 0xFF).toString(16)).slice(-2); + for (var i = 0; i < 3; ) { + color += ('00' + ((hash >> (i++ * 8)) & 0xff).toString(16)).slice(-2); } return color; } -angular.module('portainer.docker') -.filter('visualizerTask', function () { - 'use strict'; - return function (text) { - var status = _.toLower(text); - if (includeString(status, ['new', 'allocated', 'assigned', 'accepted', 'complete', 'preparing'])) { - return 'info'; - } else if (includeString(status, ['pending'])) { - return 'warning'; - } else if (includeString(status, ['shutdown', 'failed', 'rejected'])) { - return 'stopped'; - } - return 'running'; - }; -}) -.filter('visualizerTaskBorderColor', function () { - 'use strict'; - return function (str) { - var hash = strToHash(str); - var color = hashToHexColor(hash); - return color; - }; -}) -.filter('taskstatusbadge', function () { - 'use strict'; - return function (text) { - var status = _.toLower(text); - var labelStyle = 'default'; - if (includeString(status, ['new', 'allocated', 'assigned', 'accepted', 'preparing', 'ready', 'starting', 'remove'])) { - labelStyle = 'info'; - } else if (includeString(status, ['pending'])) { - labelStyle = 'warning'; - } else if (includeString(status, ['shutdown', 'failed', 'rejected', 'orphaned'])) { - labelStyle = 'danger'; - } else if (includeString(status, ['complete'])) { - labelStyle = 'primary'; - } else if (includeString(status, ['running'])) { - labelStyle = 'success'; - } - return labelStyle; - }; -}) -.filter('taskhaslogs', function () { - 'use strict'; - return function (state) { - var validState = ['running', 'complete', 'failed', 'shutdown']; - if (validState.indexOf(state) > -1) { - return true; - } - return false; - }; -}) -.filter('containerstatusbadge', function () { - 'use strict'; - return function (text) { - var status = _.toLower(text); - if (includeString(status, ['paused', 'starting', 'unhealthy'])) { - return 'warning'; - } else if (includeString(status, ['created'])) { - return 'info'; - } else if (includeString(status, ['stopped', 'dead', 'exited'])) { - return 'danger'; - } - return 'success'; - }; -}) -.filter('nodestatusbadge', function () { - 'use strict'; - return function (text) { - if (text === 'down' || text === 'Unhealthy') { - return 'danger'; - } - return 'success'; - }; -}) -.filter('dockerNodeAvailabilityBadge', function () { - 'use strict'; - return function (text) { - if (text === 'pause') { - return 'warning'; - } - else if (text === 'drain') { - return 'danger'; - } - return 'success'; - }; -}) -.filter('trimcontainername', function () { - 'use strict'; - return function (name) { - if (name) { - return (name.indexOf('/') === 0 ? name.replace('/','') : name); - } - return ''; - }; -}) -.filter('getstatetext', function () { - 'use strict'; - return function (state) { - if (state === undefined) { +angular + .module('portainer.docker') + .filter('visualizerTask', function () { + 'use strict'; + return function (text) { + var status = _.toLower(text); + if (includeString(status, ['new', 'allocated', 'assigned', 'accepted', 'complete', 'preparing'])) { + return 'info'; + } else if (includeString(status, ['pending'])) { + return 'warning'; + } else if (includeString(status, ['shutdown', 'failed', 'rejected'])) { + return 'stopped'; + } + return 'running'; + }; + }) + .filter('visualizerTaskBorderColor', function () { + 'use strict'; + return function (str) { + var hash = strToHash(str); + var color = hashToHexColor(hash); + return color; + }; + }) + .filter('taskstatusbadge', function () { + 'use strict'; + return function (text) { + var status = _.toLower(text); + var labelStyle = 'default'; + if (includeString(status, ['new', 'allocated', 'assigned', 'accepted', 'preparing', 'ready', 'starting', 'remove'])) { + labelStyle = 'info'; + } else if (includeString(status, ['pending'])) { + labelStyle = 'warning'; + } else if (includeString(status, ['shutdown', 'failed', 'rejected', 'orphaned'])) { + labelStyle = 'danger'; + } else if (includeString(status, ['complete'])) { + labelStyle = 'primary'; + } else if (includeString(status, ['running'])) { + labelStyle = 'success'; + } + return labelStyle; + }; + }) + .filter('taskhaslogs', function () { + 'use strict'; + return function (state) { + var validState = ['running', 'complete', 'failed', 'shutdown']; + if (validState.indexOf(state) > -1) { + return true; + } + return false; + }; + }) + .filter('containerstatusbadge', function () { + 'use strict'; + return function (text) { + var status = _.toLower(text); + if (includeString(status, ['paused', 'starting', 'unhealthy'])) { + return 'warning'; + } else if (includeString(status, ['created'])) { + return 'info'; + } else if (includeString(status, ['stopped', 'dead', 'exited'])) { + return 'danger'; + } + return 'success'; + }; + }) + .filter('nodestatusbadge', function () { + 'use strict'; + return function (text) { + if (text === 'down' || text === 'Unhealthy') { + return 'danger'; + } + return 'success'; + }; + }) + .filter('dockerNodeAvailabilityBadge', function () { + 'use strict'; + return function (text) { + if (text === 'pause') { + return 'warning'; + } else if (text === 'drain') { + return 'danger'; + } + return 'success'; + }; + }) + .filter('trimcontainername', function () { + 'use strict'; + return function (name) { + if (name) { + return name.indexOf('/') === 0 ? name.replace('/', '') : name; + } return ''; - } - if (state.Dead) { - return 'Dead'; - } - if (state.Ghost && state.Running) { - return 'Ghost'; - } - if (state.Running && state.Paused) { - return 'Running (Paused)'; - } - if (state.Running) { - return 'Running'; - } - if (state.Status === 'created') { - return 'Created'; - } - return 'Stopped'; - }; -}) -.filter('getstatelabel', function () { - 'use strict'; - return function (state) { - if (state === undefined) { + }; + }) + .filter('getstatetext', function () { + 'use strict'; + return function (state) { + if (state === undefined) { + return ''; + } + if (state.Dead) { + return 'Dead'; + } + if (state.Ghost && state.Running) { + return 'Ghost'; + } + if (state.Running && state.Paused) { + return 'Running (Paused)'; + } + if (state.Running) { + return 'Running'; + } + if (state.Status === 'created') { + return 'Created'; + } + return 'Stopped'; + }; + }) + .filter('getstatelabel', function () { + 'use strict'; + return function (state) { + if (state === undefined) { + return 'label-default'; + } + if (state.Ghost && state.Running) { + return 'label-important'; + } + if (state.Running) { + return 'label-success'; + } return 'label-default'; - } - if (state.Ghost && state.Running) { - return 'label-important'; - } - if (state.Running) { - return 'label-success'; - } - return 'label-default'; - }; -}) -.filter('containername', function () { - 'use strict'; - return function (container) { - var name = container.Names[0]; - return name.substring(1, name.length); - }; -}) -.filter('swarmversion', function () { - 'use strict'; - return function (text) { - return _.split(text, '/')[1]; - }; -}) -.filter('swarmhostname', function () { - 'use strict'; - return function (container) { - return _.split(container.Names[0], '/')[1]; - }; -}) -.filter('repotags', function () { - 'use strict'; - return function (image) { - if (image.RepoTags && image.RepoTags.length > 0) { - var tag = image.RepoTags[0]; - if (tag === ':') { - return []; + }; + }) + .filter('containername', function () { + 'use strict'; + return function (container) { + var name = container.Names[0]; + return name.substring(1, name.length); + }; + }) + .filter('swarmversion', function () { + 'use strict'; + return function (text) { + return _.split(text, '/')[1]; + }; + }) + .filter('swarmhostname', function () { + 'use strict'; + return function (container) { + return _.split(container.Names[0], '/')[1]; + }; + }) + .filter('repotags', function () { + 'use strict'; + return function (image) { + if (image.RepoTags && image.RepoTags.length > 0) { + var tag = image.RepoTags[0]; + if (tag === ':') { + return []; + } + return image.RepoTags; } - return image.RepoTags; - } - return []; - }; -}) -.filter('command', function () { - 'use strict'; - return function (command) { - if (command) { - return command.join(' '); - } - }; -}) -.filter('hideshasum', function () { - 'use strict'; - return function (imageName) { - if (imageName) { - return imageName.split('@sha')[0]; - } - return ''; - }; -}) -.filter('availablenodecount', ['ConstraintsHelper', function (ConstraintsHelper) { - 'use strict'; - return function (nodes, service) { - var availableNodes = 0; - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - if (node.Availability === 'active' && node.Status === 'ready' && ConstraintsHelper.matchesServiceConstraints(service, node)) { - availableNodes++; + return []; + }; + }) + .filter('command', function () { + 'use strict'; + return function (command) { + if (command) { + return command.join(' '); } - } - return availableNodes; - }; -}]) -.filter('runningtaskscount', function () { - 'use strict'; - return function (tasks) { - var runningTasks = 0; - for (var i = 0; i < tasks.length; i++) { - var task = tasks[i]; - if (task.Status.State === 'running' && task.DesiredState === 'running') { - runningTasks++; + }; + }) + .filter('hideshasum', function () { + 'use strict'; + return function (imageName) { + if (imageName) { + return imageName.split('@sha')[0]; } - } - return runningTasks; - }; -}) -.filter('runningcontainers', function () { - 'use strict'; - return function runningContainersFilter(containers) { - return containers.filter(function (container) { - return container.State === 'running'; - }).length; - }; -}) -.filter('stoppedcontainers', function () { - 'use strict'; - return function stoppedContainersFilter(containers) { - return containers.filter(function (container) { - return container.State === 'exited'; - }).length; - }; -}) -.filter('healthycontainers', function () { - 'use strict'; - return function healthyContainersFilter(containers) { - return containers.filter(function (container) { - return container.Status === 'healthy'; - }).length; - } -}) -.filter('unhealthycontainers', function () { - 'use strict'; - return function unhealthyContainersFilter(containers) { - return containers.filter(function (container) { - return container.Status === 'unhealthy'; - }).length; - } -}) -.filter('imagestotalsize', function () { - 'use strict'; - return function (images) { - var totalImageSize = 0; - for (var i = 0; i < images.length; i++) { - var item = images[i]; - totalImageSize += item.VirtualSize; - } - return totalImageSize; - }; -}) -.filter('tasknodename', function () { - 'use strict'; - return function (nodeId, nodes) { - var node = _.find(nodes, { Id: nodeId }); - if (node) { - return node.Hostname; - } - return ''; - }; -}) -.filter('imagelayercommand', function () { - 'use strict'; - return function (createdBy) { - return createdBy.replace('/bin/sh -c #(nop) ', '').replace('/bin/sh -c ', 'RUN '); - }; -}) -.filter('trimshasum', function () { - 'use strict'; - return function (imageName) { - if (!imageName) { - return; - } - if (imageName.indexOf('sha256:') === 0) { - return imageName.substring(7, 19); - } - return _.split(imageName, '@sha256')[0]; - }; -}); + return ''; + }; + }) + .filter('availablenodecount', [ + 'ConstraintsHelper', + function (ConstraintsHelper) { + 'use strict'; + return function (nodes, service) { + var availableNodes = 0; + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + if (node.Availability === 'active' && node.Status === 'ready' && ConstraintsHelper.matchesServiceConstraints(service, node)) { + availableNodes++; + } + } + return availableNodes; + }; + }, + ]) + .filter('runningtaskscount', function () { + 'use strict'; + return function (tasks) { + var runningTasks = 0; + for (var i = 0; i < tasks.length; i++) { + var task = tasks[i]; + if (task.Status.State === 'running' && task.DesiredState === 'running') { + runningTasks++; + } + } + return runningTasks; + }; + }) + .filter('runningcontainers', function () { + 'use strict'; + return function runningContainersFilter(containers) { + return containers.filter(function (container) { + return container.State === 'running'; + }).length; + }; + }) + .filter('stoppedcontainers', function () { + 'use strict'; + return function stoppedContainersFilter(containers) { + return containers.filter(function (container) { + return container.State === 'exited'; + }).length; + }; + }) + .filter('healthycontainers', function () { + 'use strict'; + return function healthyContainersFilter(containers) { + return containers.filter(function (container) { + return container.Status === 'healthy'; + }).length; + }; + }) + .filter('unhealthycontainers', function () { + 'use strict'; + return function unhealthyContainersFilter(containers) { + return containers.filter(function (container) { + return container.Status === 'unhealthy'; + }).length; + }; + }) + .filter('imagestotalsize', function () { + 'use strict'; + return function (images) { + var totalImageSize = 0; + for (var i = 0; i < images.length; i++) { + var item = images[i]; + totalImageSize += item.VirtualSize; + } + return totalImageSize; + }; + }) + .filter('tasknodename', function () { + 'use strict'; + return function (nodeId, nodes) { + var node = _.find(nodes, { Id: nodeId }); + if (node) { + return node.Hostname; + } + return ''; + }; + }) + .filter('imagelayercommand', function () { + 'use strict'; + return function (createdBy) { + return createdBy.replace('/bin/sh -c #(nop) ', '').replace('/bin/sh -c ', 'RUN '); + }; + }) + .filter('trimshasum', function () { + 'use strict'; + return function (imageName) { + if (!imageName) { + return; + } + if (imageName.indexOf('sha256:') === 0) { + return imageName.substring(7, 19); + } + return _.split(imageName, '@sha256')[0]; + }; + }); diff --git a/app/docker/helpers/configHelper.js b/app/docker/helpers/configHelper.js index 1043774149e69..00c1dbcdef8cd 100644 --- a/app/docker/helpers/configHelper.js +++ b/app/docker/helpers/configHelper.js @@ -1,34 +1,35 @@ -angular.module('portainer.docker') -.factory('ConfigHelper', [function ConfigHelperFactory() { - 'use strict'; - return { - flattenConfig: function(config) { - if (config) { - return { - Id: config.ConfigID, - Name: config.ConfigName, - FileName: config.File.Name, - Uid: config.File.UID, - Gid: config.File.GID, - Mode: config.File.Mode - }; - } - return {}; - }, - configConfig: function(config) { - if (config) { - return { - ConfigID: config.Id, - ConfigName: config.Name, - File: { - Name: config.FileName || config.Name, - UID: config.Uid || '0', - GID: config.Gid || '0', - Mode: config.Mode || 292 - } - }; - } - return {}; - } - }; -}]); +angular.module('portainer.docker').factory('ConfigHelper', [ + function ConfigHelperFactory() { + 'use strict'; + return { + flattenConfig: function (config) { + if (config) { + return { + Id: config.ConfigID, + Name: config.ConfigName, + FileName: config.File.Name, + Uid: config.File.UID, + Gid: config.File.GID, + Mode: config.File.Mode, + }; + } + return {}; + }, + configConfig: function (config) { + if (config) { + return { + ConfigID: config.Id, + ConfigName: config.Name, + File: { + Name: config.FileName || config.Name, + UID: config.Uid || '0', + GID: config.Gid || '0', + Mode: config.Mode || 292, + }, + }; + } + return {}; + }, + }; + }, +]); diff --git a/app/docker/helpers/constraintsHelper.js b/app/docker/helpers/constraintsHelper.js index f006dcf099b31..c2288f71b5805 100644 --- a/app/docker/helpers/constraintsHelper.js +++ b/app/docker/helpers/constraintsHelper.js @@ -12,18 +12,16 @@ var patterns = { nodeHostname: 'node.hostname', nodeRole: 'node.role', nodeLabels: 'node.labels.', - engineLabels: 'engine.labels.' + engineLabels: 'engine.labels.', }, op: { eq: '==', - neq: '!=' - } + neq: '!=', + }, }; function matchesConstraint(value, constraint) { - if (!constraint || - (constraint.op === patterns.op.eq && value === constraint.value) || - (constraint.op === patterns.op.neq && value !== constraint.value)) { + if (!constraint || (constraint.op === patterns.op.eq && value === constraint.value) || (constraint.op === patterns.op.neq && value !== constraint.value)) { return true; } return false; @@ -47,8 +45,8 @@ function extractCustomLabelKey(constraint, op, baseLabelKey) { return constraint.split(op).shift().trim().replace(baseLabelKey, ''); } -angular.module('portainer.docker') - .factory('ConstraintsHelper', [function ConstraintsHelperFactory() { +angular.module('portainer.docker').factory('ConstraintsHelper', [ + function ConstraintsHelperFactory() { 'use strict'; return { transformConstraints: function (constraints) { @@ -94,7 +92,8 @@ angular.module('portainer.docker') return true; } var constraints = this.transformConstraints(angular.copy(service.Constraints)); - if (matchesConstraint(node.Id, constraints.nodeId) && + if ( + matchesConstraint(node.Id, constraints.nodeId) && matchesConstraint(node.Hostname, constraints.nodeHostname) && matchesConstraint(node.Role, constraints.nodeRole) && matchesLabel(node.Labels, constraints.nodeLabels) && @@ -103,6 +102,7 @@ angular.module('portainer.docker') return true; } return false; - } + }, }; - }]); \ No newline at end of file + }, +]); diff --git a/app/docker/helpers/containerHelper.js b/app/docker/helpers/containerHelper.js index ab169a4c592c4..e047676b7aba3 100644 --- a/app/docker/helpers/containerHelper.js +++ b/app/docker/helpers/containerHelper.js @@ -1,5 +1,5 @@ import _ from 'lodash-es'; -import splitargs from 'splitargs/src/splitargs' +import splitargs from 'splitargs/src/splitargs'; const portPattern = /^([1-9]|[1-5]?[0-9]{2,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/m; @@ -33,8 +33,7 @@ function isValidPortRange(portRange) { portRange = parsePortRange(); } - return Array.isArray(portRange) && portRange.length === 2 && - portRange[0] > 0 && portRange[1] >= portRange[0]; + return Array.isArray(portRange) && portRange.length === 2 && portRange[0] > 0 && portRange[1] >= portRange[0]; } function createPortRange(portRangeText, port) { @@ -49,7 +48,7 @@ function createPortRange(portRangeText, port) { portRangeText = portRangeText.substr(colonIndex + 1); } - port = (typeof port === 'number' ? port : parsePort(port)); + port = typeof port === 'number' ? port : parsePort(port); const portRange = parsePortRange(portRangeText); const startPort = Math.min(portRange[0], port); const endPort = Math.max(portRange[1], port); @@ -61,178 +60,181 @@ function createPortRange(portRangeText, port) { } } -angular.module('portainer.docker') -.factory('ContainerHelper', [function ContainerHelperFactory() { - 'use strict'; - var helper = {}; - - helper.commandStringToArray = function(command) { - return splitargs(command); - }; - - helper.commandArrayToString = function(array) { - return array.map(function(elem) { - return '\'' + elem + '\''; - }).join(' '); - }; - - helper.configFromContainer = function(container) { - var config = container.Config; - // HostConfig - config.HostConfig = container.HostConfig; - // Name - config.name = container.Name.replace(/^\//g, ''); - // Network - var mode = config.HostConfig.NetworkMode; - config.NetworkingConfig = { - 'EndpointsConfig': {} +angular.module('portainer.docker').factory('ContainerHelper', [ + function ContainerHelperFactory() { + 'use strict'; + var helper = {}; + + helper.commandStringToArray = function (command) { + return splitargs(command); + }; + + helper.commandArrayToString = function (array) { + return array + .map(function (elem) { + return "'" + elem + "'"; + }) + .join(' '); }; - config.NetworkingConfig.EndpointsConfig = container.NetworkSettings.Networks; - if (mode.indexOf('container:') !== -1) { - delete config.Hostname; - delete config.ExposedPorts; - } - // Set volumes - var binds = []; - var volumes = {}; - for (var v in container.Mounts) { - if ({}.hasOwnProperty.call(container.Mounts, v)) { - var mount = container.Mounts[v]; - var name = mount.Name || mount.Source; - var containerPath = mount.Destination; - if (name && containerPath) { - var bind = name + ':' + containerPath; - volumes[containerPath] = {}; - if (mount.RW === false) { - bind += ':ro'; + + helper.configFromContainer = function (container) { + var config = container.Config; + // HostConfig + config.HostConfig = container.HostConfig; + // Name + config.name = container.Name.replace(/^\//g, ''); + // Network + var mode = config.HostConfig.NetworkMode; + config.NetworkingConfig = { + EndpointsConfig: {}, + }; + config.NetworkingConfig.EndpointsConfig = container.NetworkSettings.Networks; + if (mode.indexOf('container:') !== -1) { + delete config.Hostname; + delete config.ExposedPorts; + } + // Set volumes + var binds = []; + var volumes = {}; + for (var v in container.Mounts) { + if ({}.hasOwnProperty.call(container.Mounts, v)) { + var mount = container.Mounts[v]; + var name = mount.Name || mount.Source; + var containerPath = mount.Destination; + if (name && containerPath) { + var bind = name + ':' + containerPath; + volumes[containerPath] = {}; + if (mount.RW === false) { + bind += ':ro'; + } + binds.push(bind); } - binds.push(bind); } } - } - config.HostConfig.Binds = binds; - config.Volumes = volumes; - return config; - }; - - helper.preparePortBindings = function(portBindings) { - const bindings = {}; - _.forEach(portBindings, (portBinding) => { - if (!portBinding.containerPort) { - return; - } - - let hostPort = portBinding.hostPort; - const containerPortRange = parsePortRange(portBinding.containerPort); - if (!isValidPortRange(containerPortRange)) { - throw new Error('Invalid port specification: ' + portBinding.containerPort); - } + config.HostConfig.Binds = binds; + config.Volumes = volumes; + return config; + }; - const startPort = containerPortRange[0]; - const endPort = containerPortRange[1]; - let hostIp = undefined; - let startHostPort = 0; - let endHostPort = 0; - if (hostPort) { - if (hostPort.indexOf(':') > -1) { - const hostAndPort = _.split(hostPort, ':'); - hostIp = hostAndPort[0]; - hostPort = hostAndPort[1]; + helper.preparePortBindings = function (portBindings) { + const bindings = {}; + _.forEach(portBindings, (portBinding) => { + if (!portBinding.containerPort) { + return; } - const hostPortRange = parsePortRange(hostPort); - if (!isValidPortRange(hostPortRange)) { - throw new Error('Invalid port specification: ' + hostPort); + let hostPort = portBinding.hostPort; + const containerPortRange = parsePortRange(portBinding.containerPort); + if (!isValidPortRange(containerPortRange)) { + throw new Error('Invalid port specification: ' + portBinding.containerPort); } - startHostPort = hostPortRange[0]; - endHostPort = hostPortRange[1]; - if (endPort !== startPort && (endPort - startPort) !== (endHostPort - startHostPort)) { - throw new Error('Invalid port specification: ' + hostPort); - } - } + const startPort = containerPortRange[0]; + const endPort = containerPortRange[1]; + let hostIp = undefined; + let startHostPort = 0; + let endHostPort = 0; + if (hostPort) { + if (hostPort.indexOf(':') > -1) { + const hostAndPort = _.split(hostPort, ':'); + hostIp = hostAndPort[0]; + hostPort = hostAndPort[1]; + } + + const hostPortRange = parsePortRange(hostPort); + if (!isValidPortRange(hostPortRange)) { + throw new Error('Invalid port specification: ' + hostPort); + } - for (let i = 0; i <= (endPort - startPort); i++) { - const containerPort = (startPort + i).toString(); - if (startHostPort > 0) { - hostPort = (startHostPort + i).toString(); + startHostPort = hostPortRange[0]; + endHostPort = hostPortRange[1]; + if (endPort !== startPort && endPort - startPort !== endHostPort - startHostPort) { + throw new Error('Invalid port specification: ' + hostPort); + } } - if (startPort === endPort && startHostPort !== endHostPort) { - hostPort += '-' + endHostPort.toString(); + + for (let i = 0; i <= endPort - startPort; i++) { + const containerPort = (startPort + i).toString(); + if (startHostPort > 0) { + hostPort = (startHostPort + i).toString(); + } + if (startPort === endPort && startHostPort !== endHostPort) { + hostPort += '-' + endHostPort.toString(); + } + + const bindKey = containerPort + '/' + portBinding.protocol; + bindings[bindKey] = [{ HostIp: hostIp, HostPort: hostPort }]; } + }); + return bindings; + }; - const bindKey = containerPort + '/' + portBinding.protocol; - bindings[bindKey] = [{ HostIp: hostIp, HostPort: hostPort }]; - } - }); - return bindings; - }; - - helper.sortAndCombinePorts = function(portBindings) { - const bindings = []; - const portBindingKeys = _.keys(portBindings); - - // Group the port bindings by protocol - const portBindingKeysByProtocol = _.groupBy(portBindingKeys, (portKey) => { - return _.split(portKey, '/')[1]; - }); - - _.forEach(portBindingKeysByProtocol, (portBindingKeys, protocol) => { - // Group the port bindings by host IP - const portBindingKeysByHostIp = _.groupBy(portBindingKeys, (portKey) => { - const portBinding = portBindings[portKey][0]; - return portBinding.HostIp || ''; + helper.sortAndCombinePorts = function (portBindings) { + const bindings = []; + const portBindingKeys = _.keys(portBindings); + + // Group the port bindings by protocol + const portBindingKeysByProtocol = _.groupBy(portBindingKeys, (portKey) => { + return _.split(portKey, '/')[1]; }); - _.forEach(portBindingKeysByHostIp, (portBindingKeys) => { - // Sort by host port - const sortedPortBindingKeys = _.orderBy(portBindingKeys, (portKey) => { - return parseInt(_.split(portKey, '/')[0]); + _.forEach(portBindingKeysByProtocol, (portBindingKeys, protocol) => { + // Group the port bindings by host IP + const portBindingKeysByHostIp = _.groupBy(portBindingKeys, (portKey) => { + const portBinding = portBindings[portKey][0]; + return portBinding.HostIp || ''; }); - let previousHostPort = -1; - let previousContainerPort = -1; - _.forEach(sortedPortBindingKeys, (portKey) => { - const portKeySplit = _.split(portKey, '/'); - const containerPort = parseInt(portKeySplit[0]); - const portBinding = portBindings[portKey][0]; - const hostPort = parsePort(portBinding.HostPort); - - // We only combine single ports, and skip the host port ranges on one container port - if (hostPort > 0) { - // If we detect consecutive ports, we create a range of them - if (bindings.length > 0 && previousHostPort === (hostPort - 1) && previousContainerPort === (containerPort - 1)) { - bindings[bindings.length-1].hostPort = createPortRange(bindings[bindings.length-1].hostPort, hostPort); - bindings[bindings.length-1].containerPort = createPortRange(bindings[bindings.length-1].containerPort, containerPort); + _.forEach(portBindingKeysByHostIp, (portBindingKeys) => { + // Sort by host port + const sortedPortBindingKeys = _.orderBy(portBindingKeys, (portKey) => { + return parseInt(_.split(portKey, '/')[0]); + }); + + let previousHostPort = -1; + let previousContainerPort = -1; + _.forEach(sortedPortBindingKeys, (portKey) => { + const portKeySplit = _.split(portKey, '/'); + const containerPort = parseInt(portKeySplit[0]); + const portBinding = portBindings[portKey][0]; + const hostPort = parsePort(portBinding.HostPort); + + // We only combine single ports, and skip the host port ranges on one container port + if (hostPort > 0) { + // If we detect consecutive ports, we create a range of them + if (bindings.length > 0 && previousHostPort === hostPort - 1 && previousContainerPort === containerPort - 1) { + bindings[bindings.length - 1].hostPort = createPortRange(bindings[bindings.length - 1].hostPort, hostPort); + bindings[bindings.length - 1].containerPort = createPortRange(bindings[bindings.length - 1].containerPort, containerPort); + previousHostPort = hostPort; + previousContainerPort = containerPort; + return; + } + previousHostPort = hostPort; previousContainerPort = containerPort; - return; + } else { + previousHostPort = -1; + previousContainerPort = -1; } - previousHostPort = hostPort; - previousContainerPort = containerPort; - } else { - previousHostPort = -1; - previousContainerPort = -1; - } - - let bindingHostPort = portBinding.HostPort.toString(); - if (portBinding.HostIp) { - bindingHostPort = portBinding.HostIp + ':' + bindingHostPort; - } + let bindingHostPort = portBinding.HostPort.toString(); + if (portBinding.HostIp) { + bindingHostPort = portBinding.HostIp + ':' + bindingHostPort; + } - const binding = { - hostPort: bindingHostPort, - containerPort: containerPort, - protocol: protocol - }; - bindings.push(binding); + const binding = { + hostPort: bindingHostPort, + containerPort: containerPort, + protocol: protocol, + }; + bindings.push(binding); + }); }); }); - }); - return bindings; - }; + return bindings; + }; - return helper; -}]); + return helper; + }, +]); diff --git a/app/docker/helpers/imageHelper.js b/app/docker/helpers/imageHelper.js index b1bd8d78daf66..ea28976def1f6 100644 --- a/app/docker/helpers/imageHelper.js +++ b/app/docker/helpers/imageHelper.js @@ -1,72 +1,73 @@ import _ from 'lodash-es'; import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes'; -angular.module('portainer.docker') -.factory('ImageHelper', [function ImageHelperFactory() { - 'use strict'; +angular.module('portainer.docker').factory('ImageHelper', [ + function ImageHelperFactory() { + 'use strict'; - var helper = {}; + var helper = {}; - helper.isValidTag = isValidTag; - helper.createImageConfigForContainer = createImageConfigForContainer; - helper.getImagesNamesForDownload = getImagesNamesForDownload; - helper.removeDigestFromRepository = removeDigestFromRepository; - helper.imageContainsURL = imageContainsURL; + helper.isValidTag = isValidTag; + helper.createImageConfigForContainer = createImageConfigForContainer; + helper.getImagesNamesForDownload = getImagesNamesForDownload; + helper.removeDigestFromRepository = removeDigestFromRepository; + helper.imageContainsURL = imageContainsURL; - function isValidTag(tag) { - return tag.match(/^(?![\.\-])([a-zA-Z0-9\_\.\-])+$/g); - } + function isValidTag(tag) { + return tag.match(/^(?![\.\-])([a-zA-Z0-9\_\.\-])+$/g); + } - function getImagesNamesForDownload(images) { - var names = images.map(function(image) { - return image.RepoTags[0] !== ':' ? image.RepoTags[0] : image.Id; - }); - return { - names: names - }; - } + function getImagesNamesForDownload(images) { + var names = images.map(function (image) { + return image.RepoTags[0] !== ':' ? image.RepoTags[0] : image.Id; + }); + return { + names: names, + }; + } - /** - * - * @param {PorImageRegistryModel} registry - */ - function createImageConfigForContainer(registry) { - const data = { - fromImage: '' - }; - let fullImageName = ''; + /** + * + * @param {PorImageRegistryModel} registry + */ + function createImageConfigForContainer(registry) { + const data = { + fromImage: '', + }; + let fullImageName = ''; - if (registry.UseRegistry) { - if (registry.Registry.Type === RegistryTypes.GITLAB) { - const slash = _.startsWith(registry.Image, ':') ? '' : '/'; - fullImageName = registry.Registry.URL + '/' + registry.Registry.Gitlab.ProjectPath + slash + registry.Image; + if (registry.UseRegistry) { + if (registry.Registry.Type === RegistryTypes.GITLAB) { + const slash = _.startsWith(registry.Image, ':') ? '' : '/'; + fullImageName = registry.Registry.URL + '/' + registry.Registry.Gitlab.ProjectPath + slash + registry.Image; + } else { + const url = registry.Registry.URL ? registry.Registry.URL + '/' : ''; + fullImageName = url + registry.Image; + } + if (!_.includes(registry.Image, ':')) { + fullImageName += ':latest'; + } } else { - const url = registry.Registry.URL ? registry.Registry.URL + '/' : ''; - fullImageName = url + registry.Image; + fullImageName = registry.Image; } - if (!_.includes(registry.Image, ':')) { - fullImageName += ':latest'; - } - } else { - fullImageName = registry.Image; - } - data.fromImage = fullImageName; - return data; - } + data.fromImage = fullImageName; + return data; + } - function imageContainsURL(image) { - const split = _.split(image, '/'); - const url = split[0]; - if (split.length > 1) { - return _.includes(url, '.') || _.includes(url, ':'); + function imageContainsURL(image) { + const split = _.split(image, '/'); + const url = split[0]; + if (split.length > 1) { + return _.includes(url, '.') || _.includes(url, ':'); + } + return false; } - return false; - } - function removeDigestFromRepository(repository) { - return repository.split('@sha')[0]; - } + function removeDigestFromRepository(repository) { + return repository.split('@sha')[0]; + } - return helper; -}]); + return helper; + }, +]); diff --git a/app/docker/helpers/infoHelper.js b/app/docker/helpers/infoHelper.js index 05f00012e0a41..3ec824793c3ca 100644 --- a/app/docker/helpers/infoHelper.js +++ b/app/docker/helpers/infoHelper.js @@ -1,38 +1,39 @@ import _ from 'lodash-es'; -angular.module('portainer.docker') -.factory('InfoHelper', [function InfoHelperFactory() { - 'use strict'; +angular.module('portainer.docker').factory('InfoHelper', [ + function InfoHelperFactory() { + 'use strict'; - var helper = {}; + var helper = {}; - helper.determineEndpointMode = function(info, type) { - var mode = { - provider: '', - role: '', - agentProxy: false - }; - - if (type === 2 || type === 4) { - mode.agentProxy = true; - } + helper.determineEndpointMode = function (info, type) { + var mode = { + provider: '', + role: '', + agentProxy: false, + }; - if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) { - if (info.ID === 'vSphere Integrated Containers') { - mode.provider = 'VMWARE_VIC'; - } else { - mode.provider = 'DOCKER_STANDALONE'; + if (type === 2 || type === 4) { + mode.agentProxy = true; } - } else { - mode.provider = 'DOCKER_SWARM_MODE'; - if (info.Swarm.ControlAvailable) { - mode.role = 'MANAGER'; + + if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) { + if (info.ID === 'vSphere Integrated Containers') { + mode.provider = 'VMWARE_VIC'; + } else { + mode.provider = 'DOCKER_STANDALONE'; + } } else { - mode.role = 'WORKER'; + mode.provider = 'DOCKER_SWARM_MODE'; + if (info.Swarm.ControlAvailable) { + mode.role = 'MANAGER'; + } else { + mode.role = 'WORKER'; + } } - } - return mode; - }; + return mode; + }; - return helper; -}]); + return helper; + }, +]); diff --git a/app/docker/helpers/labelHelper.js b/app/docker/helpers/labelHelper.js index 85de78ab18a8d..b19c552cb4fc8 100644 --- a/app/docker/helpers/labelHelper.js +++ b/app/docker/helpers/labelHelper.js @@ -1,25 +1,26 @@ -angular.module('portainer.docker') -.factory('LabelHelper', [function LabelHelperFactory() { - 'use strict'; - return { - fromLabelHashToKeyValue: function(labels) { - if (labels) { - return Object.keys(labels).map(function(key) { - return {key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true}; - }); - } - return []; - }, - fromKeyValueToLabelHash: function(labelKV) { - var labels = {}; - if (labelKV) { - labelKV.forEach(function(label) { - if (label.key) { - labels[label.key] = label.value; - } - }); - } - return labels; - } - }; -}]); +angular.module('portainer.docker').factory('LabelHelper', [ + function LabelHelperFactory() { + 'use strict'; + return { + fromLabelHashToKeyValue: function (labels) { + if (labels) { + return Object.keys(labels).map(function (key) { + return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true }; + }); + } + return []; + }, + fromKeyValueToLabelHash: function (labelKV) { + var labels = {}; + if (labelKV) { + labelKV.forEach(function (label) { + if (label.key) { + labels[label.key] = label.value; + } + }); + } + return labels; + }, + }; + }, +]); diff --git a/app/docker/helpers/logHelper.js b/app/docker/helpers/logHelper.js index 47f9030568cdc..052a6984307a0 100644 --- a/app/docker/helpers/logHelper.js +++ b/app/docker/helpers/logHelper.js @@ -1,22 +1,22 @@ -angular.module('portainer.docker') -.factory('LogHelper', [function LogHelperFactory() { - 'use strict'; - var helper = {}; +angular.module('portainer.docker').factory('LogHelper', [ + function LogHelperFactory() { + 'use strict'; + var helper = {}; - // Return an array with each line being an entry. - // It will also remove any ANSI code related character sequences. - // If the skipHeaders param is specified, it will strip the 8 first characters of each line. - helper.formatLogs = function(logs, skipHeaders) { - logs = logs.replace( - /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); + // Return an array with each line being an entry. + // It will also remove any ANSI code related character sequences. + // If the skipHeaders param is specified, it will strip the 8 first characters of each line. + helper.formatLogs = function (logs, skipHeaders) { + logs = logs.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); - if (skipHeaders) { - logs = logs.substring(8); - logs = logs.replace(/\n(.{8})/g, '\n\r'); - } + if (skipHeaders) { + logs = logs.substring(8); + logs = logs.replace(/\n(.{8})/g, '\n\r'); + } - return logs.split('\n'); - }; + return logs.split('\n'); + }; - return helper; -}]); + return helper; + }, +]); diff --git a/app/docker/helpers/nodeHelper.js b/app/docker/helpers/nodeHelper.js index ba02dfdd14c05..a97a7b81c5ae8 100644 --- a/app/docker/helpers/nodeHelper.js +++ b/app/docker/helpers/nodeHelper.js @@ -1,14 +1,15 @@ -angular.module('portainer.docker') -.factory('NodeHelper', [function NodeHelperFactory() { - 'use strict'; - return { - nodeToConfig: function(node) { - return { - Name: node.Spec.Name, - Role: node.Spec.Role, - Labels: node.Spec.Labels, - Availability: node.Spec.Availability - }; - } - }; -}]); +angular.module('portainer.docker').factory('NodeHelper', [ + function NodeHelperFactory() { + 'use strict'; + return { + nodeToConfig: function (node) { + return { + Name: node.Spec.Name, + Role: node.Spec.Role, + Labels: node.Spec.Labels, + Availability: node.Spec.Availability, + }; + }, + }; + }, +]); diff --git a/app/docker/helpers/secretHelper.js b/app/docker/helpers/secretHelper.js index a962414743906..9948f8481147f 100644 --- a/app/docker/helpers/secretHelper.js +++ b/app/docker/helpers/secretHelper.js @@ -1,34 +1,35 @@ -angular.module('portainer.docker') -.factory('SecretHelper', [function SecretHelperFactory() { - 'use strict'; - return { - flattenSecret: function(secret) { - if (secret) { - return { - Id: secret.SecretID, - Name: secret.SecretName, - FileName: secret.File.Name, - Uid: secret.File.UID, - Gid: secret.File.GID, - Mode: secret.File.Mode - }; - } - return {}; - }, - secretConfig: function(secret) { - if (secret) { - return { - SecretID: secret.Id, - SecretName: secret.Name, - File: { - Name: secret.FileName, - UID: secret.Uid || '0', - GID: secret.Gid || '0', - Mode: secret.Mode || 444 - } - }; - } - return {}; - } - }; -}]); +angular.module('portainer.docker').factory('SecretHelper', [ + function SecretHelperFactory() { + 'use strict'; + return { + flattenSecret: function (secret) { + if (secret) { + return { + Id: secret.SecretID, + Name: secret.SecretName, + FileName: secret.File.Name, + Uid: secret.File.UID, + Gid: secret.File.GID, + Mode: secret.File.Mode, + }; + } + return {}; + }, + secretConfig: function (secret) { + if (secret) { + return { + SecretID: secret.Id, + SecretName: secret.Name, + File: { + Name: secret.FileName, + UID: secret.Uid || '0', + GID: secret.Gid || '0', + Mode: secret.Mode || 444, + }, + }; + } + return {}; + }, + }; + }, +]); diff --git a/app/docker/helpers/serviceHelper.js b/app/docker/helpers/serviceHelper.js index f9d2fb6891788..8788c2c74db9a 100644 --- a/app/docker/helpers/serviceHelper.js +++ b/app/docker/helpers/serviceHelper.js @@ -1,250 +1,251 @@ import moment from 'moment'; -angular.module('portainer.docker') -.factory('ServiceHelper', [function ServiceHelperFactory() { - 'use strict'; - - var helper = {}; - - helper.associateTasksToService = function(service, tasks) { - service.Tasks = []; - var otherServicesTasks = []; - for (var i = 0; i < tasks.length; i++) { - var task = tasks[i]; - if (task.ServiceId === service.Id) { - service.Tasks.push(task); - task.ServiceName = service.Name; - } else { - otherServicesTasks.push(task); +angular.module('portainer.docker').factory('ServiceHelper', [ + function ServiceHelperFactory() { + 'use strict'; + + var helper = {}; + + helper.associateTasksToService = function (service, tasks) { + service.Tasks = []; + var otherServicesTasks = []; + for (var i = 0; i < tasks.length; i++) { + var task = tasks[i]; + if (task.ServiceId === service.Id) { + service.Tasks.push(task); + task.ServiceName = service.Name; + } else { + otherServicesTasks.push(task); + } } - } - tasks = otherServicesTasks; - }; - - helper.serviceToConfig = function(service) { - return { - Name: service.Spec.Name, - Labels: service.Spec.Labels, - TaskTemplate: service.Spec.TaskTemplate, - Mode: service.Spec.Mode, - UpdateConfig: service.Spec.UpdateConfig, - Networks: service.Spec.Networks, - EndpointSpec: service.Spec.EndpointSpec + tasks = otherServicesTasks; + }; + + helper.serviceToConfig = function (service) { + return { + Name: service.Spec.Name, + Labels: service.Spec.Labels, + TaskTemplate: service.Spec.TaskTemplate, + Mode: service.Spec.Mode, + UpdateConfig: service.Spec.UpdateConfig, + Networks: service.Spec.Networks, + EndpointSpec: service.Spec.EndpointSpec, + }; }; - }; - - helper.translateKeyValueToPlacementPreferences = function(keyValuePreferences) { - if (keyValuePreferences) { - var preferences = []; - keyValuePreferences.forEach(function(preference) { - if (preference.strategy && preference.strategy !== '' && preference.value && preference.value !== '') { - switch (preference.strategy.toLowerCase()) { - case 'spread': - preferences.push({ - 'Spread': { - 'SpreadDescriptor': preference.value - } - }); - break; + + helper.translateKeyValueToPlacementPreferences = function (keyValuePreferences) { + if (keyValuePreferences) { + var preferences = []; + keyValuePreferences.forEach(function (preference) { + if (preference.strategy && preference.strategy !== '' && preference.value && preference.value !== '') { + switch (preference.strategy.toLowerCase()) { + case 'spread': + preferences.push({ + Spread: { + SpreadDescriptor: preference.value, + }, + }); + break; + } } - } - }); - return preferences; - } - return []; - }; - - helper.translateKeyValueToPlacementConstraints = function(keyValueConstraints) { - if (keyValueConstraints) { - var constraints = []; - keyValueConstraints.forEach(function(constraint) { - if (constraint.key && constraint.key !== '' && constraint.value && constraint.value !== '') { - constraints.push(constraint.key + constraint.operator + constraint.value); - } - }); - return constraints; - } - return []; - }; - - helper.translateEnvironmentVariables = function(env) { - if (env) { - var variables = []; - env.forEach(function(variable) { - var idx = variable.indexOf('='); - var keyValue = [variable.slice(0, idx), variable.slice(idx + 1)]; - var originalValue = (keyValue.length > 1) ? keyValue[1] : ''; - variables.push({ - key: keyValue[0], - value: originalValue, - originalKey: keyValue[0], - originalValue: originalValue, - added: true }); - }); - return variables; - } - return []; - }; - - helper.translateEnvironmentVariablesToEnv = function(env) { - if (env) { - var variables = []; - env.forEach(function(variable) { - if (variable.key && variable.key !== '') { - variables.push(variable.key + '=' + variable.value); - } - }); - return variables; - } - return []; - }; - - helper.translatePreferencesToKeyValue = function(preferences) { - if (preferences) { - var keyValuePreferences = []; - preferences.forEach(function(preference) { - if (preference.Spread) { - keyValuePreferences.push({ - strategy: 'Spread', - value: preference.Spread.SpreadDescriptor - }); - } - }); - return keyValuePreferences; - } - return []; - }; - - helper.translateConstraintsToKeyValue = function(constraints) { - function getOperator(constraint) { - var indexEquals = constraint.indexOf('=='); - if (indexEquals >= 0) { - return [indexEquals, '==']; + return preferences; } - return [constraint.indexOf('!='), '!=']; - } - if (constraints) { - var keyValueConstraints = []; - constraints.forEach(function(constraint) { - var operatorIndices = getOperator(constraint); - - var key = constraint.slice(0, operatorIndices[0]); - var operator = operatorIndices[1]; - var value = constraint.slice(operatorIndices[0] + 2); - - keyValueConstraints.push({ - key: key, - value: value, - operator: operator, - originalKey: key, - originalValue: value + return []; + }; + + helper.translateKeyValueToPlacementConstraints = function (keyValueConstraints) { + if (keyValueConstraints) { + var constraints = []; + keyValueConstraints.forEach(function (constraint) { + if (constraint.key && constraint.key !== '' && constraint.value && constraint.value !== '') { + constraints.push(constraint.key + constraint.operator + constraint.value); + } }); - }); - return keyValueConstraints; - } - }; - - helper.translateHumanDurationToNanos = function(humanDuration) { - var nanos; - var regex = /^([0-9]+)(h|m|s|ms|us|ns)$/i; - var matches = humanDuration.match(regex); - - if (matches !== null && matches.length === 3) { - var duration = parseInt(matches[1], 10); - var unit = matches[2]; - // Moment.js cannot use micro or nanoseconds - switch (unit) { - case 'ns': - nanos = duration; - break; - case 'us': - nanos = duration * 1000; - break; - default: - nanos = moment.duration(duration, unit).asMilliseconds() * 1000000; + return constraints; + } + return []; + }; + + helper.translateEnvironmentVariables = function (env) { + if (env) { + var variables = []; + env.forEach(function (variable) { + var idx = variable.indexOf('='); + var keyValue = [variable.slice(0, idx), variable.slice(idx + 1)]; + var originalValue = keyValue.length > 1 ? keyValue[1] : ''; + variables.push({ + key: keyValue[0], + value: originalValue, + originalKey: keyValue[0], + originalValue: originalValue, + added: true, + }); + }); + return variables; } - } - return nanos; - }; - - // Convert nanoseconds to the higher unit possible - // e.g 1840 nanoseconds = 1804ns - // e.g 300000000000 nanoseconds = 5m - // e.g 3510000000000 nanoseconds = 3510s - // e.g 3540000000000 nanoseconds = 59m - // e.g 3600000000000 nanoseconds = 1h - - helper.translateNanosToHumanDuration = function(nanos) { - var humanDuration = '0s'; - var conversionFromNano = {}; - conversionFromNano['ns'] = 1; - conversionFromNano['us'] = conversionFromNano['ns'] * 1000; - conversionFromNano['ms'] = conversionFromNano['us'] * 1000; - conversionFromNano['s'] = conversionFromNano['ms'] * 1000; - conversionFromNano['m'] = conversionFromNano['s'] * 60; - conversionFromNano['h'] = conversionFromNano['m'] * 60; - - Object.keys(conversionFromNano).forEach(function(unit) { - if ( nanos % conversionFromNano[unit] === 0 && (nanos / conversionFromNano[unit]) > 0) { - humanDuration = (nanos / conversionFromNano[unit]) + unit; + return []; + }; + + helper.translateEnvironmentVariablesToEnv = function (env) { + if (env) { + var variables = []; + env.forEach(function (variable) { + if (variable.key && variable.key !== '') { + variables.push(variable.key + '=' + variable.value); + } + }); + return variables; } - }); - return humanDuration; - }; - - helper.translateLogDriverOptsToKeyValue = function(logOptions) { - var options = []; - if (logOptions) { - Object.keys(logOptions).forEach(function(key) { - options.push({ - key: key, - value: logOptions[key], - originalKey: key, - originalValue: logOptions[key], - added: true + return []; + }; + + helper.translatePreferencesToKeyValue = function (preferences) { + if (preferences) { + var keyValuePreferences = []; + preferences.forEach(function (preference) { + if (preference.Spread) { + keyValuePreferences.push({ + strategy: 'Spread', + value: preference.Spread.SpreadDescriptor, + }); + } }); - }); - } - return options; - }; - - helper.translateKeyValueToLogDriverOpts = function(keyValueLogDriverOpts) { - var options = {}; - if (keyValueLogDriverOpts) { - keyValueLogDriverOpts.forEach(function(option) { - if (option.key && option.key !== '' && option.value && option.value !== '') { - options[option.key] = option.value; + return keyValuePreferences; + } + return []; + }; + + helper.translateConstraintsToKeyValue = function (constraints) { + function getOperator(constraint) { + var indexEquals = constraint.indexOf('=='); + if (indexEquals >= 0) { + return [indexEquals, '==']; } - }); - } - return options; - }; - - helper.translateHostsEntriesToHostnameIP = function(entries) { - var ipHostEntries = []; - if (entries) { - entries.forEach(function(entry) { - if (entry.indexOf(' ') && entry.split(' ').length === 2) { - var keyValue = entry.split(' '); - ipHostEntries.push({ hostname: keyValue[1], ip: keyValue[0]}); + return [constraint.indexOf('!='), '!=']; + } + if (constraints) { + var keyValueConstraints = []; + constraints.forEach(function (constraint) { + var operatorIndices = getOperator(constraint); + + var key = constraint.slice(0, operatorIndices[0]); + var operator = operatorIndices[1]; + var value = constraint.slice(operatorIndices[0] + 2); + + keyValueConstraints.push({ + key: key, + value: value, + operator: operator, + originalKey: key, + originalValue: value, + }); + }); + return keyValueConstraints; + } + }; + + helper.translateHumanDurationToNanos = function (humanDuration) { + var nanos; + var regex = /^([0-9]+)(h|m|s|ms|us|ns)$/i; + var matches = humanDuration.match(regex); + + if (matches !== null && matches.length === 3) { + var duration = parseInt(matches[1], 10); + var unit = matches[2]; + // Moment.js cannot use micro or nanoseconds + switch (unit) { + case 'ns': + nanos = duration; + break; + case 'us': + nanos = duration * 1000; + break; + default: + nanos = moment.duration(duration, unit).asMilliseconds() * 1000000; } - }); - } - return ipHostEntries; - }; - - helper.translateHostnameIPToHostsEntries = function(entries) { - var ipHostEntries = []; - if (entries) { - entries.forEach(function(entry) { - if (entry.ip && entry.hostname) { - ipHostEntries.push(entry.ip + ' ' + entry.hostname); + } + return nanos; + }; + + // Convert nanoseconds to the higher unit possible + // e.g 1840 nanoseconds = 1804ns + // e.g 300000000000 nanoseconds = 5m + // e.g 3510000000000 nanoseconds = 3510s + // e.g 3540000000000 nanoseconds = 59m + // e.g 3600000000000 nanoseconds = 1h + + helper.translateNanosToHumanDuration = function (nanos) { + var humanDuration = '0s'; + var conversionFromNano = {}; + conversionFromNano['ns'] = 1; + conversionFromNano['us'] = conversionFromNano['ns'] * 1000; + conversionFromNano['ms'] = conversionFromNano['us'] * 1000; + conversionFromNano['s'] = conversionFromNano['ms'] * 1000; + conversionFromNano['m'] = conversionFromNano['s'] * 60; + conversionFromNano['h'] = conversionFromNano['m'] * 60; + + Object.keys(conversionFromNano).forEach(function (unit) { + if (nanos % conversionFromNano[unit] === 0 && nanos / conversionFromNano[unit] > 0) { + humanDuration = nanos / conversionFromNano[unit] + unit; } }); - } - return ipHostEntries; - }; + return humanDuration; + }; + + helper.translateLogDriverOptsToKeyValue = function (logOptions) { + var options = []; + if (logOptions) { + Object.keys(logOptions).forEach(function (key) { + options.push({ + key: key, + value: logOptions[key], + originalKey: key, + originalValue: logOptions[key], + added: true, + }); + }); + } + return options; + }; + + helper.translateKeyValueToLogDriverOpts = function (keyValueLogDriverOpts) { + var options = {}; + if (keyValueLogDriverOpts) { + keyValueLogDriverOpts.forEach(function (option) { + if (option.key && option.key !== '' && option.value && option.value !== '') { + options[option.key] = option.value; + } + }); + } + return options; + }; + + helper.translateHostsEntriesToHostnameIP = function (entries) { + var ipHostEntries = []; + if (entries) { + entries.forEach(function (entry) { + if (entry.indexOf(' ') && entry.split(' ').length === 2) { + var keyValue = entry.split(' '); + ipHostEntries.push({ hostname: keyValue[1], ip: keyValue[0] }); + } + }); + } + return ipHostEntries; + }; + + helper.translateHostnameIPToHostsEntries = function (entries) { + var ipHostEntries = []; + if (entries) { + entries.forEach(function (entry) { + if (entry.ip && entry.hostname) { + ipHostEntries.push(entry.ip + ' ' + entry.hostname); + } + }); + } + return ipHostEntries; + }; - return helper; -}]); + return helper; + }, +]); diff --git a/app/docker/helpers/taskHelper.js b/app/docker/helpers/taskHelper.js index 5526c3c328915..2a134318bbc45 100644 --- a/app/docker/helpers/taskHelper.js +++ b/app/docker/helpers/taskHelper.js @@ -1,18 +1,19 @@ -angular.module('portainer.docker') -.factory('TaskHelper', [function TaskHelperFactory() { - 'use strict'; +angular.module('portainer.docker').factory('TaskHelper', [ + function TaskHelperFactory() { + 'use strict'; - var helper = {}; + var helper = {}; - helper.associateContainerToTask = function(task, containers) { - for (var i = 0; i < containers.length; i++) { - var container = containers[i]; - if (task.ContainerId === container.Id) { - task.Container = container; - break; + helper.associateContainerToTask = function (task, containers) { + for (var i = 0; i < containers.length; i++) { + var container = containers[i]; + if (task.ContainerId === container.Id) { + task.Container = container; + break; + } } - } - }; + }; - return helper; -}]); + return helper; + }, +]); diff --git a/app/docker/helpers/volumeHelper.js b/app/docker/helpers/volumeHelper.js index 20cc66c2db34f..58f7353973b35 100644 --- a/app/docker/helpers/volumeHelper.js +++ b/app/docker/helpers/volumeHelper.js @@ -1,30 +1,31 @@ -angular.module('portainer.docker') -.factory('VolumeHelper', [function VolumeHelperFactory() { - 'use strict'; - var helper = {}; +angular.module('portainer.docker').factory('VolumeHelper', [ + function VolumeHelperFactory() { + 'use strict'; + var helper = {}; - helper.createDriverOptions = function(optionArray) { - var options = {}; - optionArray.forEach(function (option) { - options[option.name] = option.value; - }); - return options; - }; + helper.createDriverOptions = function (optionArray) { + var options = {}; + optionArray.forEach(function (option) { + options[option.name] = option.value; + }); + return options; + }; - helper.isVolumeUsedByAService = function(volume, services) { - for (var i = 0; i < services.length; i++) { - var service = services[i]; - var mounts = service.Mounts; - for (var j = 0; j < mounts.length; j++) { - var mount = mounts[j]; - if (mount.Source === volume.Id) { - return true; + helper.isVolumeUsedByAService = function (volume, services) { + for (var i = 0; i < services.length; i++) { + var service = services[i]; + var mounts = service.Mounts; + for (var j = 0; j < mounts.length; j++) { + var mount = mounts[j]; + if (mount.Source === volume.Id) { + return true; + } } } - } - return false; - }; + return false; + }; - return helper; -}]); + return helper; + }, +]); diff --git a/app/docker/interceptors/containersInterceptor.js b/app/docker/interceptors/containersInterceptor.js index 066ffe4fe48cf..b6a9b340d4b11 100644 --- a/app/docker/interceptors/containersInterceptor.js +++ b/app/docker/interceptors/containersInterceptor.js @@ -1,5 +1,7 @@ -angular.module('portainer.app') - .factory('ContainersInterceptor', ['$q', 'EndpointProvider', function ($q, EndpointProvider) { +angular.module('portainer.app').factory('ContainersInterceptor', [ + '$q', + 'EndpointProvider', + function ($q, EndpointProvider) { 'use strict'; var interceptor = {}; @@ -18,4 +20,5 @@ angular.module('portainer.app') return $q.reject(rejection); } return interceptor; - }]); \ No newline at end of file + }, +]); diff --git a/app/docker/interceptors/imagesInterceptor.js b/app/docker/interceptors/imagesInterceptor.js index 9d79fbc860f6e..5294d0b38c762 100644 --- a/app/docker/interceptors/imagesInterceptor.js +++ b/app/docker/interceptors/imagesInterceptor.js @@ -1,5 +1,7 @@ -angular.module('portainer.app') - .factory('ImagesInterceptor', ['$q', 'EndpointProvider', function ($q, EndpointProvider) { +angular.module('portainer.app').factory('ImagesInterceptor', [ + '$q', + 'EndpointProvider', + function ($q, EndpointProvider) { 'use strict'; var interceptor = {}; @@ -18,4 +20,5 @@ angular.module('portainer.app') return $q.reject(rejection); } return interceptor; - }]); \ No newline at end of file + }, +]); diff --git a/app/docker/interceptors/infoInterceptor.js b/app/docker/interceptors/infoInterceptor.js index 17f310641a2b0..d7b6e97dec935 100644 --- a/app/docker/interceptors/infoInterceptor.js +++ b/app/docker/interceptors/infoInterceptor.js @@ -1,5 +1,7 @@ -angular.module('portainer.app') - .factory('InfoInterceptor', ['$q', 'EndpointProvider', function ($q, EndpointProvider) { +angular.module('portainer.app').factory('InfoInterceptor', [ + '$q', + 'EndpointProvider', + function ($q, EndpointProvider) { 'use strict'; var interceptor = {}; @@ -18,4 +20,5 @@ angular.module('portainer.app') return $q.reject(rejection); } return interceptor; - }]); \ No newline at end of file + }, +]); diff --git a/app/docker/interceptors/networksInterceptor.js b/app/docker/interceptors/networksInterceptor.js index b5068534cdc4e..b3cfdce19ddc4 100644 --- a/app/docker/interceptors/networksInterceptor.js +++ b/app/docker/interceptors/networksInterceptor.js @@ -1,5 +1,7 @@ -angular.module('portainer.app') - .factory('NetworksInterceptor', ['$q', 'EndpointProvider', function ($q, EndpointProvider) { +angular.module('portainer.app').factory('NetworksInterceptor', [ + '$q', + 'EndpointProvider', + function ($q, EndpointProvider) { 'use strict'; var interceptor = {}; @@ -18,4 +20,5 @@ angular.module('portainer.app') return $q.reject(rejection); } return interceptor; - }]); \ No newline at end of file + }, +]); diff --git a/app/docker/interceptors/versionInterceptor.js b/app/docker/interceptors/versionInterceptor.js index 95a5d56c4375b..019a34c52d6f1 100644 --- a/app/docker/interceptors/versionInterceptor.js +++ b/app/docker/interceptors/versionInterceptor.js @@ -1,5 +1,7 @@ -angular.module('portainer.app') - .factory('VersionInterceptor', ['$q', 'EndpointProvider', function ($q, EndpointProvider) { +angular.module('portainer.app').factory('VersionInterceptor', [ + '$q', + 'EndpointProvider', + function ($q, EndpointProvider) { 'use strict'; var interceptor = {}; @@ -18,4 +20,5 @@ angular.module('portainer.app') return $q.reject(rejection); } return interceptor; - }]); + }, +]); diff --git a/app/docker/interceptors/volumesInterceptor.js b/app/docker/interceptors/volumesInterceptor.js index a42addd2c5dcc..bbb1dae3544df 100644 --- a/app/docker/interceptors/volumesInterceptor.js +++ b/app/docker/interceptors/volumesInterceptor.js @@ -1,5 +1,7 @@ -angular.module('portainer.app') - .factory('VolumesInterceptor', ['$q', 'EndpointProvider', function ($q, EndpointProvider) { +angular.module('portainer.app').factory('VolumesInterceptor', [ + '$q', + 'EndpointProvider', + function ($q, EndpointProvider) { 'use strict'; var interceptor = {}; @@ -18,4 +20,5 @@ angular.module('portainer.app') return $q.reject(rejection); } return interceptor; - }]); \ No newline at end of file + }, +]); diff --git a/app/docker/models/config.js b/app/docker/models/config.js index e9b2db69f6f08..0939c692b9cee 100644 --- a/app/docker/models/config.js +++ b/app/docker/models/config.js @@ -1,9 +1,14 @@ import { ResourceControlViewModel } from 'Portainer/models/resourceControl/resourceControl'; function b64DecodeUnicode(str) { - return decodeURIComponent(atob(str).split('').map(function(c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); + return decodeURIComponent( + atob(str) + .split('') + .map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join('') + ); } export function ConfigViewModel(data) { diff --git a/app/docker/models/container.js b/app/docker/models/container.js index c09ad7748aae6..58cc51a20a5d4 100644 --- a/app/docker/models/container.js +++ b/app/docker/models/container.js @@ -68,12 +68,14 @@ export function ContainerViewModel(data) { export function ContainerStatsViewModel(data) { this.read = data.read; this.preread = data.preread; - if(data.memory_stats.privateworkingset !== undefined) { // Windows + if (data.memory_stats.privateworkingset !== undefined) { + // Windows this.MemoryUsage = data.memory_stats.privateworkingset; this.MemoryCache = 0; this.NumProcs = data.num_procs; this.isWindows = true; - } else { // Linux + } else { + // Linux if (data.memory_stats.stats === undefined || data.memory_stats.usage === undefined) { this.MemoryUsage = this.MemoryCache = 0; } else { diff --git a/app/docker/models/containerCapabilities.js b/app/docker/models/containerCapabilities.js index 61706cc51f992..dadf14eac01df 100644 --- a/app/docker/models/containerCapabilities.js +++ b/app/docker/models/containerCapabilities.js @@ -1,90 +1,90 @@ var capDesc = { - 'SETPCAP': 'Modify process capabilities.', - 'MKNOD': 'Create special files using mknod(2).', - 'AUDIT_WRITE': 'Write records to kernel auditing log.', - 'CHOWN': 'Make arbitrary changes to file UIDs and GIDs (see chown(2)).', - 'NET_RAW': 'Use RAW and PACKET sockets.', - 'DAC_OVERRIDE': 'Bypass file read, write, and execute permission checks.', - 'FOWNER': 'Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file.', - 'FSETID': 'Don’t clear set-user-ID and set-group-ID permission bits when a file is modified.', - 'KILL': 'Bypass permission checks for sending signals.', - 'SETGID': 'Make arbitrary manipulations of process GIDs and supplementary GID list.', - 'SETUID': 'Make arbitrary manipulations of process UIDs.', - 'NET_BIND_SERVICE': 'Bind a socket to internet domain privileged ports (port numbers less than 1024).', - 'SYS_CHROOT': 'Use chroot(2), change root directory.', - 'SETFCAP': 'Set file capabilities.', - 'SYS_MODULE': 'Load and unload kernel modules.', - 'SYS_RAWIO': 'Perform I/O port operations (iopl(2) and ioperm(2)).', - 'SYS_PACCT': 'Use acct(2), switch process accounting on or off.', - 'SYS_ADMIN': 'Perform a range of system administration operations.', - 'SYS_NICE': 'Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes.', - 'SYS_RESOURCE': 'Override resource Limits.', - 'SYS_TIME': 'Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock.', - 'SYS_TTY_CONFIG': 'Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals.', - 'AUDIT_CONTROL': 'Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules.', - 'MAC_ADMIN': 'Allow MAC configuration or state changes. Implemented for the Smack LSM.', - 'MAC_OVERRIDE': 'Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM).', - 'NET_ADMIN': 'Perform various network-related operations.', - 'SYSLOG': 'Perform privileged syslog(2) operations.', - 'DAC_READ_SEARCH': 'Bypass file read permission checks and directory read and execute permission checks.', - 'LINUX_IMMUTABLE': 'Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags.', - 'NET_BROADCAST': 'Make socket broadcasts, and listen to multicasts.', - 'IPC_LOCK': 'Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)).', - 'IPC_OWNER': 'Bypass permission checks for operations on System V IPC objects.', - 'SYS_PTRACE': 'Trace arbitrary processes using ptrace(2).', - 'SYS_BOOT': 'Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution.', - 'LEASE': 'Establish leases on arbitrary files (see fcntl(2)).', - 'WAKE_ALARM': 'Trigger something that will wake up the system.', - 'BLOCK_SUSPEND': 'Employ features that can block system suspend.' + SETPCAP: 'Modify process capabilities.', + MKNOD: 'Create special files using mknod(2).', + AUDIT_WRITE: 'Write records to kernel auditing log.', + CHOWN: 'Make arbitrary changes to file UIDs and GIDs (see chown(2)).', + NET_RAW: 'Use RAW and PACKET sockets.', + DAC_OVERRIDE: 'Bypass file read, write, and execute permission checks.', + FOWNER: 'Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file.', + FSETID: 'Don’t clear set-user-ID and set-group-ID permission bits when a file is modified.', + KILL: 'Bypass permission checks for sending signals.', + SETGID: 'Make arbitrary manipulations of process GIDs and supplementary GID list.', + SETUID: 'Make arbitrary manipulations of process UIDs.', + NET_BIND_SERVICE: 'Bind a socket to internet domain privileged ports (port numbers less than 1024).', + SYS_CHROOT: 'Use chroot(2), change root directory.', + SETFCAP: 'Set file capabilities.', + SYS_MODULE: 'Load and unload kernel modules.', + SYS_RAWIO: 'Perform I/O port operations (iopl(2) and ioperm(2)).', + SYS_PACCT: 'Use acct(2), switch process accounting on or off.', + SYS_ADMIN: 'Perform a range of system administration operations.', + SYS_NICE: 'Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes.', + SYS_RESOURCE: 'Override resource Limits.', + SYS_TIME: 'Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock.', + SYS_TTY_CONFIG: 'Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals.', + AUDIT_CONTROL: 'Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules.', + MAC_ADMIN: 'Allow MAC configuration or state changes. Implemented for the Smack LSM.', + MAC_OVERRIDE: 'Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM).', + NET_ADMIN: 'Perform various network-related operations.', + SYSLOG: 'Perform privileged syslog(2) operations.', + DAC_READ_SEARCH: 'Bypass file read permission checks and directory read and execute permission checks.', + LINUX_IMMUTABLE: 'Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags.', + NET_BROADCAST: 'Make socket broadcasts, and listen to multicasts.', + IPC_LOCK: 'Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)).', + IPC_OWNER: 'Bypass permission checks for operations on System V IPC objects.', + SYS_PTRACE: 'Trace arbitrary processes using ptrace(2).', + SYS_BOOT: 'Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution.', + LEASE: 'Establish leases on arbitrary files (see fcntl(2)).', + WAKE_ALARM: 'Trigger something that will wake up the system.', + BLOCK_SUSPEND: 'Employ features that can block system suspend.', }; export function ContainerCapabilities() { - // all capabilities can be found at https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities - return [ - new ContainerCapability('SETPCAP', true), - new ContainerCapability('MKNOD', true), - new ContainerCapability('AUDIT_WRITE', true), - new ContainerCapability('CHOWN', true), - new ContainerCapability('NET_RAW', true), - new ContainerCapability('DAC_OVERRIDE', true), - new ContainerCapability('FOWNER', true), - new ContainerCapability('FSETID', true), - new ContainerCapability('KILL', true), - new ContainerCapability('SETGID', true), - new ContainerCapability('SETUID', true), - new ContainerCapability('NET_BIND_SERVICE', true), - new ContainerCapability('SYS_CHROOT', true), - new ContainerCapability('SETFCAP', true), - new ContainerCapability('SYS_MODULE', false), - new ContainerCapability('SYS_RAWIO', false), - new ContainerCapability('SYS_PACCT', false), - new ContainerCapability('SYS_ADMIN', false), - new ContainerCapability('SYS_NICE', false), - new ContainerCapability('SYS_RESOURCE', false), - new ContainerCapability('SYS_TIME', false), - new ContainerCapability('SYS_TTY_CONFIG', false), - new ContainerCapability('AUDIT_CONTROL', false), - new ContainerCapability('MAC_ADMIN', false), - new ContainerCapability('MAC_OVERRIDE', false), - new ContainerCapability('NET_ADMIN', false), - new ContainerCapability('SYSLOG', false), - new ContainerCapability('DAC_READ_SEARCH', false), - new ContainerCapability('LINUX_IMMUTABLE', false), - new ContainerCapability('NET_BROADCAST', false), - new ContainerCapability('IPC_LOCK', false), - new ContainerCapability('IPC_OWNER', false), - new ContainerCapability('SYS_PTRACE', false), - new ContainerCapability('SYS_BOOT', false), - new ContainerCapability('LEASE', false), - new ContainerCapability('WAKE_ALARM', false), - new ContainerCapability('BLOCK_SUSPEND', false) - ].sort(function (a, b) { - return a.capability < b.capability ? -1 : 1; - }); + // all capabilities can be found at https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities + return [ + new ContainerCapability('SETPCAP', true), + new ContainerCapability('MKNOD', true), + new ContainerCapability('AUDIT_WRITE', true), + new ContainerCapability('CHOWN', true), + new ContainerCapability('NET_RAW', true), + new ContainerCapability('DAC_OVERRIDE', true), + new ContainerCapability('FOWNER', true), + new ContainerCapability('FSETID', true), + new ContainerCapability('KILL', true), + new ContainerCapability('SETGID', true), + new ContainerCapability('SETUID', true), + new ContainerCapability('NET_BIND_SERVICE', true), + new ContainerCapability('SYS_CHROOT', true), + new ContainerCapability('SETFCAP', true), + new ContainerCapability('SYS_MODULE', false), + new ContainerCapability('SYS_RAWIO', false), + new ContainerCapability('SYS_PACCT', false), + new ContainerCapability('SYS_ADMIN', false), + new ContainerCapability('SYS_NICE', false), + new ContainerCapability('SYS_RESOURCE', false), + new ContainerCapability('SYS_TIME', false), + new ContainerCapability('SYS_TTY_CONFIG', false), + new ContainerCapability('AUDIT_CONTROL', false), + new ContainerCapability('MAC_ADMIN', false), + new ContainerCapability('MAC_OVERRIDE', false), + new ContainerCapability('NET_ADMIN', false), + new ContainerCapability('SYSLOG', false), + new ContainerCapability('DAC_READ_SEARCH', false), + new ContainerCapability('LINUX_IMMUTABLE', false), + new ContainerCapability('NET_BROADCAST', false), + new ContainerCapability('IPC_LOCK', false), + new ContainerCapability('IPC_OWNER', false), + new ContainerCapability('SYS_PTRACE', false), + new ContainerCapability('SYS_BOOT', false), + new ContainerCapability('LEASE', false), + new ContainerCapability('WAKE_ALARM', false), + new ContainerCapability('BLOCK_SUSPEND', false), + ].sort(function (a, b) { + return a.capability < b.capability ? -1 : 1; + }); } export function ContainerCapability(cap, allowed) { - this.capability = cap; - this.allowed = allowed; - this.description = capDesc[cap]; -} \ No newline at end of file + this.capability = cap; + this.allowed = allowed; + this.description = capDesc[cap]; +} diff --git a/app/docker/models/event.js b/app/docker/models/event.js index 46f9f8c106184..914740a043d75 100644 --- a/app/docker/models/event.js +++ b/app/docker/models/event.js @@ -3,148 +3,148 @@ function createEventDetails(event) { var details = ''; switch (event.Type) { case 'container': - switch (event.Action) { - case 'stop': - details = 'Container ' + eventAttr.name + ' stopped'; - break; - case 'destroy': - details = 'Container ' + eventAttr.name + ' deleted'; - break; - case 'create': - details = 'Container ' + eventAttr.name + ' created'; - break; - case 'start': - details = 'Container ' + eventAttr.name + ' started'; - break; - case 'kill': - details = 'Container ' + eventAttr.name + ' killed'; - break; - case 'die': - details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode; - break; - case 'commit': - details = 'Container ' + eventAttr.name + ' committed'; - break; - case 'restart': - details = 'Container ' + eventAttr.name + ' restarted'; - break; - case 'pause': - details = 'Container ' + eventAttr.name + ' paused'; - break; - case 'unpause': - details = 'Container ' + eventAttr.name + ' unpaused'; - break; - case 'attach': - details = 'Container ' + eventAttr.name + ' attached'; - break; - case 'detach': - details = 'Container ' + eventAttr.name + ' detached'; - break; - case 'copy': - details = 'Container ' + eventAttr.name + ' copied'; - break; - case 'export': - details = 'Container ' + eventAttr.name + ' exported'; - break; - case 'health_status': - details = 'Container ' + eventAttr.name + ' executed health status'; - break; - case 'oom': - details = 'Container ' + eventAttr.name + ' goes in out of memory'; - break; - case 'rename': - details = 'Container ' + eventAttr.name + ' renamed'; - break; - case 'resize': - details = 'Container ' + eventAttr.name + ' resized'; - break; - case 'top': - details = 'Showed running processes for container ' + eventAttr.name; - break; - case 'update': - details = 'Container ' + eventAttr.name + ' updated'; - break; - default: - if (event.Action.indexOf('exec_create') === 0) { - details = 'Exec instance created'; - } else if (event.Action.indexOf('exec_start') === 0) { - details = 'Exec instance started'; - } else { - details = 'Unsupported event'; + switch (event.Action) { + case 'stop': + details = 'Container ' + eventAttr.name + ' stopped'; + break; + case 'destroy': + details = 'Container ' + eventAttr.name + ' deleted'; + break; + case 'create': + details = 'Container ' + eventAttr.name + ' created'; + break; + case 'start': + details = 'Container ' + eventAttr.name + ' started'; + break; + case 'kill': + details = 'Container ' + eventAttr.name + ' killed'; + break; + case 'die': + details = 'Container ' + eventAttr.name + ' exited with status code ' + eventAttr.exitCode; + break; + case 'commit': + details = 'Container ' + eventAttr.name + ' committed'; + break; + case 'restart': + details = 'Container ' + eventAttr.name + ' restarted'; + break; + case 'pause': + details = 'Container ' + eventAttr.name + ' paused'; + break; + case 'unpause': + details = 'Container ' + eventAttr.name + ' unpaused'; + break; + case 'attach': + details = 'Container ' + eventAttr.name + ' attached'; + break; + case 'detach': + details = 'Container ' + eventAttr.name + ' detached'; + break; + case 'copy': + details = 'Container ' + eventAttr.name + ' copied'; + break; + case 'export': + details = 'Container ' + eventAttr.name + ' exported'; + break; + case 'health_status': + details = 'Container ' + eventAttr.name + ' executed health status'; + break; + case 'oom': + details = 'Container ' + eventAttr.name + ' goes in out of memory'; + break; + case 'rename': + details = 'Container ' + eventAttr.name + ' renamed'; + break; + case 'resize': + details = 'Container ' + eventAttr.name + ' resized'; + break; + case 'top': + details = 'Showed running processes for container ' + eventAttr.name; + break; + case 'update': + details = 'Container ' + eventAttr.name + ' updated'; + break; + default: + if (event.Action.indexOf('exec_create') === 0) { + details = 'Exec instance created'; + } else if (event.Action.indexOf('exec_start') === 0) { + details = 'Exec instance started'; + } else { + details = 'Unsupported event'; + } } - } - break; - case 'image': - switch (event.Action) { - case 'delete': - details = 'Image deleted'; break; - case 'import': - details = 'Image ' + event.Actor.ID + ' imported'; - break; - case 'load': - details = 'Image ' + event.Actor.ID + ' loaded'; - break; - case 'tag': - details = 'New tag created for ' + eventAttr.name; - break; - case 'untag': - details = 'Image untagged'; - break; - case 'save': - details = 'Image ' + event.Actor.ID + ' saved'; - break; - case 'pull': - details = 'Image ' + event.Actor.ID + ' pulled'; - break; - case 'push': - details = 'Image ' + event.Actor.ID + ' pushed'; + case 'image': + switch (event.Action) { + case 'delete': + details = 'Image deleted'; + break; + case 'import': + details = 'Image ' + event.Actor.ID + ' imported'; + break; + case 'load': + details = 'Image ' + event.Actor.ID + ' loaded'; + break; + case 'tag': + details = 'New tag created for ' + eventAttr.name; + break; + case 'untag': + details = 'Image untagged'; + break; + case 'save': + details = 'Image ' + event.Actor.ID + ' saved'; + break; + case 'pull': + details = 'Image ' + event.Actor.ID + ' pulled'; + break; + case 'push': + details = 'Image ' + event.Actor.ID + ' pushed'; + break; + default: + details = 'Unsupported event'; + } break; - default: - details = 'Unsupported event'; - } - break; case 'network': - switch (event.Action) { - case 'create': - details = 'Network ' + eventAttr.name + ' created'; - break; - case 'destroy': - details = 'Network ' + eventAttr.name + ' deleted'; - break; - case 'remove': - details = 'Network ' + eventAttr.name + ' removed'; - break; - case 'connect': - details = 'Container connected to ' + eventAttr.name + ' network'; - break; - case 'disconnect': - details = 'Container disconnected from ' + eventAttr.name + ' network'; + switch (event.Action) { + case 'create': + details = 'Network ' + eventAttr.name + ' created'; + break; + case 'destroy': + details = 'Network ' + eventAttr.name + ' deleted'; + break; + case 'remove': + details = 'Network ' + eventAttr.name + ' removed'; + break; + case 'connect': + details = 'Container connected to ' + eventAttr.name + ' network'; + break; + case 'disconnect': + details = 'Container disconnected from ' + eventAttr.name + ' network'; + break; + default: + details = 'Unsupported event'; + } break; - default: - details = 'Unsupported event'; - } - break; case 'volume': - switch (event.Action) { - case 'create': - details = 'Volume ' + event.Actor.ID + ' created'; - break; - case 'destroy': - details = 'Volume ' + event.Actor.ID + ' deleted'; - break; - case 'mount': - details = 'Volume ' + event.Actor.ID + ' mounted'; - break; - case 'unmount': - details = 'Volume ' + event.Actor.ID + ' unmounted'; + switch (event.Action) { + case 'create': + details = 'Volume ' + event.Actor.ID + ' created'; + break; + case 'destroy': + details = 'Volume ' + event.Actor.ID + ' deleted'; + break; + case 'mount': + details = 'Volume ' + event.Actor.ID + ' mounted'; + break; + case 'unmount': + details = 'Volume ' + event.Actor.ID + ' unmounted'; + break; + default: + details = 'Unsupported event'; + } break; - default: - details = 'Unsupported event'; - } - break; default: - details = 'Unsupported event'; + details = 'Unsupported event'; } return details; } diff --git a/app/docker/models/network.js b/app/docker/models/network.js index a629eb0d32bfc..e3beb5e1f0944 100644 --- a/app/docker/models/network.js +++ b/app/docker/models/network.js @@ -29,4 +29,4 @@ export function NetworkViewModel(data) { this.ConfigFrom = data.ConfigFrom; this.ConfigOnly = data.ConfigOnly; -} \ No newline at end of file +} diff --git a/app/docker/models/node.js b/app/docker/models/node.js index 4057141f4b199..9386bf9846b19 100644 --- a/app/docker/models/node.js +++ b/app/docker/models/node.js @@ -10,7 +10,7 @@ export function NodeViewModel(data) { var labels = data.Spec.Labels; if (labels) { - this.Labels = Object.keys(labels).map(function(key) { + this.Labels = Object.keys(labels).map(function (key) { return { key: key, value: labels[key], originalKey: key, originalValue: labels[key], added: true }; }); } else { @@ -19,7 +19,7 @@ export function NodeViewModel(data) { var engineLabels = data.Description.Engine.Labels; if (engineLabels) { - this.EngineLabels = Object.keys(engineLabels).map(function(key) { + this.EngineLabels = Object.keys(engineLabels).map(function (key) { return { key: key, value: engineLabels[key] }; }); } else { diff --git a/app/docker/models/porImageRegistry.js b/app/docker/models/porImageRegistry.js index 38ea4ad943b65..c34c318ab033a 100644 --- a/app/docker/models/porImageRegistry.js +++ b/app/docker/models/porImageRegistry.js @@ -1,11 +1,11 @@ /** * This model should be used with por-image-registry component * And bound to the 'model' attribute - * + * * // viewController.js - * + * * this.imageModel = new PorImageRegistryModel(); - * + * * // view.html * */ @@ -13,4 +13,4 @@ export function PorImageRegistryModel() { this.UseRegistry = true; this.Registry = {}; this.Image = ''; -} \ No newline at end of file +} diff --git a/app/docker/models/service.js b/app/docker/models/service.js index 84f58eed0afbe..df72a282fd694 100644 --- a/app/docker/models/service.js +++ b/app/docker/models/service.js @@ -10,7 +10,7 @@ export function ServiceViewModel(data, runningTasks, allTasks) { this.Image = data.Spec.TaskTemplate.ContainerSpec.Image; this.Version = data.Version.Index; if (data.Spec.Mode.Replicated) { - this.Mode = 'replicated' ; + this.Mode = 'replicated'; this.Replicas = data.Spec.Mode.Replicated.Replicas; } else { this.Mode = 'global'; @@ -23,12 +23,12 @@ export function ServiceViewModel(data, runningTasks, allTasks) { } if (data.Spec.TaskTemplate.Resources) { if (data.Spec.TaskTemplate.Resources.Limits) { - this.LimitNanoCPUs = data.Spec.TaskTemplate.Resources.Limits.NanoCPUs; - this.LimitMemoryBytes = data.Spec.TaskTemplate.Resources.Limits.MemoryBytes; + this.LimitNanoCPUs = data.Spec.TaskTemplate.Resources.Limits.NanoCPUs; + this.LimitMemoryBytes = data.Spec.TaskTemplate.Resources.Limits.MemoryBytes; } if (data.Spec.TaskTemplate.Resources.Reservations) { - this.ReservationNanoCPUs = data.Spec.TaskTemplate.Resources.Reservations.NanoCPUs; - this.ReservationMemoryBytes = data.Spec.TaskTemplate.Resources.Reservations.MemoryBytes; + this.ReservationNanoCPUs = data.Spec.TaskTemplate.Resources.Reservations.NanoCPUs; + this.ReservationMemoryBytes = data.Spec.TaskTemplate.Resources.Reservations.MemoryBytes; } } @@ -92,7 +92,7 @@ export function ServiceViewModel(data, runningTasks, allTasks) { this.VirtualIPs = data.Endpoint ? data.Endpoint.VirtualIPs : []; if (data.Spec.UpdateConfig) { - this.UpdateParallelism = (typeof data.Spec.UpdateConfig.Parallelism !== undefined) ? data.Spec.UpdateConfig.Parallelism || 0 : 1; + this.UpdateParallelism = typeof data.Spec.UpdateConfig.Parallelism !== undefined ? data.Spec.UpdateConfig.Parallelism || 0 : 1; this.UpdateDelay = data.Spec.UpdateConfig.Delay || 0; this.UpdateFailureAction = data.Spec.UpdateConfig.FailureAction || 'pause'; this.UpdateOrder = data.Spec.UpdateConfig.Order || 'stop-first'; diff --git a/app/docker/rest/build.js b/app/docker/rest/build.js index e48d12d51edff..b0ba607ad12e4 100644 --- a/app/docker/rest/build.js +++ b/app/docker/rest/build.js @@ -1,20 +1,31 @@ import { jsonObjectsToArrayHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('Build', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function BuildFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/build', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Build', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function BuildFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/build', + { + endpointId: EndpointProvider.endpointID, + }, + { + buildImage: { + method: 'POST', + ignoreLoadingBar: true, + transformResponse: jsonObjectsToArrayHandler, + isArray: true, + headers: { 'Content-Type': 'application/x-tar' }, + }, + buildImageOverride: { + method: 'POST', + ignoreLoadingBar: true, + transformResponse: jsonObjectsToArrayHandler, + isArray: true, + }, + } + ); }, - { - buildImage: { - method: 'POST', ignoreLoadingBar: true, - transformResponse: jsonObjectsToArrayHandler, isArray: true, - headers: { 'Content-Type': 'application/x-tar' } - }, - buildImageOverride: { - method: 'POST', ignoreLoadingBar: true, - transformResponse: jsonObjectsToArrayHandler, isArray: true - } - }); -}]); +]); diff --git a/app/docker/rest/commit.js b/app/docker/rest/commit.js index 36461029733b9..5d768a119d254 100644 --- a/app/docker/rest/commit.js +++ b/app/docker/rest/commit.js @@ -1,10 +1,17 @@ -angular.module('portainer.docker') -.factory('Commit', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function CommitFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/commit', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Commit', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function CommitFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/commit', + { + endpointId: EndpointProvider.endpointID, + }, + { + commitContainer: { method: 'POST', params: { container: '@id', repo: '@repo' }, ignoreLoadingBar: true }, + } + ); }, - { - commitContainer: {method: 'POST', params: {container: '@id', repo: '@repo'}, ignoreLoadingBar: true} - }); -}]); +]); diff --git a/app/docker/rest/config.js b/app/docker/rest/config.js index 2b21ee2d05652..af3bcecc77a36 100644 --- a/app/docker/rest/config.js +++ b/app/docker/rest/config.js @@ -1,12 +1,20 @@ -angular.module('portainer.docker') -.factory('Config', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function ConfigFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/configs/:id/:action', { - endpointId: EndpointProvider.endpointID - }, { - get: { method: 'GET', params: { id: '@id' } }, - query: { method: 'GET', isArray: true }, - create: { method: 'POST', params: { action: 'create' }, ignoreLoadingBar: true }, - remove: { method: 'DELETE', params: { id: '@id' } } - }); -}]); +angular.module('portainer.docker').factory('Config', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function ConfigFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/configs/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + get: { method: 'GET', params: { id: '@id' } }, + query: { method: 'GET', isArray: true }, + create: { method: 'POST', params: { action: 'create' }, ignoreLoadingBar: true }, + remove: { method: 'DELETE', params: { id: '@id' } }, + } + ); + }, +]); diff --git a/app/docker/rest/container.js b/app/docker/rest/container.js index 1aeb38e2605d6..aae286f1123c3 100644 --- a/app/docker/rest/container.js +++ b/app/docker/rest/container.js @@ -1,82 +1,115 @@ -import {genericHandler, logsHandler} from './response/handlers'; +import { genericHandler, logsHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('Container', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'ContainersInterceptor', -function ContainerFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, ContainersInterceptor) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/containers/:id/:action', { - name: '@name', - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Container', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'ContainersInterceptor', + function ContainerFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, ContainersInterceptor) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/containers/:id/:action', + { + name: '@name', + endpointId: EndpointProvider.endpointID, + }, + { + query: { + method: 'GET', + params: { all: 0, action: 'json', filters: '@filters' }, + isArray: true, + interceptor: ContainersInterceptor, + timeout: 15000, + }, + get: { + method: 'GET', + params: { action: 'json' }, + }, + stop: { + method: 'POST', + params: { id: '@id', action: 'stop' }, + }, + restart: { + method: 'POST', + params: { id: '@id', action: 'restart' }, + }, + kill: { + method: 'POST', + params: { id: '@id', action: 'kill' }, + }, + pause: { + method: 'POST', + params: { id: '@id', action: 'pause' }, + }, + unpause: { + method: 'POST', + params: { id: '@id', action: 'unpause' }, + }, + logs: { + method: 'GET', + params: { id: '@id', action: 'logs' }, + timeout: 4500, + ignoreLoadingBar: true, + transformResponse: logsHandler, + }, + stats: { + method: 'GET', + params: { id: '@id', stream: false, action: 'stats' }, + timeout: 4500, + ignoreLoadingBar: true, + }, + top: { + method: 'GET', + params: { id: '@id', action: 'top' }, + timeout: 4500, + ignoreLoadingBar: true, + }, + start: { + method: 'POST', + params: { id: '@id', action: 'start' }, + transformResponse: genericHandler, + }, + create: { + method: 'POST', + params: { action: 'create' }, + transformResponse: genericHandler, + ignoreLoadingBar: true, + }, + remove: { + method: 'DELETE', + params: { id: '@id', v: '@v', force: '@force' }, + transformResponse: genericHandler, + }, + rename: { + method: 'POST', + params: { id: '@id', action: 'rename', name: '@name' }, + transformResponse: genericHandler, + }, + exec: { + method: 'POST', + params: { id: '@id', action: 'exec' }, + transformResponse: genericHandler, + ignoreLoadingBar: true, + }, + inspect: { + method: 'GET', + params: { id: '@id', action: 'json' }, + }, + update: { + method: 'POST', + params: { id: '@id', action: 'update' }, + }, + prune: { + method: 'POST', + params: { action: 'prune', filters: '@filters' }, + }, + resize: { + method: 'POST', + params: { id: '@id', action: 'resize', h: '@height', w: '@width' }, + transformResponse: genericHandler, + ignoreLoadingBar: true, + }, + } + ); }, - { - query: { - method: 'GET', params: { all: 0, action: 'json', filters: '@filters' }, - isArray: true, interceptor: ContainersInterceptor, timeout: 15000 - }, - get: { - method: 'GET', params: { action: 'json' } - }, - stop: { - method: 'POST', params: { id: '@id', action: 'stop' } - }, - restart: { - method: 'POST', params: { id: '@id', action: 'restart' } - }, - kill: { - method: 'POST', params: { id: '@id', action: 'kill' } - }, - pause: { - method: 'POST', params: { id: '@id', action: 'pause' } - }, - unpause: { - method: 'POST', params: { id: '@id', action: 'unpause' } - }, - logs: { - method: 'GET', params: { id: '@id', action: 'logs' }, - timeout: 4500, ignoreLoadingBar: true, - transformResponse: logsHandler - }, - stats: { - method: 'GET', params: { id: '@id', stream: false, action: 'stats' }, - timeout: 4500, ignoreLoadingBar: true - }, - top: { - method: 'GET', params: { id: '@id', action: 'top' }, - timeout: 4500, ignoreLoadingBar: true - }, - start: { - method: 'POST', params: {id: '@id', action: 'start'}, - transformResponse: genericHandler - }, - create: { - method: 'POST', params: {action: 'create'}, - transformResponse: genericHandler, - ignoreLoadingBar: true - }, - remove: { - method: 'DELETE', params: {id: '@id', v: '@v', force: '@force'}, - transformResponse: genericHandler - }, - rename: { - method: 'POST', params: { id: '@id', action: 'rename', name: '@name' }, - transformResponse: genericHandler - }, - exec: { - method: 'POST', params: {id: '@id', action: 'exec'}, - transformResponse: genericHandler, ignoreLoadingBar: true - }, - inspect: { - method: 'GET', params: { id: '@id', action: 'json' } - }, - update: { - method: 'POST', params: { id: '@id', action: 'update'} - }, - prune: { - method: 'POST', params: { action: 'prune', filters: '@filters' } - }, - resize: { - method: 'POST', params: {id: '@id', action: 'resize', h: '@height', w: '@width'}, - transformResponse: genericHandler, ignoreLoadingBar: true - } - }); -}]); +]); diff --git a/app/docker/rest/exec.js b/app/docker/rest/exec.js index 6ad7b4f3539b6..20c5035e442ac 100644 --- a/app/docker/rest/exec.js +++ b/app/docker/rest/exec.js @@ -1,16 +1,24 @@ import { genericHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('Exec', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', -function ExecFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/exec/:id/:action', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Exec', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function ExecFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/exec/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + resize: { + method: 'POST', + params: { id: '@id', action: 'resize', h: '@height', w: '@width' }, + transformResponse: genericHandler, + ignoreLoadingBar: true, + }, + } + ); }, - { - resize: { - method: 'POST', params: {id: '@id', action: 'resize', h: '@height', w: '@width'}, - transformResponse: genericHandler, ignoreLoadingBar: true - } - }); -}]); +]); diff --git a/app/docker/rest/image.js b/app/docker/rest/image.js index a0cfd0cfcdbef..aa0d8a6a9e656 100644 --- a/app/docker/rest/image.js +++ b/app/docker/rest/image.js @@ -1,43 +1,58 @@ -import {deleteImageHandler, jsonObjectsToArrayHandler} from './response/handlers'; -import {imageGetResponse} from './response/image'; +import { deleteImageHandler, jsonObjectsToArrayHandler } from './response/handlers'; +import { imageGetResponse } from './response/image'; -angular.module('portainer.docker') -.factory('Image', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'HttpRequestHelper', 'ImagesInterceptor', -function ImageFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper, ImagesInterceptor) { - 'use strict'; +angular.module('portainer.docker').factory('Image', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'HttpRequestHelper', + 'ImagesInterceptor', + function ImageFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper, ImagesInterceptor) { + 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/images/:id/:action', { - endpointId: EndpointProvider.endpointID + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/images/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + query: { method: 'GET', params: { all: 0, action: 'json' }, isArray: true, interceptor: ImagesInterceptor, timeout: 15000 }, + get: { method: 'GET', params: { action: 'json' } }, + search: { method: 'GET', params: { action: 'search' } }, + history: { method: 'GET', params: { action: 'history' }, isArray: true }, + insert: { method: 'POST', params: { id: '@id', action: 'insert' } }, + tag: { method: 'POST', params: { id: '@id', action: 'tag', force: 0, repo: '@repo' }, ignoreLoadingBar: true }, + inspect: { method: 'GET', params: { id: '@id', action: 'json' } }, + push: { + method: 'POST', + params: { action: 'push', id: '@imageName' }, + isArray: true, + transformResponse: jsonObjectsToArrayHandler, + headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader }, + ignoreLoadingBar: true, + }, + create: { + method: 'POST', + params: { action: 'create', fromImage: '@fromImage' }, + isArray: true, + transformResponse: jsonObjectsToArrayHandler, + headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader }, + ignoreLoadingBar: true, + }, + download: { + method: 'GET', + params: { action: 'get', names: '@names' }, + transformResponse: imageGetResponse, + responseType: 'blob', + ignoreLoadingBar: true, + }, + remove: { + method: 'DELETE', + params: { id: '@id', force: '@force' }, + isArray: true, + transformResponse: deleteImageHandler, + }, + } + ); }, - { - query: {method: 'GET', params: {all: 0, action: 'json'}, isArray: true, interceptor: ImagesInterceptor, timeout: 15000}, - get: {method: 'GET', params: {action: 'json'}}, - search: {method: 'GET', params: {action: 'search'}}, - history: {method: 'GET', params: {action: 'history'}, isArray: true}, - insert: {method: 'POST', params: {id: '@id', action: 'insert'}}, - tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo'}, ignoreLoadingBar: true}, - inspect: {method: 'GET', params: {id: '@id', action: 'json'}}, - push: { - method: 'POST', params: {action: 'push', id: '@imageName'}, - isArray: true, transformResponse: jsonObjectsToArrayHandler, - headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader }, - ignoreLoadingBar: true - }, - create: { - method: 'POST', params: {action: 'create', fromImage: '@fromImage'}, - isArray: true, transformResponse: jsonObjectsToArrayHandler, - headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader }, - ignoreLoadingBar: true - }, - download: { - method: 'GET', params: {action:'get', names: '@names'}, - transformResponse: imageGetResponse, - responseType: 'blob', - ignoreLoadingBar: true - }, - remove: { - method: 'DELETE', params: {id: '@id', force: '@force'}, - isArray: true, transformResponse: deleteImageHandler - } - }); -}]); +]); diff --git a/app/docker/rest/network.js b/app/docker/rest/network.js index 5830483efa9c1..8c8480158e96e 100644 --- a/app/docker/rest/network.js +++ b/app/docker/rest/network.js @@ -1,32 +1,47 @@ -import {genericHandler} from './response/handlers'; +import { genericHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('Network', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'NetworksInterceptor', -function NetworkFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, NetworksInterceptor) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/networks/:id/:action', { - id: '@id', - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Network', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'NetworksInterceptor', + function NetworkFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, NetworksInterceptor) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/networks/:id/:action', + { + id: '@id', + endpointId: EndpointProvider.endpointID, + }, + { + query: { + method: 'GET', + isArray: true, + interceptor: NetworksInterceptor, + timeout: 15000, + }, + get: { + method: 'GET', + }, + create: { + method: 'POST', + params: { action: 'create' }, + transformResponse: genericHandler, + ignoreLoadingBar: true, + }, + remove: { + method: 'DELETE', + transformResponse: genericHandler, + }, + connect: { + method: 'POST', + params: { action: 'connect' }, + }, + disconnect: { + method: 'POST', + params: { action: 'disconnect' }, + }, + } + ); }, - { - query: { - method: 'GET', isArray: true, interceptor: NetworksInterceptor, timeout: 15000 - }, - get: { - method: 'GET' - }, - create: { - method: 'POST', params: {action: 'create'}, - transformResponse: genericHandler, ignoreLoadingBar: true - }, - remove: { - method: 'DELETE', transformResponse: genericHandler - }, - connect: { - method: 'POST', params: { action: 'connect' } - }, - disconnect: { - method: 'POST', params: { action: 'disconnect' } - } - }); -}]); +]); diff --git a/app/docker/rest/node.js b/app/docker/rest/node.js index 821e40328a3f5..aa35d1a13bad1 100644 --- a/app/docker/rest/node.js +++ b/app/docker/rest/node.js @@ -1,13 +1,20 @@ -angular.module('portainer.docker') -.factory('Node', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function NodeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/nodes/:id/:action', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Node', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function NodeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/nodes/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + query: { method: 'GET', isArray: true }, + get: { method: 'GET', params: { id: '@id' } }, + update: { method: 'POST', params: { id: '@id', action: 'update', version: '@version' } }, + remove: { method: 'DELETE', params: { id: '@id' } }, + } + ); }, - { - query: {method: 'GET', isArray: true}, - get: {method: 'GET', params: {id: '@id'}}, - update: { method: 'POST', params: {id: '@id', action: 'update', version: '@version'} }, - remove: { method: 'DELETE', params: {id: '@id'} } - }); -}]); +]); diff --git a/app/docker/rest/plugin.js b/app/docker/rest/plugin.js index c73817fd42598..ed2e5784be193 100644 --- a/app/docker/rest/plugin.js +++ b/app/docker/rest/plugin.js @@ -1,9 +1,17 @@ -angular.module('portainer.docker') -.factory('Plugin', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function PluginFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/plugins/:id/:action', { - endpointId: EndpointProvider.endpointID - }, { - query: { method: 'GET', isArray: true } - }); -}]); +angular.module('portainer.docker').factory('Plugin', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function PluginFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/plugins/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + query: { method: 'GET', isArray: true }, + } + ); + }, +]); diff --git a/app/docker/rest/response/handlers.js b/app/docker/rest/response/handlers.js index 1a3854c34b19a..7e14da0236b66 100644 --- a/app/docker/rest/response/handlers.js +++ b/app/docker/rest/response/handlers.js @@ -1,15 +1,14 @@ function isJSONArray(jsonString) { - return Object.prototype.toString.call(jsonString) === '[object Array]'; + return Object.prototype.toString.call(jsonString) === '[object Array]'; } function isJSON(jsonString) { try { var o = JSON.parse(jsonString); if (o && typeof o === 'object') { - return o; + return o; } - } - catch (e) { + } catch (e) { //empty } return false; @@ -50,7 +49,7 @@ export function genericHandler(data) { // This handler wraps the data in a JSON object under the "logs" property. export function logsHandler(data) { return { - logs: data + logs: data, }; } @@ -63,7 +62,7 @@ export function deleteImageHandler(data) { // A string is returned on failure (Docker < 1.12) var response = []; if (!isJSON(data)) { - response.push({message: data}); + response.push({ message: data }); } // A JSON object is returned on failure (Docker = 1.12) else if (!isJSONArray(data)) { diff --git a/app/docker/rest/response/image.js b/app/docker/rest/response/image.js index c2e63cd8232a4..7d61e594333db 100644 --- a/app/docker/rest/response/image.js +++ b/app/docker/rest/response/image.js @@ -3,8 +3,7 @@ // This functions simply creates a response object and assign // the data to a field. export function imageGetResponse(data) { - var response = {}; - response.file = data; - return response; - } - \ No newline at end of file + var response = {}; + response.file = data; + return response; +} diff --git a/app/docker/rest/secret.js b/app/docker/rest/secret.js index 1f5f8ab95884c..9969a18d26efa 100644 --- a/app/docker/rest/secret.js +++ b/app/docker/rest/secret.js @@ -1,12 +1,20 @@ -angular.module('portainer.docker') -.factory('Secret', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function SecretFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/secrets/:id/:action', { - endpointId: EndpointProvider.endpointID - }, { - get: { method: 'GET', params: {id: '@id'} }, - query: { method: 'GET', isArray: true }, - create: { method: 'POST', params: {action: 'create'}, ignoreLoadingBar: true }, - remove: { method: 'DELETE', params: {id: '@id'} } - }); -}]); +angular.module('portainer.docker').factory('Secret', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function SecretFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/secrets/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + get: { method: 'GET', params: { id: '@id' } }, + query: { method: 'GET', isArray: true }, + create: { method: 'POST', params: { action: 'create' }, ignoreLoadingBar: true }, + remove: { method: 'DELETE', params: { id: '@id' } }, + } + ); + }, +]); diff --git a/app/docker/rest/service.js b/app/docker/rest/service.js index 81a8eca7e7132..aecd4acbe9012 100644 --- a/app/docker/rest/service.js +++ b/app/docker/rest/service.js @@ -1,34 +1,45 @@ import { logsHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('Service', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'HttpRequestHelper', -function ServiceFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/services/:id/:action', { - endpointId: EndpointProvider.endpointID - }, - { - get: { method: 'GET', params: {id: '@id'} }, - query: { method: 'GET', isArray: true, params: {filters: '@filters'} }, - create: { - method: 'POST', params: {action: 'create'}, - headers: { - 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader, - 'version': '1.29' +angular.module('portainer.docker').factory('Service', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'HttpRequestHelper', + function ServiceFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpRequestHelper) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/services/:id/:action', + { + endpointId: EndpointProvider.endpointID, }, - ignoreLoadingBar: true - }, - update: { - method: 'POST', params: { id: '@id', action: 'update', version: '@version', rollback: '@rollback' }, - headers: { - 'version': '1.29' + { + get: { method: 'GET', params: { id: '@id' } }, + query: { method: 'GET', isArray: true, params: { filters: '@filters' } }, + create: { + method: 'POST', + params: { action: 'create' }, + headers: { + 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader, + version: '1.29', + }, + ignoreLoadingBar: true, + }, + update: { + method: 'POST', + params: { id: '@id', action: 'update', version: '@version', rollback: '@rollback' }, + headers: { + version: '1.29', + }, + }, + remove: { method: 'DELETE', params: { id: '@id' } }, + logs: { + method: 'GET', + params: { id: '@id', action: 'logs' }, + timeout: 4500, + ignoreLoadingBar: true, + transformResponse: logsHandler, + }, } - }, - remove: { method: 'DELETE', params: {id: '@id'} }, - logs: { - method: 'GET', params: { id: '@id', action: 'logs' }, - timeout: 4500, ignoreLoadingBar: true, - transformResponse: logsHandler - } - }); -}]); + ); + }, +]); diff --git a/app/docker/rest/swarm.js b/app/docker/rest/swarm.js index de4cc85a43c14..0cdc1fd9ad4f0 100644 --- a/app/docker/rest/swarm.js +++ b/app/docker/rest/swarm.js @@ -1,10 +1,17 @@ -angular.module('portainer.docker') -.factory('Swarm', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function SwarmFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/swarm', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Swarm', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function SwarmFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/swarm', + { + endpointId: EndpointProvider.endpointID, + }, + { + get: { method: 'GET' }, + } + ); }, - { - get: { method: 'GET' } - }); -}]); +]); diff --git a/app/docker/rest/system.js b/app/docker/rest/system.js index 844ae046f2f92..fa69df9528bb5 100644 --- a/app/docker/rest/system.js +++ b/app/docker/rest/system.js @@ -1,23 +1,36 @@ -import {jsonObjectsToArrayHandler} from './response/handlers'; +import { jsonObjectsToArrayHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('System', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'InfoInterceptor', 'VersionInterceptor', +angular.module('portainer.docker').factory('System', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'InfoInterceptor', + 'VersionInterceptor', function SystemFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, InfoInterceptor, VersionInterceptor) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/:action/:subAction', { - name: '@name', - endpointId: EndpointProvider.endpointID + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/:action/:subAction', + { + name: '@name', + endpointId: EndpointProvider.endpointID, + }, + { + info: { + method: 'GET', + params: { action: 'info' }, + timeout: 15000, + interceptor: InfoInterceptor, + }, + version: { method: 'GET', params: { action: 'version' }, timeout: 4500, interceptor: VersionInterceptor }, + events: { + method: 'GET', + params: { action: 'events', since: '@since', until: '@until' }, + isArray: true, + transformResponse: jsonObjectsToArrayHandler, + }, + auth: { method: 'POST', params: { action: 'auth' } }, + dataUsage: { method: 'GET', params: { action: 'system', subAction: 'df' } }, + } + ); }, - { - info: { - method: 'GET', params: { action: 'info' }, timeout: 15000, interceptor: InfoInterceptor - }, - version: { method: 'GET', params: { action: 'version' }, timeout: 4500, interceptor: VersionInterceptor }, - events: { - method: 'GET', params: { action: 'events', since: '@since', until: '@until' }, - isArray: true, transformResponse: jsonObjectsToArrayHandler - }, - auth: { method: 'POST', params: { action: 'auth' } }, - dataUsage: { method: 'GET', params: { action: 'system', subAction: 'df' } } - }); -}]); +]); diff --git a/app/docker/rest/systemEndpoint.js b/app/docker/rest/systemEndpoint.js index c54b8bda1164a..2bdad3df3b41d 100644 --- a/app/docker/rest/systemEndpoint.js +++ b/app/docker/rest/systemEndpoint.js @@ -1,13 +1,19 @@ -angular.module('portainer.docker') -.factory('SystemEndpoint', ['$resource', 'API_ENDPOINT_ENDPOINTS', +angular.module('portainer.docker').factory('SystemEndpoint', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', function SystemEndpointFactory($resource, API_ENDPOINT_ENDPOINTS) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/:action/:subAction', { - name: '@name' + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/:action/:subAction', + { + name: '@name', + }, + { + ping: { + method: 'GET', + params: { action: '_ping', endpointId: '@endpointId' }, + }, + } + ); }, - { - ping: { - method: 'GET', params: { action: '_ping', endpointId: '@endpointId' } - } - }); -}]); +]); diff --git a/app/docker/rest/task.js b/app/docker/rest/task.js index b4860784f9595..e2fa19603ada7 100644 --- a/app/docker/rest/task.js +++ b/app/docker/rest/task.js @@ -1,18 +1,27 @@ import { logsHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('Task', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', function TaskFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/tasks/:id/:action', { - endpointId: EndpointProvider.endpointID +angular.module('portainer.docker').factory('Task', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + function TaskFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/tasks/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + get: { method: 'GET', params: { id: '@id' } }, + query: { method: 'GET', isArray: true, params: { filters: '@filters' } }, + logs: { + method: 'GET', + params: { id: '@id', action: 'logs' }, + timeout: 4500, + ignoreLoadingBar: true, + transformResponse: logsHandler, + }, + } + ); }, - { - get: { method: 'GET', params: {id: '@id'} }, - query: { method: 'GET', isArray: true, params: {filters: '@filters'} }, - logs: { - method: 'GET', params: { id: '@id', action: 'logs' }, - timeout: 4500, ignoreLoadingBar: true, - transformResponse: logsHandler - } - }); -}]); +]); diff --git a/app/docker/rest/volume.js b/app/docker/rest/volume.js index 1a85ceef67d8b..2a6f441655ec0 100644 --- a/app/docker/rest/volume.js +++ b/app/docker/rest/volume.js @@ -1,19 +1,27 @@ -import {genericHandler} from './response/handlers'; +import { genericHandler } from './response/handlers'; -angular.module('portainer.docker') -.factory('Volume', ['$resource', 'API_ENDPOINT_ENDPOINTS', 'EndpointProvider', 'VolumesInterceptor', +angular.module('portainer.docker').factory('Volume', [ + '$resource', + 'API_ENDPOINT_ENDPOINTS', + 'EndpointProvider', + 'VolumesInterceptor', function VolumeFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, VolumesInterceptor) { - 'use strict'; - return $resource(API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/volumes/:id/:action', - { - endpointId: EndpointProvider.endpointID + 'use strict'; + return $resource( + API_ENDPOINT_ENDPOINTS + '/:endpointId/docker/volumes/:id/:action', + { + endpointId: EndpointProvider.endpointID, + }, + { + query: { method: 'GET', interceptor: VolumesInterceptor, timeout: 15000 }, + get: { method: 'GET', params: { id: '@id' } }, + create: { method: 'POST', params: { action: 'create' }, transformResponse: genericHandler, ignoreLoadingBar: true }, + remove: { + method: 'DELETE', + transformResponse: genericHandler, + params: { id: '@id' }, + }, + } + ); }, - { - query: { method: 'GET', interceptor: VolumesInterceptor, timeout: 15000}, - get: { method: 'GET', params: {id: '@id'} }, - create: {method: 'POST', params: {action: 'create'}, transformResponse: genericHandler, ignoreLoadingBar: true}, - remove: { - method: 'DELETE', transformResponse: genericHandler, params: {id: '@id'} - } - }); -}]); +]); diff --git a/app/docker/services/buildService.js b/app/docker/services/buildService.js index 53f50688e14e0..e9e5fc43311da 100644 --- a/app/docker/services/buildService.js +++ b/app/docker/services/buildService.js @@ -1,67 +1,71 @@ -import { ImageBuildModel } from "../models/image"; +import { ImageBuildModel } from '../models/image'; -angular.module('portainer.docker') -.factory('BuildService', ['$q', 'Build', 'FileUploadService', function BuildServiceFactory($q, Build, FileUploadService) { - 'use strict'; - var service = {}; +angular.module('portainer.docker').factory('BuildService', [ + '$q', + 'Build', + 'FileUploadService', + function BuildServiceFactory($q, Build, FileUploadService) { + 'use strict'; + var service = {}; - service.buildImageFromUpload = function(names, file, path) { - var deferred = $q.defer(); + service.buildImageFromUpload = function (names, file, path) { + var deferred = $q.defer(); - FileUploadService.buildImage(names, file, path) - .then(function success(response) { - var model = new ImageBuildModel(response.data); - deferred.resolve(model); - }) - .catch(function error(err) { - deferred.reject(err); - }); + FileUploadService.buildImage(names, file, path) + .then(function success(response) { + var model = new ImageBuildModel(response.data); + deferred.resolve(model); + }) + .catch(function error(err) { + deferred.reject(err); + }); - return deferred.promise; - }; - - service.buildImageFromURL = function(names, url, path) { - var params = { - t: names, - remote: url, - dockerfile: path + return deferred.promise; }; - var deferred = $q.defer(); + service.buildImageFromURL = function (names, url, path) { + var params = { + t: names, + remote: url, + dockerfile: path, + }; - Build.buildImage(params, {}).$promise - .then(function success(data) { - var model = new ImageBuildModel(data); - deferred.resolve(model); - }) - .catch(function error(err) { - deferred.reject(err); - }); + var deferred = $q.defer(); - return deferred.promise; - }; + Build.buildImage(params, {}) + .$promise.then(function success(data) { + var model = new ImageBuildModel(data); + deferred.resolve(model); + }) + .catch(function error(err) { + deferred.reject(err); + }); - service.buildImageFromDockerfileContent = function(names, content) { - var params = { - t: names - }; - var payload = { - content: content + return deferred.promise; }; - var deferred = $q.defer(); + service.buildImageFromDockerfileContent = function (names, content) { + var params = { + t: names, + }; + var payload = { + content: content, + }; - Build.buildImageOverride(params, payload).$promise - .then(function success(data) { - var model = new ImageBuildModel(data); - deferred.resolve(model); - }) - .catch(function error(err) { - deferred.reject(err); - }); + var deferred = $q.defer(); - return deferred.promise; - }; + Build.buildImageOverride(params, payload) + .$promise.then(function success(data) { + var model = new ImageBuildModel(data); + deferred.resolve(model); + }) + .catch(function error(err) { + deferred.reject(err); + }); + + return deferred.promise; + }; - return service; -}]); + return service; + }, +]); diff --git a/app/docker/services/configService.js b/app/docker/services/configService.js index 520e190a6a7e9..1d773eeaf07d3 100644 --- a/app/docker/services/configService.js +++ b/app/docker/services/configService.js @@ -1,63 +1,66 @@ import { ConfigViewModel } from '../models/config'; -angular.module('portainer.docker') -.factory('ConfigService', ['$q', 'Config', function ConfigServiceFactory($q, Config) { - 'use strict'; - var service = {}; - - service.config = function(configId) { - var deferred = $q.defer(); - - Config.get({id: configId}).$promise - .then(function success(data) { - var config = new ConfigViewModel(data); - deferred.resolve(config); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve config details', err: err }); - }); - - return deferred.promise; - }; - - service.configs = function() { - var deferred = $q.defer(); - - Config.query({}).$promise - .then(function success(data) { - var configs = data.map(function (item) { - return new ConfigViewModel(item); - }); - deferred.resolve(configs); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve configs', err: err }); - }); - - return deferred.promise; - }; - - service.remove = function(configId) { - var deferred = $q.defer(); - - Config.remove({ id: configId }).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message }); - } else { - deferred.resolve(); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to remove config', err: err }); - }); - - return deferred.promise; - }; - - service.create = function(config) { - return Config.create(config).$promise; - }; - - return service; -}]); +angular.module('portainer.docker').factory('ConfigService', [ + '$q', + 'Config', + function ConfigServiceFactory($q, Config) { + 'use strict'; + var service = {}; + + service.config = function (configId) { + var deferred = $q.defer(); + + Config.get({ id: configId }) + .$promise.then(function success(data) { + var config = new ConfigViewModel(data); + deferred.resolve(config); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve config details', err: err }); + }); + + return deferred.promise; + }; + + service.configs = function () { + var deferred = $q.defer(); + + Config.query({}) + .$promise.then(function success(data) { + var configs = data.map(function (item) { + return new ConfigViewModel(item); + }); + deferred.resolve(configs); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve configs', err: err }); + }); + + return deferred.promise; + }; + + service.remove = function (configId) { + var deferred = $q.defer(); + + Config.remove({ id: configId }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message }); + } else { + deferred.resolve(); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to remove config', err: err }); + }); + + return deferred.promise; + }; + + service.create = function (config) { + return Config.create(config).$promise; + }; + + return service; + }, +]); diff --git a/app/docker/services/containerService.js b/app/docker/services/containerService.js index 300a09f916a82..65a90ba728485 100644 --- a/app/docker/services/containerService.js +++ b/app/docker/services/containerService.js @@ -1,213 +1,217 @@ import { ContainerDetailsViewModel, ContainerViewModel, ContainerStatsViewModel } from '../models/container'; -angular.module('portainer.docker') -.factory('ContainerService', ['$q', 'Container', 'ResourceControlService', 'LogHelper', '$timeout', -function ContainerServiceFactory($q, Container, ResourceControlService, LogHelper, $timeout) { - 'use strict'; - var service = {}; - - service.container = function(id) { - var deferred = $q.defer(); - - Container.get({ id: id }).$promise - .then(function success(data) { - var container = new ContainerDetailsViewModel(data); - deferred.resolve(container); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve container information', err: err }); - }); - - return deferred.promise; - }; - - service.containers = function(all, filters) { - var deferred = $q.defer(); - Container.query({ all : all, filters: filters }).$promise - .then(function success(data) { - var containers = data.map(function (item) { - return new ContainerViewModel(item); - }); - deferred.resolve(containers); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve containers', err: err }); - }); - - return deferred.promise; - }; - - service.resizeTTY = function (id, width, height, timeout) { - var deferred = $q.defer(); - - $timeout(function() { - Container.resize({}, {id: id, height: height, width: width}).$promise - .then(function success(data) { +angular.module('portainer.docker').factory('ContainerService', [ + '$q', + 'Container', + 'ResourceControlService', + 'LogHelper', + '$timeout', + function ContainerServiceFactory($q, Container, ResourceControlService, LogHelper, $timeout) { + 'use strict'; + var service = {}; + + service.container = function (id) { + var deferred = $q.defer(); + + Container.get({ id: id }) + .$promise.then(function success(data) { + var container = new ContainerDetailsViewModel(data); + deferred.resolve(container); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve container information', err: err }); + }); + + return deferred.promise; + }; + + service.containers = function (all, filters) { + var deferred = $q.defer(); + Container.query({ all: all, filters: filters }) + .$promise.then(function success(data) { + var containers = data.map(function (item) { + return new ContainerViewModel(item); + }); + deferred.resolve(containers); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve containers', err: err }); + }); + + return deferred.promise; + }; + + service.resizeTTY = function (id, width, height, timeout) { + var deferred = $q.defer(); + + $timeout(function () { + Container.resize({}, { id: id, height: height, width: width }) + .$promise.then(function success(data) { if (data.message) { - deferred.reject({msg: 'Unable to resize tty of container ' + id, err: data.message}); + deferred.reject({ msg: 'Unable to resize tty of container ' + id, err: data.message }); } else { deferred.resolve(data); } }) .catch(function error(err) { - deferred.reject({msg: 'Unable to resize tty of container ' + id, err: err}); + deferred.reject({ msg: 'Unable to resize tty of container ' + id, err: err }); }); - }, timeout); - - return deferred.promise; - }; - - service.startContainer = function(id) { - return Container.start({ id: id }, {}).$promise; - }; - - service.stopContainer = function(id) { - return Container.stop({ id: id }, {}).$promise; - }; - - service.restartContainer = function(id) { - return Container.restart({ id: id }, {}).$promise; - }; - - service.killContainer = function(id) { - return Container.kill({ id: id }, {}).$promise; - }; - - service.pauseContainer = function(id) { - return Container.pause({ id: id }, {}).$promise; - }; - - service.resumeContainer = function(id) { - return Container.unpause({ id: id }, {}).$promise; - }; - - service.renameContainer = function(id, newContainerName) { - return Container.rename({id: id, name: newContainerName }, {}).$promise; - }; - - service.updateRestartPolicy = updateRestartPolicy; - - function updateRestartPolicy(id, restartPolicy, maximumRetryCounts) { - return Container.update({ id: id }, - { RestartPolicy: { Name: restartPolicy, MaximumRetryCount: maximumRetryCounts } } - ).$promise; - } - - service.createContainer = function(configuration) { - var deferred = $q.defer(); - Container.create(configuration).$promise - .then(function success(data) { - deferred.resolve(data); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to create container', err: err }); - }); - return deferred.promise; - }; - - service.createAndStartContainer = function(configuration) { - var deferred = $q.defer(); - var container; - service.createContainer(configuration) - .then(function success(data) { - container = data; - return service.startContainer(container.Id); - }) - .then(function success() { - deferred.resolve(container); - }) - .catch(function error(err) { - deferred.reject(err); - }); - return deferred.promise; - }; - - service.remove = function(container, removeVolumes) { - var deferred = $q.defer(); - - Container.remove({ id: container.Id, v: (removeVolumes) ? 1 : 0, force: true }).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message, err: data.message }); - } else { - deferred.resolve(); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to remove container', err: err }); - }); - - return deferred.promise; - }; - - service.createExec = function(execConfig) { - var deferred = $q.defer(); - - Container.exec({}, execConfig).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message, err: data.message }); - } else { - deferred.resolve(data); - } - }) - .catch(function error(err) { - deferred.reject(err); - }); - - return deferred.promise; - }; - - service.logs = function(id, stdout, stderr, timestamps, since, tail, stripHeaders) { - var deferred = $q.defer(); - - var parameters = { - id: id, - stdout: stdout || 0, - stderr: stderr || 0, - timestamps: timestamps || 0, - since: since || 0, - tail: tail || 'all' - }; - - Container.logs(parameters).$promise - .then(function success(data) { - var logs = LogHelper.formatLogs(data.logs, stripHeaders); - deferred.resolve(logs); - }) - .catch(function error(err) { - deferred.reject(err); - }); - - return deferred.promise; - }; - - service.containerStats = function(id) { - var deferred = $q.defer(); - - Container.stats({ id: id }).$promise - .then(function success(data) { - var containerStats = new ContainerStatsViewModel(data); - deferred.resolve(containerStats); - }) - .catch(function error(err) { - deferred.reject(err); - }); - - return deferred.promise; - }; - - service.containerTop = function(id) { - return Container.top({ id: id }).$promise; - }; - - service.inspect = function(id) { - return Container.inspect({ id: id }).$promise; - }; - - service.prune = function(filters) { - return Container.prune({ filters: filters }).$promise; - }; - - return service; -}]); + }, timeout); + + return deferred.promise; + }; + + service.startContainer = function (id) { + return Container.start({ id: id }, {}).$promise; + }; + + service.stopContainer = function (id) { + return Container.stop({ id: id }, {}).$promise; + }; + + service.restartContainer = function (id) { + return Container.restart({ id: id }, {}).$promise; + }; + + service.killContainer = function (id) { + return Container.kill({ id: id }, {}).$promise; + }; + + service.pauseContainer = function (id) { + return Container.pause({ id: id }, {}).$promise; + }; + + service.resumeContainer = function (id) { + return Container.unpause({ id: id }, {}).$promise; + }; + + service.renameContainer = function (id, newContainerName) { + return Container.rename({ id: id, name: newContainerName }, {}).$promise; + }; + + service.updateRestartPolicy = updateRestartPolicy; + + function updateRestartPolicy(id, restartPolicy, maximumRetryCounts) { + return Container.update({ id: id }, { RestartPolicy: { Name: restartPolicy, MaximumRetryCount: maximumRetryCounts } }).$promise; + } + + service.createContainer = function (configuration) { + var deferred = $q.defer(); + Container.create(configuration) + .$promise.then(function success(data) { + deferred.resolve(data); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to create container', err: err }); + }); + return deferred.promise; + }; + + service.createAndStartContainer = function (configuration) { + var deferred = $q.defer(); + var container; + service + .createContainer(configuration) + .then(function success(data) { + container = data; + return service.startContainer(container.Id); + }) + .then(function success() { + deferred.resolve(container); + }) + .catch(function error(err) { + deferred.reject(err); + }); + return deferred.promise; + }; + + service.remove = function (container, removeVolumes) { + var deferred = $q.defer(); + + Container.remove({ id: container.Id, v: removeVolumes ? 1 : 0, force: true }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message, err: data.message }); + } else { + deferred.resolve(); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to remove container', err: err }); + }); + + return deferred.promise; + }; + + service.createExec = function (execConfig) { + var deferred = $q.defer(); + + Container.exec({}, execConfig) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message, err: data.message }); + } else { + deferred.resolve(data); + } + }) + .catch(function error(err) { + deferred.reject(err); + }); + + return deferred.promise; + }; + + service.logs = function (id, stdout, stderr, timestamps, since, tail, stripHeaders) { + var deferred = $q.defer(); + + var parameters = { + id: id, + stdout: stdout || 0, + stderr: stderr || 0, + timestamps: timestamps || 0, + since: since || 0, + tail: tail || 'all', + }; + + Container.logs(parameters) + .$promise.then(function success(data) { + var logs = LogHelper.formatLogs(data.logs, stripHeaders); + deferred.resolve(logs); + }) + .catch(function error(err) { + deferred.reject(err); + }); + + return deferred.promise; + }; + + service.containerStats = function (id) { + var deferred = $q.defer(); + + Container.stats({ id: id }) + .$promise.then(function success(data) { + var containerStats = new ContainerStatsViewModel(data); + deferred.resolve(containerStats); + }) + .catch(function error(err) { + deferred.reject(err); + }); + + return deferred.promise; + }; + + service.containerTop = function (id) { + return Container.top({ id: id }).$promise; + }; + + service.inspect = function (id) { + return Container.inspect({ id: id }).$promise; + }; + + service.prune = function (filters) { + return Container.prune({ filters: filters }).$promise; + }; + + return service; + }, +]); diff --git a/app/docker/services/execService.js b/app/docker/services/execService.js index bcb09b77cc9be..a6a29490fd417 100644 --- a/app/docker/services/execService.js +++ b/app/docker/services/execService.js @@ -1,27 +1,31 @@ -angular.module('portainer.docker') - .factory('ExecService', ['$q', '$timeout', 'Exec', function ExecServiceFactory($q, $timeout, Exec) { - 'use strict'; - var service = {}; +angular.module('portainer.docker').factory('ExecService', [ + '$q', + '$timeout', + 'Exec', + function ExecServiceFactory($q, $timeout, Exec) { + 'use strict'; + var service = {}; - service.resizeTTY = function (execId, width, height, timeout) { - var deferred = $q.defer(); + service.resizeTTY = function (execId, width, height, timeout) { + var deferred = $q.defer(); - $timeout(function() { - Exec.resize({}, {id: execId, height: height, width: width}).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({msg: "Unable to resize tty of exec", err: data.message}); - } else { - deferred.resolve(data); - } - }) - .catch(function error(err) { - deferred.reject({msg: "Unable to resize tty of exec", err: err}); - }); - }, timeout); + $timeout(function () { + Exec.resize({}, { id: execId, height: height, width: width }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: 'Unable to resize tty of exec', err: data.message }); + } else { + deferred.resolve(data); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to resize tty of exec', err: err }); + }); + }, timeout); - return deferred.promise; - }; + return deferred.promise; + }; - return service; - }]); + return service; + }, +]); diff --git a/app/docker/services/imageService.js b/app/docker/services/imageService.js index afd70aff3af6b..56a13983e5aa5 100644 --- a/app/docker/services/imageService.js +++ b/app/docker/services/imageService.js @@ -1,205 +1,212 @@ import _ from 'lodash-es'; import { ImageViewModel } from '../models/image'; -import { ImageDetailsViewModel } from "../models/imageDetails"; -import { ImageLayerViewModel } from "../models/imageLayer"; - -angular.module('portainer.docker') -.factory('ImageService', ['$q', 'Image', 'ImageHelper', 'RegistryService', 'HttpRequestHelper', 'ContainerService', 'FileUploadService', +import { ImageDetailsViewModel } from '../models/imageDetails'; +import { ImageLayerViewModel } from '../models/imageLayer'; + +angular.module('portainer.docker').factory('ImageService', [ + '$q', + 'Image', + 'ImageHelper', + 'RegistryService', + 'HttpRequestHelper', + 'ContainerService', + 'FileUploadService', function ImageServiceFactory($q, Image, ImageHelper, RegistryService, HttpRequestHelper, ContainerService, FileUploadService) { - 'use strict'; - var service = {}; - - service.image = function(imageId) { - var deferred = $q.defer(); - Image.get({id: imageId}).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message }); - } else { - var image = new ImageDetailsViewModel(data); - deferred.resolve(image); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve image details', err: err }); - }); - return deferred.promise; - }; - - service.images = function(withUsage) { - var deferred = $q.defer(); - - $q.all({ - containers: withUsage ? ContainerService.containers(1) : [], - images: Image.query({}).$promise - }) - .then(function success(data) { - var containers = data.containers; - - var images = data.images.map(function(item) { - item.ContainerCount = 0; - for (var i = 0; i < containers.length; i++) { - var container = containers[i]; - if (container.ImageID === item.Id) { - item.ContainerCount++; + 'use strict'; + var service = {}; + + service.image = function (imageId) { + var deferred = $q.defer(); + Image.get({ id: imageId }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message }); + } else { + var image = new ImageDetailsViewModel(data); + deferred.resolve(image); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve image details', err: err }); + }); + return deferred.promise; + }; + + service.images = function (withUsage) { + var deferred = $q.defer(); + + $q.all({ + containers: withUsage ? ContainerService.containers(1) : [], + images: Image.query({}).$promise, + }) + .then(function success(data) { + var containers = data.containers; + + var images = data.images.map(function (item) { + item.ContainerCount = 0; + for (var i = 0; i < containers.length; i++) { + var container = containers[i]; + if (container.ImageID === item.Id) { + item.ContainerCount++; + } + } + return new ImageViewModel(item); + }); + + deferred.resolve(images); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve images', err: err }); + }); + return deferred.promise; + }; + + service.history = function (imageId) { + var deferred = $q.defer(); + Image.history({ id: imageId }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message }); + } else { + var layers = []; + var order = data.length; + angular.forEach(data, function (imageLayer) { + layers.push(new ImageLayerViewModel(order, imageLayer)); + order--; + }); + deferred.resolve(layers); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve image details', err: err }); + }); + return deferred.promise; + }; + + service.pushImage = pushImage; + /** + * + * @param {PorImageRegistryModel} registryModel + */ + function pushImage(registryModel) { + var deferred = $q.defer(); + + var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : ''; + HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); + + const imageConfiguration = ImageHelper.createImageConfigForContainer(registryModel); + + Image.push({ imageName: imageConfiguration.fromImage }) + .$promise.then(function success(data) { + if (data[data.length - 1].error) { + deferred.reject({ msg: data[data.length - 1].error }); + } else { + deferred.resolve(); } - } - return new ImageViewModel(item); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to push image tag', err: err }); + }); + return deferred.promise; + } + + /** + * PULL IMAGE + */ + + function pullImageAndIgnoreErrors(imageConfiguration) { + var deferred = $q.defer(); + + Image.create({}, imageConfiguration).$promise.finally(function final() { + deferred.resolve(); }); - deferred.resolve(images); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve images', err: err }); - }); - return deferred.promise; - }; - - service.history = function(imageId) { - var deferred = $q.defer(); - Image.history({id: imageId}).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message }); - } else { - var layers = []; - var order = data.length; - angular.forEach(data, function(imageLayer) { - layers.push(new ImageLayerViewModel(order, imageLayer)); - order--; + return deferred.promise; + } + + function pullImageAndAcknowledgeErrors(imageConfiguration) { + var deferred = $q.defer(); + + Image.create({}, imageConfiguration) + .$promise.then(function success(data) { + var err = data.length > 0 && data[data.length - 1].hasOwnProperty('message'); + if (err) { + var detail = data[data.length - 1]; + deferred.reject({ msg: detail.message }); + } else { + deferred.resolve(data); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to pull image', err: err }); }); - deferred.resolve(layers); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve image details', err: err }); - }); - return deferred.promise; - }; - - service.pushImage = pushImage; - /** - * - * @param {PorImageRegistryModel} registryModel - */ - function pushImage(registryModel) { - var deferred = $q.defer(); - - var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : ''; - HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); - - const imageConfiguration = ImageHelper.createImageConfigForContainer(registryModel); - - Image.push({imageName: imageConfiguration.fromImage}).$promise - .then(function success(data) { - if (data[data.length - 1].error) { - deferred.reject({ msg: data[data.length - 1].error }); - } else { - deferred.resolve(); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to push image tag', err: err }); - }); - return deferred.promise; - } - - /** - * PULL IMAGE - */ - - function pullImageAndIgnoreErrors(imageConfiguration) { - var deferred = $q.defer(); - - Image.create({}, imageConfiguration).$promise - .finally(function final() { - deferred.resolve(); - }); - - return deferred.promise; - } - - function pullImageAndAcknowledgeErrors(imageConfiguration) { - var deferred = $q.defer(); - - Image.create({}, imageConfiguration).$promise - .then(function success(data) { - var err = data.length > 0 && data[data.length - 1].hasOwnProperty('message'); - if (err) { - var detail = data[data.length - 1]; - deferred.reject({ msg: detail.message }); - } else { - deferred.resolve(data); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to pull image', err: err }); - }); - return deferred.promise; - } + return deferred.promise; + } - service.pullImage = pullImage; + service.pullImage = pullImage; - /** - * - * @param {PorImageRegistryModel} registry - * @param {bool} ignoreErrors - */ - function pullImage(registry, ignoreErrors) { - var authenticationDetails = registry.Registry.Authentication ? RegistryService.encodedCredentials(registry.Registry) : ''; - HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); + /** + * + * @param {PorImageRegistryModel} registry + * @param {bool} ignoreErrors + */ + function pullImage(registry, ignoreErrors) { + var authenticationDetails = registry.Registry.Authentication ? RegistryService.encodedCredentials(registry.Registry) : ''; + HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); - var imageConfiguration = ImageHelper.createImageConfigForContainer(registry); + var imageConfiguration = ImageHelper.createImageConfigForContainer(registry); - if (ignoreErrors) { - return pullImageAndIgnoreErrors(imageConfiguration); - } - return pullImageAndAcknowledgeErrors(imageConfiguration); - } - - /** - * ! PULL IMAGE - */ - - service.tagImage = function(id, image) { - return Image.tag({id: id, repo: image}).$promise; - }; - - service.downloadImages = function(images) { - var names = ImageHelper.getImagesNamesForDownload(images); - return Image.download(names).$promise; - }; - - service.uploadImage = function(file) { - return FileUploadService.loadImages(file); - }; - - service.deleteImage = function(id, forceRemoval) { - var deferred = $q.defer(); - Image.remove({id: id, force: forceRemoval}).$promise - .then(function success(data) { - if (data[0].message) { - deferred.reject({ msg: data[0].message }); - } else { - deferred.resolve(); + if (ignoreErrors) { + return pullImageAndIgnoreErrors(imageConfiguration); } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to remove image', err: err }); - }); - return deferred.promise; - }; - - service.getUniqueTagListFromImages = function (availableImages) { - - return _.uniq(_.flatMap(availableImages, function (image) { - _.remove(image.RepoTags, function (item) { - return item.indexOf('') !== -1; - }); - return image.RepoTags ? _.uniqWith(image.RepoTags, _.isEqual) : []; - })); - }; + return pullImageAndAcknowledgeErrors(imageConfiguration); + } - return service; -}]); + /** + * ! PULL IMAGE + */ + + service.tagImage = function (id, image) { + return Image.tag({ id: id, repo: image }).$promise; + }; + + service.downloadImages = function (images) { + var names = ImageHelper.getImagesNamesForDownload(images); + return Image.download(names).$promise; + }; + + service.uploadImage = function (file) { + return FileUploadService.loadImages(file); + }; + + service.deleteImage = function (id, forceRemoval) { + var deferred = $q.defer(); + Image.remove({ id: id, force: forceRemoval }) + .$promise.then(function success(data) { + if (data[0].message) { + deferred.reject({ msg: data[0].message }); + } else { + deferred.resolve(); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to remove image', err: err }); + }); + return deferred.promise; + }; + + service.getUniqueTagListFromImages = function (availableImages) { + return _.uniq( + _.flatMap(availableImages, function (image) { + _.remove(image.RepoTags, function (item) { + return item.indexOf('') !== -1; + }); + return image.RepoTags ? _.uniqWith(image.RepoTags, _.isEqual) : []; + }) + ); + }; + + return service; + }, +]); diff --git a/app/docker/services/networkService.js b/app/docker/services/networkService.js index f8400b693e4a9..3804aaeed0ccd 100644 --- a/app/docker/services/networkService.js +++ b/app/docker/services/networkService.js @@ -1,87 +1,92 @@ import { NetworkViewModel } from '../models/network'; -angular.module('portainer.docker') -.factory('NetworkService', ['$q', 'Network', function NetworkServiceFactory($q, Network) { - 'use strict'; - var service = {}; +angular.module('portainer.docker').factory('NetworkService', [ + '$q', + 'Network', + function NetworkServiceFactory($q, Network) { + 'use strict'; + var service = {}; - service.create = function(networkConfiguration) { - var deferred = $q.defer(); + service.create = function (networkConfiguration) { + var deferred = $q.defer(); - Network.create(networkConfiguration).$promise - .then(function success(data) { - deferred.resolve(data); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to create network', err: err }); - }); - return deferred.promise; - }; - - service.network = function(id) { - var deferred = $q.defer(); + Network.create(networkConfiguration) + .$promise.then(function success(data) { + deferred.resolve(data); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to create network', err: err }); + }); + return deferred.promise; + }; - Network.get({ id: id }).$promise - .then(function success(data) { - var network = new NetworkViewModel(data); - deferred.resolve(network); - }) - .catch(function error(err) { - deferred.reject({msg: 'Unable to retrieve network details', err: err}); - }); + service.network = function (id) { + var deferred = $q.defer(); - return deferred.promise; - }; + Network.get({ id: id }) + .$promise.then(function success(data) { + var network = new NetworkViewModel(data); + deferred.resolve(network); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve network details', err: err }); + }); - service.networks = function(localNetworks, swarmNetworks, swarmAttachableNetworks, filters) { - var deferred = $q.defer(); + return deferred.promise; + }; - Network.query({ filters: filters }).$promise - .then(function success(data) { - var networks = data; + service.networks = function (localNetworks, swarmNetworks, swarmAttachableNetworks, filters) { + var deferred = $q.defer(); - var filteredNetworks = networks.filter(function(network) { - if (localNetworks && network.Scope === 'local') { - return network; - } - if (swarmNetworks && network.Scope === 'swarm') { - return network; - } - if (swarmAttachableNetworks && network.Scope === 'swarm' && network.Attachable === true) { - return network; - } - }).map(function (item) { - return new NetworkViewModel(item); - }); + Network.query({ filters: filters }) + .$promise.then(function success(data) { + var networks = data; - deferred.resolve(filteredNetworks); - }) - .catch(function error(err) { - deferred.reject({msg: 'Unable to retrieve networks', err: err}); - }); + var filteredNetworks = networks + .filter(function (network) { + if (localNetworks && network.Scope === 'local') { + return network; + } + if (swarmNetworks && network.Scope === 'swarm') { + return network; + } + if (swarmAttachableNetworks && network.Scope === 'swarm' && network.Attachable === true) { + return network; + } + }) + .map(function (item) { + return new NetworkViewModel(item); + }); - return deferred.promise; - }; + deferred.resolve(filteredNetworks); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve networks', err: err }); + }); - service.remove = function(id) { - return Network.remove({ id: id }).$promise; - }; + return deferred.promise; + }; - service.disconnectContainer = function(networkId, containerId, force) { - return Network.disconnect({ id: networkId }, { Container: containerId, Force: force }).$promise; - }; + service.remove = function (id) { + return Network.remove({ id: id }).$promise; + }; - service.connectContainer = function(networkId, containerId, aliases) { - var payload = { - Container: containerId, + service.disconnectContainer = function (networkId, containerId, force) { + return Network.disconnect({ id: networkId }, { Container: containerId, Force: force }).$promise; }; - if (aliases) { - payload.EndpointConfig = { - Aliases: aliases, + + service.connectContainer = function (networkId, containerId, aliases) { + var payload = { + Container: containerId, + }; + if (aliases) { + payload.EndpointConfig = { + Aliases: aliases, + }; } - } - return Network.connect({ id: networkId }, payload).$promise; - }; + return Network.connect({ id: networkId }, payload).$promise; + }; - return service; -}]); + return service; + }, +]); diff --git a/app/docker/services/nodeService.js b/app/docker/services/nodeService.js index 943f4201d7b65..42ddf7ed82082 100644 --- a/app/docker/services/nodeService.js +++ b/app/docker/services/nodeService.js @@ -1,7 +1,8 @@ import { NodeViewModel } from '../models/node'; angular.module('portainer.docker').factory('NodeService', [ - '$q', 'Node', + '$q', + 'Node', function NodeServiceFactory($q, Node) { 'use strict'; var service = {}; @@ -30,7 +31,7 @@ angular.module('portainer.docker').factory('NodeService', [ Node.query({}) .$promise.then(function success(data) { - var nodes = data.map(function(item) { + var nodes = data.map(function (item) { return new NodeViewModel(item); }); deferred.resolve(nodes); @@ -49,7 +50,8 @@ angular.module('portainer.docker').factory('NodeService', [ function getActiveManager() { var deferred = $q.defer(); - service.nodes() + service + .nodes() .then(function success(data) { for (var i = 0; i < data.length; ++i) { var node = data[i]; @@ -67,5 +69,5 @@ angular.module('portainer.docker').factory('NodeService', [ } return service; - } + }, ]); diff --git a/app/docker/services/pluginService.js b/app/docker/services/pluginService.js index 49c6d2afb0109..cdf22b90a8385 100644 --- a/app/docker/services/pluginService.js +++ b/app/docker/services/pluginService.js @@ -1,72 +1,76 @@ import _ from 'lodash-es'; -import { PluginViewModel } from "../models/plugin"; +import { PluginViewModel } from '../models/plugin'; -angular.module('portainer.docker') -.factory('PluginService', ['$q', 'Plugin', 'SystemService', function PluginServiceFactory($q, Plugin, SystemService) { - 'use strict'; - var service = {}; +angular.module('portainer.docker').factory('PluginService', [ + '$q', + 'Plugin', + 'SystemService', + function PluginServiceFactory($q, Plugin, SystemService) { + 'use strict'; + var service = {}; - service.plugins = function() { - var deferred = $q.defer(); - var plugins = []; + service.plugins = function () { + var deferred = $q.defer(); + var plugins = []; - Plugin.query({}).$promise - .then(function success(data) { - for (var i = 0; i < data.length; i++) { - var plugin = new PluginViewModel(data[i]); - plugins.push(plugin); - } - }) - .finally(function final() { - deferred.resolve(plugins); - }); + Plugin.query({}) + .$promise.then(function success(data) { + for (var i = 0; i < data.length; i++) { + var plugin = new PluginViewModel(data[i]); + plugins.push(plugin); + } + }) + .finally(function final() { + deferred.resolve(plugins); + }); - return deferred.promise; - }; + return deferred.promise; + }; - function servicePlugins(systemOnly, pluginType, pluginVersion) { - var deferred = $q.defer(); + function servicePlugins(systemOnly, pluginType, pluginVersion) { + var deferred = $q.defer(); - $q.all({ - system: SystemService.plugins(), - plugins: systemOnly ? [] : service.plugins() - }) - .then(function success(data) { - var aggregatedPlugins = []; - var systemPlugins = data.system; - var plugins = data.plugins; + $q.all({ + system: SystemService.plugins(), + plugins: systemOnly ? [] : service.plugins(), + }) + .then(function success(data) { + var aggregatedPlugins = []; + var systemPlugins = data.system; + var plugins = data.plugins; - if (systemPlugins[pluginType]) { - aggregatedPlugins = aggregatedPlugins.concat(systemPlugins[pluginType]); - } + if (systemPlugins[pluginType]) { + aggregatedPlugins = aggregatedPlugins.concat(systemPlugins[pluginType]); + } - for (var i = 0; i < plugins.length; i++) { - var plugin = plugins[i]; - if (plugin.Enabled && _.includes(plugin.Config.Interface.Types, pluginVersion)) { - aggregatedPlugins.push(plugin.Name); - } - } + for (var i = 0; i < plugins.length; i++) { + var plugin = plugins[i]; + if (plugin.Enabled && _.includes(plugin.Config.Interface.Types, pluginVersion)) { + aggregatedPlugins.push(plugin.Name); + } + } - deferred.resolve(aggregatedPlugins); - }) - .catch(function error(err) { - deferred.reject({ msg: err.msg, err: err }); - }); + deferred.resolve(aggregatedPlugins); + }) + .catch(function error(err) { + deferred.reject({ msg: err.msg, err: err }); + }); - return deferred.promise; - } + return deferred.promise; + } - service.volumePlugins = function(systemOnly) { - return servicePlugins(systemOnly, 'Volume', 'docker.volumedriver/1.0'); - }; + service.volumePlugins = function (systemOnly) { + return servicePlugins(systemOnly, 'Volume', 'docker.volumedriver/1.0'); + }; - service.networkPlugins = function(systemOnly) { - return servicePlugins(systemOnly, 'Network', 'docker.networkdriver/1.0'); - }; + service.networkPlugins = function (systemOnly) { + return servicePlugins(systemOnly, 'Network', 'docker.networkdriver/1.0'); + }; - service.loggingPlugins = function(systemOnly) { - return servicePlugins(systemOnly, 'Log', 'docker.logdriver/1.0'); - }; + service.loggingPlugins = function (systemOnly) { + return servicePlugins(systemOnly, 'Log', 'docker.logdriver/1.0'); + }; - return service; -}]); + return service; + }, +]); diff --git a/app/docker/services/secretService.js b/app/docker/services/secretService.js index f1249492974b8..40268c3fbc5e1 100644 --- a/app/docker/services/secretService.js +++ b/app/docker/services/secretService.js @@ -1,63 +1,66 @@ import { SecretViewModel } from '../models/secret'; -angular.module('portainer.docker') -.factory('SecretService', ['$q', 'Secret', function SecretServiceFactory($q, Secret) { - 'use strict'; - var service = {}; - - service.secret = function(secretId) { - var deferred = $q.defer(); - - Secret.get({id: secretId}).$promise - .then(function success(data) { - var secret = new SecretViewModel(data); - deferred.resolve(secret); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve secret details', err: err }); - }); - - return deferred.promise; - }; - - service.secrets = function() { - var deferred = $q.defer(); - - Secret.query({}).$promise - .then(function success(data) { - var secrets = data.map(function (item) { - return new SecretViewModel(item); - }); - deferred.resolve(secrets); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve secrets', err: err }); - }); - - return deferred.promise; - }; - - service.remove = function(secretId) { - var deferred = $q.defer(); - - Secret.remove({ id: secretId }).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message }); - } else { - deferred.resolve(); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to remove secret', err: err }); - }); - - return deferred.promise; - }; - - service.create = function(secretConfig) { - return Secret.create(secretConfig).$promise; - }; - - return service; -}]); +angular.module('portainer.docker').factory('SecretService', [ + '$q', + 'Secret', + function SecretServiceFactory($q, Secret) { + 'use strict'; + var service = {}; + + service.secret = function (secretId) { + var deferred = $q.defer(); + + Secret.get({ id: secretId }) + .$promise.then(function success(data) { + var secret = new SecretViewModel(data); + deferred.resolve(secret); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve secret details', err: err }); + }); + + return deferred.promise; + }; + + service.secrets = function () { + var deferred = $q.defer(); + + Secret.query({}) + .$promise.then(function success(data) { + var secrets = data.map(function (item) { + return new SecretViewModel(item); + }); + deferred.resolve(secrets); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve secrets', err: err }); + }); + + return deferred.promise; + }; + + service.remove = function (secretId) { + var deferred = $q.defer(); + + Secret.remove({ id: secretId }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message }); + } else { + deferred.resolve(); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to remove secret', err: err }); + }); + + return deferred.promise; + }; + + service.create = function (secretConfig) { + return Secret.create(secretConfig).$promise; + }; + + return service; + }, +]); diff --git a/app/docker/services/serviceService.js b/app/docker/services/serviceService.js index 95f16d2140d65..d2caa7e69ea43 100644 --- a/app/docker/services/serviceService.js +++ b/app/docker/services/serviceService.js @@ -1,98 +1,103 @@ import { ServiceViewModel } from '../models/service'; +angular.module('portainer.docker').factory('ServiceService', [ + '$q', + 'Service', + 'ServiceHelper', + 'TaskService', + 'ResourceControlService', + 'LogHelper', + function ServiceServiceFactory($q, Service, ServiceHelper, TaskService, ResourceControlService, LogHelper) { + 'use strict'; + var service = {}; + + service.services = function (filters) { + var deferred = $q.defer(); + + Service.query({ filters: filters ? filters : {} }) + .$promise.then(function success(data) { + var services = data.map(function (item) { + return new ServiceViewModel(item); + }); + deferred.resolve(services); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve services', err: err }); + }); + + return deferred.promise; + }; + + service.service = function (id) { + var deferred = $q.defer(); -angular.module('portainer.docker') -.factory('ServiceService', ['$q', 'Service', 'ServiceHelper', 'TaskService', 'ResourceControlService', 'LogHelper', -function ServiceServiceFactory($q, Service, ServiceHelper, TaskService, ResourceControlService, LogHelper) { - 'use strict'; - var service = {}; + Service.get({ id: id }) + .$promise.then(function success(data) { + var service = new ServiceViewModel(data); + deferred.resolve(service); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve service details', err: err }); + }); - service.services = function(filters) { - var deferred = $q.defer(); + return deferred.promise; + }; + + service.remove = function (service) { + var deferred = $q.defer(); + + Service.remove({ id: service.Id }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message, err: data.message }); + } else { + deferred.resolve(); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to remove service', err: err }); + }); + + return deferred.promise; + }; - Service.query({ filters: filters ? filters : {} }).$promise - .then(function success(data) { - var services = data.map(function (item) { - return new ServiceViewModel(item); + service.update = function (serv, config, rollback) { + return service.service(serv.Id).then((data) => { + const params = { + id: serv.Id, + version: data.Version, + }; + if (rollback) { + params.rollback = rollback; + } + return Service.update(params, config).$promise; }); - deferred.resolve(services); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve services', err: err }); - }); - - return deferred.promise; - }; - - service.service = function(id) { - var deferred = $q.defer(); - - Service.get({ id: id }).$promise - .then(function success(data) { - var service = new ServiceViewModel(data); - deferred.resolve(service); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve service details', err: err }); - }); - - return deferred.promise; - }; - - service.remove = function(service) { - var deferred = $q.defer(); - - Service.remove({id: service.Id}).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message, err: data.message }); - } else { - deferred.resolve(); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to remove service', err: err }); - }); - - return deferred.promise; - }; - - service.update = function(serv, config, rollback) { - return service.service(serv.Id).then((data) => { - const params = { - id: serv.Id, - version: data.Version - }; - if (rollback) { - params.rollback = rollback - } - return Service.update(params, config).$promise; - }); - }; - - service.logs = function(id, stdout, stderr, timestamps, since, tail) { - var deferred = $q.defer(); - - var parameters = { - id: id, - stdout: stdout || 0, - stderr: stderr || 0, - timestamps: timestamps || 0, - since: since || 0, - tail: tail || 'all' }; - Service.logs(parameters).$promise - .then(function success(data) { - var logs = LogHelper.formatLogs(data.logs, true); - deferred.resolve(logs); - }) - .catch(function error(err) { - deferred.reject(err); - }); + service.logs = function (id, stdout, stderr, timestamps, since, tail) { + var deferred = $q.defer(); + + var parameters = { + id: id, + stdout: stdout || 0, + stderr: stderr || 0, + timestamps: timestamps || 0, + since: since || 0, + tail: tail || 'all', + }; - return deferred.promise; - }; + Service.logs(parameters) + .$promise.then(function success(data) { + var logs = LogHelper.formatLogs(data.logs, true); + deferred.resolve(logs); + }) + .catch(function error(err) { + deferred.reject(err); + }); + + return deferred.promise; + }; - return service; -}]); + return service; + }, +]); diff --git a/app/docker/services/swarmService.js b/app/docker/services/swarmService.js index 308163ef55f14..9ad18d2125b7f 100644 --- a/app/docker/services/swarmService.js +++ b/app/docker/services/swarmService.js @@ -1,24 +1,27 @@ import { SwarmViewModel } from '../models/swarm'; -angular.module('portainer.docker') -.factory('SwarmService', ['$q', 'Swarm', function SwarmServiceFactory($q, Swarm) { - 'use strict'; - var service = {}; +angular.module('portainer.docker').factory('SwarmService', [ + '$q', + 'Swarm', + function SwarmServiceFactory($q, Swarm) { + 'use strict'; + var service = {}; - service.swarm = function() { - var deferred = $q.defer(); + service.swarm = function () { + var deferred = $q.defer(); - Swarm.get().$promise - .then(function success(data) { - var swarm = new SwarmViewModel(data); - deferred.resolve(swarm); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve Swarm details', err: err }); - }); + Swarm.get() + .$promise.then(function success(data) { + var swarm = new SwarmViewModel(data); + deferred.resolve(swarm); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve Swarm details', err: err }); + }); - return deferred.promise; - }; + return deferred.promise; + }; - return service; -}]); + return service; + }, +]); diff --git a/app/docker/services/systemService.js b/app/docker/services/systemService.js index 9e89300ce355b..90ac1168eb4f2 100644 --- a/app/docker/services/systemService.js +++ b/app/docker/services/systemService.js @@ -1,55 +1,59 @@ -import {EventViewModel} from '../models/event'; - -angular.module('portainer.docker') -.factory('SystemService', ['$q', 'System', 'SystemEndpoint', function SystemServiceFactory($q, System, SystemEndpoint) { - 'use strict'; - var service = {}; - - service.plugins = function() { - var deferred = $q.defer(); - System.info({}).$promise - .then(function success(data) { - var plugins = data.Plugins; - deferred.resolve(plugins); - }) - .catch(function error(err) { - deferred.reject({msg: 'Unable to retrieve plugins information from system', err: err}); - }); - return deferred.promise; - }; - - service.info = function() { - return System.info({}).$promise; - }; - - service.ping = function(endpointId) { - return SystemEndpoint.ping({endpointId: endpointId}).$promise; - }; - - service.version = function() { - return System.version({}).$promise; - }; - - service.events = function(from, to) { - var deferred = $q.defer(); - - System.events({since: from, until: to}).$promise - .then(function success(data) { - var events = data.map(function (item) { - return new EventViewModel(item); - }); - deferred.resolve(events); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve engine events', err: err }); - }); - - return deferred.promise; - }; - - service.dataUsage = function () { - return System.dataUsage().$promise; - }; - - return service; -}]); +import { EventViewModel } from '../models/event'; + +angular.module('portainer.docker').factory('SystemService', [ + '$q', + 'System', + 'SystemEndpoint', + function SystemServiceFactory($q, System, SystemEndpoint) { + 'use strict'; + var service = {}; + + service.plugins = function () { + var deferred = $q.defer(); + System.info({}) + .$promise.then(function success(data) { + var plugins = data.Plugins; + deferred.resolve(plugins); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve plugins information from system', err: err }); + }); + return deferred.promise; + }; + + service.info = function () { + return System.info({}).$promise; + }; + + service.ping = function (endpointId) { + return SystemEndpoint.ping({ endpointId: endpointId }).$promise; + }; + + service.version = function () { + return System.version({}).$promise; + }; + + service.events = function (from, to) { + var deferred = $q.defer(); + + System.events({ since: from, until: to }) + .$promise.then(function success(data) { + var events = data.map(function (item) { + return new EventViewModel(item); + }); + deferred.resolve(events); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve engine events', err: err }); + }); + + return deferred.promise; + }; + + service.dataUsage = function () { + return System.dataUsage().$promise; + }; + + return service; + }, +]); diff --git a/app/docker/services/taskService.js b/app/docker/services/taskService.js index 472ca051c255d..e5e6d4708ef1a 100644 --- a/app/docker/services/taskService.js +++ b/app/docker/services/taskService.js @@ -1,66 +1,69 @@ import { TaskViewModel } from '../models/task'; -angular.module('portainer.docker') -.factory('TaskService', ['$q', 'Task', 'LogHelper', -function TaskServiceFactory($q, Task, LogHelper) { - 'use strict'; - var service = {}; +angular.module('portainer.docker').factory('TaskService', [ + '$q', + 'Task', + 'LogHelper', + function TaskServiceFactory($q, Task, LogHelper) { + 'use strict'; + var service = {}; - service.task = function(id) { - var deferred = $q.defer(); + service.task = function (id) { + var deferred = $q.defer(); - Task.get({ id: id }).$promise - .then(function success(data) { - var task = new TaskViewModel(data); - deferred.resolve(task); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve task details', err: err }); - }); + Task.get({ id: id }) + .$promise.then(function success(data) { + var task = new TaskViewModel(data); + deferred.resolve(task); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve task details', err: err }); + }); - return deferred.promise; - }; + return deferred.promise; + }; - service.tasks = function(filters) { - var deferred = $q.defer(); + service.tasks = function (filters) { + var deferred = $q.defer(); - Task.query({ filters: filters ? filters : {} }).$promise - .then(function success(data) { - var tasks = data.map(function (item) { - return new TaskViewModel(item); - }); - deferred.resolve(tasks); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve tasks', err: err }); - }); + Task.query({ filters: filters ? filters : {} }) + .$promise.then(function success(data) { + var tasks = data.map(function (item) { + return new TaskViewModel(item); + }); + deferred.resolve(tasks); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve tasks', err: err }); + }); - return deferred.promise; - }; + return deferred.promise; + }; - service.logs = function(id, stdout, stderr, timestamps, since, tail) { - var deferred = $q.defer(); + service.logs = function (id, stdout, stderr, timestamps, since, tail) { + var deferred = $q.defer(); - var parameters = { - id: id, - stdout: stdout || 0, - stderr: stderr || 0, - timestamps: timestamps || 0, - since: since || 0, - tail: tail || 'all' - }; + var parameters = { + id: id, + stdout: stdout || 0, + stderr: stderr || 0, + timestamps: timestamps || 0, + since: since || 0, + tail: tail || 'all', + }; - Task.logs(parameters).$promise - .then(function success(data) { - var logs = LogHelper.formatLogs(data.logs, true); - deferred.resolve(logs); - }) - .catch(function error(err) { - deferred.reject(err); - }); + Task.logs(parameters) + .$promise.then(function success(data) { + var logs = LogHelper.formatLogs(data.logs, true); + deferred.resolve(logs); + }) + .catch(function error(err) { + deferred.reject(err); + }); - return deferred.promise; - }; + return deferred.promise; + }; - return service; -}]); + return service; + }, +]); diff --git a/app/docker/services/volumeService.js b/app/docker/services/volumeService.js index 1ad5cbb9ccf5e..a812a86cb7d90 100644 --- a/app/docker/services/volumeService.js +++ b/app/docker/services/volumeService.js @@ -1,101 +1,105 @@ import { VolumeViewModel } from '../models/volume'; -angular.module('portainer.docker') -.factory('VolumeService', ['$q', 'Volume', 'VolumeHelper', function VolumeServiceFactory($q, Volume, VolumeHelper) { - 'use strict'; - var service = {}; +angular.module('portainer.docker').factory('VolumeService', [ + '$q', + 'Volume', + 'VolumeHelper', + function VolumeServiceFactory($q, Volume, VolumeHelper) { + 'use strict'; + var service = {}; - service.volumes = function(params) { - var deferred = $q.defer(); - Volume.query(params).$promise - .then(function success(data) { - var volumes = data.Volumes || []; - volumes = volumes.map(function (item) { - return new VolumeViewModel(item); - }); - deferred.resolve(volumes); - }) - .catch(function error(err) { - deferred.reject({msg: 'Unable to retrieve volumes', err: err}); - }); - return deferred.promise; - }; + service.volumes = function (params) { + var deferred = $q.defer(); + Volume.query(params) + .$promise.then(function success(data) { + var volumes = data.Volumes || []; + volumes = volumes.map(function (item) { + return new VolumeViewModel(item); + }); + deferred.resolve(volumes); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve volumes', err: err }); + }); + return deferred.promise; + }; - service.volume = function(id) { - var deferred = $q.defer(); - Volume.get({id: id}).$promise - .then(function success(data) { - var volume = new VolumeViewModel(data); - deferred.resolve(volume); - }) - .catch(function error(err) { - deferred.reject({msg: 'Unable to retrieve volume details', err: err}); - }); - return deferred.promise; - }; + service.volume = function (id) { + var deferred = $q.defer(); + Volume.get({ id: id }) + .$promise.then(function success(data) { + var volume = new VolumeViewModel(data); + deferred.resolve(volume); + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to retrieve volume details', err: err }); + }); + return deferred.promise; + }; - service.getVolumes = function() { - return Volume.query({}).$promise; - }; + service.getVolumes = function () { + return Volume.query({}).$promise; + }; - service.remove = function(volume) { - var deferred = $q.defer(); + service.remove = function (volume) { + var deferred = $q.defer(); - Volume.remove({id: volume.Id}).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message, err: data.message }); - } else { - deferred.resolve(); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to remove volume', err: err }); - }); + Volume.remove({ id: volume.Id }) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message, err: data.message }); + } else { + deferred.resolve(); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to remove volume', err: err }); + }); - return deferred.promise; - }; + return deferred.promise; + }; - service.createVolumeConfiguration = function(name, driver, driverOptions) { - var volumeConfiguration = { - Name: name, - Driver: driver, - DriverOpts: VolumeHelper.createDriverOptions(driverOptions) + service.createVolumeConfiguration = function (name, driver, driverOptions) { + var volumeConfiguration = { + Name: name, + Driver: driver, + DriverOpts: VolumeHelper.createDriverOptions(driverOptions), + }; + return volumeConfiguration; }; - return volumeConfiguration; - }; - service.createVolume = function(volumeConfiguration) { - var deferred = $q.defer(); - Volume.create(volumeConfiguration).$promise - .then(function success(data) { - if (data.message) { - deferred.reject({ msg: data.message }); - } else { - var volume = new VolumeViewModel(data); - deferred.resolve(volume); - } - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to create volume', err: err }); - }); - return deferred.promise; - }; + service.createVolume = function (volumeConfiguration) { + var deferred = $q.defer(); + Volume.create(volumeConfiguration) + .$promise.then(function success(data) { + if (data.message) { + deferred.reject({ msg: data.message }); + } else { + var volume = new VolumeViewModel(data); + deferred.resolve(volume); + } + }) + .catch(function error(err) { + deferred.reject({ msg: 'Unable to create volume', err: err }); + }); + return deferred.promise; + }; - service.createVolumes = function(volumeConfigurations) { - var createVolumeQueries = volumeConfigurations.map(function(volumeConfiguration) { - return service.createVolume(volumeConfiguration); - }); - return $q.all(createVolumeQueries); - }; + service.createVolumes = function (volumeConfigurations) { + var createVolumeQueries = volumeConfigurations.map(function (volumeConfiguration) { + return service.createVolume(volumeConfiguration); + }); + return $q.all(createVolumeQueries); + }; - service.createXAutoGeneratedLocalVolumes = function (x) { - var createVolumeQueries = []; - for (var i = 0; i < x; i++) { - createVolumeQueries.push(service.createVolume({ Driver: 'local' })); - } - return $q.all(createVolumeQueries); - }; + service.createXAutoGeneratedLocalVolumes = function (x) { + var createVolumeQueries = []; + for (var i = 0; i < x; i++) { + createVolumeQueries.push(service.createVolume({ Driver: 'local' })); + } + return $q.all(createVolumeQueries); + }; - return service; -}]); + return service; + }, +]); diff --git a/app/docker/views/configs/configs.html b/app/docker/views/configs/configs.html index d124e86c76579..0145c737e208b 100644 --- a/app/docker/views/configs/configs.html +++ b/app/docker/views/configs/configs.html @@ -10,12 +10,14 @@
diff --git a/app/docker/views/configs/configsController.js b/app/docker/views/configs/configsController.js index 41cd2e31389d2..013dbf9f7de5d 100644 --- a/app/docker/views/configs/configsController.js +++ b/app/docker/views/configs/configsController.js @@ -1,7 +1,6 @@ import angular from 'angular'; class ConfigsController { - /* @ngInject */ constructor($state, ConfigService, Notifications, $async) { this.$state = $state; diff --git a/app/docker/views/configs/create/createConfigController.js b/app/docker/views/configs/create/createConfigController.js index 316250c4758db..5d1e686f91dc9 100644 --- a/app/docker/views/configs/create/createConfigController.js +++ b/app/docker/views/configs/create/createConfigController.js @@ -1,7 +1,7 @@ -import _ from "lodash-es"; -import { AccessControlFormData } from "Portainer/components/accessControlForm/porAccessControlFormModel"; +import _ from 'lodash-es'; +import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel'; -import angular from "angular"; +import angular from 'angular'; class CreateConfigController { /* @ngInject */ @@ -16,14 +16,14 @@ class CreateConfigController { this.$async = $async; this.formValues = { - Name: "", + Name: '', Labels: [], AccessControlData: new AccessControlFormData(), - ConfigContent: "" + ConfigContent: '', }; this.state = { - formValidationError: "" + formValidationError: '', }; this.editorUpdate = this.editorUpdate.bind(this); @@ -38,7 +38,7 @@ class CreateConfigController { try { let data = await this.ConfigService.config(this.$transition$.params().id); - this.formValues.Name = data.Name + "_copy"; + this.formValues.Name = data.Name + '_copy'; this.formValues.Data = data.Data; let labels = _.keys(data.Labels); for (let i = 0; i < labels.length; i++) { @@ -49,12 +49,12 @@ class CreateConfigController { this.formValues.displayCodeEditor = true; } catch (err) { this.formValues.displayCodeEditor = true; - this.Notifications.error("Failure", err, "Unable to clone config"); + this.Notifications.error('Failure', err, 'Unable to clone config'); } } addLabel() { - this.formValues.Labels.push({ name: "", value: "" }); + this.formValues.Labels.push({ name: '', value: '' }); } removeLabel(index) { @@ -63,7 +63,7 @@ class CreateConfigController { prepareLabelsConfig(config) { let labels = {}; - this.formValues.Labels.forEach(function(label) { + this.formValues.Labels.forEach(function (label) { if (label.name && label.value) { labels[label.name] = label.value; } @@ -85,12 +85,9 @@ class CreateConfigController { } validateForm(accessControlData, isAdmin) { - this.state.formValidationError = ""; - let error = ""; - error = this.FormValidator.validateAccessControl( - accessControlData, - isAdmin - ); + this.state.formValidationError = ''; + let error = ''; + error = this.FormValidator.validateAccessControl(accessControlData, isAdmin); if (error) { this.state.formValidationError = error; @@ -108,8 +105,8 @@ class CreateConfigController { const userDetails = this.Authentication.getUserDetails(); const isAdmin = this.Authentication.isAdmin(); - if (this.formValues.ConfigContent === "") { - this.state.formValidationError = "Config content must not be empty"; + if (this.formValues.ConfigContent === '') { + this.state.formValidationError = 'Config content must not be empty'; return; } @@ -124,10 +121,10 @@ class CreateConfigController { const resourceControl = data.Portainer.ResourceControl; const userId = userDetails.ID; await this.ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl); - this.Notifications.success("Config successfully created"); - this.$state.go("docker.configs", {}, { reload: true }); + this.Notifications.success('Config successfully created'); + this.$state.go('docker.configs', {}, { reload: true }); } catch (err) { - this.Notifications.error("Failure", err, "Unable to create config"); + this.Notifications.error('Failure', err, 'Unable to create config'); } } @@ -137,6 +134,4 @@ class CreateConfigController { } export default CreateConfigController; -angular - .module("portainer.docker") - .controller("CreateConfigController", CreateConfigController); +angular.module('portainer.docker').controller('CreateConfigController', CreateConfigController); diff --git a/app/docker/views/configs/create/createconfig.html b/app/docker/views/configs/create/createconfig.html index dbbfeda36ed42..e921866393294 100644 --- a/app/docker/views/configs/create/createconfig.html +++ b/app/docker/views/configs/create/createconfig.html @@ -1,8 +1,6 @@ - - Configs > Add config - + Configs > Add config
@@ -14,7 +12,7 @@
- +
@@ -25,7 +23,7 @@ identifier="config-creation-editor" placeholder="Define or paste the content of your config here" yml="false" - on-change="ctrl.editorUpdate" + on-change="(ctrl.editorUpdate)" value="ctrl.formValues.Data" >
@@ -44,11 +42,11 @@
name - +
value - +
- + + @@ -55,11 +59,7 @@
- + @@ -71,12 +71,7 @@
- +
diff --git a/app/docker/views/configs/edit/configController.js b/app/docker/views/configs/edit/configController.js index 2457c9609a011..516d65b693147 100644 --- a/app/docker/views/configs/edit/configController.js +++ b/app/docker/views/configs/edit/configController.js @@ -1,27 +1,31 @@ -angular.module('portainer.docker') -.controller('ConfigController', ['$scope', '$transition$', '$state', 'ConfigService', 'Notifications', -function ($scope, $transition$, $state, ConfigService, Notifications) { +angular.module('portainer.docker').controller('ConfigController', [ + '$scope', + '$transition$', + '$state', + 'ConfigService', + 'Notifications', + function ($scope, $transition$, $state, ConfigService, Notifications) { + $scope.removeConfig = function removeConfig(configId) { + ConfigService.remove(configId) + .then(function success() { + Notifications.success('Config successfully removed'); + $state.go('docker.configs', {}); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove config'); + }); + }; - $scope.removeConfig = function removeConfig(configId) { - ConfigService.remove(configId) - .then(function success() { - Notifications.success('Config successfully removed'); - $state.go('docker.configs', {}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove config'); - }); - }; + function initView() { + ConfigService.config($transition$.params().id) + .then(function success(data) { + $scope.config = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve config details'); + }); + } - function initView() { - ConfigService.config($transition$.params().id) - .then(function success(data) { - $scope.config = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve config details'); - }); - } - - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/containers/console/attach.html b/app/docker/views/containers/console/attach.html index 98e61884801de..703b57cb5d641 100644 --- a/app/docker/views/containers/console/attach.html +++ b/app/docker/views/containers/console/attach.html @@ -1,7 +1,7 @@ - Containers > {{ container.Name|trimcontainername }} > Console + Containers > {{ container.Name | trimcontainername }} > Console @@ -10,7 +10,6 @@ -

@@ -32,12 +31,16 @@

- -
diff --git a/app/docker/views/containers/console/containerConsoleController.js b/app/docker/views/containers/console/containerConsoleController.js index 2fa3c3e4661d8..27bee1fa6a431 100644 --- a/app/docker/views/containers/console/containerConsoleController.js +++ b/app/docker/views/containers/console/containerConsoleController.js @@ -1,41 +1,63 @@ -import {Terminal} from 'xterm'; - -angular.module('portainer.docker') - .controller('ContainerConsoleController', ['$scope', '$transition$', 'ContainerService', 'ImageService', 'EndpointProvider', 'Notifications', 'ContainerHelper', 'ExecService', 'HttpRequestHelper', 'LocalStorage', 'CONSOLE_COMMANDS_LABEL_PREFIX', - function ($scope, $transition$, ContainerService, ImageService, EndpointProvider, Notifications, ContainerHelper, ExecService, HttpRequestHelper, LocalStorage, CONSOLE_COMMANDS_LABEL_PREFIX) { - var socket, term; - - let states = Object.freeze({ - disconnected: 0, - connecting: 1, - connected: 2, - }); - - $scope.loaded = false; - $scope.states = states; - $scope.state = states.disconnected; - - $scope.formValues = {}; - $scope.containerCommands = []; - - // Ensure the socket is closed before leaving the view - $scope.$on('$stateChangeStart', function () { - $scope.disconnect(); - }); - - $scope.connectAttach = function() { - if ($scope.state > states.disconnected) { - return; - } - - $scope.state = states.connecting; +import { Terminal } from 'xterm'; + +angular.module('portainer.docker').controller('ContainerConsoleController', [ + '$scope', + '$transition$', + 'ContainerService', + 'ImageService', + 'EndpointProvider', + 'Notifications', + 'ContainerHelper', + 'ExecService', + 'HttpRequestHelper', + 'LocalStorage', + 'CONSOLE_COMMANDS_LABEL_PREFIX', + function ( + $scope, + $transition$, + ContainerService, + ImageService, + EndpointProvider, + Notifications, + ContainerHelper, + ExecService, + HttpRequestHelper, + LocalStorage, + CONSOLE_COMMANDS_LABEL_PREFIX + ) { + var socket, term; + + let states = Object.freeze({ + disconnected: 0, + connecting: 1, + connected: 2, + }); + + $scope.loaded = false; + $scope.states = states; + $scope.state = states.disconnected; + + $scope.formValues = {}; + $scope.containerCommands = []; + + // Ensure the socket is closed before leaving the view + $scope.$on('$stateChangeStart', function () { + $scope.disconnect(); + }); + + $scope.connectAttach = function () { + if ($scope.state > states.disconnected) { + return; + } - let attachId = $transition$.params().id; + $scope.state = states.connecting; - ContainerService.container(attachId).then((details) => { + let attachId = $transition$.params().id; + ContainerService.container(attachId) + .then((details) => { if (!details.State.Running) { - Notifications.error("Failure", details, "Container " + attachId + " is not running!"); + Notifications.error('Failure', details, 'Container ' + attachId + ' is not running!'); $scope.disconnect(); return; } @@ -43,173 +65,177 @@ angular.module('portainer.docker') const params = { token: LocalStorage.getJWT(), endpointId: EndpointProvider.endpointID(), - id: attachId + id: attachId, }; - var url = window.location.href.split('#')[0] + 'api/websocket/attach?' + (Object.keys(params).map((k) => k + "=" + params[k]).join("&")); + var url = + window.location.href.split('#')[0] + + 'api/websocket/attach?' + + Object.keys(params) + .map((k) => k + '=' + params[k]) + .join('&'); initTerm(url, ContainerService.resizeTTY.bind(this, attachId)); }) - .catch(function error(err) { - Notifications.error('Error', err, 'Unable to retrieve container details'); - $scope.disconnect(); - }); - }; - - $scope.connectExec = function () { - if ($scope.state > states.disconnected) { - return; - } - - $scope.state = states.connecting; - var command = $scope.formValues.isCustomCommand ? - $scope.formValues.customCommand : $scope.formValues.command; - var execConfig = { - id: $transition$.params().id, - AttachStdin: true, - AttachStdout: true, - AttachStderr: true, - Tty: true, - User: $scope.formValues.user, - Cmd: ContainerHelper.commandStringToArray(command) - }; - - ContainerService.createExec(execConfig) - .then(function success(data) { - - const params = { - token: LocalStorage.getJWT(), - endpointId: EndpointProvider.endpointID(), - id: data.Id - }; - - var url = window.location.href.split('#')[0] + 'api/websocket/exec?' + (Object.keys(params).map((k) => k + "=" + params[k]).join("&")); - - initTerm(url, ExecService.resizeTTY.bind(this, params.id)); - - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to exec into container'); - $scope.disconnect(); - }); - }; + .catch(function error(err) { + Notifications.error('Error', err, 'Unable to retrieve container details'); + $scope.disconnect(); + }); + }; - $scope.disconnect = function () { - if (socket) { - socket.close(); - } - if ($scope.state > states.disconnected) { - $scope.state = states.disconnected; - if (term) { - term.write("\n\r(connection closed)"); - term.dispose(); - } - } - }; + $scope.connectExec = function () { + if ($scope.state > states.disconnected) { + return; + } - $scope.autoconnectAttachView = function () { - return $scope.initView().then(function success() { - if ($scope.container.State.Running) { - $scope.connectAttach(); - } - }); + $scope.state = states.connecting; + var command = $scope.formValues.isCustomCommand ? $scope.formValues.customCommand : $scope.formValues.command; + var execConfig = { + id: $transition$.params().id, + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + Tty: true, + User: $scope.formValues.user, + Cmd: ContainerHelper.commandStringToArray(command), }; - function resize(restcall, add) { - add = add || 0; + ContainerService.createExec(execConfig) + .then(function success(data) { + const params = { + token: LocalStorage.getJWT(), + endpointId: EndpointProvider.endpointID(), + id: data.Id, + }; - term.fit(); - var termWidth = term.cols; - var termHeight = 30; - term.resize(termWidth, termHeight); + var url = + window.location.href.split('#')[0] + + 'api/websocket/exec?' + + Object.keys(params) + .map((k) => k + '=' + params[k]) + .join('&'); + initTerm(url, ExecService.resizeTTY.bind(this, params.id)); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to exec into container'); + $scope.disconnect(); + }); + }; - restcall(termWidth + add, termHeight + add, 1); + $scope.disconnect = function () { + if (socket) { + socket.close(); + } + if ($scope.state > states.disconnected) { + $scope.state = states.disconnected; + if (term) { + term.write('\n\r(connection closed)'); + term.dispose(); + } } + }; - function initTerm(url, resizeRestCall) { + $scope.autoconnectAttachView = function () { + return $scope.initView().then(function success() { + if ($scope.container.State.Running) { + $scope.connectAttach(); + } + }); + }; - let resizefun = resize.bind(this, resizeRestCall); + function resize(restcall, add) { + add = add || 0; - if ($transition$.params().nodeName) { - url += '&nodeName=' + $transition$.params().nodeName; - } - if (url.indexOf('https') > -1) { - url = url.replace('https://', 'wss://'); - } else { - url = url.replace('http://', 'ws://'); - } + term.fit(); + var termWidth = term.cols; + var termHeight = 30; + term.resize(termWidth, termHeight); - socket = new WebSocket(url); + restcall(termWidth + add, termHeight + add, 1); + } + function initTerm(url, resizeRestCall) { + let resizefun = resize.bind(this, resizeRestCall); - socket.onopen = function () { - $scope.state = states.connected; - term = new Terminal(); + if ($transition$.params().nodeName) { + url += '&nodeName=' + $transition$.params().nodeName; + } + if (url.indexOf('https') > -1) { + url = url.replace('https://', 'wss://'); + } else { + url = url.replace('http://', 'ws://'); + } + socket = new WebSocket(url); - term.on('data', function (data) { - socket.send(data); - }); - var terminal_container = document.getElementById('terminal-container'); - term.open(terminal_container); - term.focus(); - term.setOption('cursorBlink', true); + socket.onopen = function () { + $scope.state = states.connected; + term = new Terminal(); - window.onresize = function () { - resizefun(); - $scope.$apply(); - }; + term.on('data', function (data) { + socket.send(data); + }); + var terminal_container = document.getElementById('terminal-container'); + term.open(terminal_container); + term.focus(); + term.setOption('cursorBlink', true); - $scope.$watch('toggle', function () { - setTimeout(resizefun, 400); - }); + window.onresize = function () { + resizefun(); + $scope.$apply(); + }; - socket.onmessage = function (e) { - term.write(e.data); - }; - socket.onerror = function (err) { - $scope.disconnect(); - $scope.$apply(); - Notifications.error("Failure", err, "Connection error"); - }; - socket.onclose = function () { - $scope.disconnect(); - $scope.$apply(); - }; + $scope.$watch('toggle', function () { + setTimeout(resizefun, 400); + }); - resizefun(1); + socket.onmessage = function (e) { + term.write(e.data); + }; + socket.onerror = function (err) { + $scope.disconnect(); + $scope.$apply(); + Notifications.error('Failure', err, 'Connection error'); + }; + socket.onclose = function () { + $scope.disconnect(); $scope.$apply(); }; - } - $scope.initView = function () { - HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); - return ContainerService.container($transition$.params().id) - .then(function success(data) { - var container = data; - $scope.container = container; - return ImageService.image(container.Image); - }) - .then(function success(data) { - var image = data; - var containerLabels = $scope.container.Config.Labels; - $scope.imageOS = image.Os; - $scope.formValues.command = image.Os === 'windows' ? 'powershell' : 'bash'; - $scope.containerCommands = Object.keys(containerLabels) - .filter(function (label) { - return label.indexOf(CONSOLE_COMMANDS_LABEL_PREFIX) === 0; - }) - .map(function (label) { - return { - title: label.replace(CONSOLE_COMMANDS_LABEL_PREFIX, ''), - command: containerLabels[label] - }; - }); - $scope.loaded = true; - }) - .catch(function error(err) { - Notifications.error('Error', err, 'Unable to retrieve container details'); - }); - } - }]); + resizefun(1); + $scope.$apply(); + }; + } + + $scope.initView = function () { + HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); + return ContainerService.container($transition$.params().id) + .then(function success(data) { + var container = data; + $scope.container = container; + return ImageService.image(container.Image); + }) + .then(function success(data) { + var image = data; + var containerLabels = $scope.container.Config.Labels; + $scope.imageOS = image.Os; + $scope.formValues.command = image.Os === 'windows' ? 'powershell' : 'bash'; + $scope.containerCommands = Object.keys(containerLabels) + .filter(function (label) { + return label.indexOf(CONSOLE_COMMANDS_LABEL_PREFIX) === 0; + }) + .map(function (label) { + return { + title: label.replace(CONSOLE_COMMANDS_LABEL_PREFIX, ''), + command: containerLabels[label], + }; + }); + $scope.loaded = true; + }) + .catch(function error(err) { + Notifications.error('Error', err, 'Unable to retrieve container details'); + }); + }; + }, +]); diff --git a/app/docker/views/containers/console/exec.html b/app/docker/views/containers/console/exec.html index 31594f1c688a8..13ddfb32c8184 100644 --- a/app/docker/views/containers/console/exec.html +++ b/app/docker/views/containers/console/exec.html @@ -1,7 +1,7 @@ - Containers > {{ container.Name|trimcontainername }} > Console + Containers > {{ container.Name | trimcontainername }} > Console @@ -13,32 +13,30 @@
-
- -
-
- - - - - -
- +
+ +
+
+ + + + +
+
+
- +
- +
@@ -62,7 +60,10 @@
- + @@ -182,7 +196,7 @@
- +
@@ -190,7 +204,7 @@
- +
@@ -198,11 +212,11 @@
- +
- +
@@ -212,13 +226,13 @@
@@ -226,13 +240,13 @@
@@ -255,7 +269,8 @@

- Logging driver that will override the default docker daemon driver. Select Default logging driver if you don't want to override it. Supported logging drivers can be found in the Docker documentation. + Logging driver that will override the default docker daemon driver. Select Default logging driver if you don't want to override it. Supported logging drivers + can be found in the Docker documentation.

@@ -265,9 +280,16 @@
- + add logging driver option
@@ -276,11 +298,11 @@
option - +
value - +
- -
@@ -314,7 +334,7 @@
container - +
@@ -338,14 +358,14 @@ volume
host - +
@@ -399,7 +419,7 @@
- +
@@ -407,7 +427,7 @@
- +
@@ -415,7 +435,7 @@
- +
@@ -423,7 +443,7 @@
- +
@@ -431,7 +451,7 @@
- +
@@ -439,7 +459,7 @@
- +
@@ -447,7 +467,7 @@
- +
@@ -464,7 +484,7 @@
value - +
@@ -592,8 +610,7 @@
-
@@ -609,16 +626,16 @@ add device
- +
host - +
container - +
- +

@@ -658,7 +675,7 @@

- +

@@ -687,7 +704,7 @@

- +
diff --git a/app/docker/views/containers/edit/container.html b/app/docker/views/containers/edit/container.html index 11a927aaae7be..2d7e9fadbc053 100644 --- a/app/docker/views/containers/edit/container.html +++ b/app/docker/views/containers/edit/container.html @@ -1,277 +1,326 @@ - - + - Containers > {{ container.Name|trimcontainername }} + Containers > {{ container.Name | trimcontainername }} -
-
- - - -
- - - - - - - -
-
- - Duplicate/Edit -
-
-
-
+
+
+ + + +
+ + + + + + + +
+
+ + Duplicate/Edit +
+
+
+
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID{{ container.Id }}
Name - {{ container.Name|trimcontainername }} - - -
- - - -
-
IP address{{ container.NetworkSettings.IPAddress }}
Status - - - {{ container.State|getstatetext }} for {{ activityTime }} with exit code {{ container.State.ExitCode }} -
Created{{ container.Created|getisodate }}
Start time{{ container.State.StartedAt|getisodate }}
Finished{{ container.State.FinishedAt|getisodate }}
- -
-
-
-
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID{{ container.Id }}
Name + {{ container.Name | trimcontainername }} + + +
+ + + +
+
IP address{{ container.NetworkSettings.IPAddress }}
Status + + + {{ container.State | getstatetext }} for {{ activityTime + }} with exit code {{ container.State.ExitCode }} +
Created{{ container.Created | getisodate }}
Start time{{ container.State.StartedAt | getisodate }}
Finished{{ container.State.FinishedAt | getisodate }}
+ +
+
+
+
- - - - + + + + -
-
- - - - - - - - - - - - - - - - - - -
Status - - {{ container.State.Health.Status }} -
Failure count{{ container.State.Health.FailingStreak }}
Last output{{ container.State.Health.Log[container.State.Health.Log.length - 1].Output }}
-
-
-
+
+
+ + + + + + + + + + + + + + + + + + +
Status + + {{ container.State.Health.Status }} +
Failure count{{ container.State.Health.FailingStreak }}
Last output{{ container.State.Health.Log[container.State.Health.Log.length - 1].Output }}
+
+
+
-
-
- - - -
- -
-
- - You can create an image from this container, this allows you to backup important data or save - helpful configurations. You'll be able to spin up another container based on this image afterward. - -
+
+
+ + + + + +
+
+ + You can create an image from this container, this allows you to backup important data or save helpful configurations. You'll be able to spin up another container + based on this image afterward. +
- - - - - -
-
- Note: if you don't specify the tag in the image name, latest will be used. -
+
+ + + + + +
+
+ Note: if you don't specify the tag in the image name, latest will be used.
- -
-
- -
+
+ +
+
+
- - - -
+
+ + +
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Image{{ container.Config.Image}}@{{container.Image}}
Port configuration -
- {{ portMapping.host }} {{ portMapping.container }} -
-
CMD{{ container.Config.Cmd|command }}
ENTRYPOINT{{ container.Config.Entrypoint ? (container.Config.Entrypoint|command) : "null" }}
ENV - - - - - -
{{ var|key: '=' }}{{ var|value: '=' }}
-
Labels - - - - - -
{{ k }}{{ v }}
-
Restart policies - -
-
-
-
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Image{{ container.Config.Image }}@{{ container.Image }}
Port configuration +
{{ portMapping.host }} {{ portMapping.container }}
+
CMD{{ container.Config.Cmd | command }}
ENTRYPOINT{{ container.Config.Entrypoint ? (container.Config.Entrypoint | command) : 'null' }}
ENV + + + + + +
{{ var|key: '=' }}{{ var|value: '=' }}
+
Labels + + + + + +
{{ k }}{{ v }}
+
Restart policies + + +
+
+
+
-
-
- - - - - - - - - - - - - - - - - -
Host/volumePath in container
{{ vol.Source }}{{ vol.Name }}{{ vol.Destination }}
-
-
-
+
+
+ + + + + + + + + + + + + + + + + +
Host/volumePath in container
{{ vol.Source }}{{ vol.Name }}{{ vol.Destination }}
+
+
+
-
-
- +
+ -
+ >
+
diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index f775fbc1efb7e..6528669f149ef 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -1,356 +1,383 @@ import moment from 'moment'; import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; -angular.module('portainer.docker') -.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', '$async', 'ExtensionService', 'Commit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', 'HttpRequestHelper', 'Authentication', -function ($q, $scope, $state, $transition$, $filter, $async, ExtensionService, Commit, ContainerHelper, ContainerService, ImageHelper, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService, HttpRequestHelper, Authentication) { - $scope.activityTime = 0; - $scope.portBindings = []; - $scope.displayRecreateButton = false; - - $scope.config = { - RegistryModel: new PorImageRegistryModel(), - commitInProgress: false - }; - - $scope.state = { - recreateContainerInProgress: false, - joinNetworkInProgress: false, - leaveNetworkInProgress: false - }; - - $scope.updateRestartPolicy = updateRestartPolicy; - - var update = function () { - var nodeName = $transition$.params().nodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - $scope.nodeName = nodeName; - - ContainerService.container($transition$.params().id) - .then(function success(data) { - var container = data; - $scope.container = container; - $scope.container.edit = false; - $scope.container.newContainerName = $filter('trimcontainername')(container.Name); - - if (container.State.Running) { - $scope.activityTime = moment.duration(moment(container.State.StartedAt).utc().diff(moment().utc())).humanize(); - } else if (container.State.Status === 'created') { - $scope.activityTime = moment.duration(moment(container.Created).utc().diff(moment().utc())).humanize(); - } else { - $scope.activityTime = moment.duration(moment().utc().diff(moment(container.State.FinishedAt).utc())).humanize(); - } +angular.module('portainer.docker').controller('ContainerController', [ + '$q', + '$scope', + '$state', + '$transition$', + '$filter', + '$async', + 'ExtensionService', + 'Commit', + 'ContainerHelper', + 'ContainerService', + 'ImageHelper', + 'NetworkService', + 'Notifications', + 'ModalService', + 'ResourceControlService', + 'RegistryService', + 'ImageService', + 'HttpRequestHelper', + 'Authentication', + function ( + $q, + $scope, + $state, + $transition$, + $filter, + $async, + ExtensionService, + Commit, + ContainerHelper, + ContainerService, + ImageHelper, + NetworkService, + Notifications, + ModalService, + ResourceControlService, + RegistryService, + ImageService, + HttpRequestHelper, + Authentication + ) { + $scope.activityTime = 0; + $scope.portBindings = []; + $scope.displayRecreateButton = false; + + $scope.config = { + RegistryModel: new PorImageRegistryModel(), + commitInProgress: false, + }; + + $scope.state = { + recreateContainerInProgress: false, + joinNetworkInProgress: false, + leaveNetworkInProgress: false, + }; + + $scope.updateRestartPolicy = updateRestartPolicy; + + var update = function () { + var nodeName = $transition$.params().nodeName; + HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); + $scope.nodeName = nodeName; + + ContainerService.container($transition$.params().id) + .then(function success(data) { + var container = data; + $scope.container = container; + $scope.container.edit = false; + $scope.container.newContainerName = $filter('trimcontainername')(container.Name); + + if (container.State.Running) { + $scope.activityTime = moment.duration(moment(container.State.StartedAt).utc().diff(moment().utc())).humanize(); + } else if (container.State.Status === 'created') { + $scope.activityTime = moment.duration(moment(container.Created).utc().diff(moment().utc())).humanize(); + } else { + $scope.activityTime = moment.duration(moment().utc().diff(moment(container.State.FinishedAt).utc())).humanize(); + } - $scope.portBindings = []; - if (container.NetworkSettings.Ports) { - angular.forEach(Object.keys(container.NetworkSettings.Ports), function(portMapping) { - if (container.NetworkSettings.Ports[portMapping]) { - var mapping = {}; - mapping.container = portMapping; - mapping.host = container.NetworkSettings.Ports[portMapping][0].HostIp + ':' + container.NetworkSettings.Ports[portMapping][0].HostPort; - $scope.portBindings.push(mapping); + $scope.portBindings = []; + if (container.NetworkSettings.Ports) { + angular.forEach(Object.keys(container.NetworkSettings.Ports), function (portMapping) { + if (container.NetworkSettings.Ports[portMapping]) { + var mapping = {}; + mapping.container = portMapping; + mapping.host = container.NetworkSettings.Ports[portMapping][0].HostIp + ':' + container.NetworkSettings.Ports[portMapping][0].HostPort; + $scope.portBindings.push(mapping); + } + }); } - }); - } - const inSwarm = $scope.container.Config.Labels['com.docker.swarm.service.id']; - const autoRemove = $scope.container.HostConfig.AutoRemove; - const admin = Authentication.isAdmin(); + const inSwarm = $scope.container.Config.Labels['com.docker.swarm.service.id']; + const autoRemove = $scope.container.HostConfig.AutoRemove; + const admin = Authentication.isAdmin(); - ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then((rbacEnabled) => { - $scope.displayRecreateButton = !inSwarm && !autoRemove && (rbacEnabled ? admin : true) - }); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve container info'); - }); - }; - - function executeContainerAction(id, action, successMessage, errorMessage) { - action(id) - .then(function success() { - Notifications.success(successMessage, id); - update(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, errorMessage); - }); - } - - $scope.start = function () { - var successMessage = 'Container successfully started'; - var errorMessage = 'Unable to start container'; - executeContainerAction($transition$.params().id, ContainerService.startContainer, successMessage, errorMessage); - }; - - $scope.stop = function () { - var successMessage = 'Container successfully stopped'; - var errorMessage = 'Unable to stop container'; - executeContainerAction($transition$.params().id, ContainerService.stopContainer, successMessage, errorMessage); - }; - - $scope.kill = function () { - var successMessage = 'Container successfully killed'; - var errorMessage = 'Unable to kill container'; - executeContainerAction($transition$.params().id, ContainerService.killContainer, successMessage, errorMessage); - }; - - $scope.pause = function() { - var successMessage = 'Container successfully paused'; - var errorMessage = 'Unable to pause container'; - executeContainerAction($transition$.params().id, ContainerService.pauseContainer, successMessage, errorMessage); - }; - - $scope.unpause = function() { - var successMessage = 'Container successfully resumed'; - var errorMessage = 'Unable to resume container'; - executeContainerAction($transition$.params().id, ContainerService.resumeContainer, successMessage, errorMessage); - }; - - $scope.restart = function () { - var successMessage = 'Container successfully restarted'; - var errorMessage = 'Unable to restart container'; - executeContainerAction($transition$.params().id, ContainerService.restartContainer, successMessage, errorMessage); - }; - - $scope.renameContainer = function () { - var container = $scope.container; - ContainerService.renameContainer($transition$.params().id, container.newContainerName) - .then(function success() { - container.Name = container.newContainerName; - Notifications.success('Container successfully renamed', container.Name); - }) - .catch(function error(err) { - container.newContainerName = container.Name; - Notifications.error('Failure', err, 'Unable to rename container'); - }) - .finally(function final() { - $scope.container.edit = false; - }); - }; - - $scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) { - $scope.state.leaveNetworkInProgress = true; - NetworkService.disconnectContainer(networkId, container.Id, false) - .then(function success() { - Notifications.success('Container left network', container.Id); - $state.reload(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to disconnect container from network'); - }) - .finally(function final() { - $scope.state.leaveNetworkInProgress = false; - }); - }; - - $scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) { - $scope.state.joinNetworkInProgress = true; - NetworkService.connectContainer(networkId, container.Id) - .then(function success() { - Notifications.success('Container joined network', container.Id); - $state.reload(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to connect container to network'); - }) - .finally(function final() { - $scope.state.joinNetworkInProgress = false; - }); - }; - - async function commitContainerAsync() { - $scope.config.commitInProgress = true; - const registryModel = $scope.config.RegistryModel; - const imageConfig = ImageHelper.createImageConfigForContainer(registryModel); - try { - await Commit.commitContainer({id: $transition$.params().id, repo: imageConfig.fromImage}).$promise; - Notifications.success('Image created', $transition$.params().id); - $state.reload(); - } catch (err) { - Notifications.error('Failure', err, 'Unable to create image'); - $scope.config.commitInProgress = false; + ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then((rbacEnabled) => { + $scope.displayRecreateButton = !inSwarm && !autoRemove && (rbacEnabled ? admin : true); + }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve container info'); + }); + }; + + function executeContainerAction(id, action, successMessage, errorMessage) { + action(id) + .then(function success() { + Notifications.success(successMessage, id); + update(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, errorMessage); + }); } - } - - $scope.commit = function () { - return $async(commitContainerAsync); - }; - $scope.confirmRemove = function () { - var title = 'You are about to remove a container.'; - if ($scope.container.State.Running) { - title = 'You are about to remove a running container.'; + $scope.start = function () { + var successMessage = 'Container successfully started'; + var errorMessage = 'Unable to start container'; + executeContainerAction($transition$.params().id, ContainerService.startContainer, successMessage, errorMessage); + }; + + $scope.stop = function () { + var successMessage = 'Container successfully stopped'; + var errorMessage = 'Unable to stop container'; + executeContainerAction($transition$.params().id, ContainerService.stopContainer, successMessage, errorMessage); + }; + + $scope.kill = function () { + var successMessage = 'Container successfully killed'; + var errorMessage = 'Unable to kill container'; + executeContainerAction($transition$.params().id, ContainerService.killContainer, successMessage, errorMessage); + }; + + $scope.pause = function () { + var successMessage = 'Container successfully paused'; + var errorMessage = 'Unable to pause container'; + executeContainerAction($transition$.params().id, ContainerService.pauseContainer, successMessage, errorMessage); + }; + + $scope.unpause = function () { + var successMessage = 'Container successfully resumed'; + var errorMessage = 'Unable to resume container'; + executeContainerAction($transition$.params().id, ContainerService.resumeContainer, successMessage, errorMessage); + }; + + $scope.restart = function () { + var successMessage = 'Container successfully restarted'; + var errorMessage = 'Unable to restart container'; + executeContainerAction($transition$.params().id, ContainerService.restartContainer, successMessage, errorMessage); + }; + + $scope.renameContainer = function () { + var container = $scope.container; + ContainerService.renameContainer($transition$.params().id, container.newContainerName) + .then(function success() { + container.Name = container.newContainerName; + Notifications.success('Container successfully renamed', container.Name); + }) + .catch(function error(err) { + container.newContainerName = container.Name; + Notifications.error('Failure', err, 'Unable to rename container'); + }) + .finally(function final() { + $scope.container.edit = false; + }); + }; + + $scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) { + $scope.state.leaveNetworkInProgress = true; + NetworkService.disconnectContainer(networkId, container.Id, false) + .then(function success() { + Notifications.success('Container left network', container.Id); + $state.reload(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to disconnect container from network'); + }) + .finally(function final() { + $scope.state.leaveNetworkInProgress = false; + }); + }; + + $scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) { + $scope.state.joinNetworkInProgress = true; + NetworkService.connectContainer(networkId, container.Id) + .then(function success() { + Notifications.success('Container joined network', container.Id); + $state.reload(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to connect container to network'); + }) + .finally(function final() { + $scope.state.joinNetworkInProgress = false; + }); + }; + + async function commitContainerAsync() { + $scope.config.commitInProgress = true; + const registryModel = $scope.config.RegistryModel; + const imageConfig = ImageHelper.createImageConfigForContainer(registryModel); + try { + await Commit.commitContainer({ id: $transition$.params().id, repo: imageConfig.fromImage }).$promise; + Notifications.success('Image created', $transition$.params().id); + $state.reload(); + } catch (err) { + Notifications.error('Failure', err, 'Unable to create image'); + $scope.config.commitInProgress = false; + } } - ModalService.confirmContainerDeletion( - title, - function (result) { - if(!result) { return; } + + $scope.commit = function () { + return $async(commitContainerAsync); + }; + + $scope.confirmRemove = function () { + var title = 'You are about to remove a container.'; + if ($scope.container.State.Running) { + title = 'You are about to remove a running container.'; + } + ModalService.confirmContainerDeletion(title, function (result) { + if (!result) { + return; + } var cleanAssociatedVolumes = false; if (result[0]) { cleanAssociatedVolumes = true; } removeContainer(cleanAssociatedVolumes); - } - ); - }; - - function removeContainer(cleanAssociatedVolumes) { - ContainerService.remove($scope.container, cleanAssociatedVolumes) - .then(function success() { - Notifications.success('Container successfully removed'); - $state.go('docker.containers', {}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove container'); - }); - } - - function recreateContainer(pullImage) { - var container = $scope.container; - var config = ContainerHelper.configFromContainer(container.Model); - $scope.state.recreateContainerInProgress = true; - var isRunning = container.State.Running; - - return pullImageIfNeeded() - .then(stopContainerIfNeeded) - .then(renameContainer) - .then(setMainNetworkAndCreateContainer) - .then(connectContainerToOtherNetworks) - .then(startContainerIfNeeded) - .then(createResourceControl) - .then(deleteOldContainer) - .then(notifyAndChangeView) - .catch(notifyOnError); - - function stopContainerIfNeeded() { - if (!isRunning) { - return $q.when(); - } - return ContainerService.stopContainer(container.Id); + }); + }; + + function removeContainer(cleanAssociatedVolumes) { + ContainerService.remove($scope.container, cleanAssociatedVolumes) + .then(function success() { + Notifications.success('Container successfully removed'); + $state.go('docker.containers', {}, { reload: true }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove container'); + }); } - function renameContainer() { - return ContainerService.renameContainer(container.Id, container.Name + '-old'); - } + function recreateContainer(pullImage) { + var container = $scope.container; + var config = ContainerHelper.configFromContainer(container.Model); + $scope.state.recreateContainerInProgress = true; + var isRunning = container.State.Running; + + return pullImageIfNeeded() + .then(stopContainerIfNeeded) + .then(renameContainer) + .then(setMainNetworkAndCreateContainer) + .then(connectContainerToOtherNetworks) + .then(startContainerIfNeeded) + .then(createResourceControl) + .then(deleteOldContainer) + .then(notifyAndChangeView) + .catch(notifyOnError); + + function stopContainerIfNeeded() { + if (!isRunning) { + return $q.when(); + } + return ContainerService.stopContainer(container.Id); + } - function pullImageIfNeeded() { - if (!pullImage) { - return $q.when(); + function renameContainer() { + return ContainerService.renameContainer(container.Id, container.Name + '-old'); } - return RegistryService.retrievePorRegistryModelFromRepository(container.Config.Image) - .then(function pullImage(registryModel) { - return ImageService.pullImage(registryModel, true); - }); - } - function setMainNetworkAndCreateContainer() { - var networks = config.NetworkingConfig.EndpointsConfig; - var networksNames = Object.keys(networks); - if (networksNames.length > 1) { - config.NetworkingConfig.EndpointsConfig = {}; - config.NetworkingConfig.EndpointsConfig[networksNames[0]] = networks[0]; + function pullImageIfNeeded() { + if (!pullImage) { + return $q.when(); + } + return RegistryService.retrievePorRegistryModelFromRepository(container.Config.Image).then(function pullImage(registryModel) { + return ImageService.pullImage(registryModel, true); + }); } - return $q.all([ContainerService.createContainer(config), networks]); - } - function connectContainerToOtherNetworks(createContainerData) { - var newContainer = createContainerData[0]; - var networks = createContainerData[1]; - var networksNames = Object.keys(networks); - var connectionPromises = networksNames.map(function connectToNetwork(name) { - NetworkService.connectContainer(name, newContainer.Id); - }); - return $q.all(connectionPromises) - .then(function onConnectToNetworkSuccess() { + function setMainNetworkAndCreateContainer() { + var networks = config.NetworkingConfig.EndpointsConfig; + var networksNames = Object.keys(networks); + if (networksNames.length > 1) { + config.NetworkingConfig.EndpointsConfig = {}; + config.NetworkingConfig.EndpointsConfig[networksNames[0]] = networks[0]; + } + return $q.all([ContainerService.createContainer(config), networks]); + } + + function connectContainerToOtherNetworks(createContainerData) { + var newContainer = createContainerData[0]; + var networks = createContainerData[1]; + var networksNames = Object.keys(networks); + var connectionPromises = networksNames.map(function connectToNetwork(name) { + NetworkService.connectContainer(name, newContainer.Id); + }); + return $q.all(connectionPromises).then(function onConnectToNetworkSuccess() { return newContainer; }); - } + } - function deleteOldContainer(newContainer) { - return ContainerService.remove(container, true).then( - function onRemoveSuccess() { + function deleteOldContainer(newContainer) { + return ContainerService.remove(container, true).then(function onRemoveSuccess() { return newContainer; + }); + } + + function startContainerIfNeeded(newContainer) { + if (!isRunning) { + return $q.when(newContainer); } - ); - } + return ContainerService.startContainer(newContainer.Id).then(function onStartSuccess() { + return newContainer; + }); + } - function startContainerIfNeeded(newContainer) { - if (!isRunning) { - return $q.when(newContainer); + function createResourceControl(newContainer) { + const userId = Authentication.getUserDetails().ID; + const oldResourceControl = container.ResourceControl; + const newResourceControl = newContainer.Portainer.ResourceControl; + return ResourceControlService.duplicateResourceControl(userId, oldResourceControl, newResourceControl); } - return ContainerService.startContainer(newContainer.Id).then( - function onStartSuccess() { - return newContainer; - } - ); - } - function createResourceControl(newContainer) { - const userId = Authentication.getUserDetails().ID; - const oldResourceControl = container.ResourceControl; - const newResourceControl = newContainer.Portainer.ResourceControl; - return ResourceControlService.duplicateResourceControl(userId, oldResourceControl, newResourceControl); - } + function notifyAndChangeView() { + Notifications.success('Container successfully re-created'); + $state.go('docker.containers', {}, { reload: true }); + } - function notifyAndChangeView() { - Notifications.success('Container successfully re-created'); - $state.go('docker.containers', {}, { reload: true }); + function notifyOnError(err) { + Notifications.error('Failure', err, 'Unable to re-create container'); + $scope.state.recreateContainerInProgress = false; + } } - function notifyOnError(err) { - Notifications.error('Failure', err, 'Unable to re-create container'); - $scope.state.recreateContainerInProgress = false; - } - } - - $scope.recreate = function() { - ModalService.confirmContainerRecreation(function (result) { - if(!result) { return; } - var pullImage = false; - if (result[0]) { - pullImage = true; + $scope.recreate = function () { + ModalService.confirmContainerRecreation(function (result) { + if (!result) { + return; + } + var pullImage = false; + if (result[0]) { + pullImage = true; + } + recreateContainer(pullImage); + }); + }; + + function updateRestartPolicy(restartPolicy, maximumRetryCount) { + maximumRetryCount = restartPolicy === 'on-failure' ? maximumRetryCount : undefined; + + return ContainerService.updateRestartPolicy($scope.container.Id, restartPolicy, maximumRetryCount).then(onUpdateSuccess).catch(notifyOnError); + + function onUpdateSuccess() { + $scope.container.HostConfig.RestartPolicy = { + Name: restartPolicy, + MaximumRetryCount: maximumRetryCount, + }; + Notifications.success('Restart policy updated'); } - recreateContainer(pullImage); - }); - }; - - function updateRestartPolicy(restartPolicy, maximumRetryCount) { - maximumRetryCount = restartPolicy === 'on-failure' ? maximumRetryCount : undefined; - - return ContainerService - .updateRestartPolicy($scope.container.Id, restartPolicy, maximumRetryCount) - .then(onUpdateSuccess) - .catch(notifyOnError); - - function onUpdateSuccess() { - $scope.container.HostConfig.RestartPolicy = { - Name: restartPolicy, - MaximumRetryCount: maximumRetryCount - }; - Notifications.success('Restart policy updated'); - } - function notifyOnError(err) { - Notifications.error('Failure', err, 'Unable to update restart policy'); - return $q.reject(err); + function notifyOnError(err) { + Notifications.error('Failure', err, 'Unable to update restart policy'); + return $q.reject(err); + } } - } - - var provider = $scope.applicationState.endpoint.mode.provider; - var apiVersion = $scope.applicationState.endpoint.apiVersion; - NetworkService.networks( - provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE', - false, - provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25 - ) - .then(function success(data) { - var networks = data; - $scope.availableNetworks = networks; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve networks'); - }); - - update(); -}]); + + var provider = $scope.applicationState.endpoint.mode.provider; + var apiVersion = $scope.applicationState.endpoint.apiVersion; + NetworkService.networks(provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE', false, provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25) + .then(function success(data) { + var networks = data; + $scope.availableNetworks = networks; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve networks'); + }); + + update(); + }, +]); diff --git a/app/docker/views/containers/inspect/containerInspectController.js b/app/docker/views/containers/inspect/containerInspectController.js index 458a0e6a47b8c..823f9d2b6296f 100644 --- a/app/docker/views/containers/inspect/containerInspectController.js +++ b/app/docker/views/containers/inspect/containerInspectController.js @@ -1,22 +1,26 @@ -angular.module('portainer.docker') -.controller('ContainerInspectController', ['$scope', '$transition$', 'Notifications', 'ContainerService', 'HttpRequestHelper', -function ($scope, $transition$, Notifications, ContainerService, HttpRequestHelper) { +angular.module('portainer.docker').controller('ContainerInspectController', [ + '$scope', + '$transition$', + 'Notifications', + 'ContainerService', + 'HttpRequestHelper', + function ($scope, $transition$, Notifications, ContainerService, HttpRequestHelper) { + $scope.state = { + DisplayTextView: false, + }; + $scope.containerInfo = {}; - $scope.state = { - DisplayTextView: false - }; - $scope.containerInfo = {}; + function initView() { + HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); + ContainerService.inspect($transition$.params().id) + .then(function success(d) { + $scope.containerInfo = d; + }) + .catch(function error(e) { + Notifications.error('Failure', e, 'Unable to inspect container'); + }); + } - function initView() { - HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); - ContainerService.inspect($transition$.params().id) - .then(function success(d) { - $scope.containerInfo = d; - }) - .catch(function error(e) { - Notifications.error('Failure', e, 'Unable to inspect container'); - }); - } - - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/containers/inspect/containerinspect.html b/app/docker/views/containers/inspect/containerinspect.html index b381ed2bb5449..def4954d5596d 100644 --- a/app/docker/views/containers/inspect/containerinspect.html +++ b/app/docker/views/containers/inspect/containerinspect.html @@ -1,8 +1,8 @@ - - + - Containers > {{ containerInfo.Name|trimcontainername }} > Inspect + Containers > {{ containerInfo.Name | trimcontainername }} > + Inspect @@ -16,7 +16,7 @@ -
{{ containerInfo|json:4 }}
+
{{ containerInfo | json: 4 }}
diff --git a/app/docker/views/containers/logs/containerLogsController.js b/app/docker/views/containers/logs/containerLogsController.js index 695bfd3394a3d..d6799a4518e3e 100644 --- a/app/docker/views/containers/logs/containerLogsController.js +++ b/app/docker/views/containers/logs/containerLogsController.js @@ -1,73 +1,87 @@ import moment from 'moment'; -angular.module('portainer.docker') -.controller('ContainerLogsController', ['$scope', '$transition$', '$interval', 'ContainerService', 'Notifications', 'HttpRequestHelper', -function ($scope, $transition$, $interval, ContainerService, Notifications, HttpRequestHelper) { - $scope.state = { - refreshRate: 3, - lineCount: 100, - sinceTimestamp: '', - displayTimestamps: false - }; +angular.module('portainer.docker').controller('ContainerLogsController', [ + '$scope', + '$transition$', + '$interval', + 'ContainerService', + 'Notifications', + 'HttpRequestHelper', + function ($scope, $transition$, $interval, ContainerService, Notifications, HttpRequestHelper) { + $scope.state = { + refreshRate: 3, + lineCount: 100, + sinceTimestamp: '', + displayTimestamps: false, + }; - $scope.changeLogCollection = function(logCollectionStatus) { - if (!logCollectionStatus) { + $scope.changeLogCollection = function (logCollectionStatus) { + if (!logCollectionStatus) { + stopRepeater(); + } else { + setUpdateRepeater(!$scope.container.Config.Tty); + } + }; + + $scope.$on('$destroy', function () { stopRepeater(); - } else { - setUpdateRepeater(!$scope.container.Config.Tty); - } - }; + }); - $scope.$on('$destroy', function() { - stopRepeater(); - }); + function stopRepeater() { + var repeater = $scope.repeater; + if (angular.isDefined(repeater)) { + $interval.cancel(repeater); + repeater = null; + } + } - function stopRepeater() { - var repeater = $scope.repeater; - if (angular.isDefined(repeater)) { - $interval.cancel(repeater); - repeater = null; + function setUpdateRepeater(skipHeaders) { + var refreshRate = $scope.state.refreshRate; + $scope.repeater = $interval(function () { + ContainerService.logs( + $transition$.params().id, + 1, + 1, + $scope.state.displayTimestamps ? 1 : 0, + moment($scope.state.sinceTimestamp).unix(), + $scope.state.lineCount, + skipHeaders + ) + .then(function success(data) { + $scope.logs = data; + }) + .catch(function error(err) { + stopRepeater(); + Notifications.error('Failure', err, 'Unable to retrieve container logs'); + }); + }, refreshRate * 1000); } - } - function setUpdateRepeater(skipHeaders) { - var refreshRate = $scope.state.refreshRate; - $scope.repeater = $interval(function() { + function startLogPolling(skipHeaders) { ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, moment($scope.state.sinceTimestamp).unix(), $scope.state.lineCount, skipHeaders) - .then(function success(data) { - $scope.logs = data; - }) - .catch(function error(err) { - stopRepeater(); - Notifications.error('Failure', err, 'Unable to retrieve container logs'); - }); - }, refreshRate * 1000); - } - - function startLogPolling(skipHeaders) { - ContainerService.logs($transition$.params().id, 1, 1, $scope.state.displayTimestamps ? 1 : 0, moment($scope.state.sinceTimestamp).unix(), $scope.state.lineCount, skipHeaders) - .then(function success(data) { - $scope.logs = data; - setUpdateRepeater(skipHeaders); - }) - .catch(function error(err) { - stopRepeater(); - Notifications.error('Failure', err, 'Unable to retrieve container logs'); - }); - } + .then(function success(data) { + $scope.logs = data; + setUpdateRepeater(skipHeaders); + }) + .catch(function error(err) { + stopRepeater(); + Notifications.error('Failure', err, 'Unable to retrieve container logs'); + }); + } - function initView() { - HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); - ContainerService.container($transition$.params().id) - .then(function success(data) { - var container = data; - $scope.container = container; - startLogPolling(!container.Config.Tty); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve container information'); - }); - } + function initView() { + HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); + ContainerService.container($transition$.params().id) + .then(function success(data) { + var container = data; + $scope.container = container; + startLogPolling(!container.Config.Tty); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve container information'); + }); + } - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/containers/logs/containerlogs.html b/app/docker/views/containers/logs/containerlogs.html index 133e354c24ba5..0cba93e8a07d7 100644 --- a/app/docker/views/containers/logs/containerlogs.html +++ b/app/docker/views/containers/logs/containerlogs.html @@ -1,10 +1,15 @@ - Containers > {{ container.Name|trimcontainername }} > Logs + Containers > {{ container.Name | trimcontainername }} > Logs diff --git a/app/docker/views/containers/stats/containerStatsController.js b/app/docker/views/containers/stats/containerStatsController.js index a1c57c10c08e7..9fef44e413286 100644 --- a/app/docker/views/containers/stats/containerStatsController.js +++ b/app/docker/views/containers/stats/containerStatsController.js @@ -1,156 +1,163 @@ import moment from 'moment'; -angular.module('portainer.docker') -.controller('ContainerStatsController', ['$q', '$scope', '$transition$', '$document', '$interval', 'ContainerService', 'ChartService', 'Notifications', 'HttpRequestHelper', -function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications, HttpRequestHelper) { - - $scope.state = { - refreshRate: '5', - networkStatsUnavailable: false - }; - - $scope.$on('$destroy', function() { - stopRepeater(); - }); - - function stopRepeater() { - var repeater = $scope.repeater; - if (angular.isDefined(repeater)) { - $interval.cancel(repeater); - repeater = null; +angular.module('portainer.docker').controller('ContainerStatsController', [ + '$q', + '$scope', + '$transition$', + '$document', + '$interval', + 'ContainerService', + 'ChartService', + 'Notifications', + 'HttpRequestHelper', + function ($q, $scope, $transition$, $document, $interval, ContainerService, ChartService, Notifications, HttpRequestHelper) { + $scope.state = { + refreshRate: '5', + networkStatsUnavailable: false, + }; + + $scope.$on('$destroy', function () { + stopRepeater(); + }); + + function stopRepeater() { + var repeater = $scope.repeater; + if (angular.isDefined(repeater)) { + $interval.cancel(repeater); + repeater = null; + } + } + + function updateNetworkChart(stats, chart) { + if (stats.Networks.length > 0) { + var rx = stats.Networks[0].rx_bytes; + var tx = stats.Networks[0].tx_bytes; + var label = moment(stats.read).format('HH:mm:ss'); + + ChartService.UpdateNetworkChart(label, rx, tx, chart); + } } - } - function updateNetworkChart(stats, chart) { - if (stats.Networks.length > 0) { - var rx = stats.Networks[0].rx_bytes; - var tx = stats.Networks[0].tx_bytes; + function updateMemoryChart(stats, chart) { var label = moment(stats.read).format('HH:mm:ss'); - ChartService.UpdateNetworkChart(label, rx, tx, chart); + ChartService.UpdateMemoryChart(label, stats.MemoryUsage, stats.MemoryCache, chart); } - } - function updateMemoryChart(stats, chart) { - var label = moment(stats.read).format('HH:mm:ss'); + function updateCPUChart(stats, chart) { + var label = moment(stats.read).format('HH:mm:ss'); + var value = stats.isWindows ? calculateCPUPercentWindows(stats) : calculateCPUPercentUnix(stats); - ChartService.UpdateMemoryChart(label, stats.MemoryUsage, stats.MemoryCache, chart); - } + ChartService.UpdateCPUChart(label, value, chart); + } - function updateCPUChart(stats, chart) { - var label = moment(stats.read).format('HH:mm:ss'); - var value = stats.isWindows ? calculateCPUPercentWindows(stats) : calculateCPUPercentUnix(stats); + function calculateCPUPercentUnix(stats) { + var cpuPercent = 0.0; + var cpuDelta = stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage; + var systemDelta = stats.CurrentCPUSystemUsage - stats.PreviousCPUSystemUsage; - ChartService.UpdateCPUChart(label, value, chart); - } + if (systemDelta > 0.0 && cpuDelta > 0.0) { + cpuPercent = (cpuDelta / systemDelta) * stats.CPUCores * 100.0; + } - function calculateCPUPercentUnix(stats) { - var cpuPercent = 0.0; - var cpuDelta = stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage; - var systemDelta = stats.CurrentCPUSystemUsage - stats.PreviousCPUSystemUsage; + return cpuPercent; + } - if (systemDelta > 0.0 && cpuDelta > 0.0) { - cpuPercent = (cpuDelta / systemDelta) * stats.CPUCores * 100.0; + function calculateCPUPercentWindows(stats) { + var possIntervals = + stats.NumProcs * parseFloat(moment(stats.read, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf() - moment(stats.preread, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf()); + var windowsCpuUsage = 0.0; + if (possIntervals > 0) { + windowsCpuUsage = parseFloat(stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage) / parseFloat(possIntervals * 100); + } + return windowsCpuUsage; } - return cpuPercent; - } + $scope.changeUpdateRepeater = function () { + var networkChart = $scope.networkChart; + var cpuChart = $scope.cpuChart; + var memoryChart = $scope.memoryChart; - function calculateCPUPercentWindows(stats) { - var possIntervals = stats.NumProcs * parseFloat( - moment(stats.read, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf() - moment(stats.preread, 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ').valueOf()); - var windowsCpuUsage = 0.0; - if(possIntervals > 0) { - windowsCpuUsage = parseFloat(stats.CurrentCPUTotalUsage - stats.PreviousCPUTotalUsage) / parseFloat(possIntervals * 100); - } - return windowsCpuUsage; - } - - - $scope.changeUpdateRepeater = function() { - var networkChart = $scope.networkChart; - var cpuChart = $scope.cpuChart; - var memoryChart = $scope.memoryChart; - - stopRepeater(); - setUpdateRepeater(networkChart, cpuChart, memoryChart); - $('#refreshRateChange').show(); - $('#refreshRateChange').fadeOut(1500); - }; - - function startChartUpdate(networkChart, cpuChart, memoryChart) { - $q.all({ - stats: ContainerService.containerStats($transition$.params().id), - top: ContainerService.containerTop($transition$.params().id) - }) - .then(function success(data) { - var stats = data.stats; - $scope.processInfo = data.top; - if (stats.Networks.length === 0) { - $scope.state.networkStatsUnavailable = true; - } - updateNetworkChart(stats, networkChart); - updateMemoryChart(stats, memoryChart); - updateCPUChart(stats, cpuChart); - setUpdateRepeater(networkChart, cpuChart, memoryChart); - }) - .catch(function error(err) { stopRepeater(); - Notifications.error('Failure', err, 'Unable to retrieve container statistics'); - }); - } + setUpdateRepeater(networkChart, cpuChart, memoryChart); + $('#refreshRateChange').show(); + $('#refreshRateChange').fadeOut(1500); + }; - function setUpdateRepeater(networkChart, cpuChart, memoryChart) { - var refreshRate = $scope.state.refreshRate; - $scope.repeater = $interval(function() { + function startChartUpdate(networkChart, cpuChart, memoryChart) { $q.all({ stats: ContainerService.containerStats($transition$.params().id), - top: ContainerService.containerTop($transition$.params().id) + top: ContainerService.containerTop($transition$.params().id), }) - .then(function success(data) { - var stats = data.stats; - $scope.processInfo = data.top; - updateNetworkChart(stats, networkChart); - updateMemoryChart(stats, memoryChart); - updateCPUChart(stats, cpuChart); - }) - .catch(function error(err) { - stopRepeater(); - Notifications.error('Failure', err, 'Unable to retrieve container statistics'); - }); - }, refreshRate * 1000); - } - - function initCharts() { - var networkChartCtx = $('#networkChart'); - var networkChart = ChartService.CreateNetworkChart(networkChartCtx); - $scope.networkChart = networkChart; - - var cpuChartCtx = $('#cpuChart'); - var cpuChart = ChartService.CreateCPUChart(cpuChartCtx); - $scope.cpuChart = cpuChart; - - var memoryChartCtx = $('#memoryChart'); - var memoryChart = ChartService.CreateMemoryChart(memoryChartCtx); - $scope.memoryChart = memoryChart; - - startChartUpdate(networkChart, cpuChart, memoryChart); - } - - function initView() { - HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); - ContainerService.container($transition$.params().id) - .then(function success(data) { - $scope.container = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve container information'); - }); + .then(function success(data) { + var stats = data.stats; + $scope.processInfo = data.top; + if (stats.Networks.length === 0) { + $scope.state.networkStatsUnavailable = true; + } + updateNetworkChart(stats, networkChart); + updateMemoryChart(stats, memoryChart); + updateCPUChart(stats, cpuChart); + setUpdateRepeater(networkChart, cpuChart, memoryChart); + }) + .catch(function error(err) { + stopRepeater(); + Notifications.error('Failure', err, 'Unable to retrieve container statistics'); + }); + } - $document.ready(function() { - initCharts(); - }); - } + function setUpdateRepeater(networkChart, cpuChart, memoryChart) { + var refreshRate = $scope.state.refreshRate; + $scope.repeater = $interval(function () { + $q.all({ + stats: ContainerService.containerStats($transition$.params().id), + top: ContainerService.containerTop($transition$.params().id), + }) + .then(function success(data) { + var stats = data.stats; + $scope.processInfo = data.top; + updateNetworkChart(stats, networkChart); + updateMemoryChart(stats, memoryChart); + updateCPUChart(stats, cpuChart); + }) + .catch(function error(err) { + stopRepeater(); + Notifications.error('Failure', err, 'Unable to retrieve container statistics'); + }); + }, refreshRate * 1000); + } + + function initCharts() { + var networkChartCtx = $('#networkChart'); + var networkChart = ChartService.CreateNetworkChart(networkChartCtx); + $scope.networkChart = networkChart; + + var cpuChartCtx = $('#cpuChart'); + var cpuChart = ChartService.CreateCPUChart(cpuChartCtx); + $scope.cpuChart = cpuChart; + + var memoryChartCtx = $('#memoryChart'); + var memoryChart = ChartService.CreateMemoryChart(memoryChartCtx); + $scope.memoryChart = memoryChart; + + startChartUpdate(networkChart, cpuChart, memoryChart); + } + + function initView() { + HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); + ContainerService.container($transition$.params().id) + .then(function success(data) { + $scope.container = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve container information'); + }); + + $document.ready(function () { + initCharts(); + }); + } - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/containers/stats/containerstats.html b/app/docker/views/containers/stats/containerstats.html index a14def6e2d9e6..c4d800b9a1f8f 100644 --- a/app/docker/views/containers/stats/containerstats.html +++ b/app/docker/views/containers/stats/containerstats.html @@ -1,22 +1,21 @@ - Containers > {{ container.Name|trimcontainername }} > Stats + Containers > {{ container.Name | trimcontainername }} > Stats
- - +
- This view displays real-time statistics about the container {{ container.Name|trimcontainername }} as well as a list of the running processes - inside this container. + This view displays real-time statistics about the container {{ container.Name | trimcontainername }} as well as a list of the running processes inside this + container.
@@ -40,9 +39,7 @@
- - Network stats are unavailable for this container. - + Network stats are unavailable for this container.
@@ -52,7 +49,7 @@
-
+
@@ -62,7 +59,7 @@
-
+
@@ -85,9 +82,11 @@
diff --git a/app/docker/views/dashboard/dashboard.html b/app/docker/views/dashboard/dashboard.html index b40e9bc62cbf1..59c6e44d2c162 100644 --- a/app/docker/views/dashboard/dashboard.html +++ b/app/docker/views/dashboard/dashboard.html @@ -10,14 +10,19 @@
+ dismiss-action="dismissInformationPanel('docker-dashboard-info-01')" +>

- Portainer is connected to a node that is part of a Swarm cluster. Some resources located on other nodes in the cluster might not be available for management, have a look - at our agent setup for more details. + Portainer is connected to a node that is part of a Swarm cluster. Some resources located on other nodes in the cluster might not be available for management, have a look at + our agent setup for more details.

@@ -38,10 +43,12 @@ {{ endpoint.Name }} - {{ endpoint.Snapshots[0].TotalCPU }} - {{ endpoint.Snapshots[0].TotalMemory | humansize }} + {{ endpoint.Snapshots[0].TotalCPU }} {{ endpoint.Snapshots[0].TotalMemory | humansize }} - - {{ info.Swarm && info.Swarm.NodeID !== '' ? 'Swarm' : 'Standalone' }} {{ info.ServerVersion }} + Agent + + - {{ info.Swarm && info.Swarm.NodeID !== '' ? 'Swarm' : 'Standalone' }} {{ info.ServerVersion }} + + Agent @@ -55,9 +62,7 @@ - - - {{ tag }}{{ $last? '' : ', ' }} - + {{ tag }}{{ $last ? '' : ', ' }} @@ -109,7 +114,7 @@

-
+
{{ containers | runningcontainers }} running
{{ containers | stoppedcontainers }} stopped
diff --git a/app/docker/views/dashboard/dashboardController.js b/app/docker/views/dashboard/dashboardController.js index 5f1efa332aaea..9a83cdf4e31e4 100644 --- a/app/docker/views/dashboard/dashboardController.js +++ b/app/docker/views/dashboard/dashboardController.js @@ -1,42 +1,68 @@ -angular.module('portainer.docker') -.controller('DashboardController', ['$scope', '$q', 'ContainerService', 'ImageService', 'NetworkService', 'VolumeService', 'SystemService', 'ServiceService', 'StackService', 'EndpointService', 'Notifications', 'EndpointProvider', 'StateManager', -function ($scope, $q, ContainerService, ImageService, NetworkService, VolumeService, SystemService, ServiceService, StackService, EndpointService, Notifications, EndpointProvider, StateManager) { +angular.module('portainer.docker').controller('DashboardController', [ + '$scope', + '$q', + 'ContainerService', + 'ImageService', + 'NetworkService', + 'VolumeService', + 'SystemService', + 'ServiceService', + 'StackService', + 'EndpointService', + 'Notifications', + 'EndpointProvider', + 'StateManager', + function ( + $scope, + $q, + ContainerService, + ImageService, + NetworkService, + VolumeService, + SystemService, + ServiceService, + StackService, + EndpointService, + Notifications, + EndpointProvider, + StateManager + ) { + $scope.dismissInformationPanel = function (id) { + StateManager.dismissInformationPanel(id); + }; - $scope.dismissInformationPanel = function(id) { - StateManager.dismissInformationPanel(id); - }; + $scope.offlineMode = false; - $scope.offlineMode = false; + function initView() { + var endpointMode = $scope.applicationState.endpoint.mode; + var endpointId = EndpointProvider.endpointID(); - function initView() { - var endpointMode = $scope.applicationState.endpoint.mode; - var endpointId = EndpointProvider.endpointID(); + $q.all({ + containers: ContainerService.containers(1), + images: ImageService.images(false), + volumes: VolumeService.volumes(), + networks: NetworkService.networks(true, true, true), + services: endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' ? ServiceService.services() : [], + stacks: StackService.stacks(true, endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER', endpointId), + info: SystemService.info(), + endpoint: EndpointService.endpoint(endpointId), + }) + .then(function success(data) { + $scope.containers = data.containers; + $scope.images = data.images; + $scope.volumeCount = data.volumes.length; + $scope.networkCount = data.networks.length; + $scope.serviceCount = data.services.length; + $scope.stackCount = data.stacks.length; + $scope.info = data.info; + $scope.endpoint = data.endpoint; + $scope.offlineMode = EndpointProvider.offlineMode(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to load dashboard data'); + }); + } - $q.all({ - containers: ContainerService.containers(1), - images: ImageService.images(false), - volumes: VolumeService.volumes(), - networks: NetworkService.networks(true, true, true), - services: endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' ? ServiceService.services() : [], - stacks: StackService.stacks(true, endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER', endpointId), - info: SystemService.info(), - endpoint: EndpointService.endpoint(endpointId) - }) - .then(function success(data) { - $scope.containers = data.containers; - $scope.images = data.images; - $scope.volumeCount = data.volumes.length; - $scope.networkCount = data.networks.length; - $scope.serviceCount = data.services.length; - $scope.stackCount = data.stacks.length; - $scope.info = data.info; - $scope.endpoint = data.endpoint; - $scope.offlineMode = EndpointProvider.offlineMode(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to load dashboard data'); - }); - } - - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/events/events.html b/app/docker/views/events/events.html index d67e3fc44348e..98caa58da9f8c 100644 --- a/app/docker/views/events/events.html +++ b/app/docker/views/events/events.html @@ -9,11 +9,6 @@
- +
diff --git a/app/docker/views/events/eventsController.js b/app/docker/views/events/eventsController.js index f0d0ef68fbef3..8affeaf84eed5 100644 --- a/app/docker/views/events/eventsController.js +++ b/app/docker/views/events/eventsController.js @@ -1,21 +1,23 @@ import moment from 'moment'; -angular.module('portainer.docker') -.controller('EventsController', ['$scope', 'Notifications', 'SystemService', -function ($scope, Notifications, SystemService) { +angular.module('portainer.docker').controller('EventsController', [ + '$scope', + 'Notifications', + 'SystemService', + function ($scope, Notifications, SystemService) { + function initView() { + var from = moment().subtract(24, 'hour').unix(); + var to = moment().unix(); - function initView() { - var from = moment().subtract(24, 'hour').unix(); - var to = moment().unix(); + SystemService.events(from, to) + .then(function success(data) { + $scope.events = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to load events'); + }); + } - SystemService.events(from, to) - .then(function success(data) { - $scope.events = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to load events'); - }); - } - - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/host/host-browser-view/host-browser-view-controller.js b/app/docker/views/host/host-browser-view/host-browser-view-controller.js index 3f45fcc488090..4024fd152bb3d 100644 --- a/app/docker/views/host/host-browser-view/host-browser-view-controller.js +++ b/app/docker/views/host/host-browser-view/host-browser-view-controller.js @@ -1,17 +1,18 @@ angular.module('portainer.docker').controller('HostBrowserViewController', [ - 'SystemService', 'Notifications', + 'SystemService', + 'Notifications', function HostBrowserViewController(SystemService, Notifications) { var ctrl = this; ctrl.$onInit = $onInit; function $onInit() { SystemService.info() - .then(function onInfoLoaded(host) { - ctrl.host = host; - }) - .catch(function onError(err) { - Notifications.error('Unable to retrieve host information', err); - }); + .then(function onInfoLoaded(host) { + ctrl.host = host; + }) + .catch(function onError(err) { + Notifications.error('Unable to retrieve host information', err); + }); } - } + }, ]); diff --git a/app/docker/views/host/host-browser-view/host-browser-view.html b/app/docker/views/host/host-browser-view/host-browser-view.html index 2d87e4b2e7d8f..b8836c773fe2f 100644 --- a/app/docker/views/host/host-browser-view/host-browser-view.html +++ b/app/docker/views/host/host-browser-view/host-browser-view.html @@ -1,14 +1,12 @@ - Host > {{ $ctrl.host.Name }} > browse + Host > {{ $ctrl.host.Name }} > browse
- +
diff --git a/app/docker/views/host/host-browser-view/host-browser-view.js b/app/docker/views/host/host-browser-view/host-browser-view.js index 8d2bd1a207a21..4887dece2752f 100644 --- a/app/docker/views/host/host-browser-view/host-browser-view.js +++ b/app/docker/views/host/host-browser-view/host-browser-view.js @@ -1,4 +1,4 @@ angular.module('portainer.docker').component('hostBrowserView', { templateUrl: './host-browser-view.html', - controller: 'HostBrowserViewController' + controller: 'HostBrowserViewController', }); diff --git a/app/docker/views/host/host-job/host-job-controller.js b/app/docker/views/host/host-job/host-job-controller.js index 811509f7bffe3..9e34700c4bfa4 100644 --- a/app/docker/views/host/host-job/host-job-controller.js +++ b/app/docker/views/host/host-job/host-job-controller.js @@ -1,17 +1,18 @@ angular.module('portainer.docker').controller('HostJobController', [ - 'SystemService', 'Notifications', + 'SystemService', + 'Notifications', function HostJobController(SystemService, Notifications) { var ctrl = this; ctrl.$onInit = $onInit; function $onInit() { SystemService.info() - .then(function onInfoLoaded(host) { - ctrl.host = host; - }) - .catch(function onError(err) { - Notifications.error('Unable to retrieve host information', err); - }); + .then(function onInfoLoaded(host) { + ctrl.host = host; + }) + .catch(function onError(err) { + Notifications.error('Unable to retrieve host information', err); + }); } - } + }, ]); diff --git a/app/docker/views/host/host-job/host-job.js b/app/docker/views/host/host-job/host-job.js index 2436075ec7a30..c7959a63c0f1d 100644 --- a/app/docker/views/host/host-job/host-job.js +++ b/app/docker/views/host/host-job/host-job.js @@ -1,4 +1,4 @@ angular.module('portainer.docker').component('hostJobView', { templateUrl: './host-job.html', - controller: 'HostJobController' + controller: 'HostJobController', }); diff --git a/app/docker/views/host/host-view-controller.js b/app/docker/views/host/host-view-controller.js index 2bc6421b2da88..ebb5a6975f124 100644 --- a/app/docker/views/host/host-view-controller.js +++ b/app/docker/views/host/host-view-controller.js @@ -1,5 +1,12 @@ angular.module('portainer.docker').controller('HostViewController', [ - '$q', 'SystemService', 'Notifications', 'StateManager', 'AgentService', 'ContainerService', 'Authentication', 'EndpointProvider', + '$q', + 'SystemService', + 'Notifications', + 'StateManager', + 'AgentService', + 'ContainerService', + 'Authentication', + 'EndpointProvider', function HostViewController($q, SystemService, Notifications, StateManager, AgentService, ContainerService, Authentication, EndpointProvider) { var ctrl = this; @@ -7,8 +14,8 @@ angular.module('portainer.docker').controller('HostViewController', [ ctrl.state = { isAgent: false, - isAdmin : false, - offlineMode: false + isAdmin: false, + offlineMode: false, }; this.engineDetails = {}; @@ -27,28 +34,24 @@ angular.module('portainer.docker').controller('HostViewController', [ $q.all({ version: SystemService.version(), info: SystemService.info(), - jobs: ctrl.state.isAdmin ? ContainerService.containers(true, { label: ['io.portainer.job.endpoint'] }) : [] + jobs: ctrl.state.isAdmin ? ContainerService.containers(true, { label: ['io.portainer.job.endpoint'] }) : [], }) - .then(function success(data) { - ctrl.engineDetails = buildEngineDetails(data); - ctrl.hostDetails = buildHostDetails(data.info); - ctrl.state.offlineMode = EndpointProvider.offlineMode(); - ctrl.jobs = data.jobs; + .then(function success(data) { + ctrl.engineDetails = buildEngineDetails(data); + ctrl.hostDetails = buildHostDetails(data.info); + ctrl.state.offlineMode = EndpointProvider.offlineMode(); + ctrl.jobs = data.jobs; - if (ctrl.state.isAgent && agentApiVersion > 1) { - return AgentService.hostInfo(data.info.Hostname).then(function onHostInfoLoad(agentHostInfo) { - ctrl.devices = agentHostInfo.PCIDevices; - ctrl.disks = agentHostInfo.PhysicalDisks; - }); - } - }) - .catch(function error(err) { - Notifications.error( - 'Failure', - err, - 'Unable to retrieve engine details' - ); - }); + if (ctrl.state.isAgent && agentApiVersion > 1) { + return AgentService.hostInfo(data.info.Hostname).then(function onHostInfoLoad(agentHostInfo) { + ctrl.devices = agentHostInfo.PCIDevices; + ctrl.disks = agentHostInfo.PhysicalDisks; + }); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve engine details'); + }); } function buildEngineDetails(data) { @@ -61,7 +64,7 @@ angular.module('portainer.docker').controller('HostViewController', [ storageDriver: info.Driver, loggingDriver: info.LoggingDriver, volumePlugins: info.Plugins.Volume, - networkPlugins: info.Plugins.Network + networkPlugins: info.Plugins.Network, }; } @@ -70,13 +73,13 @@ angular.module('portainer.docker').controller('HostViewController', [ os: { arch: info.Architecture, type: info.OSType, - name: info.OperatingSystem + name: info.OperatingSystem, }, name: info.Name, kernelVersion: info.KernelVersion, totalCPU: info.NCPU, - totalMemory: info.MemTotal + totalMemory: info.MemTotal, }; } - } + }, ]); diff --git a/app/docker/views/host/host-view.js b/app/docker/views/host/host-view.js index d6171c666ea6a..eec3d03e54527 100644 --- a/app/docker/views/host/host-view.js +++ b/app/docker/views/host/host-view.js @@ -1,4 +1,4 @@ angular.module('portainer.docker').component('hostView', { templateUrl: './host-view.html', - controller: 'HostViewController' + controller: 'HostViewController', }); diff --git a/app/docker/views/images/build/buildImageController.js b/app/docker/views/images/build/buildImageController.js index 5f4aec6233e4f..0503642ff8fa0 100644 --- a/app/docker/views/images/build/buildImageController.js +++ b/app/docker/views/images/build/buildImageController.js @@ -1,94 +1,98 @@ -angular.module('portainer.docker') -.controller('BuildImageController', ['$scope', '$state', 'BuildService', 'Notifications', 'HttpRequestHelper', -function ($scope, $state, BuildService, Notifications, HttpRequestHelper) { +angular.module('portainer.docker').controller('BuildImageController', [ + '$scope', + '$state', + 'BuildService', + 'Notifications', + 'HttpRequestHelper', + function ($scope, $state, BuildService, Notifications, HttpRequestHelper) { + $scope.state = { + BuildType: 'editor', + actionInProgress: false, + activeTab: 0, + }; - $scope.state = { - BuildType: 'editor', - actionInProgress: false, - activeTab: 0 - }; + $scope.formValues = { + ImageNames: [{ Name: '' }], + UploadFile: null, + DockerFileContent: '', + URL: '', + Path: 'Dockerfile', + NodeName: null, + }; - $scope.formValues = { - ImageNames: [{ Name: '' }], - UploadFile: null, - DockerFileContent: '', - URL: '', - Path: 'Dockerfile', - NodeName: null - }; + $scope.addImageName = function () { + $scope.formValues.ImageNames.push({ Name: '' }); + }; - $scope.addImageName = function() { - $scope.formValues.ImageNames.push({ Name: '' }); - }; + $scope.removeImageName = function (index) { + $scope.formValues.ImageNames.splice(index, 1); + }; - $scope.removeImageName = function(index) { - $scope.formValues.ImageNames.splice(index, 1); - }; + function buildImageBasedOnBuildType(method, names) { + var buildType = $scope.state.BuildType; + var dockerfilePath = $scope.formValues.Path; - function buildImageBasedOnBuildType(method, names) { - var buildType = $scope.state.BuildType; - var dockerfilePath = $scope.formValues.Path; + if (buildType === 'upload') { + var file = $scope.formValues.UploadFile; + return BuildService.buildImageFromUpload(names, file, dockerfilePath); + } else if (buildType === 'url') { + var URL = $scope.formValues.URL; + return BuildService.buildImageFromURL(names, URL, dockerfilePath); + } else { + var dockerfileContent = $scope.formValues.DockerFileContent; + return BuildService.buildImageFromDockerfileContent(names, dockerfileContent); + } + } - if (buildType === 'upload') { - var file = $scope.formValues.UploadFile; - return BuildService.buildImageFromUpload(names, file, dockerfilePath); - } else if (buildType === 'url') { - var URL = $scope.formValues.URL; - return BuildService.buildImageFromURL(names, URL, dockerfilePath); - } else { - var dockerfileContent = $scope.formValues.DockerFileContent; - return BuildService.buildImageFromDockerfileContent(names, dockerfileContent); - } - } + $scope.buildImage = function () { + var buildType = $scope.state.BuildType; - $scope.buildImage = function() { - var buildType = $scope.state.BuildType; + if (buildType === 'editor' && $scope.formValues.DockerFileContent === '') { + $scope.state.formValidationError = 'Dockerfile content must not be empty'; + return; + } - if (buildType === 'editor' && $scope.formValues.DockerFileContent === '') { - $scope.state.formValidationError = 'Dockerfile content must not be empty'; - return; - } + $scope.state.actionInProgress = true; - $scope.state.actionInProgress = true; + var imageNames = $scope.formValues.ImageNames.filter(function filterNull(x) { + return x.Name; + }).map(function getNames(x) { + return x.Name; + }); - var imageNames = $scope.formValues.ImageNames.filter(function filterNull(x) { - return x.Name; - }).map(function getNames(x) { - return x.Name; - }); + var nodeName = $scope.formValues.NodeName; + HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - var nodeName = $scope.formValues.NodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); + buildImageBasedOnBuildType(buildType, imageNames) + .then(function success(data) { + $scope.buildLogs = data.buildLogs; + $scope.state.activeTab = 1; + if (data.hasError) { + Notifications.error('An error occured during build', { msg: 'Please check build logs output' }); + } else { + Notifications.success('Image successfully built'); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to build image'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + }; - buildImageBasedOnBuildType(buildType, imageNames) - .then(function success(data) { - $scope.buildLogs = data.buildLogs; - $scope.state.activeTab = 1; - if (data.hasError) { - Notifications.error('An error occured during build', { msg: 'Please check build logs output' }); - } else { - Notifications.success('Image successfully built'); - } - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to build image'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }; + $scope.validImageNames = function () { + for (var i = 0; i < $scope.formValues.ImageNames.length; i++) { + var item = $scope.formValues.ImageNames[i]; + if (item.Name !== '') { + return true; + } + } + return false; + }; - $scope.validImageNames = function() { - for (var i = 0; i < $scope.formValues.ImageNames.length; i++) { - var item = $scope.formValues.ImageNames[i]; - if (item.Name !== '') { - return true; - } - } - return false; - }; - - $scope.editorUpdate = function(cm) { - $scope.formValues.DockerFileContent = cm.getValue(); - }; -}]); + $scope.editorUpdate = function (cm) { + $scope.formValues.DockerFileContent = cm.getValue(); + }; + }, +]); diff --git a/app/docker/views/images/build/buildimage.html b/app/docker/views/images/build/buildimage.html index 7820c4b9bcb06..2bde8f32a4e80 100644 --- a/app/docker/views/images/build/buildimage.html +++ b/app/docker/views/images/build/buildimage.html @@ -1,8 +1,6 @@ - - Images > Build image - + Images > Build image
@@ -11,9 +9,7 @@ - - Builder - + Builder
Naming @@ -42,18 +38,21 @@
- A name must be specified in one of the following formats: name:tag, repository/name:tag or registryfqdn:port/repository/name:tag format. If you omit the tag the default latest value is assumed. + A name must be specified in one of the following formats: name:tag, repository/name:tag or + registryfqdn:port/repository/name:tag format. If you omit the tag the default latest value is assumed.
-
+
name - - + +
@@ -74,10 +73,10 @@ Build method
-
+
- +
- Specify the URL to a Dockerfile, a tarball or a public Git repository (suffixed by .git). When using a tarball or a Git repository URL, the root folder will be used as the build context. + Specify the URL to a Dockerfile, a tarball or a public Git repository (suffixed by .git). When using a tarball or a Git repository URL, the root folder + will be used as the build context.
- +
@@ -189,7 +191,7 @@
- +
@@ -199,9 +201,7 @@ Deployment
- - +
@@ -210,12 +210,16 @@
- @@ -226,9 +230,7 @@ - - Output - + Output
               

{{ line }}

No build output available.

diff --git a/app/docker/views/images/edit/image.html b/app/docker/views/images/edit/image.html index 5dcc3ba261ff4..ba3de195a274c 100644 --- a/app/docker/views/images/edit/image.html +++ b/app/docker/views/images/edit/image.html @@ -1,4 +1,4 @@ - + Images > {{ image.Id }} @@ -13,8 +13,8 @@
-
-
+
+
{{ tag }} @@ -34,9 +34,9 @@
- Note: you can click on the upload icon to push an image - or on the download icon to pull an image - or on the trash icon to delete a tag. + Note: you can click on the upload icon to push an image or on the download icon + to pull an image or on the trash icon to delete a + tag.
@@ -63,10 +63,7 @@ - +
@@ -97,29 +94,39 @@ ID {{ image.Id }} - - + Parent - {{ image.Parent }} + {{ image.Parent }} Size - {{ image.VirtualSize|humansize }} + {{ image.VirtualSize | humansize }} Created - {{ image.Created|getisodate }} + {{ image.Created | getisodate }} Build - Docker {{ image.DockerVersion }} on {{ image.Os}}, {{ image.Architecture }} + Docker {{ image.DockerVersion }} on {{ image.Os }}, {{ image.Architecture }} Author @@ -141,11 +148,15 @@ CMD - {{ image.Command|command }} + {{ image.Command | command }} ENTRYPOINT - {{ image.Entrypoint|command }} + {{ image.Entrypoint | command }} EXPOSE @@ -179,7 +190,6 @@
-
@@ -188,7 +198,7 @@ - - - @@ -56,7 +58,8 @@ resource-id="network.Id" resource-control="network.ResourceControl" resource-type="'network'" - disable-ownership-change="isSystemNetwork()"> + disable-ownership-change="isSystemNetwork()" +> @@ -78,7 +81,6 @@ -
@@ -94,12 +96,16 @@
- + diff --git a/app/docker/views/networks/edit/networkController.js b/app/docker/views/networks/edit/networkController.js index 294c76c5e55ad..06431ad1a1077 100644 --- a/app/docker/views/networks/edit/networkController.js +++ b/app/docker/views/networks/edit/networkController.js @@ -1,96 +1,112 @@ -angular.module('portainer.docker') -.controller('NetworkController', ['$scope', '$state', '$transition$', '$filter', 'NetworkService', 'Container', 'Notifications', 'HttpRequestHelper', 'NetworkHelper', -function ($scope, $state, $transition$, $filter, NetworkService, Container, Notifications, HttpRequestHelper, NetworkHelper) { - - $scope.removeNetwork = function removeNetwork() { - NetworkService.remove($transition$.params().id, $transition$.params().id) - .then(function success() { - Notifications.success('Network removed', $transition$.params().id); - $state.go('docker.networks', {}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove network'); - }); - }; +angular.module('portainer.docker').controller('NetworkController', [ + '$scope', + '$state', + '$transition$', + '$filter', + 'NetworkService', + 'Container', + 'Notifications', + 'HttpRequestHelper', + 'NetworkHelper', + function ($scope, $state, $transition$, $filter, NetworkService, Container, Notifications, HttpRequestHelper, NetworkHelper) { + $scope.removeNetwork = function removeNetwork() { + NetworkService.remove($transition$.params().id, $transition$.params().id) + .then(function success() { + Notifications.success('Network removed', $transition$.params().id); + $state.go('docker.networks', {}); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove network'); + }); + }; - $scope.containerLeaveNetwork = function containerLeaveNetwork(network, container) { - HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName); - NetworkService.disconnectContainer($transition$.params().id, container.Id, false) - .then(function success() { - Notifications.success('Container left network', $transition$.params().id); - $state.go('docker.networks.network', { id: network.Id }, { reload: true }); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to disconnect container from network'); - }); - }; + $scope.containerLeaveNetwork = function containerLeaveNetwork(network, container) { + HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName); + NetworkService.disconnectContainer($transition$.params().id, container.Id, false) + .then(function success() { + Notifications.success('Container left network', $transition$.params().id); + $state.go('docker.networks.network', { id: network.Id }, { reload: true }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to disconnect container from network'); + }); + }; - $scope.isSystemNetwork = function() { - return $scope.network && NetworkHelper.isSystemNetwork($scope.network); - } + $scope.isSystemNetwork = function () { + return $scope.network && NetworkHelper.isSystemNetwork($scope.network); + }; - $scope.allowRemove = function() { - return !$scope.isSystemNetwork(); - }; + $scope.allowRemove = function () { + return !$scope.isSystemNetwork(); + }; - function filterContainersInNetwork(network, containers) { - var containersInNetwork = []; - containers.forEach(function(container) { - var containerInNetwork = network.Containers[container.Id]; - if (containerInNetwork) { - containerInNetwork.Id = container.Id; - // Name is not available in Docker 1.9 - if (!containerInNetwork.Name) { - containerInNetwork.Name = $filter('trimcontainername')(container.Names[0]); + function filterContainersInNetwork(network, containers) { + var containersInNetwork = []; + containers.forEach(function (container) { + var containerInNetwork = network.Containers[container.Id]; + if (containerInNetwork) { + containerInNetwork.Id = container.Id; + // Name is not available in Docker 1.9 + if (!containerInNetwork.Name) { + containerInNetwork.Name = $filter('trimcontainername')(container.Names[0]); + } + containersInNetwork.push(containerInNetwork); } - containersInNetwork.push(containerInNetwork); - } - }); - $scope.containersInNetwork = containersInNetwork; - } + }); + $scope.containersInNetwork = containersInNetwork; + } - function getContainersInNetwork(network) { - var apiVersion = $scope.applicationState.endpoint.apiVersion; - if (network.Containers) { - if (apiVersion < 1.24) { - Container.query({}, function success(data) { - var containersInNetwork = data.filter(function filter(container) { - if (container.HostConfig.NetworkMode === network.Name) { - return container; + function getContainersInNetwork(network) { + var apiVersion = $scope.applicationState.endpoint.apiVersion; + if (network.Containers) { + if (apiVersion < 1.24) { + Container.query( + {}, + function success(data) { + var containersInNetwork = data.filter(function filter(container) { + if (container.HostConfig.NetworkMode === network.Name) { + return container; + } + }); + filterContainersInNetwork(network, containersInNetwork); + }, + function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve containers in network'); } - }); - filterContainersInNetwork(network, containersInNetwork); - }, function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve containers in network'); - }); - } else { - Container.query({ - filters: { network: [$transition$.params().id] } - }, function success(data) { - filterContainersInNetwork(network, data); - }, function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve containers in network'); - }); + ); + } else { + Container.query( + { + filters: { network: [$transition$.params().id] }, + }, + function success(data) { + filterContainersInNetwork(network, data); + }, + function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve containers in network'); + } + ); + } } } - } - function initView() { - var nodeName = $transition$.params().nodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - $scope.nodeName = nodeName; - NetworkService.network($transition$.params().id) - .then(function success(data) { - $scope.network = data; - var endpointProvider = $scope.applicationState.endpoint.mode.provider; - if (endpointProvider !== 'VMWARE_VIC') { - getContainersInNetwork(data); - } - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve network info'); - }); - } + function initView() { + var nodeName = $transition$.params().nodeName; + HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); + $scope.nodeName = nodeName; + NetworkService.network($transition$.params().id) + .then(function success(data) { + $scope.network = data; + var endpointProvider = $scope.applicationState.endpoint.mode.provider; + if (endpointProvider !== 'VMWARE_VIC') { + getContainersInNetwork(data); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve network info'); + }); + } - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/networks/networks.html b/app/docker/views/networks/networks.html index 497d95cde5356..737063089f342 100644 --- a/app/docker/views/networks/networks.html +++ b/app/docker/views/networks/networks.html @@ -10,14 +10,16 @@
diff --git a/app/docker/views/networks/networksController.js b/app/docker/views/networks/networksController.js index 6beb8da73c1b3..be66b64b90ad1 100644 --- a/app/docker/views/networks/networksController.js +++ b/app/docker/views/networks/networksController.js @@ -1,80 +1,87 @@ import _ from 'lodash-es'; -angular.module('portainer.docker') -.controller('NetworksController', ['$q', '$scope', '$state', 'NetworkService', 'Notifications', 'HttpRequestHelper', 'EndpointProvider', 'AgentService', -function ($q, $scope, $state, NetworkService, Notifications, HttpRequestHelper, EndpointProvider, AgentService) { - - $scope.removeAction = function (selectedItems) { - var actionCount = selectedItems.length; - angular.forEach(selectedItems, function (network) { - HttpRequestHelper.setPortainerAgentTargetHeader(network.NodeName); - NetworkService.remove(network.Id) - .then(function success() { - Notifications.success('Network successfully removed', network.Name); - var index = $scope.networks.indexOf(network); - $scope.networks.splice(index, 1); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove network'); - }) - .finally(function final() { - --actionCount; - if (actionCount === 0) { - $state.reload(); - } +angular.module('portainer.docker').controller('NetworksController', [ + '$q', + '$scope', + '$state', + 'NetworkService', + 'Notifications', + 'HttpRequestHelper', + 'EndpointProvider', + 'AgentService', + function ($q, $scope, $state, NetworkService, Notifications, HttpRequestHelper, EndpointProvider, AgentService) { + $scope.removeAction = function (selectedItems) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (network) { + HttpRequestHelper.setPortainerAgentTargetHeader(network.NodeName); + NetworkService.remove(network.Id) + .then(function success() { + Notifications.success('Network successfully removed', network.Name); + var index = $scope.networks.indexOf(network); + $scope.networks.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove network'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); - }); - }; - - $scope.offlineMode = false; - - $scope.getNetworks = getNetworks; + }; - function groupSwarmNetworksManagerNodesFirst(networks, agents) { - const getRole = (item) => _.find(agents, (agent) => agent.NodeName === item.NodeName).NodeRole; + $scope.offlineMode = false; - const nonSwarmNetworks = _.remove(networks, (item) => item.Scope !== 'swarm') - const grouped = _.toArray(_.groupBy(networks, (item) => item.Id)); - const sorted = _.map(grouped, (arr) => _.sortBy(arr, (item) => getRole(item))); - const arr = _.map(sorted, (a) => { - const item = a[0]; - for (let i = 1; i < a.length; i++) { - item.Subs.push(a[i]); - } - return item; - }); - const res = _.concat(arr, ...nonSwarmNetworks); - return res; - } + $scope.getNetworks = getNetworks; - function getNetworks() { - const req = { - networks: NetworkService.networks(true, true, true) - }; + function groupSwarmNetworksManagerNodesFirst(networks, agents) { + const getRole = (item) => _.find(agents, (agent) => agent.NodeName === item.NodeName).NodeRole; - if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { - req.agents = AgentService.agents(); + const nonSwarmNetworks = _.remove(networks, (item) => item.Scope !== 'swarm'); + const grouped = _.toArray(_.groupBy(networks, (item) => item.Id)); + const sorted = _.map(grouped, (arr) => _.sortBy(arr, (item) => getRole(item))); + const arr = _.map(sorted, (a) => { + const item = a[0]; + for (let i = 1; i < a.length; i++) { + item.Subs.push(a[i]); + } + return item; + }); + const res = _.concat(arr, ...nonSwarmNetworks); + return res; } - $q.all(req) - .then((data) => { - $scope.offlineMode = EndpointProvider.offlineMode(); - const networks = _.forEach(data.networks, (item) => item.Subs = []); + function getNetworks() { + const req = { + networks: NetworkService.networks(true, true, true), + }; + if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { - $scope.networks = groupSwarmNetworksManagerNodesFirst(data.networks, data.agents); - } else { - $scope.networks = networks; + req.agents = AgentService.agents(); } - }) - .catch((err) => { - $scope.networks = []; - Notifications.error('Failure', err, 'Unable to retrieve networks'); - }); - } - function initView() { - getNetworks(); - } + $q.all(req) + .then((data) => { + $scope.offlineMode = EndpointProvider.offlineMode(); + const networks = _.forEach(data.networks, (item) => (item.Subs = [])); + if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { + $scope.networks = groupSwarmNetworksManagerNodesFirst(data.networks, data.agents); + } else { + $scope.networks = networks; + } + }) + .catch((err) => { + $scope.networks = []; + Notifications.error('Failure', err, 'Unable to retrieve networks'); + }); + } + + function initView() { + getNetworks(); + } - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/nodes/node-browser/node-browser-controller.js b/app/docker/views/nodes/node-browser/node-browser-controller.js index 5c55c3e1be472..e34fcef5af734 100644 --- a/app/docker/views/nodes/node-browser/node-browser-controller.js +++ b/app/docker/views/nodes/node-browser/node-browser-controller.js @@ -1,5 +1,8 @@ angular.module('portainer.docker').controller('NodeBrowserController', [ - '$stateParams', 'NodeService', 'HttpRequestHelper', 'Notifications', + '$stateParams', + 'NodeService', + 'HttpRequestHelper', + 'Notifications', function NodeBrowserController($stateParams, NodeService, HttpRequestHelper, Notifications) { var ctrl = this; ctrl.$onInit = $onInit; @@ -8,13 +11,13 @@ angular.module('portainer.docker').controller('NodeBrowserController', [ ctrl.nodeId = $stateParams.id; NodeService.node(ctrl.nodeId) - .then(function onNodeLoaded(node) { - HttpRequestHelper.setPortainerAgentTargetHeader(node.Hostname); - ctrl.node = node; - }) - .catch(function onError(err) { - Notifications.error('Unable to retrieve host information', err); - }); + .then(function onNodeLoaded(node) { + HttpRequestHelper.setPortainerAgentTargetHeader(node.Hostname); + ctrl.node = node; + }) + .catch(function onError(err) { + Notifications.error('Unable to retrieve host information', err); + }); } - } + }, ]); diff --git a/app/docker/views/nodes/node-browser/node-browser.html b/app/docker/views/nodes/node-browser/node-browser.html index 2edeae199488f..7e9dfe3cee43b 100644 --- a/app/docker/views/nodes/node-browser/node-browser.html +++ b/app/docker/views/nodes/node-browser/node-browser.html @@ -1,14 +1,12 @@ - Swarm > {{ $ctrl.node.Hostname }} > browse + Swarm > {{ $ctrl.node.Hostname }} > browse
- +
diff --git a/app/docker/views/nodes/node-browser/node-browser.js b/app/docker/views/nodes/node-browser/node-browser.js index a43fa97feadb7..b073a2a40ad9d 100644 --- a/app/docker/views/nodes/node-browser/node-browser.js +++ b/app/docker/views/nodes/node-browser/node-browser.js @@ -1,4 +1,4 @@ angular.module('portainer.docker').component('nodeBrowserView', { templateUrl: './node-browser.html', - controller: 'NodeBrowserController' + controller: 'NodeBrowserController', }); diff --git a/app/docker/views/nodes/node-details/node-details-view-controller.js b/app/docker/views/nodes/node-details/node-details-view-controller.js index 3cc0a6e90e9a3..020c3b5865048 100644 --- a/app/docker/views/nodes/node-details/node-details-view-controller.js +++ b/app/docker/views/nodes/node-details/node-details-view-controller.js @@ -1,5 +1,11 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [ - '$q', '$stateParams', 'NodeService', 'StateManager', 'AgentService', 'ContainerService', 'Authentication', + '$q', + '$stateParams', + 'NodeService', + 'StateManager', + 'AgentService', + 'ContainerService', + 'Authentication', function NodeDetailsViewController($q, $stateParams, NodeService, StateManager, AgentService, ContainerService, Authentication) { var ctrl = this; @@ -7,7 +13,7 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [ ctrl.state = { isAgent: false, - isAdmin: false + isAdmin: false, }; function initView() { @@ -21,9 +27,8 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [ var nodeId = $stateParams.id; $q.all({ node: NodeService.node(nodeId), - jobs: fetchJobs ? ContainerService.containers(true, { label: ['io.portainer.job.endpoint'] }) : [] - }) - .then(function (data) { + jobs: fetchJobs ? ContainerService.containers(true, { label: ['io.portainer.job.endpoint'] }) : [], + }).then(function (data) { var node = data.node; ctrl.originalNode = node; ctrl.hostDetails = buildHostDetails(node); @@ -37,8 +42,7 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [ return; } - AgentService.hostInfo(node.Hostname) - .then(function onHostInfoLoad(agentHostInfo) { + AgentService.hostInfo(node.Hostname).then(function onHostInfoLoad(agentHostInfo) { ctrl.devices = agentHostInfo.PCIDevices; ctrl.disks = agentHostInfo.PhysicalDisks; }); @@ -50,11 +54,11 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [ return { os: { arch: node.PlatformArchitecture, - type: node.PlatformOS + type: node.PlatformOS, }, name: node.Hostname, totalCPU: node.CPUs / 1e9, - totalMemory: node.Memory + totalMemory: node.Memory, }; } @@ -74,18 +78,18 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [ managerAddress: node.ManagerAddr, availability: node.Availability, status: node.Status, - nodeLabels: node.Labels + nodeLabels: node.Labels, }; } function transformPlugins(pluginsList, type) { return pluginsList - .filter(function(plugin) { - return plugin.Type === type; - }) - .map(function(plugin) { - return plugin.Name; - }); + .filter(function (plugin) { + return plugin.Type === type; + }) + .map(function (plugin) { + return plugin.Name; + }); } - } + }, ]); diff --git a/app/docker/views/nodes/node-details/node-details-view.html b/app/docker/views/nodes/node-details/node-details-view.html index c2dd7c7e20867..571d2dde00722 100644 --- a/app/docker/views/nodes/node-details/node-details-view.html +++ b/app/docker/views/nodes/node-details/node-details-view.html @@ -12,8 +12,5 @@ job-url="docker.nodes.node.job" jobs="$ctrl.jobs" > - + diff --git a/app/docker/views/nodes/node-details/node-details-view.js b/app/docker/views/nodes/node-details/node-details-view.js index e0ba268cf3585..929b4f4adcd45 100644 --- a/app/docker/views/nodes/node-details/node-details-view.js +++ b/app/docker/views/nodes/node-details/node-details-view.js @@ -1,4 +1,4 @@ angular.module('portainer.docker').component('nodeDetailsView', { templateUrl: './node-details-view.html', - controller: 'NodeDetailsViewController' + controller: 'NodeDetailsViewController', }); diff --git a/app/docker/views/nodes/node-job/node-job-controller.js b/app/docker/views/nodes/node-job/node-job-controller.js index 9f1173d091d09..f4f47d3630934 100644 --- a/app/docker/views/nodes/node-job/node-job-controller.js +++ b/app/docker/views/nodes/node-job/node-job-controller.js @@ -1,5 +1,8 @@ angular.module('portainer.docker').controller('NodeJobController', [ - '$stateParams', 'NodeService', 'HttpRequestHelper', 'Notifications', + '$stateParams', + 'NodeService', + 'HttpRequestHelper', + 'Notifications', function NodeJobController($stateParams, NodeService, HttpRequestHelper, Notifications) { var ctrl = this; ctrl.$onInit = $onInit; @@ -8,13 +11,13 @@ angular.module('portainer.docker').controller('NodeJobController', [ ctrl.nodeId = $stateParams.id; NodeService.node(ctrl.nodeId) - .then(function onNodeLoaded(node) { - HttpRequestHelper.setPortainerAgentTargetHeader(node.Hostname); - ctrl.node = node; - }) - .catch(function onError(err) { - Notifications.error('Unable to retrieve host information', err); - }); + .then(function onNodeLoaded(node) { + HttpRequestHelper.setPortainerAgentTargetHeader(node.Hostname); + ctrl.node = node; + }) + .catch(function onError(err) { + Notifications.error('Unable to retrieve host information', err); + }); } - } + }, ]); diff --git a/app/docker/views/nodes/node-job/node-job.html b/app/docker/views/nodes/node-job/node-job.html index 90ae92d14af39..a842fa746644f 100644 --- a/app/docker/views/nodes/node-job/node-job.html +++ b/app/docker/views/nodes/node-job/node-job.html @@ -1,7 +1,7 @@ - Swarm > {{ $ctrl.node.Hostname }} > execute job + Swarm > {{ $ctrl.node.Hostname }} > execute job @@ -9,9 +9,7 @@
- +
diff --git a/app/docker/views/nodes/node-job/node-job.js b/app/docker/views/nodes/node-job/node-job.js index 334752fffc20b..b659b4e71d533 100644 --- a/app/docker/views/nodes/node-job/node-job.js +++ b/app/docker/views/nodes/node-job/node-job.js @@ -1,4 +1,4 @@ angular.module('portainer.docker').component('nodeJobView', { templateUrl: './node-job.html', - controller: 'NodeJobController' + controller: 'NodeJobController', }); diff --git a/app/docker/views/secrets/create/createSecretController.js b/app/docker/views/secrets/create/createSecretController.js index b488ed37d4743..cbd654c772358 100644 --- a/app/docker/views/secrets/create/createSecretController.js +++ b/app/docker/views/secrets/create/createSecretController.js @@ -1,89 +1,95 @@ import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel'; -angular.module('portainer.docker') -.controller('CreateSecretController', ['$scope', '$state', 'Notifications', 'SecretService', 'LabelHelper', 'Authentication', 'ResourceControlService', 'FormValidator', -function ($scope, $state, Notifications, SecretService, LabelHelper, Authentication, ResourceControlService, FormValidator) { +angular.module('portainer.docker').controller('CreateSecretController', [ + '$scope', + '$state', + 'Notifications', + 'SecretService', + 'LabelHelper', + 'Authentication', + 'ResourceControlService', + 'FormValidator', + function ($scope, $state, Notifications, SecretService, LabelHelper, Authentication, ResourceControlService, FormValidator) { + $scope.formValues = { + Name: '', + Data: '', + Labels: [], + encodeSecret: true, + AccessControlData: new AccessControlFormData(), + }; - $scope.formValues = { - Name: '', - Data: '', - Labels: [], - encodeSecret: true, - AccessControlData: new AccessControlFormData() - }; + $scope.state = { + formValidationError: '', + actionInProgress: false, + }; - $scope.state = { - formValidationError: '', - actionInProgress: false - }; + $scope.addLabel = function () { + $scope.formValues.Labels.push({ key: '', value: '' }); + }; - $scope.addLabel = function() { - $scope.formValues.Labels.push({ key: '', value: ''}); - }; + $scope.removeLabel = function (index) { + $scope.formValues.Labels.splice(index, 1); + }; - $scope.removeLabel = function(index) { - $scope.formValues.Labels.splice(index, 1); - }; - - function prepareLabelsConfig(config) { - config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); - } + function prepareLabelsConfig(config) { + config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); + } - function prepareSecretData(config) { - if ($scope.formValues.encodeSecret) { - config.Data = btoa(unescape(encodeURIComponent($scope.formValues.Data))); - } else { - config.Data = $scope.formValues.Data; + function prepareSecretData(config) { + if ($scope.formValues.encodeSecret) { + config.Data = btoa(unescape(encodeURIComponent($scope.formValues.Data))); + } else { + config.Data = $scope.formValues.Data; + } } - } - function prepareConfiguration() { - var config = {}; - config.Name = $scope.formValues.Name; - prepareSecretData(config); - prepareLabelsConfig(config); - return config; - } + function prepareConfiguration() { + var config = {}; + config.Name = $scope.formValues.Name; + prepareSecretData(config); + prepareLabelsConfig(config); + return config; + } - function validateForm(accessControlData, isAdmin) { - $scope.state.formValidationError = ''; - var error = ''; - error = FormValidator.validateAccessControl(accessControlData, isAdmin); + function validateForm(accessControlData, isAdmin) { + $scope.state.formValidationError = ''; + var error = ''; + error = FormValidator.validateAccessControl(accessControlData, isAdmin); - if (error) { - $scope.state.formValidationError = error; - return false; + if (error) { + $scope.state.formValidationError = error; + return false; + } + return true; } - return true; - } - $scope.create = function () { + $scope.create = function () { + const accessControlData = $scope.formValues.AccessControlData; + const userDetails = Authentication.getUserDetails(); + const isAdmin = Authentication.isAdmin(); - const accessControlData = $scope.formValues.AccessControlData; - const userDetails = Authentication.getUserDetails(); - const isAdmin = Authentication.isAdmin(); - - if (!validateForm(accessControlData, isAdmin)) { - return; - } + if (!validateForm(accessControlData, isAdmin)) { + return; + } - $scope.state.actionInProgress = true; - var secretConfiguration = prepareConfiguration(); - SecretService.create(secretConfiguration) - .then(function success(data) { - const userId = userDetails.ID; - const resourceControl = data.Portainer.ResourceControl; - return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl); - }) - .then(function success() { - Notifications.success('Secret successfully created'); - $state.go('docker.secrets', {}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to create secret'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }; -}]); + $scope.state.actionInProgress = true; + var secretConfiguration = prepareConfiguration(); + SecretService.create(secretConfiguration) + .then(function success(data) { + const userId = userDetails.ID; + const resourceControl = data.Portainer.ResourceControl; + return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl); + }) + .then(function success() { + Notifications.success('Secret successfully created'); + $state.go('docker.secrets', {}, { reload: true }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to create secret'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + }; + }, +]); diff --git a/app/docker/views/secrets/create/createsecret.html b/app/docker/views/secrets/create/createsecret.html index 167e373f77129..fae2453fad055 100644 --- a/app/docker/views/secrets/create/createsecret.html +++ b/app/docker/views/secrets/create/createsecret.html @@ -1,8 +1,6 @@ - - Secrets > Add secret - + Secrets > Add secret
@@ -14,7 +12,7 @@
- +
@@ -33,9 +31,7 @@ Encode secret - +
@@ -43,20 +39,18 @@
- - add label - + add label
name - +
value - +
- diff --git a/app/docker/views/secrets/edit/secret.html b/app/docker/views/secrets/edit/secret.html index 857820d18f11a..6375294eb4e1a 100644 --- a/app/docker/views/secrets/edit/secret.html +++ b/app/docker/views/secrets/edit/secret.html @@ -24,7 +24,9 @@
@@ -54,10 +56,6 @@ - + diff --git a/app/docker/views/secrets/edit/secretController.js b/app/docker/views/secrets/edit/secretController.js index 15aecfc1f2e58..69fa6077c4f71 100644 --- a/app/docker/views/secrets/edit/secretController.js +++ b/app/docker/views/secrets/edit/secretController.js @@ -1,27 +1,31 @@ -angular.module('portainer.docker') -.controller('SecretController', ['$scope', '$transition$', '$state', 'SecretService', 'Notifications', -function ($scope, $transition$, $state, SecretService, Notifications) { +angular.module('portainer.docker').controller('SecretController', [ + '$scope', + '$transition$', + '$state', + 'SecretService', + 'Notifications', + function ($scope, $transition$, $state, SecretService, Notifications) { + $scope.removeSecret = function removeSecret(secretId) { + SecretService.remove(secretId) + .then(function success() { + Notifications.success('Secret successfully removed'); + $state.go('docker.secrets', {}); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove secret'); + }); + }; - $scope.removeSecret = function removeSecret(secretId) { - SecretService.remove(secretId) - .then(function success() { - Notifications.success('Secret successfully removed'); - $state.go('docker.secrets', {}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove secret'); - }); - }; + function initView() { + SecretService.secret($transition$.params().id) + .then(function success(data) { + $scope.secret = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve secret details'); + }); + } - function initView() { - SecretService.secret($transition$.params().id) - .then(function success(data) { - $scope.secret = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve secret details'); - }); - } - - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/secrets/secrets.html b/app/docker/views/secrets/secrets.html index 76c8205d71b49..a6ddde5afcc86 100644 --- a/app/docker/views/secrets/secrets.html +++ b/app/docker/views/secrets/secrets.html @@ -10,12 +10,14 @@
diff --git a/app/docker/views/secrets/secretsController.js b/app/docker/views/secrets/secretsController.js index 98f44e5e32976..69d22b196d45a 100644 --- a/app/docker/views/secrets/secretsController.js +++ b/app/docker/views/secrets/secretsController.js @@ -1,44 +1,47 @@ -angular.module('portainer.docker') -.controller('SecretsController', ['$scope', '$state', 'SecretService', 'Notifications', -function ($scope, $state, SecretService, Notifications) { - - $scope.removeAction = function (selectedItems) { - var actionCount = selectedItems.length; - angular.forEach(selectedItems, function (secret) { - SecretService.remove(secret.Id) - .then(function success() { - Notifications.success('Secret successfully removed', secret.Name); - var index = $scope.secrets.indexOf(secret); - $scope.secrets.splice(index, 1); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove secret'); - }) - .finally(function final() { - --actionCount; - if (actionCount === 0) { - $state.reload(); - } +angular.module('portainer.docker').controller('SecretsController', [ + '$scope', + '$state', + 'SecretService', + 'Notifications', + function ($scope, $state, SecretService, Notifications) { + $scope.removeAction = function (selectedItems) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (secret) { + SecretService.remove(secret.Id) + .then(function success() { + Notifications.success('Secret successfully removed', secret.Name); + var index = $scope.secrets.indexOf(secret); + $scope.secrets.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove secret'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); - }); - }; + }; - $scope.getSecrets = getSecrets; + $scope.getSecrets = getSecrets; - function getSecrets() { - SecretService.secrets() - .then(function success(data) { - $scope.secrets = data; - }) - .catch(function error(err) { - $scope.secrets = []; - Notifications.error('Failure', err, 'Unable to retrieve secrets'); - }); - } + function getSecrets() { + SecretService.secrets() + .then(function success(data) { + $scope.secrets = data; + }) + .catch(function error(err) { + $scope.secrets = []; + Notifications.error('Failure', err, 'Unable to retrieve secrets'); + }); + } - function initView() { - getSecrets(); - } + function initView() { + getSecrets(); + } - initView(); -}]); + initView(); + }, +]); diff --git a/app/docker/views/services/create/createServiceController.js b/app/docker/views/services/create/createServiceController.js index 477fb6f6546f1..212519bd55c21 100644 --- a/app/docker/views/services/create/createServiceController.js +++ b/app/docker/views/services/create/createServiceController.js @@ -2,529 +2,584 @@ import _ from 'lodash-es'; import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel'; import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; -require('./includes/update-restart.html') -require('./includes/secret.html') -require('./includes/config.html') -require('./includes/resources-placement.html') - -angular.module('portainer.docker') -.controller('CreateServiceController', ['$q', '$scope', '$state', '$timeout', 'Service', 'ServiceHelper', 'ConfigService', 'ConfigHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'FormValidator', 'PluginService', 'RegistryService', 'HttpRequestHelper', 'NodeService', 'SettingsService', 'WebhookService','EndpointProvider', -function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, ConfigHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, FormValidator, PluginService, RegistryService, HttpRequestHelper, NodeService, SettingsService, WebhookService,EndpointProvider) { - - $scope.formValues = { - Name: '', - RegistryModel: new PorImageRegistryModel(), - Mode: 'replicated', - Replicas: 1, - Command: '', - EntryPoint: '', - WorkingDir: '', - User: '', - Env: [], - Labels: [], - ContainerLabels: [], - Volumes: [], - Network: '', - ExtraNetworks: [], - HostsEntries: [], - Ports: [], - Parallelism: 1, - PlacementConstraints: [], - PlacementPreferences: [], - UpdateDelay: '0s', - UpdateOrder: 'stop-first', - FailureAction: 'pause', - Secrets: [], - Configs: [], - AccessControlData: new AccessControlFormData(), - CpuLimit: 0, - CpuReservation: 0, - MemoryLimit: 0, - MemoryReservation: 0, - MemoryLimitUnit: 'MB', - MemoryReservationUnit: 'MB', - RestartCondition: 'any', - RestartDelay: '5s', - RestartMaxAttempts: 0, - RestartWindow: '0s', - LogDriverName: '', - LogDriverOpts: [], - Webhook: false - }; - - $scope.state = { - formValidationError: '', - actionInProgress: false - }; - - $scope.refreshSlider = function () { - $timeout(function () { - $scope.$broadcast('rzSliderForceRender'); - }); - }; - - $scope.addPortBinding = function() { - $scope.formValues.Ports.push({ PublishedPort: '', TargetPort: '', Protocol: 'tcp', PublishMode: 'ingress' }); - }; - - $scope.removePortBinding = function(index) { - $scope.formValues.Ports.splice(index, 1); - }; - - $scope.addExtraNetwork = function() { - $scope.formValues.ExtraNetworks.push({ Name: '' }); - }; - - $scope.removeExtraNetwork = function(index) { - $scope.formValues.ExtraNetworks.splice(index, 1); - }; - - $scope.addHostsEntry = function() { - $scope.formValues.HostsEntries.push({}); - }; - - $scope.removeHostsEntry = function(index) { - $scope.formValues.HostsEntries.splice(index, 1); - }; - - $scope.addVolume = function() { - $scope.formValues.Volumes.push({ Source: '', Target: '', ReadOnly: false, Type: 'volume' }); - }; - - $scope.removeVolume = function(index) { - $scope.formValues.Volumes.splice(index, 1); - }; - - $scope.addConfig = function() { - $scope.formValues.Configs.push({}); - }; - - $scope.removeConfig = function(index) { - $scope.formValues.Configs.splice(index, 1); - }; - - $scope.addSecret = function() { - $scope.formValues.Secrets.push({ overrideTarget: false }); - }; - - $scope.removeSecret = function(index) { - $scope.formValues.Secrets.splice(index, 1); - }; - - $scope.addEnvironmentVariable = function() { - $scope.formValues.Env.push({ name: '', value: ''}); - }; - - $scope.removeEnvironmentVariable = function(index) { - $scope.formValues.Env.splice(index, 1); - }; - - $scope.addPlacementConstraint = function() { - $scope.formValues.PlacementConstraints.push({ key: '', operator: '==', value: '' }); - }; - - $scope.removePlacementConstraint = function(index) { - $scope.formValues.PlacementConstraints.splice(index, 1); - }; - - $scope.addPlacementPreference = function() { - $scope.formValues.PlacementPreferences.push({ strategy: 'spread', value: '' }); - }; - - $scope.removePlacementPreference = function(index) { - $scope.formValues.PlacementPreferences.splice(index, 1); - }; - - $scope.addLabel = function() { - $scope.formValues.Labels.push({ key: '', value: ''}); - }; - - $scope.removeLabel = function(index) { - $scope.formValues.Labels.splice(index, 1); - }; - - $scope.addContainerLabel = function() { - $scope.formValues.ContainerLabels.push({ key: '', value: ''}); - }; - - $scope.removeContainerLabel = function(index) { - $scope.formValues.ContainerLabels.splice(index, 1); - }; - - $scope.addLogDriverOpt = function() { - $scope.formValues.LogDriverOpts.push({ name: '', value: ''}); - }; - - $scope.removeLogDriverOpt = function(index) { - $scope.formValues.LogDriverOpts.splice(index, 1); - }; - - function prepareImageConfig(config, input) { - var imageConfig = ImageHelper.createImageConfigForContainer(input.RegistryModel); - config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage; - } - - function preparePortsConfig(config, input) { - var ports = []; - input.Ports.forEach(function (binding) { - var port = { - Protocol: binding.Protocol, - PublishMode: binding.PublishMode - }; - if (binding.TargetPort) { - port.TargetPort = +binding.TargetPort; - if (binding.PublishedPort) { - port.PublishedPort = +binding.PublishedPort; - } - ports.push(port); - } - }); - config.EndpointSpec.Ports = ports; - } - - function prepareSchedulingConfig(config, input) { - if (input.Mode === 'replicated') { - config.Mode.Replicated = { - Replicas: input.Replicas - }; - } else { - config.Mode.Global = {}; - } - } - - function commandToArray(cmd) { - var tokens = [].concat.apply([], cmd.split('\'').map(function(v,i) { - return i%2 ? v : v.split(' '); - })).filter(Boolean); - return tokens; - } - - function prepareCommandConfig(config, input) { - if (input.EntryPoint) { - config.TaskTemplate.ContainerSpec.Command = commandToArray(input.EntryPoint); +require('./includes/update-restart.html'); +require('./includes/secret.html'); +require('./includes/config.html'); +require('./includes/resources-placement.html'); + +angular.module('portainer.docker').controller('CreateServiceController', [ + '$q', + '$scope', + '$state', + '$timeout', + 'Service', + 'ServiceHelper', + 'ConfigService', + 'ConfigHelper', + 'SecretHelper', + 'SecretService', + 'VolumeService', + 'NetworkService', + 'ImageHelper', + 'LabelHelper', + 'Authentication', + 'ResourceControlService', + 'Notifications', + 'FormValidator', + 'PluginService', + 'RegistryService', + 'HttpRequestHelper', + 'NodeService', + 'SettingsService', + 'WebhookService', + 'EndpointProvider', + function ( + $q, + $scope, + $state, + $timeout, + Service, + ServiceHelper, + ConfigService, + ConfigHelper, + SecretHelper, + SecretService, + VolumeService, + NetworkService, + ImageHelper, + LabelHelper, + Authentication, + ResourceControlService, + Notifications, + FormValidator, + PluginService, + RegistryService, + HttpRequestHelper, + NodeService, + SettingsService, + WebhookService, + EndpointProvider + ) { + $scope.formValues = { + Name: '', + RegistryModel: new PorImageRegistryModel(), + Mode: 'replicated', + Replicas: 1, + Command: '', + EntryPoint: '', + WorkingDir: '', + User: '', + Env: [], + Labels: [], + ContainerLabels: [], + Volumes: [], + Network: '', + ExtraNetworks: [], + HostsEntries: [], + Ports: [], + Parallelism: 1, + PlacementConstraints: [], + PlacementPreferences: [], + UpdateDelay: '0s', + UpdateOrder: 'stop-first', + FailureAction: 'pause', + Secrets: [], + Configs: [], + AccessControlData: new AccessControlFormData(), + CpuLimit: 0, + CpuReservation: 0, + MemoryLimit: 0, + MemoryReservation: 0, + MemoryLimitUnit: 'MB', + MemoryReservationUnit: 'MB', + RestartCondition: 'any', + RestartDelay: '5s', + RestartMaxAttempts: 0, + RestartWindow: '0s', + LogDriverName: '', + LogDriverOpts: [], + Webhook: false, + }; + + $scope.state = { + formValidationError: '', + actionInProgress: false, + }; + + $scope.refreshSlider = function () { + $timeout(function () { + $scope.$broadcast('rzSliderForceRender'); + }); + }; + + $scope.addPortBinding = function () { + $scope.formValues.Ports.push({ PublishedPort: '', TargetPort: '', Protocol: 'tcp', PublishMode: 'ingress' }); + }; + + $scope.removePortBinding = function (index) { + $scope.formValues.Ports.splice(index, 1); + }; + + $scope.addExtraNetwork = function () { + $scope.formValues.ExtraNetworks.push({ Name: '' }); + }; + + $scope.removeExtraNetwork = function (index) { + $scope.formValues.ExtraNetworks.splice(index, 1); + }; + + $scope.addHostsEntry = function () { + $scope.formValues.HostsEntries.push({}); + }; + + $scope.removeHostsEntry = function (index) { + $scope.formValues.HostsEntries.splice(index, 1); + }; + + $scope.addVolume = function () { + $scope.formValues.Volumes.push({ Source: '', Target: '', ReadOnly: false, Type: 'volume' }); + }; + + $scope.removeVolume = function (index) { + $scope.formValues.Volumes.splice(index, 1); + }; + + $scope.addConfig = function () { + $scope.formValues.Configs.push({}); + }; + + $scope.removeConfig = function (index) { + $scope.formValues.Configs.splice(index, 1); + }; + + $scope.addSecret = function () { + $scope.formValues.Secrets.push({ overrideTarget: false }); + }; + + $scope.removeSecret = function (index) { + $scope.formValues.Secrets.splice(index, 1); + }; + + $scope.addEnvironmentVariable = function () { + $scope.formValues.Env.push({ name: '', value: '' }); + }; + + $scope.removeEnvironmentVariable = function (index) { + $scope.formValues.Env.splice(index, 1); + }; + + $scope.addPlacementConstraint = function () { + $scope.formValues.PlacementConstraints.push({ key: '', operator: '==', value: '' }); + }; + + $scope.removePlacementConstraint = function (index) { + $scope.formValues.PlacementConstraints.splice(index, 1); + }; + + $scope.addPlacementPreference = function () { + $scope.formValues.PlacementPreferences.push({ strategy: 'spread', value: '' }); + }; + + $scope.removePlacementPreference = function (index) { + $scope.formValues.PlacementPreferences.splice(index, 1); + }; + + $scope.addLabel = function () { + $scope.formValues.Labels.push({ key: '', value: '' }); + }; + + $scope.removeLabel = function (index) { + $scope.formValues.Labels.splice(index, 1); + }; + + $scope.addContainerLabel = function () { + $scope.formValues.ContainerLabels.push({ key: '', value: '' }); + }; + + $scope.removeContainerLabel = function (index) { + $scope.formValues.ContainerLabels.splice(index, 1); + }; + + $scope.addLogDriverOpt = function () { + $scope.formValues.LogDriverOpts.push({ name: '', value: '' }); + }; + + $scope.removeLogDriverOpt = function (index) { + $scope.formValues.LogDriverOpts.splice(index, 1); + }; + + function prepareImageConfig(config, input) { + var imageConfig = ImageHelper.createImageConfigForContainer(input.RegistryModel); + config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage; } - if (input.Command) { - config.TaskTemplate.ContainerSpec.Args = commandToArray(input.Command); + + function preparePortsConfig(config, input) { + var ports = []; + input.Ports.forEach(function (binding) { + var port = { + Protocol: binding.Protocol, + PublishMode: binding.PublishMode, + }; + if (binding.TargetPort) { + port.TargetPort = +binding.TargetPort; + if (binding.PublishedPort) { + port.PublishedPort = +binding.PublishedPort; + } + ports.push(port); + } + }); + config.EndpointSpec.Ports = ports; } - if (input.User) { - config.TaskTemplate.ContainerSpec.User = input.User; + + function prepareSchedulingConfig(config, input) { + if (input.Mode === 'replicated') { + config.Mode.Replicated = { + Replicas: input.Replicas, + }; + } else { + config.Mode.Global = {}; + } } - if (input.WorkingDir) { - config.TaskTemplate.ContainerSpec.Dir = input.WorkingDir; + + function commandToArray(cmd) { + var tokens = [].concat + .apply( + [], + cmd.split("'").map(function (v, i) { + return i % 2 ? v : v.split(' '); + }) + ) + .filter(Boolean); + return tokens; } - } - function prepareEnvConfig(config, input) { - var env = []; - input.Env.forEach(function (v) { - if (v.name) { - env.push(v.name + '=' + v.value); + function prepareCommandConfig(config, input) { + if (input.EntryPoint) { + config.TaskTemplate.ContainerSpec.Command = commandToArray(input.EntryPoint); } - }); - config.TaskTemplate.ContainerSpec.Env = env; - } - - function prepareLabelsConfig(config, input) { - config.Labels = LabelHelper.fromKeyValueToLabelHash(input.Labels); - config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(input.ContainerLabels); - } - - function createMountObjectFromVolume(volumeObject, target, readonly) { - return { - Target: target, - Source: volumeObject.Id, - Type: 'volume', - ReadOnly: readonly, - VolumeOptions: { - Labels: volumeObject.Labels, - DriverConfig: { - Name: volumeObject.Driver, - Options: volumeObject.Options - } + if (input.Command) { + config.TaskTemplate.ContainerSpec.Args = commandToArray(input.Command); } - }; - } - - function prepareVolumes(config, input) { - input.Volumes.forEach(function (volume) { - if (volume.Source && volume.Target) { - if (volume.Type !== 'volume') { - config.TaskTemplate.ContainerSpec.Mounts.push(volume); - } else { - var volumeObject = volume.Source; - var mount = createMountObjectFromVolume(volumeObject, volume.Target, volume.ReadOnly); - config.TaskTemplate.ContainerSpec.Mounts.push(mount); - } + if (input.User) { + config.TaskTemplate.ContainerSpec.User = input.User; } - }); - } - - function prepareNetworks(config, input) { - var networks = []; - if (input.Network) { - networks.push({ Target: input.Network }); - } - input.ExtraNetworks.forEach(function (network) { - networks.push({ Target: network.Name }); - }); - config.Networks = _.uniqWith(networks, _.isEqual); - } - - function prepareHostsEntries(config, input) { - var hostsEntries = []; - if (input.HostsEntries) { - input.HostsEntries.forEach(function (host_ip) { - if (host_ip.value && host_ip.value.indexOf(':') && host_ip.value.split(':').length === 2) { - var keyVal = host_ip.value.split(':'); - // Hosts file format, IP_address canonical_hostname - hostsEntries.push(keyVal[1] + ' ' + keyVal[0]); - } - }); - if (hostsEntries.length > 0) { - config.TaskTemplate.ContainerSpec.Hosts = hostsEntries; + if (input.WorkingDir) { + config.TaskTemplate.ContainerSpec.Dir = input.WorkingDir; } } - } - - function prepareUpdateConfig(config, input) { - config.UpdateConfig = { - Parallelism: input.Parallelism || 0, - Delay: ServiceHelper.translateHumanDurationToNanos(input.UpdateDelay) || 0, - FailureAction: input.FailureAction, - Order: input.UpdateOrder - }; - } - - function prepareRestartPolicy(config, input) { - config.TaskTemplate.RestartPolicy = { - Condition: input.RestartCondition || 'any', - Delay: ServiceHelper.translateHumanDurationToNanos(input.RestartDelay) || 5000000000, - MaxAttempts: input.RestartMaxAttempts || 0, - Window: ServiceHelper.translateHumanDurationToNanos(input.RestartWindow) || 0 - }; - } - - function preparePlacementConfig(config, input) { - config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(input.PlacementConstraints); - config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(input.PlacementPreferences); - } - - function prepareConfigConfig(config, input) { - if (input.Configs) { - var configs = []; - angular.forEach(input.Configs, function(config) { - if (config.model) { - var s = ConfigHelper.configConfig(config.model); - s.File.Name = config.FileName || s.File.Name; - configs.push(s); + + function prepareEnvConfig(config, input) { + var env = []; + input.Env.forEach(function (v) { + if (v.name) { + env.push(v.name + '=' + v.value); } }); - config.TaskTemplate.ContainerSpec.Configs = configs; + config.TaskTemplate.ContainerSpec.Env = env; } - } - - function prepareSecretConfig(config, input) { - if (input.Secrets) { - var secrets = []; - angular.forEach(input.Secrets, function(secret) { - if (secret.model) { - var s = SecretHelper.secretConfig(secret.model); - s.File.Name = s.SecretName; - if (secret.overrideTarget && secret.target && secret.target !== '') { - s.File.Name = secret.target; + + function prepareLabelsConfig(config, input) { + config.Labels = LabelHelper.fromKeyValueToLabelHash(input.Labels); + config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(input.ContainerLabels); + } + + function createMountObjectFromVolume(volumeObject, target, readonly) { + return { + Target: target, + Source: volumeObject.Id, + Type: 'volume', + ReadOnly: readonly, + VolumeOptions: { + Labels: volumeObject.Labels, + DriverConfig: { + Name: volumeObject.Driver, + Options: volumeObject.Options, + }, + }, + }; + } + + function prepareVolumes(config, input) { + input.Volumes.forEach(function (volume) { + if (volume.Source && volume.Target) { + if (volume.Type !== 'volume') { + config.TaskTemplate.ContainerSpec.Mounts.push(volume); + } else { + var volumeObject = volume.Source; + var mount = createMountObjectFromVolume(volumeObject, volume.Target, volume.ReadOnly); + config.TaskTemplate.ContainerSpec.Mounts.push(mount); } - secrets.push(s); } }); - config.TaskTemplate.ContainerSpec.Secrets = secrets; } - } - function prepareResourcesCpuConfig(config, input) { - // CPU Limit - if (input.CpuLimit > 0) { - config.TaskTemplate.Resources.Limits.NanoCPUs = input.CpuLimit * 1000000000; + function prepareNetworks(config, input) { + var networks = []; + if (input.Network) { + networks.push({ Target: input.Network }); + } + input.ExtraNetworks.forEach(function (network) { + networks.push({ Target: network.Name }); + }); + config.Networks = _.uniqWith(networks, _.isEqual); } - // CPU Reservation - if (input.CpuReservation > 0) { - config.TaskTemplate.Resources.Reservations.NanoCPUs = input.CpuReservation * 1000000000; + + function prepareHostsEntries(config, input) { + var hostsEntries = []; + if (input.HostsEntries) { + input.HostsEntries.forEach(function (host_ip) { + if (host_ip.value && host_ip.value.indexOf(':') && host_ip.value.split(':').length === 2) { + var keyVal = host_ip.value.split(':'); + // Hosts file format, IP_address canonical_hostname + hostsEntries.push(keyVal[1] + ' ' + keyVal[0]); + } + }); + if (hostsEntries.length > 0) { + config.TaskTemplate.ContainerSpec.Hosts = hostsEntries; + } + } } - } - - function prepareResourcesMemoryConfig(config, input) { - // Memory Limit - Round to 0.125 - var memoryLimit = (Math.round(input.MemoryLimit * 8) / 8).toFixed(3); - memoryLimit *= 1024 * 1024; - if (input.MemoryLimitUnit === 'GB') { - memoryLimit *= 1024; + + function prepareUpdateConfig(config, input) { + config.UpdateConfig = { + Parallelism: input.Parallelism || 0, + Delay: ServiceHelper.translateHumanDurationToNanos(input.UpdateDelay) || 0, + FailureAction: input.FailureAction, + Order: input.UpdateOrder, + }; } - if (memoryLimit > 0) { - config.TaskTemplate.Resources.Limits.MemoryBytes = memoryLimit; + + function prepareRestartPolicy(config, input) { + config.TaskTemplate.RestartPolicy = { + Condition: input.RestartCondition || 'any', + Delay: ServiceHelper.translateHumanDurationToNanos(input.RestartDelay) || 5000000000, + MaxAttempts: input.RestartMaxAttempts || 0, + Window: ServiceHelper.translateHumanDurationToNanos(input.RestartWindow) || 0, + }; } - // Memory Resevation - Round to 0.125 - var memoryReservation = (Math.round(input.MemoryReservation * 8) / 8).toFixed(3); - memoryReservation *= 1024 * 1024; - if (input.MemoryReservationUnit === 'GB') { - memoryReservation *= 1024; + + function preparePlacementConfig(config, input) { + config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(input.PlacementConstraints); + config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(input.PlacementPreferences); } - if (memoryReservation > 0) { - config.TaskTemplate.Resources.Reservations.MemoryBytes = memoryReservation; + + function prepareConfigConfig(config, input) { + if (input.Configs) { + var configs = []; + angular.forEach(input.Configs, function (config) { + if (config.model) { + var s = ConfigHelper.configConfig(config.model); + s.File.Name = config.FileName || s.File.Name; + configs.push(s); + } + }); + config.TaskTemplate.ContainerSpec.Configs = configs; + } } - } - - function prepareLogDriverConfig(config, input) { - var logOpts = {}; - if (input.LogDriverName) { - config.TaskTemplate.LogDriver = { Name: input.LogDriverName }; - if (input.LogDriverName !== 'none') { - input.LogDriverOpts.forEach(function (opt) { - if (opt.name) { - logOpts[opt.name] = opt.value; + + function prepareSecretConfig(config, input) { + if (input.Secrets) { + var secrets = []; + angular.forEach(input.Secrets, function (secret) { + if (secret.model) { + var s = SecretHelper.secretConfig(secret.model); + s.File.Name = s.SecretName; + if (secret.overrideTarget && secret.target && secret.target !== '') { + s.File.Name = secret.target; + } + secrets.push(s); } }); - if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) { - config.TaskTemplate.LogDriver.Options = logOpts; + config.TaskTemplate.ContainerSpec.Secrets = secrets; + } + } + + function prepareResourcesCpuConfig(config, input) { + // CPU Limit + if (input.CpuLimit > 0) { + config.TaskTemplate.Resources.Limits.NanoCPUs = input.CpuLimit * 1000000000; + } + // CPU Reservation + if (input.CpuReservation > 0) { + config.TaskTemplate.Resources.Reservations.NanoCPUs = input.CpuReservation * 1000000000; + } + } + + function prepareResourcesMemoryConfig(config, input) { + // Memory Limit - Round to 0.125 + var memoryLimit = (Math.round(input.MemoryLimit * 8) / 8).toFixed(3); + memoryLimit *= 1024 * 1024; + if (input.MemoryLimitUnit === 'GB') { + memoryLimit *= 1024; + } + if (memoryLimit > 0) { + config.TaskTemplate.Resources.Limits.MemoryBytes = memoryLimit; + } + // Memory Resevation - Round to 0.125 + var memoryReservation = (Math.round(input.MemoryReservation * 8) / 8).toFixed(3); + memoryReservation *= 1024 * 1024; + if (input.MemoryReservationUnit === 'GB') { + memoryReservation *= 1024; + } + if (memoryReservation > 0) { + config.TaskTemplate.Resources.Reservations.MemoryBytes = memoryReservation; + } + } + + function prepareLogDriverConfig(config, input) { + var logOpts = {}; + if (input.LogDriverName) { + config.TaskTemplate.LogDriver = { Name: input.LogDriverName }; + if (input.LogDriverName !== 'none') { + input.LogDriverOpts.forEach(function (opt) { + if (opt.name) { + logOpts[opt.name] = opt.value; + } + }); + if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) { + config.TaskTemplate.LogDriver.Options = logOpts; + } } } } - } - - function prepareConfiguration() { - var input = $scope.formValues; - var config = { - Name: input.Name, - TaskTemplate: { - ContainerSpec: { - Mounts: [] + + function prepareConfiguration() { + var input = $scope.formValues; + var config = { + Name: input.Name, + TaskTemplate: { + ContainerSpec: { + Mounts: [], + }, + Placement: {}, + Resources: { + Limits: {}, + Reservations: {}, + }, }, - Placement: {}, - Resources: { - Limits: {}, - Reservations: {} - } - }, - Mode: {}, - EndpointSpec: {} - }; - prepareSchedulingConfig(config, input); - prepareImageConfig(config, input); - preparePortsConfig(config, input); - prepareCommandConfig(config, input); - prepareEnvConfig(config, input); - prepareLabelsConfig(config, input); - prepareVolumes(config, input); - prepareNetworks(config, input); - prepareHostsEntries(config, input); - prepareUpdateConfig(config, input); - prepareConfigConfig(config, input); - prepareSecretConfig(config, input); - preparePlacementConfig(config, input); - prepareResourcesCpuConfig(config, input); - prepareResourcesMemoryConfig(config, input); - prepareRestartPolicy(config, input); - prepareLogDriverConfig(config, input); - return config; - } - - function createNewService(config, accessControlData) { - const registryModel = $scope.formValues.RegistryModel; - var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : ''; - HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); - - Service.create(config).$promise - .then(function success(data) { - const serviceId = data.ID; - const resourceControl = data.Portainer.ResourceControl; - const userId = Authentication.getUserDetails().ID; - const rcPromise = ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl); - const webhookPromise = $q.when($scope.formValues.Webhook && WebhookService.createServiceWebhook(serviceId, EndpointProvider.endpointID())); - return $q.all([rcPromise, webhookPromise]); - }) - .then(function success() { - Notifications.success('Service successfully created'); - $state.go('docker.services', {}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to create service'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - } - - function validateForm(accessControlData, isAdmin) { - $scope.state.formValidationError = ''; - var error = ''; - error = FormValidator.validateAccessControl(accessControlData, isAdmin); - - if (error) { - $scope.state.formValidationError = error; - return false; + Mode: {}, + EndpointSpec: {}, + }; + prepareSchedulingConfig(config, input); + prepareImageConfig(config, input); + preparePortsConfig(config, input); + prepareCommandConfig(config, input); + prepareEnvConfig(config, input); + prepareLabelsConfig(config, input); + prepareVolumes(config, input); + prepareNetworks(config, input); + prepareHostsEntries(config, input); + prepareUpdateConfig(config, input); + prepareConfigConfig(config, input); + prepareSecretConfig(config, input); + preparePlacementConfig(config, input); + prepareResourcesCpuConfig(config, input); + prepareResourcesMemoryConfig(config, input); + prepareRestartPolicy(config, input); + prepareLogDriverConfig(config, input); + return config; } - return true; - } - $scope.create = function createService() { - var accessControlData = $scope.formValues.AccessControlData; + function createNewService(config, accessControlData) { + const registryModel = $scope.formValues.RegistryModel; + var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : ''; + HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails); + + Service.create(config) + .$promise.then(function success(data) { + const serviceId = data.ID; + const resourceControl = data.Portainer.ResourceControl; + const userId = Authentication.getUserDetails().ID; + const rcPromise = ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl); + const webhookPromise = $q.when($scope.formValues.Webhook && WebhookService.createServiceWebhook(serviceId, EndpointProvider.endpointID())); + return $q.all([rcPromise, webhookPromise]); + }) + .then(function success() { + Notifications.success('Service successfully created'); + $state.go('docker.services', {}, { reload: true }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to create service'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + } - if (!validateForm(accessControlData, $scope.isAdmin)) { - return; + function validateForm(accessControlData, isAdmin) { + $scope.state.formValidationError = ''; + var error = ''; + error = FormValidator.validateAccessControl(accessControlData, isAdmin); + + if (error) { + $scope.state.formValidationError = error; + return false; + } + return true; } - $scope.state.actionInProgress = true; - var config = prepareConfiguration(); - createNewService(config, accessControlData); - }; - - function initSlidersMaxValuesBasedOnNodeData(nodes) { - var maxCpus = 0; - var maxMemory = 0; - for (var n in nodes) { - if (nodes[n].CPUs && nodes[n].CPUs > maxCpus) { - maxCpus = nodes[n].CPUs; + $scope.create = function createService() { + var accessControlData = $scope.formValues.AccessControlData; + + if (!validateForm(accessControlData, $scope.isAdmin)) { + return; + } + + $scope.state.actionInProgress = true; + var config = prepareConfiguration(); + createNewService(config, accessControlData); + }; + + function initSlidersMaxValuesBasedOnNodeData(nodes) { + var maxCpus = 0; + var maxMemory = 0; + for (var n in nodes) { + if (nodes[n].CPUs && nodes[n].CPUs > maxCpus) { + maxCpus = nodes[n].CPUs; + } + if (nodes[n].Memory && nodes[n].Memory > maxMemory) { + maxMemory = nodes[n].Memory; + } } - if (nodes[n].Memory && nodes[n].Memory > maxMemory) { - maxMemory = nodes[n].Memory; + if (maxCpus > 0) { + $scope.state.sliderMaxCpu = maxCpus / 1000000000; + } else { + $scope.state.sliderMaxCpu = 32; + } + if (maxMemory > 0) { + $scope.state.sliderMaxMemory = Math.floor(maxMemory / 1000 / 1000); + } else { + $scope.state.sliderMaxMemory = 32768; } } - if (maxCpus > 0) { - $scope.state.sliderMaxCpu = maxCpus / 1000000000; - } else { - $scope.state.sliderMaxCpu = 32; - } - if (maxMemory > 0) { - $scope.state.sliderMaxMemory = Math.floor(maxMemory / 1000 / 1000); - } else { - $scope.state.sliderMaxMemory = 32768; + + function initView() { + var apiVersion = $scope.applicationState.endpoint.apiVersion; + + $q.all({ + volumes: VolumeService.volumes(), + networks: NetworkService.networks(true, true, false), + secrets: apiVersion >= 1.25 ? SecretService.secrets() : [], + configs: apiVersion >= 1.3 ? ConfigService.configs() : [], + nodes: NodeService.nodes(), + settings: SettingsService.publicSettings(), + availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25), + }) + .then(function success(data) { + $scope.availableVolumes = data.volumes; + $scope.availableNetworks = data.networks; + $scope.availableSecrets = data.secrets; + $scope.availableConfigs = data.configs; + $scope.availableLoggingDrivers = data.availableLoggingDrivers; + initSlidersMaxValuesBasedOnNodeData(data.nodes); + $scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers; + $scope.isAdmin = Authentication.isAdmin(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to initialize view'); + }); } - } - - function initView() { - var apiVersion = $scope.applicationState.endpoint.apiVersion; - - $q.all({ - volumes: VolumeService.volumes(), - networks: NetworkService.networks(true, true, false), - secrets: apiVersion >= 1.25 ? SecretService.secrets() : [], - configs: apiVersion >= 1.30 ? ConfigService.configs() : [], - nodes: NodeService.nodes(), - settings: SettingsService.publicSettings(), - availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25) - }) - .then(function success(data) { - $scope.availableVolumes = data.volumes; - $scope.availableNetworks = data.networks; - $scope.availableSecrets = data.secrets; - $scope.availableConfigs = data.configs; - $scope.availableLoggingDrivers = data.availableLoggingDrivers; - initSlidersMaxValuesBasedOnNodeData(data.nodes); - $scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers; - $scope.isAdmin = Authentication.isAdmin(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to initialize view'); - }); - } - - initView(); -}]); + + initView(); + }, +]); diff --git a/app/docker/views/services/create/createservice.html b/app/docker/views/services/create/createservice.html index 458148975ae7f..3494a88f01383 100644 --- a/app/docker/views/services/create/createservice.html +++ b/app/docker/views/services/create/createservice.html @@ -1,8 +1,6 @@ - - Services > Add service - + Services > Add service
@@ -14,7 +12,7 @@
- +
@@ -22,11 +20,7 @@ Image configuration
- +
Scheduling @@ -48,7 +42,7 @@ - +
@@ -68,7 +62,7 @@
host - +
@@ -77,7 +71,7 @@
container - +
@@ -108,11 +102,12 @@
- +
@@ -125,7 +120,13 @@
- @@ -150,7 +151,7 @@
  • Labels
  • Update config & Restart
  • Secrets
  • -
  • Configs
  • +
  • Configs
  • Resources & Placement
  • @@ -165,7 +166,7 @@
    - +
    @@ -173,7 +174,7 @@
    - +
    @@ -181,11 +182,11 @@
    - +
    - +
    @@ -202,11 +203,11 @@
    name - +
    value - +

    - Logging driver for service that will override the default docker daemon driver. Select Default logging driver if you don't want to override it. Supported logging drivers can be found in the Docker documentation. + Logging driver for service that will override the default docker daemon driver. Select Default logging driver if you don't want to override it. Supported + logging drivers can be found + in the Docker documentation.

    @@ -242,9 +245,16 @@
    - + add logging driver option
    @@ -253,11 +263,11 @@
    option - +
    value - +
    - -
    @@ -292,7 +300,7 @@
    container - +
    @@ -314,7 +322,11 @@
    volume -
    @@ -322,7 +334,7 @@
    host - +
    @@ -395,7 +407,7 @@
    value - +
    -
    +
    @@ -114,11 +114,11 @@
    strategy - +
    value - +
    + Order @@ -212,28 +222,28 @@
    + {{ layer.Order }} + {{ layer.Size | humansize }}
    -
    - + {{ layer.CreatedBy | imagelayercommand }}
    diff --git a/app/docker/views/images/edit/imageController.js b/app/docker/views/images/edit/imageController.js index c13cd72efa60e..6a04714458d51 100644 --- a/app/docker/views/images/edit/imageController.js +++ b/app/docker/views/images/edit/imageController.js @@ -1,152 +1,167 @@ import _ from 'lodash-es'; import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; -angular.module('portainer.docker') -.controller('ImageController', ['$q', '$scope', '$transition$', '$state', '$timeout', 'ImageService', 'ImageHelper', 'RegistryService', 'Notifications', 'HttpRequestHelper', 'ModalService', 'FileSaver', 'Blob', -function ($q, $scope, $transition$, $state, $timeout, ImageService, ImageHelper, RegistryService, Notifications, HttpRequestHelper, ModalService, FileSaver, Blob) { - $scope.formValues = { - RegistryModel: new PorImageRegistryModel() - }; - - $scope.state = { - exportInProgress: false - }; - - $scope.sortType = 'Order'; - $scope.sortReverse = false; - - $scope.order = function(sortType) { - $scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false; - $scope.sortType = sortType; - }; - - $scope.toggleLayerCommand = function(layerId) { - $('#layer-command-expander'+layerId+' span').toggleClass('glyphicon-plus-sign glyphicon-minus-sign'); - $('#layer-command-'+layerId+'-short').toggle(); - $('#layer-command-'+layerId+'-full').toggle(); - }; - - $scope.tagImage = function() { - const registryModel = $scope.formValues.RegistryModel; - - const image = ImageHelper.createImageConfigForContainer(registryModel); - - ImageService.tagImage($transition$.params().id, image.fromImage) - .then(function success() { - Notifications.success('Image successfully tagged'); - $state.go('docker.images.image', {id: $transition$.params().id}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to tag image'); - }); - }; - - $scope.pushTag = function(repository) { - $('#uploadResourceHint').show(); - RegistryService.retrievePorRegistryModelFromRepository(repository) - .then(function success(registryModel) { - return ImageService.pushImage(registryModel); - }) - .then(function success() { - Notifications.success('Image successfully pushed', repository); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to push image to repository'); - }) - .finally(function final() { - $('#uploadResourceHint').hide(); - }); - }; - - $scope.pullTag = function(repository) { - $('#downloadResourceHint').show(); - RegistryService.retrievePorRegistryModelFromRepository(repository) - .then(function success(registryModel) { - return ImageService.pullImage(registryModel, false); - }) - .then(function success() { - Notifications.success('Image successfully pulled', repository); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to pull image'); - }) - .finally(function final() { - $('#downloadResourceHint').hide(); - }); - }; - - $scope.removeTag = function(repository) { - ImageService.deleteImage(repository, false) - .then(function success() { - if ($scope.image.RepoTags.length === 1) { - Notifications.success('Image successfully deleted', repository); - $state.go('docker.images', {}, {reload: true}); - } else { - Notifications.success('Tag successfully deleted', repository); - $state.go('docker.images.image', {id: $transition$.params().id}, {reload: true}); - } - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove image'); - }); - }; - - $scope.removeImage = function (id) { - ImageService.deleteImage(id, false) - .then(function success() { - Notifications.success('Image successfully deleted', id); - $state.go('docker.images', {}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove image'); - }); - }; - - function exportImage(image) { - HttpRequestHelper.setPortainerAgentTargetHeader(image.NodeName); - $scope.state.exportInProgress = true; - ImageService.downloadImages([image]) - .then(function success(data) { - var downloadData = new Blob([data.file], { type: 'application/x-tar' }); - FileSaver.saveAs(downloadData, 'images.tar'); - Notifications.success('Image successfully downloaded'); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to download image'); - }) - .finally(function final() { - $scope.state.exportInProgress = false; - }); - } - - $scope.exportImage = function (image) { - if (image.RepoTags.length === 0 || _.includes(image.RepoTags, '')) { - Notifications.warning('', 'Cannot download a untagged image'); - return; - } - - ModalService.confirmImageExport(function (confirmed) { - if(!confirmed) { return; } - exportImage(image); - }); - }; - - function initView() { - HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); - var endpointProvider = $scope.applicationState.endpoint.mode.provider; - $q.all({ - image: ImageService.image($transition$.params().id), - history: endpointProvider !== 'VMWARE_VIC' ? ImageService.history($transition$.params().id) : [] - }) - .then(function success(data) { - $scope.image = data.image; - $scope.history = data.history; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve image details'); - $state.go('docker.images'); - }); - } - - initView(); -}]); +angular.module('portainer.docker').controller('ImageController', [ + '$q', + '$scope', + '$transition$', + '$state', + '$timeout', + 'ImageService', + 'ImageHelper', + 'RegistryService', + 'Notifications', + 'HttpRequestHelper', + 'ModalService', + 'FileSaver', + 'Blob', + function ($q, $scope, $transition$, $state, $timeout, ImageService, ImageHelper, RegistryService, Notifications, HttpRequestHelper, ModalService, FileSaver, Blob) { + $scope.formValues = { + RegistryModel: new PorImageRegistryModel(), + }; + + $scope.state = { + exportInProgress: false, + }; + + $scope.sortType = 'Order'; + $scope.sortReverse = false; + + $scope.order = function (sortType) { + $scope.sortReverse = $scope.sortType === sortType ? !$scope.sortReverse : false; + $scope.sortType = sortType; + }; + + $scope.toggleLayerCommand = function (layerId) { + $('#layer-command-expander' + layerId + ' span').toggleClass('glyphicon-plus-sign glyphicon-minus-sign'); + $('#layer-command-' + layerId + '-short').toggle(); + $('#layer-command-' + layerId + '-full').toggle(); + }; + + $scope.tagImage = function () { + const registryModel = $scope.formValues.RegistryModel; + + const image = ImageHelper.createImageConfigForContainer(registryModel); + + ImageService.tagImage($transition$.params().id, image.fromImage) + .then(function success() { + Notifications.success('Image successfully tagged'); + $state.go('docker.images.image', { id: $transition$.params().id }, { reload: true }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to tag image'); + }); + }; + + $scope.pushTag = function (repository) { + $('#uploadResourceHint').show(); + RegistryService.retrievePorRegistryModelFromRepository(repository) + .then(function success(registryModel) { + return ImageService.pushImage(registryModel); + }) + .then(function success() { + Notifications.success('Image successfully pushed', repository); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to push image to repository'); + }) + .finally(function final() { + $('#uploadResourceHint').hide(); + }); + }; + + $scope.pullTag = function (repository) { + $('#downloadResourceHint').show(); + RegistryService.retrievePorRegistryModelFromRepository(repository) + .then(function success(registryModel) { + return ImageService.pullImage(registryModel, false); + }) + .then(function success() { + Notifications.success('Image successfully pulled', repository); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to pull image'); + }) + .finally(function final() { + $('#downloadResourceHint').hide(); + }); + }; + + $scope.removeTag = function (repository) { + ImageService.deleteImage(repository, false) + .then(function success() { + if ($scope.image.RepoTags.length === 1) { + Notifications.success('Image successfully deleted', repository); + $state.go('docker.images', {}, { reload: true }); + } else { + Notifications.success('Tag successfully deleted', repository); + $state.go('docker.images.image', { id: $transition$.params().id }, { reload: true }); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove image'); + }); + }; + + $scope.removeImage = function (id) { + ImageService.deleteImage(id, false) + .then(function success() { + Notifications.success('Image successfully deleted', id); + $state.go('docker.images', {}, { reload: true }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove image'); + }); + }; + + function exportImage(image) { + HttpRequestHelper.setPortainerAgentTargetHeader(image.NodeName); + $scope.state.exportInProgress = true; + ImageService.downloadImages([image]) + .then(function success(data) { + var downloadData = new Blob([data.file], { type: 'application/x-tar' }); + FileSaver.saveAs(downloadData, 'images.tar'); + Notifications.success('Image successfully downloaded'); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to download image'); + }) + .finally(function final() { + $scope.state.exportInProgress = false; + }); + } + + $scope.exportImage = function (image) { + if (image.RepoTags.length === 0 || _.includes(image.RepoTags, '')) { + Notifications.warning('', 'Cannot download a untagged image'); + return; + } + + ModalService.confirmImageExport(function (confirmed) { + if (!confirmed) { + return; + } + exportImage(image); + }); + }; + + function initView() { + HttpRequestHelper.setPortainerAgentTargetHeader($transition$.params().nodeName); + var endpointProvider = $scope.applicationState.endpoint.mode.provider; + $q.all({ + image: ImageService.image($transition$.params().id), + history: endpointProvider !== 'VMWARE_VIC' ? ImageService.history($transition$.params().id) : [], + }) + .then(function success(data) { + $scope.image = data.image; + $scope.history = data.history; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve image details'); + $state.go('docker.images'); + }); + } + + initView(); + }, +]); diff --git a/app/docker/views/images/images.html b/app/docker/views/images/images.html index d8d82e043534e..9c7cb36b9a5b7 100644 --- a/app/docker/views/images/images.html +++ b/app/docker/views/images/images.html @@ -10,31 +10,29 @@
    - - + - +
    Deployment
    - - +
    - @@ -49,16 +47,18 @@
    diff --git a/app/docker/views/images/imagesController.js b/app/docker/views/images/imagesController.js index aed6f8cf610c2..1933af39f39af 100644 --- a/app/docker/views/images/imagesController.js +++ b/app/docker/views/images/imagesController.js @@ -1,137 +1,149 @@ import _ from 'lodash-es'; import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; -angular.module('portainer.docker') -.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'ModalService', 'HttpRequestHelper', 'FileSaver', 'Blob', 'EndpointProvider', -function ($scope, $state, ImageService, Notifications, ModalService, HttpRequestHelper, FileSaver, Blob, EndpointProvider) { - $scope.state = { - actionInProgress: false, - exportInProgress: false - }; - - $scope.formValues = { - RegistryModel: new PorImageRegistryModel(), - NodeName: null - }; - - $scope.pullImage = function() { - const registryModel = $scope.formValues.RegistryModel; - - var nodeName = $scope.formValues.NodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - - $scope.state.actionInProgress = true; - ImageService.pullImage(registryModel, false) - .then(function success() { - Notifications.success('Image successfully pulled', registryModel.Image); - $state.reload(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to pull image'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }; - - $scope.confirmRemovalAction = function (selectedItems, force) { - ModalService.confirmImageForceRemoval(function (confirmed) { - if(!confirmed) { return; } - $scope.removeAction(selectedItems, force); - }); - }; - - function isAuthorizedToDownload(selectedItems) { - - for (var i = 0; i < selectedItems.length; i++) { - var image = selectedItems[i]; - - var untagged = _.find(image.RepoTags, function (item) { - return item.indexOf('') > -1; +angular.module('portainer.docker').controller('ImagesController', [ + '$scope', + '$state', + 'ImageService', + 'Notifications', + 'ModalService', + 'HttpRequestHelper', + 'FileSaver', + 'Blob', + 'EndpointProvider', + function ($scope, $state, ImageService, Notifications, ModalService, HttpRequestHelper, FileSaver, Blob, EndpointProvider) { + $scope.state = { + actionInProgress: false, + exportInProgress: false, + }; + + $scope.formValues = { + RegistryModel: new PorImageRegistryModel(), + NodeName: null, + }; + + $scope.pullImage = function () { + const registryModel = $scope.formValues.RegistryModel; + + var nodeName = $scope.formValues.NodeName; + HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); + + $scope.state.actionInProgress = true; + ImageService.pullImage(registryModel, false) + .then(function success() { + Notifications.success('Image successfully pulled', registryModel.Image); + $state.reload(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to pull image'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + }; + + $scope.confirmRemovalAction = function (selectedItems, force) { + ModalService.confirmImageForceRemoval(function (confirmed) { + if (!confirmed) { + return; + } + $scope.removeAction(selectedItems, force); }); + }; + + function isAuthorizedToDownload(selectedItems) { + for (var i = 0; i < selectedItems.length; i++) { + var image = selectedItems[i]; + + var untagged = _.find(image.RepoTags, function (item) { + return item.indexOf('') > -1; + }); - if (untagged) { - Notifications.warning('', 'Cannot download a untagged image'); + if (untagged) { + Notifications.warning('', 'Cannot download a untagged image'); + return false; + } + } + + if (_.uniqBy(selectedItems, 'NodeName').length > 1) { + Notifications.warning('', 'Cannot download images from different nodes at the same time'); return false; } - } - if (_.uniqBy(selectedItems, 'NodeName').length > 1) { - Notifications.warning('', 'Cannot download images from different nodes at the same time'); - return false; + return true; } - return true; - } - - function exportImages(images) { - HttpRequestHelper.setPortainerAgentTargetHeader(images[0].NodeName); - $scope.state.exportInProgress = true; - ImageService.downloadImages(images) - .then(function success(data) { - var downloadData = new Blob([data.file], { type: 'application/x-tar' }); - FileSaver.saveAs(downloadData, 'images.tar'); - Notifications.success('Image(s) successfully downloaded'); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to download image(s)'); - }) - .finally(function final() { - $scope.state.exportInProgress = false; - }); - } - - $scope.downloadAction = function (selectedItems) { - if (!isAuthorizedToDownload(selectedItems)) { - return; + function exportImages(images) { + HttpRequestHelper.setPortainerAgentTargetHeader(images[0].NodeName); + $scope.state.exportInProgress = true; + ImageService.downloadImages(images) + .then(function success(data) { + var downloadData = new Blob([data.file], { type: 'application/x-tar' }); + FileSaver.saveAs(downloadData, 'images.tar'); + Notifications.success('Image(s) successfully downloaded'); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to download image(s)'); + }) + .finally(function final() { + $scope.state.exportInProgress = false; + }); } - ModalService.confirmImageExport(function (confirmed) { - if(!confirmed) { return; } - exportImages(selectedItems); - }); - }; - - $scope.removeAction = function (selectedItems, force) { - var actionCount = selectedItems.length; - angular.forEach(selectedItems, function (image) { - HttpRequestHelper.setPortainerAgentTargetHeader(image.NodeName); - ImageService.deleteImage(image.Id, force) - .then(function success() { - Notifications.success('Image successfully removed', image.Id); - var index = $scope.images.indexOf(image); - $scope.images.splice(index, 1); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to remove image'); - }) - .finally(function final() { - --actionCount; - if (actionCount === 0) { - $state.reload(); + $scope.downloadAction = function (selectedItems) { + if (!isAuthorizedToDownload(selectedItems)) { + return; + } + + ModalService.confirmImageExport(function (confirmed) { + if (!confirmed) { + return; } + exportImages(selectedItems); + }); + }; + + $scope.removeAction = function (selectedItems, force) { + var actionCount = selectedItems.length; + angular.forEach(selectedItems, function (image) { + HttpRequestHelper.setPortainerAgentTargetHeader(image.NodeName); + ImageService.deleteImage(image.Id, force) + .then(function success() { + Notifications.success('Image successfully removed', image.Id); + var index = $scope.images.indexOf(image); + $scope.images.splice(index, 1); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to remove image'); + }) + .finally(function final() { + --actionCount; + if (actionCount === 0) { + $state.reload(); + } + }); }); - }); - }; - - $scope.offlineMode = false; - - $scope.getImages = getImages; - function getImages() { - ImageService.images(true) - .then(function success(data) { - $scope.images = data; - $scope.offlineMode = EndpointProvider.offlineMode(); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve images'); - $scope.images = []; - }); - } - - function initView() { - getImages(); - } - - initView(); -}]); + }; + + $scope.offlineMode = false; + + $scope.getImages = getImages; + function getImages() { + ImageService.images(true) + .then(function success(data) { + $scope.images = data; + $scope.offlineMode = EndpointProvider.offlineMode(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve images'); + $scope.images = []; + }); + } + + function initView() { + getImages(); + } + + initView(); + }, +]); diff --git a/app/docker/views/images/import/importImageController.js b/app/docker/views/images/import/importImageController.js index b993a614faf4a..d8d7d002794d0 100644 --- a/app/docker/views/images/import/importImageController.js +++ b/app/docker/views/images/import/importImageController.js @@ -1,31 +1,35 @@ -angular.module('portainer.docker') -.controller('ImportImageController', ['$scope', '$state', 'ImageService', 'Notifications', 'HttpRequestHelper', -function ($scope, $state, ImageService, Notifications, HttpRequestHelper) { +angular.module('portainer.docker').controller('ImportImageController', [ + '$scope', + '$state', + 'ImageService', + 'Notifications', + 'HttpRequestHelper', + function ($scope, $state, ImageService, Notifications, HttpRequestHelper) { + $scope.state = { + actionInProgress: false, + }; - $scope.state = { - actionInProgress: false - }; + $scope.formValues = { + UploadFile: null, + NodeName: null, + }; - $scope.formValues = { - UploadFile: null, - NodeName: null - }; + $scope.uploadImage = function () { + $scope.state.actionInProgress = true; - $scope.uploadImage = function() { - $scope.state.actionInProgress = true; - - var nodeName = $scope.formValues.NodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - var file = $scope.formValues.UploadFile; - ImageService.uploadImage(file) - .then(function success() { - Notifications.success('Images successfully uploaded'); - }) - .catch(function error(err) { - Notifications.error('Failure', err.message, 'Unable to upload image'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }; -}]); + var nodeName = $scope.formValues.NodeName; + HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); + var file = $scope.formValues.UploadFile; + ImageService.uploadImage(file) + .then(function success() { + Notifications.success('Images successfully uploaded'); + }) + .catch(function error(err) { + Notifications.error('Failure', err.message, 'Unable to upload image'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + }; + }, +]); diff --git a/app/docker/views/images/import/importimage.html b/app/docker/views/images/import/importimage.html index 6a6b20f315fe7..5f10e4562ce0b 100644 --- a/app/docker/views/images/import/importimage.html +++ b/app/docker/views/images/import/importimage.html @@ -1,8 +1,6 @@ - - Images > Import image - + Images > Import image
    @@ -21,7 +19,9 @@
    - + {{ formValues.UploadFile.name }} @@ -34,8 +34,7 @@ Deployment
    - - +
    @@ -44,8 +43,13 @@
    - @@ -57,4 +61,4 @@
    -
    \ No newline at end of file +
    diff --git a/app/docker/views/networks/create/createNetworkController.js b/app/docker/views/networks/create/createNetworkController.js index a592582a666ad..233c221f1d001 100644 --- a/app/docker/views/networks/create/createNetworkController.js +++ b/app/docker/views/networks/create/createNetworkController.js @@ -1,218 +1,235 @@ import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel'; import { MacvlanFormData } from '../../../components/network-macvlan-form/networkMacvlanFormModel'; -angular.module('portainer.docker') - .controller('CreateNetworkController', ['$q', '$scope', '$state', 'PluginService', 'Notifications', 'NetworkService', 'LabelHelper', 'Authentication', 'ResourceControlService', 'FormValidator', 'HttpRequestHelper', - function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator, HttpRequestHelper) { - - $scope.formValues = { - DriverOptions: [], - Subnet: '', - Gateway: '', - IPRange: '', - AuxAddress: '', - Labels: [], - AccessControlData: new AccessControlFormData(), - NodeName: null, - Macvlan: new MacvlanFormData() - }; +angular.module('portainer.docker').controller('CreateNetworkController', [ + '$q', + '$scope', + '$state', + 'PluginService', + 'Notifications', + 'NetworkService', + 'LabelHelper', + 'Authentication', + 'ResourceControlService', + 'FormValidator', + 'HttpRequestHelper', + function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator, HttpRequestHelper) { + $scope.formValues = { + DriverOptions: [], + Subnet: '', + Gateway: '', + IPRange: '', + AuxAddress: '', + Labels: [], + AccessControlData: new AccessControlFormData(), + NodeName: null, + Macvlan: new MacvlanFormData(), + }; + + $scope.state = { + formValidationError: '', + actionInProgress: false, + }; + + $scope.availableNetworkDrivers = []; + + $scope.config = { + Driver: 'bridge', + CheckDuplicate: true, + Internal: false, + Attachable: false, + // Force IPAM Driver to 'default', should not be required. + // See: https://github.com/docker/docker/issues/25735 + IPAM: { + Driver: 'default', + Config: [], + }, + Labels: {}, + }; + + $scope.addDriverOption = function () { + $scope.formValues.DriverOptions.push({ + name: '', + value: '', + }); + }; + + $scope.removeDriverOption = function (index) { + $scope.formValues.DriverOptions.splice(index, 1); + }; + + $scope.addLabel = function () { + $scope.formValues.Labels.push({ + key: '', + value: '', + }); + }; + + $scope.removeLabel = function (index) { + $scope.formValues.Labels.splice(index, 1); + }; + + function prepareIPAMConfiguration(config) { + if ($scope.formValues.Subnet) { + var ipamConfig = {}; + ipamConfig.Subnet = $scope.formValues.Subnet; + if ($scope.formValues.Gateway) { + ipamConfig.Gateway = $scope.formValues.Gateway; + } + if ($scope.formValues.IPRange) { + ipamConfig.IPRange = $scope.formValues.IPRange; + } + if ($scope.formValues.AuxAddress) { + ipamConfig.AuxAddress = $scope.formValues.AuxAddress; + } + config.IPAM.Config.push(ipamConfig); + } + } - $scope.state = { - formValidationError: '', - actionInProgress: false - }; + function prepareDriverOptions(config) { + var options = {}; + $scope.formValues.DriverOptions.forEach(function (option) { + options[option.name] = option.value; + }); + config.Options = options; + } - $scope.availableNetworkDrivers = []; - - $scope.config = { - Driver: 'bridge', - CheckDuplicate: true, - Internal: false, - Attachable: false, - // Force IPAM Driver to 'default', should not be required. - // See: https://github.com/docker/docker/issues/25735 - IPAM: { - Driver: 'default', - Config: [] - }, - Labels: {} - }; + function prepareLabelsConfig(config) { + config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); + } - $scope.addDriverOption = function () { - $scope.formValues.DriverOptions.push({ - name: '', - value: '' - }); - }; + function prepareConfiguration() { + var config = angular.copy($scope.config); + prepareIPAMConfiguration(config); + prepareDriverOptions(config); + prepareLabelsConfig(config); + return config; + } - $scope.removeDriverOption = function (index) { - $scope.formValues.DriverOptions.splice(index, 1); - }; + function modifyNetworkConfigurationForMacvlanConfigOnly(config) { + config.Internal = null; + config.Attachable = null; + config.ConfigOnly = true; + config.Options.parent = $scope.formValues.Macvlan.ParentNetworkCard; + } - $scope.addLabel = function () { - $scope.formValues.Labels.push({ - key: '', - value: '' - }); + function modifyNetworkConfigurationForMacvlanConfigFrom(config, selectedNetworkConfig) { + config.ConfigFrom = { + Network: selectedNetworkConfig.Name, }; + if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { + config.Scope = 'swarm'; + } else { + config.Scope = 'local'; + } + } - $scope.removeLabel = function (index) { - $scope.formValues.Labels.splice(index, 1); - }; + function validateForm(accessControlData, isAdmin) { + $scope.state.formValidationError = ''; + var error = ''; + error = FormValidator.validateAccessControl(accessControlData, isAdmin); - function prepareIPAMConfiguration(config) { - if ($scope.formValues.Subnet) { - var ipamConfig = {}; - ipamConfig.Subnet = $scope.formValues.Subnet; - if ($scope.formValues.Gateway) { - ipamConfig.Gateway = $scope.formValues.Gateway; - } - if ($scope.formValues.IPRange) { - ipamConfig.IPRange = $scope.formValues.IPRange; - } - if ($scope.formValues.AuxAddress) { - ipamConfig.AuxAddress = $scope.formValues.AuxAddress; - } - config.IPAM.Config.push(ipamConfig); - } + if (error) { + $scope.state.formValidationError = error; + return false; } + return true; + } - function prepareDriverOptions(config) { - var options = {}; - $scope.formValues.DriverOptions.forEach(function (option) { - options[option.name] = option.value; + function createNetwork(context) { + HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName); + HttpRequestHelper.setPortainerAgentManagerOperation(context.managerOperation); + + $scope.state.actionInProgress = true; + NetworkService.create(context.networkConfiguration) + .then(function success(data) { + const userId = context.userDetails.ID; + const accessControlData = context.accessControlData; + const resourceControl = data.Portainer.ResourceControl; + return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl); + }) + .then(function success() { + Notifications.success('Network successfully created'); + if (context.reload) { + $state.go( + 'docker.networks', + {}, + { + reload: true, + } + ); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'An error occured during network creation'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; }); - config.Options = options; - } + } - function prepareLabelsConfig(config) { - config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); - } + $scope.create = function () { + var networkConfiguration = prepareConfiguration(); + var accessControlData = $scope.formValues.AccessControlData; + var userDetails = Authentication.getUserDetails(); + var isAdmin = Authentication.isAdmin(); - function prepareConfiguration() { - var config = angular.copy($scope.config); - prepareIPAMConfiguration(config); - prepareDriverOptions(config); - prepareLabelsConfig(config); - return config; + if (!validateForm(accessControlData, isAdmin)) { + return; } - function modifyNetworkConfigurationForMacvlanConfigOnly(config) { - config.Internal = null; - config.Attachable = null; - config.ConfigOnly = true; - config.Options.parent = $scope.formValues.Macvlan.ParentNetworkCard; - } + var creationContext = { + nodeName: $scope.formValues.NodeName, + managerOperation: false, + networkConfiguration: networkConfiguration, + userDetails: userDetails, + accessControlData: accessControlData, + reload: true, + }; - function modifyNetworkConfigurationForMacvlanConfigFrom(config, selectedNetworkConfig) { - config.ConfigFrom = { - Network: selectedNetworkConfig.Name - }; - if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { - config.Scope = 'swarm'; - } else { - config.Scope = 'local'; - } + if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && $scope.config.Driver === 'overlay') { + creationContext.managerOperation = true; } - function validateForm(accessControlData, isAdmin) { - $scope.state.formValidationError = ''; - var error = ''; - error = FormValidator.validateAccessControl(accessControlData, isAdmin); - - if (error) { - $scope.state.formValidationError = error; - return false; + if ($scope.config.Driver === 'macvlan') { + if ($scope.formValues.Macvlan.Scope === 'local') { + modifyNetworkConfigurationForMacvlanConfigOnly(networkConfiguration); + } else if ($scope.formValues.Macvlan.Scope === 'swarm') { + var selectedNetworkConfig = $scope.formValues.Macvlan.SelectedNetworkConfig; + modifyNetworkConfigurationForMacvlanConfigFrom(networkConfiguration, selectedNetworkConfig); + creationContext.nodeName = selectedNetworkConfig.NodeName; } - return true; } - function createNetwork(context) { - HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName); - HttpRequestHelper.setPortainerAgentManagerOperation(context.managerOperation); - - $scope.state.actionInProgress = true; - NetworkService.create(context.networkConfiguration) - .then(function success(data) { - const userId = context.userDetails.ID; - const accessControlData = context.accessControlData; - const resourceControl = data.Portainer.ResourceControl; - return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl); - }) - .then(function success() { - Notifications.success('Network successfully created'); - if (context.reload) { - $state.go('docker.networks', {}, { - reload: true - }); - } - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'An error occured during network creation'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - } - - $scope.create = function () { - var networkConfiguration = prepareConfiguration(); - var accessControlData = $scope.formValues.AccessControlData; - var userDetails = Authentication.getUserDetails(); - var isAdmin = Authentication.isAdmin(); - - if (!validateForm(accessControlData, isAdmin)) { - return; - } - - var creationContext = { - nodeName: $scope.formValues.NodeName, - managerOperation: false, - networkConfiguration: networkConfiguration, - userDetails: userDetails, - accessControlData: accessControlData, - reload: true - }; - - if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && $scope.config.Driver === 'overlay') { - creationContext.managerOperation = true; - } - - if ($scope.config.Driver === 'macvlan') { - if ($scope.formValues.Macvlan.Scope === 'local') { - modifyNetworkConfigurationForMacvlanConfigOnly(networkConfiguration); - } else if ($scope.formValues.Macvlan.Scope === 'swarm') { - var selectedNetworkConfig = $scope.formValues.Macvlan.SelectedNetworkConfig; - modifyNetworkConfigurationForMacvlanConfigFrom(networkConfiguration, selectedNetworkConfig); - creationContext.nodeName = selectedNetworkConfig.NodeName; - } - } - - if ($scope.config.Driver === 'macvlan' && $scope.formValues.Macvlan.Scope === 'local' && - $scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { - var selectedNodes = $scope.formValues.Macvlan.DatatableState.selectedItems; - selectedNodes.forEach(function (node, idx) { - creationContext.nodeName = node.Hostname; - creationContext.reload = idx === selectedNodes.length - 1 ? true : false; - createNetwork(creationContext); - }); - } else { + if ( + $scope.config.Driver === 'macvlan' && + $scope.formValues.Macvlan.Scope === 'local' && + $scope.applicationState.endpoint.mode.agentProxy && + $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' + ) { + var selectedNodes = $scope.formValues.Macvlan.DatatableState.selectedItems; + selectedNodes.forEach(function (node, idx) { + creationContext.nodeName = node.Hostname; + creationContext.reload = idx === selectedNodes.length - 1 ? true : false; createNetwork(creationContext); - } - }; - - function initView() { - var apiVersion = $scope.applicationState.endpoint.apiVersion; - - PluginService.networkPlugins(apiVersion < 1.25) - .then(function success(data) { - $scope.availableNetworkDrivers = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve network drivers'); - }); + }); + } else { + createNetwork(creationContext); } + }; - initView(); + function initView() { + var apiVersion = $scope.applicationState.endpoint.apiVersion; + + PluginService.networkPlugins(apiVersion < 1.25) + .then(function success(data) { + $scope.availableNetworkDrivers = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve network drivers'); + }); } - ]); + + initView(); + }, +]); diff --git a/app/docker/views/networks/create/createnetwork.html b/app/docker/views/networks/create/createnetwork.html index 03711c36c50f1..fedcad7bcfebe 100644 --- a/app/docker/views/networks/create/createnetwork.html +++ b/app/docker/views/networks/create/createnetwork.html @@ -1,8 +1,6 @@ - - Networks > Add network - + Networks > Add network
    @@ -14,7 +12,7 @@
    - +
    @@ -28,7 +26,7 @@ - +
    @@ -37,7 +35,10 @@
    add driver option @@ -48,11 +49,11 @@
    name - +
    value - +
    @@ -144,19 +143,25 @@ Enable manual container attachment
    -
    +
    Deployment
    - - +
    @@ -168,8 +173,13 @@
    - @@ -182,4 +192,4 @@
    -
    \ No newline at end of file + diff --git a/app/docker/views/networks/edit/network.html b/app/docker/views/networks/edit/network.html index b5ee95ce0e266..b0be069f4b6b4 100644 --- a/app/docker/views/networks/edit/network.html +++ b/app/docker/views/networks/edit/network.html @@ -20,7 +20,9 @@
    ID {{ network.Id }} - +
    {{ container.Name }}{{ container.Name }} {{ container.IPv4Address || '-' }} {{ container.IPv6Address || '-' }} {{ container.MacAddress || '-' }} - +
    ID {{ secret.Id }} - +
    @@ -25,9 +22,18 @@ - + diff --git a/app/docker/views/services/edit/includes/constraints.html b/app/docker/views/services/edit/includes/constraints.html index 69c681e4e72f5..372bd6660830b 100644 --- a/app/docker/views/services/edit/includes/constraints.html +++ b/app/docker/views/services/edit/includes/constraints.html @@ -11,7 +11,7 @@

    There are no placement constraints for this service.

    -
    {{ config.Name }}{{ config.Name }} - + {{ config.Uid }} {{ config.Gid }}
    +
    @@ -23,12 +23,27 @@ - + - +
    Name
    - +
    - @@ -36,7 +51,15 @@
    - +
    CMD{{ service.Command|command }}{{ service.Command | command }}

    Command to execute. @@ -15,7 +17,9 @@

    Args{{ service.Arguments }}{{ service.Arguments }}

    Arguments passed to command in container. diff --git a/app/docker/views/services/edit/includes/containerlabels.html b/app/docker/views/services/edit/includes/containerlabels.html index 0ebac64b203bc..6a75a84d15fb0 100644 --- a/app/docker/views/services/edit/includes/containerlabels.html +++ b/app/docker/views/services/edit/includes/containerlabels.html @@ -11,7 +11,7 @@

    There are no container labels for this service.

    - +
    @@ -23,13 +23,29 @@
    Label
    name - +
    value - + + diff --git a/app/docker/views/services/edit/includes/environmentvariables.html b/app/docker/views/services/edit/includes/environmentvariables.html index aa9298057a031..32e4d425f8b4b 100644 --- a/app/docker/views/services/edit/includes/environmentvariables.html +++ b/app/docker/views/services/edit/includes/environmentvariables.html @@ -11,7 +11,7 @@

    There are no environment variables for this service.

    - +
    @@ -23,13 +23,21 @@
    Name
    name - +
    value - +