From 6ae9485038c06c831ac3a80130c98de1463ebf4d Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 14 Jan 2014 15:19:47 -0500 Subject: [PATCH 1/6] Add the command view to the tooltips in the tree, the image side bar and the image view page --- static/css/quay.css | 47 +++++++++++++++++++++++++++++++-- static/js/app.js | 2 +- static/js/controllers.js | 25 ++++++++++++++++-- static/js/graphing.js | 15 ++++++++--- static/partials/image-view.html | 5 ++++ static/partials/view-repo.html | 6 +++++ templates/base.html | 1 + 7 files changed, 93 insertions(+), 8 deletions(-) diff --git a/static/css/quay.css b/static/css/quay.css index f9d233e57..bceafd57b 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -9,6 +9,16 @@ } } +.codetooltipcontainer .tooltip-inner { + white-space:pre; + max-width:none; +} + +.codetooltip { + font-family: Consolas, "Lucida Console", Monaco, monospace; + display: block; +} + .resource-view-element { position: relative; } @@ -1655,6 +1665,18 @@ p.editable:hover i { padding-top: 4px; } +.repo .formatted-command { + margin-top: 4px; + padding: 4px; + font-size: 12px; +} + +.repo .formatted-command.trimmed { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .repo .changes-count-container { text-align: center; } @@ -2499,16 +2521,37 @@ p.editable:hover i { display: block; } -.d3-tip .created { +.d3-tip .command { font-size: 12px; color: white; display: block; - margin-bottom: 6px; + font-family: Consolas, "Lucida Console", Monaco, monospace; +} + +.d3-tip .info-line { + display: block; + margin-top: 6px; + padding-top: 6px; +} + +.d3-tip .info-line i { + margin-right: 10px; + font-size: 14px; + color: #888; } .d3-tip .comment { display: block; font-size: 14px; + padding-bottom: 4px; + margin-bottom: 10px; + border-bottom: 1px dotted #ccc; +} + +.d3-tip .created { + font-size: 12px; + color: white; + display: block; margin-bottom: 6px; } diff --git a/static/js/app.js b/static/js/app.js index 0b881200b..e1f6a235f 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -103,7 +103,7 @@ function getMarkedDown(string) { } // Start the application code itself. -quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide, cfpLoadingBarProvider) { +quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies', 'ngSanitize'], function($provide, cfpLoadingBarProvider) { cfpLoadingBarProvider.includeSpinner = false; $provide.factory('ApiService', ['Restangular', function(Restangular) { diff --git a/static/js/controllers.js b/static/js/controllers.js index 900a12cb8..e159a554b 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -1,3 +1,14 @@ +function getFormattedCommand(command) { + if (!command || !command.length) { return ''; } + + // Handle /bin/sh commands specially. + if (command.length > 2 && command[0] == '/bin/sh' && command[1] == '-c') { + return command[2]; + } + + return command.join(' '); +} + $.fn.clipboardCopy = function() { var clip = new ZeroClipboard($(this), { 'moviePath': 'static/lib/ZeroClipboard.swf' }); @@ -157,7 +168,7 @@ function LandingCtrl($scope, UserService, ApiService) { browserchrome.update(); } -function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $timeout) { +function RepoCtrl($scope, $sanitize, Restangular, ApiService, $routeParams, $rootScope, $location, $timeout) { var namespace = $routeParams.namespace; var name = $routeParams.name; @@ -192,6 +203,14 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo // Start scope methods ////////////////////////////////////////// + $scope.getFormattedCommand = getFormattedCommand; + + $scope.getTooltipCommand = function(command) { + var formatted = getFormattedCommand(command); + var sanitized = $sanitize(formatted); + return '' + sanitized + ''; + }; + $scope.updateForDescription = function(content) { $scope.repo.description = content; $scope.repo.put(); @@ -563,7 +582,7 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo // Create the new tree. $scope.tree = new ImageHistoryTree(namespace, name, resp.images, - getFirstTextLine, $scope.getTimeSince); + getFirstTextLine, $scope.getTimeSince, $scope.getFormattedCommand); $scope.tree.draw('image-history-container'); @@ -979,6 +998,8 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService) { var name = $routeParams.name; var imageid = $routeParams.image; + $scope.getFormattedCommand = getFormattedCommand; + $scope.parseDate = function(dateString) { return Date.parse(dateString); }; diff --git a/static/js/graphing.js b/static/js/graphing.js index bfbb7a099..c9f3ef24b 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -31,7 +31,7 @@ var DEPTH_WIDTH = 132; /** * Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock) */ -function ImageHistoryTree(namespace, name, images, formatComment, formatTime) { +function ImageHistoryTree(namespace, name, images, formatComment, formatTime, formatCommand) { /** * The namespace of the repo. */ @@ -57,6 +57,11 @@ function ImageHistoryTree(namespace, name, images, formatComment, formatTime) { */ this.formatTime_ = formatTime; + /** + * Method to invoke to format the command for an image. + */ + this.formatCommand_ = formatCommand; + /** * The current tag (if any). */ @@ -187,6 +192,8 @@ ImageHistoryTree.prototype.draw = function(container) { var formatComment = this.formatComment_; var formatTime = this.formatTime_; + var formatCommand = this.formatCommand_; + var tip = d3.tip() .attr('class', 'd3-tip') .offset([-1, 24]) @@ -212,8 +219,10 @@ ImageHistoryTree.prototype.draw = function(container) { if (d.image.comment) { html += '' + formatComment(d.image.comment) + ''; } - html += '' + formatTime(d.image.created) + ''; - html += '' + d.image.id + ''; + if (d.image.command && d.image.command.length) { + html += '' + formatCommand(d.image.command) + ''; + } + html += '' + formatTime(d.image.created) + ''; return html; }) diff --git a/static/partials/image-view.html b/static/partials/image-view.html index 30ecaf5a7..102ac9bb8 100644 --- a/static/partials/image-view.html +++ b/static/partials/image-view.html @@ -43,6 +43,11 @@ title="The amount of data sent between Docker and Quay.io when pushing/pulling" bs-tooltip="tooltip.title" data-container="body">{{ image.value.size | bytes }} + +
Command
+
+
{{ getFormattedCommand(image.value.command) }}
+
diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index 053bd2952..4ad6ec016 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -167,6 +167,12 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}} title="The amount of data sent between Docker and Quay.io when pushing/pulling" bs-tooltip="tooltip.title" data-container="body">{{ currentImage.size | bytes }} +
Command
+
+
{{ getFormattedCommand(currentImage.command) }}
+
diff --git a/templates/base.html b/templates/base.html index 0f8851206..5729c40bd 100644 --- a/templates/base.html +++ b/templates/base.html @@ -42,6 +42,7 @@ + From e7e39e6146c622603c482de3aea6c6cee5734ed5 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 14 Jan 2014 15:26:31 -0500 Subject: [PATCH 2/6] Make sure we use the sanitized command everywhere we are injecting HTML --- static/js/controllers.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/static/js/controllers.js b/static/js/controllers.js index e159a554b..caadff1fb 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -59,7 +59,7 @@ function GuideCtrl($scope) { function SecurityCtrl($scope) { } -function RepoListCtrl($scope, Restangular, UserService, ApiService) { +function RepoListCtrl($scope, $sanitize, Restangular, UserService, ApiService) { $scope.namespace = null; $scope.page = 1; $scope.publicPageCount = null; @@ -562,6 +562,11 @@ function RepoCtrl($scope, $sanitize, Restangular, ApiService, $routeParams, $roo }); }; + var getSanitizedCommand = function(command) { + var formatted = getFormattedCommand(command); + return $sanitize(formatted); + }; + var listImages = function() { var params = {'repository': namespace + '/' + name}; $scope.imageHistory = ApiService.listRepositoryImagesAsResource(params).get(function(resp) { @@ -582,7 +587,7 @@ function RepoCtrl($scope, $sanitize, Restangular, ApiService, $routeParams, $roo // Create the new tree. $scope.tree = new ImageHistoryTree(namespace, name, resp.images, - getFirstTextLine, $scope.getTimeSince, $scope.getFormattedCommand); + getFirstTextLine, $scope.getTimeSince, getSanitizedCommand); $scope.tree.draw('image-history-container'); From 63899b8efd63ec1432268c8e752ffeeae70d6e3b Mon Sep 17 00:00:00 2001 From: yackob03 Date: Tue, 14 Jan 2014 15:28:24 -0500 Subject: [PATCH 3/6] Add a NOP command in the commands test db as would exist in a Dockerfile build. --- initdb.py | 1 + test/data/test.db | Bin 116736 -> 116736 bytes 2 files changed, 1 insertion(+) diff --git a/initdb.py b/initdb.py index 88468c022..0ce64c8b5 100644 --- a/initdb.py +++ b/initdb.py @@ -21,6 +21,7 @@ SAMPLE_CMDS = [['/bin/bash'], "echo \"PasswordAuthentication no\" >> /etc/ssh/sshd_config"], ["/bin/sh", "-c", "sed -i 's/#\\(force_color_prompt\\)/\\1/' /etc/skel/.bashrc"], + ["/bin/sh", "-c", "#(nop) EXPOSE [8080]"], None] REFERENCE_DATE = datetime(2013, 6, 23) diff --git a/test/data/test.db b/test/data/test.db index e8d38d652f9b2b309f450c91b2b63f519e1b6c82..677baedd3e9e0f5a2ac305abf7971e136fdd93bd 100644 GIT binary patch delta 4697 zcmbtXdvH|c6~E`+yV<-pPm+*iNeD!iG$eQL?%utdKs5W#K9WtsvbzaF2!TM@yd@8U z5br8h&=zP$zoAN@f!eAV9Vo01ltE^+RqLa6v|4N1sT$kr==2Y9Mlt>Nac@GC@Q37| zWbXN$bAIRj<*p<0yN<}8RvT+|O;&@ivCi$#wOC9xwbgAH+sE}I!CkLX0G#Z&`k6jeOzC6Z+mZ}al6NEH*~vOdz(C-V14Bd zYpbuNhi~oacKQQ7djfucpx-q-vc02cn}Ho1n)ueZn)B}R7={MMx;k5{v|fJ$v%@|x zv~6fK7+l}V4Z74^SHrG7HjT-#txMDC-MYcp(K)bl`$WK0&gnZme7)0e@>n+X`PBY^ zqkXu2xN=Wt!}froy~`Qw4w@|sv4@Q z>)O-J@2qbh8nW_(J#ODNKX=ETE`!PCsoP~5ZE1J94X&|zu4`h6nXA>B-0B8LU8C8o zaTr;P-K+7qovg9NY&F}sCM{oO)>*vn);engXSG{gRjh|M*zC=ghC064+~V?095gFA zpV8c*9UkZzak#bK!Ch5Do-uRhklxMe8v7g9H};Nfwd;7^F)+ZJn7ZBVo;^MF`mS>y zn3Z`qUo8E`ftwEE-1Nr7)cDQzO&uf<0D^)fe^63W=4AEkmeG`(=0n?iPE!#?{JiFgSdAr^9D4dp)hJmaTXA4DR|m zb)#9s25dIH&ED$tnG!T=R#%~BD_GrfmaEqA)ttVP*BMw&dv0d>GF5xC4$gkwxHNOF zZO)+6u&A8|`vRXolEG@EmH~}Ep<(p`0IXMl3^Fh+Aj8UK=(jS+UszU~#j5ATDDfyj z&IBmStePi_k~xzA(J)VXjy`FLAOPi|Omcu^c*LeHV33Eg+vGCYMph zBr!4Sc_p||3jdHN6cPl{mKUBbhhp`QN(f3vu7q@=1b@B$M+nlXgrE@0v?XmxbRl}Y z8*0(9+aZWf?t|=tm8*OE`pcGE1C5^b*5w-wYJ+;yicJ-om*nBZO^^k&^7}8(hl@5t zI?&0Jmkfw)gI9BkI{w|nD6k`%un|-(IRKjo+k}zUXM~m{D&p>h3c&te>B0UU+!FKG zs3Z&MAq~SZdWL60nQ{qR4s;%3`Y`tPcG!=;+zK^0tL!7HC{%DKh%u;8bl zwJY@3s7F@Kuq-1qrHF=AD0O?>Z$7!=ndLGOCK!`N41r2JVJe^0w?BC`#=AF3l(hj} zj4nmu8`}Yc=&fN$)i0cTl00UajH>gDAs#~)L4F1^u#V^=@NRen{Ha1?hG<%a8g|B_ zkVo@UNEQC+N-x9e7#8QIHeJLKK<6Ol6s8d9f?y7nb^eGif)YB@L`WMd+Xa`)7A8g| z?>?BvgmM&8{wk0mh?5ct1|2>CH8Qf|xwWI=Z-S7Pn4CqB)9@>1T?#N>C~?4gU(@c5$KG9)L%}S4SZg=rvVa zo(s#zB15t6z!Mp0*n7SqLhPG(jg}!B%t`q2j_>0zhlgi3nqE zU#!#D$tbzuYX-f20J2ksmI5&`DwMN7-gBi??u#?gYT=q{w2Vf_XmD{I`63nU;RsElGq+|EcS#i>G|NJJ3m+5wo7QMy58M?|Ph7xpg`g?Kh@L*u{1 z+;{yrJ{yIL@Jd?s#)*i2z9O_zDW$XmDesI0)I`hP|9Cor$jyk=^AJJ42|p(e5o>WJ zDv|mKxKl%l6_Rg6AB4+`xq~-Tjz?ob8;?SgBBUvq)uEY4U7nvP`b-e1S>GQUXw7Z_jpde%(r0=E<7zu+oV676`^);*lL9md@yL(Jr9y$Xf^syqa4fQ`B zZ~DddG&6SPBaxwj2`y1e*{(pgC*o78UfVypHjat~kx!6czi;aG;lPY5l;;oH@x7cp&U^OJB{w{YTU<)^QW&T59Txmj93q0uQgH+#mG z(!JZpPAiUN^xRp_SE=GJXtIq4cvj z$NSF54poheUiA+5U)1<8q=?u$$ws)C5}VpIEIU~r9li_g=)ev zTJdS8Qwyv+&{tu|#m!I7kfq>{-J!7Z8A!p`>&{aORQ@cyPVm=GcZrWNzjxDwKC5IF|7X^AIo=N3!rde9WE<$Kjz2vxLQOUi=_?1!LD;5nlrGTV_n( KgulB2H2puB6#O3m delta 4415 zcmbuBd2kcg9mn_Wt|S{vmT!E>7Z3}urPc0gcO@rIypoV?OUROZ$c6v`V;kFG8v`~t z0ka|@7XzeDdDF?HNt-(jZih>0L*hELVMyqNcG5|kqgM_mX$~^eluW`jeJg9%PVC7a zfqyjk_x=5TpZ7c7+x?%gu=qdrONLWms1;vpUq^5rT9;2s+sDL9vx<+$siyu)oPx-?C$X7sA8!zG2$B zBRt^P#Evv^Yr<=qyZ3}Rad;;)xP$9#DY4$hQ(QDkkJwt=G?M=fT&yLMI#MX^X{CJzIlJiq+lfaBpt)3Bav_9A{ zu(f^7=FTn74sK+tZL7bfiy0ILR@oebyRGiQA)&)L;1@c_pLLnJK4x>cw~1dp$O~N1 z@3RM~w)T$3xDJ}=$Eu+e6w z)kAw)T`q4R8l{`Uwn)_L^!mNtdUrtdw|M;_mTzedvSCMKw6%?H4mCPjg5!6)OGEYqwwXAx=)OJwl9K>)g}9N} zdFn#JP=pCI2i)~q#zxyMHrhh7Wi(&S($y?yWdx^xnwO*%j=VEefaRL}o&{1Y9xSU5JXLTM$02o`Kk z0({Z;n^V8vobp}snA-Y8blqJ0{HkfcWu8|!&G(zhZ<%LmSU)p&iR8or_Bm#}m=IHy z!r11u;LI}cPo36;Vc3TJ_;e*GwJmDE2s^$M7_o+Nk*wBBE>e$425ALbjzP=KV1hJ^ zcBRKt)`2WQrd9dU;w9^W5s)QczEc3{4dCV6+z(&gPZ>~167ymNiNAZ+E#jjHic>6ek?K(iqg^vNx&(3oZatiSDWQ}isS1J_kfm@^j=R1a#VvgvXiMaOWu6N6@!IiV8TdLm}7rPdNC{&zp@+X0qHm&I3CxH z%Da*A+grE7;#3>h?xpi1*(JvM&(MJ-YbvdD5x(pU;OlySyIMMF=#bf z3zU>*7~D5Ai|HA(m-xk8Y5bcppds}HHiZ#I;4=DIa-WzBYY%}3^;M;6Rtqe>U0yzA zWXMA1-!2)h0KWQDpv{OaFHyCKa25RQVMMt44t3TLCRu2hxRUV6Vw{w;saWL$Wbh^mV(zLOAV$o(SFOcABH75%iK(elp#+Lz2Gg~#qnj;n@9fBb+(g){1< z{&F=HeBy3paEaozL+ezTUN5aLQ)C!?>mIpD=1;G@rYcg89Rk`#Z({iG!C$a9u^V9K zA@GV`8of^OGQ$rKslkgbyg#M7<{PEnWr_@~Y!93~fR5XD81!j~C6n(SRF%0IF~?H% zRA@sw;E24ook!GCsy)a*zT~Rvn04$ZqPq>lp9jO(ZI}gBsSMtD7YM*dj)JqK;gj8a zRc*N;<}uGEfluA1%-l-7->k|?46)h@RaOZv{79`ZKHYMisw+BkKX@RO%sF#zUHqX3 zK{mS5-~Loo&6i1`@5x;Czst@%47B7@6GjO5dTPyLuA9_qQ96MZVH2bR#?gCQ6B=hPC(4YG&?{YK*AHHv>ObjViv<5KCuOA`9I1q_Q;f!o& z$qmOQVP+g;8#(ko-0W1squ1n&va4ju=}eC-%1H}26afZpaXG&w{&XB(j)S~KCJcWa zTm=+niaVYKivel+7q8-al@OKo zCO9eYbMU$3*N%qF+psfx*5%k)w8jF4e~6a>A10uKnhi07OAS#8d(X;4g+7C_bY&l{ zyBFQ`q5^G(!t=?GpmI&k)dR0mJWcTo#j_M|rzCH!B1CskIXuG4Q}hD5gEZQgHpBSe zUjTWaAZOcKcO=~sH!qqI{A0WVging38yKa?DkX>+5an?4JPN{2&5WYXwCmmz6wgz< zK=Ej7-5y1o_)eLA327@{MBgd88bi_jNntG0t)31Q2z{r>A#2Vl1=2ZQ4TR!X&jFJB EKR9Wt761SM From 6f0ea4f5a43ad8412e38f690acbca4fb4dc6af3c Mon Sep 17 00:00:00 2001 From: yackob03 Date: Tue, 14 Jan 2014 15:37:49 -0500 Subject: [PATCH 4/6] Reduce the number of nginx worker processes on staging to try and save memory. --- nginx-staging.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx-staging.conf b/nginx-staging.conf index c6927609e..b29bda2c5 100644 --- a/nginx-staging.conf +++ b/nginx-staging.conf @@ -1,4 +1,4 @@ -worker_processes 8; +worker_processes 2; user root nogroup; pid /mnt/nginx/nginx.pid; From 7b0cffa69c75cf206c9d6b22e6dd800d0a02539a Mon Sep 17 00:00:00 2001 From: yackob03 Date: Tue, 14 Jan 2014 15:47:13 -0500 Subject: [PATCH 5/6] Add an email address in a nop maintainer command as a test. --- initdb.py | 4 +++- test/data/test.db | Bin 116736 -> 117760 bytes 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/initdb.py b/initdb.py index 0ce64c8b5..2cd57708f 100644 --- a/initdb.py +++ b/initdb.py @@ -16,12 +16,14 @@ store = app.config['STORAGE'] SAMPLE_DIFFS = ['test/data/sample/diffs/diffs%s.json' % i for i in range(1, 10)] -SAMPLE_CMDS = [['/bin/bash'], +SAMPLE_CMDS = [["/bin/bash"], ["/bin/sh", "-c", "echo \"PasswordAuthentication no\" >> /etc/ssh/sshd_config"], ["/bin/sh", "-c", "sed -i 's/#\\(force_color_prompt\\)/\\1/' /etc/skel/.bashrc"], ["/bin/sh", "-c", "#(nop) EXPOSE [8080]"], + ["/bin/sh", "-c", + "#(nop) MAINTAINER Jake Moshenko "], None] REFERENCE_DATE = datetime(2013, 6, 23) diff --git a/test/data/test.db b/test/data/test.db index 677baedd3e9e0f5a2ac305abf7971e136fdd93bd..bc58279da13f6879374182a44543c36f5ca1ed8e 100644 GIT binary patch delta 5108 zcmb_f32+nF8Q!5v4b(I+mwM;Ed7djkH#zEU$ zPG^O+!ES0OH@LaNDnqTc)SxRbvpLHhRkfC4o2zDOz*5JKj`mm^S!TpCykV%Pro+`= zYaD0V+g$@=OmDTRP1mUVZnd$Y!)t7mOaeRVxYYh8twmY%xadb+yCT3F;^i|iwgUI*7Q=6DmKw(Yh#U_sjI7TmRGT?$y8;w)ipLc%Jr4DYFkBdeSIb8 zn7ZGZ&N}Ms!$Z}s{t`!d%h+g-d!l4?q(M7s9X9qFHrb7(ecTA!=PGX*bq+d9Hg%0$li6u5H53`EEG4GG z5|dl!X3fQ>27R&4>fBK4Dz;b+HSR`Bq0U=#4n0=WjxK;hfF4ZQ>Lt4#xBhA4U5>(7x`I7`JhdK!$;dv6|&D}%j zRA`cb;SUy+F`0w~d}DY6fr!5O_JW&lM}6b=B%i#1cG;YLT|{mJ+tSEwo%uhv zB{$zD=WLt8(o2KJiT|}9+=lNj2C6yHn^^)#GD<}}hM~{!?t$c3;tX+$I6?fJ_!)7S zcntO55TO|?Lc0S6=+VCpJ$lXP(ZivKM~@!eTKGyXSOk*&v$!8s z^D4Y64B6gXK&HXhdqDww_ztigzBvQdrs4FB)>kN&;wU5Kt(3__*c?DEgj7F*KHUO_ zVAg3MRpaK@E*}a&z4=lBh{0_e!E_RyU1BHDo=++f&1JxfRxk}U+kp(e*$Gsw4ohie z4f@(PXv$hV?HWh_@TT@YPrqhuH~L%9);>1UvazRK+uGl|eo+%nT|CQ$oYG+UEchq( z38q0l&iQzY8rUu9iIL^EGT03%&XIDH= zcDc;QZwcDH-O&PxX6WvSaC&jd!k0~H0h~|0K(wC+$Sin%2p)<>5F zkVeWB@;e11R^jO&3QG~4_V&byw)lKbdXbisn~)bTc7w%fYm}cAP&7l)C>Qa{NP|g` zdK^J_bODc;6FF?!3)UwoFC|y-LOw;TD3J?ey1`3F{puJAk+sZ$?GH)3VQ#7I|*+10g8=YQc0fOq&S$sm>x4Glvcc%*~W9HA%FO ziZBOKcOm;@TY^mxEQf6Qz8?*SMfAErF%sB$jL5;G(2pG@*9;4Gg1vH0T38))ZADi3 zf+AO*KYkIubT3fF`O;HG1Ui56pqo$DiEX?=R0Eofr6;n&;n6dco~68XDS|6GuyT8_ zonqs#9WPBpLj7M)0vVZ*h7mY;AKjPIgdTx$cY|M(G27c-r?2&sXmI`)r;9qwNy-BRjU0NcFCo8?(O9U9|oGy5V&%Wm&;E@nfT}Dbo zCdK)5iz9>63di}wqy2jkhlgsuVOsw9N6&~R5+2?Kq*2uvej1En)yNZ@+^1PC*ldPJ zcY&k9vw}a@$hD1k&OP;*cBf>HjzJg^UEEoRj`v}HQJqeLG zu6{torOo^RQHh?#@KfLvHj9-YHfut0t;!VwmIL?f3B@&?(A{|GP^cEY+g1uTP)xlF zsN`Gvi@khM;|#>JqM7A!))leBEJg+Al|dt4arS+|?wfZ!W7l89MeR#w_M^Iz6WH7Q ztb7Cg7?2yw0&Xb1qgug@X1)n$15J1c+2w}${m<9Jp0f9Sla7F3Of%}f3^lmy5EC@wB z8NKf4zDODW*GGYbl#`fQ7zk!mSu3FXIq*v|GxMuyxcC%!fn>IR^-O>b-2Nz#s#tyw ztPxoS=D->LsH=Q_-gAIVxI=R(tXt{bvRd!}b)6Y1c@I{d2+n%7giLv-Cn^9}u|_ej z_+{|B$Qio_esLOnl!0Atd4=L=iqlgZstr}XNI4H(tiJpOxE`H&QWrw_i%z21k(|A# zd?mjUY&aW+U&M34ZI}&(g^ly6ibNHG7lYZ?5W8hB3oiz;weI~AisL8_y%;#Gf+Hvu z=py+#g04Fo>@-ust>oCds z!*3JeIB<;r>$3om(Y%WQzkuNv&}-HoafNT8O*EMY|Mp_27?m>YuBA;OuQ(I+Z%}=V AWB>pF delta 4564 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 Date: Tue, 14 Jan 2014 16:01:37 -0500 Subject: [PATCH 6/6] Fix commands that have HTML characters in them --- static/js/app.js | 42 +++++++++++++++++++++++++++++++++ static/js/controllers.js | 31 ++++++------------------ static/js/graphing.js | 2 +- static/partials/image-view.html | 2 +- static/partials/view-repo.html | 4 ++-- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index e1f6a235f..76b05e2df 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -106,6 +106,48 @@ function getMarkedDown(string) { quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies', 'ngSanitize'], function($provide, cfpLoadingBarProvider) { cfpLoadingBarProvider.includeSpinner = false; + $provide.factory('UtilService', ['$sanitize', function($sanitize) { + var utilService = {}; + + utilService.textToSafeHtml = function(text) { + var adjusted = text.replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + + return $sanitize(adjusted); + }; + + return utilService; + }]); + + $provide.factory('ImageMetadataService', ['UtilService', function(UtilService) { + var metadataService = {}; + metadataService.getFormattedCommand = function(image) { + if (!image || !image.command || !image.command.length) { + return ''; + } + + var getCommandStr = function(command) { + // Handle /bin/sh commands specially. + if (command.length > 2 && command[0] == '/bin/sh' && command[1] == '-c') { + return command[2]; + } + + return command.join(' '); + }; + + return getCommandStr(image.command); + }; + + metadataService.getEscapedFormattedCommand = function(image) { + return UtilService.textToSafeHtml(metadataService.getFormattedCommand(image)); + }; + + return metadataService; + }]); + $provide.factory('ApiService', ['Restangular', function(Restangular) { var apiService = {}; diff --git a/static/js/controllers.js b/static/js/controllers.js index caadff1fb..5112fad6c 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -1,14 +1,3 @@ -function getFormattedCommand(command) { - if (!command || !command.length) { return ''; } - - // Handle /bin/sh commands specially. - if (command.length > 2 && command[0] == '/bin/sh' && command[1] == '-c') { - return command[2]; - } - - return command.join(' '); -} - $.fn.clipboardCopy = function() { var clip = new ZeroClipboard($(this), { 'moviePath': 'static/lib/ZeroClipboard.swf' }); @@ -168,7 +157,7 @@ function LandingCtrl($scope, UserService, ApiService) { browserchrome.update(); } -function RepoCtrl($scope, $sanitize, Restangular, ApiService, $routeParams, $rootScope, $location, $timeout) { +function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout) { var namespace = $routeParams.namespace; var name = $routeParams.name; @@ -203,11 +192,10 @@ function RepoCtrl($scope, $sanitize, Restangular, ApiService, $routeParams, $roo // Start scope methods ////////////////////////////////////////// - $scope.getFormattedCommand = getFormattedCommand; + $scope.getFormattedCommand = ImageMetadataService.getFormattedCommand; - $scope.getTooltipCommand = function(command) { - var formatted = getFormattedCommand(command); - var sanitized = $sanitize(formatted); + $scope.getTooltipCommand = function(image) { + var sanitized = ImageMetadataService.getEscapedFormattedCommand(image); return '' + sanitized + ''; }; @@ -562,11 +550,6 @@ function RepoCtrl($scope, $sanitize, Restangular, ApiService, $routeParams, $roo }); }; - var getSanitizedCommand = function(command) { - var formatted = getFormattedCommand(command); - return $sanitize(formatted); - }; - var listImages = function() { var params = {'repository': namespace + '/' + name}; $scope.imageHistory = ApiService.listRepositoryImagesAsResource(params).get(function(resp) { @@ -587,7 +570,7 @@ function RepoCtrl($scope, $sanitize, Restangular, ApiService, $routeParams, $roo // Create the new tree. $scope.tree = new ImageHistoryTree(namespace, name, resp.images, - getFirstTextLine, $scope.getTimeSince, getSanitizedCommand); + getFirstTextLine, $scope.getTimeSince, ImageMetadataService.getEscapedFormattedCommand); $scope.tree.draw('image-history-container'); @@ -998,12 +981,12 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use }; } -function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService) { +function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService) { var namespace = $routeParams.namespace; var name = $routeParams.name; var imageid = $routeParams.image; - $scope.getFormattedCommand = getFormattedCommand; + $scope.getFormattedCommand = ImageMetadataService.getFormattedCommand; $scope.parseDate = function(dateString) { return Date.parse(dateString); diff --git a/static/js/graphing.js b/static/js/graphing.js index c9f3ef24b..396228ab0 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -220,7 +220,7 @@ ImageHistoryTree.prototype.draw = function(container) { html += '' + formatComment(d.image.comment) + ''; } if (d.image.command && d.image.command.length) { - html += '' + formatCommand(d.image.command) + ''; + html += '' + formatCommand(d.image) + ''; } html += '' + formatTime(d.image.created) + ''; return html; diff --git a/static/partials/image-view.html b/static/partials/image-view.html index 102ac9bb8..45c9c7a74 100644 --- a/static/partials/image-view.html +++ b/static/partials/image-view.html @@ -46,7 +46,7 @@
Command
-
{{ getFormattedCommand(image.value.command) }}
+
{{ getFormattedCommand(image.value) }}
diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index 4ad6ec016..89091258e 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -170,8 +170,8 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}
Command
{{ getFormattedCommand(currentImage.command) }}
+ bs-tooltip="getTooltipCommand(currentImage)" + data-placement="top">{{ getFormattedCommand(currentImage) }}