diff options
author | Douwe Maan <douwe@gitlab.com> | 2015-10-07 15:15:35 +0200 |
---|---|---|
committer | Douwe Maan <douwe@gitlab.com> | 2015-10-07 15:15:35 +0200 |
commit | 4f629dab2a14c190b641bd709c28ebdad5b7a062 (patch) | |
tree | 00035d114b896a38b4a2a6ac835411ba36d3e1e5 /app | |
parent | 48837850838c8acb0c02ae60b29e18d287865878 (diff) | |
parent | 0611a18964a998b6edc81ef9b469f9f70430e542 (diff) | |
download | gitlab-ce-4f629dab2a14c190b641bd709c28ebdad5b7a062.tar.gz |
Merge branch 'master' into rs-redactor-filter
Diffstat (limited to 'app')
524 files changed, 10263 insertions, 2001 deletions
diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt new file mode 100755 index 00000000000..a9b845ed1d4 --- /dev/null +++ b/app/assets/fonts/OFL.txt @@ -0,0 +1,92 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/assets/fonts/SourceSansPro-Black.ttf b/app/assets/fonts/SourceSansPro-Black.ttf Binary files differnew file mode 100755 index 00000000000..cb89a2d171e --- /dev/null +++ b/app/assets/fonts/SourceSansPro-Black.ttf diff --git a/app/assets/fonts/SourceSansPro-BlackItalic.ttf b/app/assets/fonts/SourceSansPro-BlackItalic.ttf Binary files differnew file mode 100755 index 00000000000..c719243c0d6 --- /dev/null +++ b/app/assets/fonts/SourceSansPro-BlackItalic.ttf diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf b/app/assets/fonts/SourceSansPro-Bold.ttf Binary files differnew file mode 100755 index 00000000000..5d65c93242f --- /dev/null +++ b/app/assets/fonts/SourceSansPro-Bold.ttf diff --git a/app/assets/fonts/SourceSansPro-BoldItalic.ttf b/app/assets/fonts/SourceSansPro-BoldItalic.ttf Binary files differnew file mode 100755 index 00000000000..d20dd0c5eca --- /dev/null +++ b/app/assets/fonts/SourceSansPro-BoldItalic.ttf diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf b/app/assets/fonts/SourceSansPro-ExtraLight.ttf Binary files differnew file mode 100755 index 00000000000..bb4176c6fff --- /dev/null +++ b/app/assets/fonts/SourceSansPro-ExtraLight.ttf diff --git a/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf b/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf Binary files differnew file mode 100755 index 00000000000..2c34f3b8dc4 --- /dev/null +++ b/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf diff --git a/app/assets/fonts/SourceSansPro-Italic.ttf b/app/assets/fonts/SourceSansPro-Italic.ttf Binary files differnew file mode 100755 index 00000000000..e5a1a86e631 --- /dev/null +++ b/app/assets/fonts/SourceSansPro-Italic.ttf diff --git a/app/assets/fonts/SourceSansPro-Light.ttf b/app/assets/fonts/SourceSansPro-Light.ttf Binary files differnew file mode 100755 index 00000000000..83a0a336661 --- /dev/null +++ b/app/assets/fonts/SourceSansPro-Light.ttf diff --git a/app/assets/fonts/SourceSansPro-LightItalic.ttf b/app/assets/fonts/SourceSansPro-LightItalic.ttf Binary files differnew file mode 100755 index 00000000000..88a6778d24f --- /dev/null +++ b/app/assets/fonts/SourceSansPro-LightItalic.ttf diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf b/app/assets/fonts/SourceSansPro-Regular.ttf Binary files differnew file mode 100755 index 00000000000..44486cdc670 --- /dev/null +++ b/app/assets/fonts/SourceSansPro-Regular.ttf diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf b/app/assets/fonts/SourceSansPro-Semibold.ttf Binary files differnew file mode 100755 index 00000000000..86b00c067e0 --- /dev/null +++ b/app/assets/fonts/SourceSansPro-Semibold.ttf diff --git a/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf b/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf Binary files differnew file mode 100755 index 00000000000..2c5ad3008c3 --- /dev/null +++ b/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf diff --git a/app/assets/images/ci/arch.jpg b/app/assets/images/ci/arch.jpg Binary files differnew file mode 100644 index 00000000000..0e05674e840 --- /dev/null +++ b/app/assets/images/ci/arch.jpg diff --git a/app/assets/images/ci/favicon.ico b/app/assets/images/ci/favicon.ico Binary files differnew file mode 100644 index 00000000000..9663d4d00b9 --- /dev/null +++ b/app/assets/images/ci/favicon.ico diff --git a/app/assets/images/ci/loader.gif b/app/assets/images/ci/loader.gif Binary files differnew file mode 100644 index 00000000000..2fcb8f2da0d --- /dev/null +++ b/app/assets/images/ci/loader.gif diff --git a/app/assets/images/ci/no_avatar.png b/app/assets/images/ci/no_avatar.png Binary files differnew file mode 100644 index 00000000000..752d26adba7 --- /dev/null +++ b/app/assets/images/ci/no_avatar.png diff --git a/app/assets/images/ci/rails.png b/app/assets/images/ci/rails.png Binary files differnew file mode 100644 index 00000000000..d5edc04e65f --- /dev/null +++ b/app/assets/images/ci/rails.png diff --git a/app/assets/images/ci/service_sample.png b/app/assets/images/ci/service_sample.png Binary files differnew file mode 100644 index 00000000000..65d29e3fd89 --- /dev/null +++ b/app/assets/images/ci/service_sample.png diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee index 777c62dc1b7..63803747413 100644 --- a/app/assets/javascripts/activities.js.coffee +++ b/app/assets/javascripts/activities.js.coffee @@ -1,7 +1,7 @@ class @Activities constructor: -> Pager.init 20, true - $(".event_filter_link").bind "click", (event) => + $(".event-filter .btn").bind "click", (event) => event.preventDefault() @toggleFilter($(event.currentTarget)) @reloadActivities() @@ -12,7 +12,7 @@ class @Activities toggleFilter: (sender) -> - sender.parent().toggleClass "active" + sender.toggleClass "active" event_filters = $.cookie("event_filter") filter = sender.attr("id").split("_")[0] if event_filters diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee new file mode 100644 index 00000000000..3ab3ba66754 --- /dev/null +++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee @@ -0,0 +1,67 @@ +class @BlobFileDropzone + constructor: (form, method) -> + form_dropzone = form.find('.dropzone') + Dropzone.autoDiscover = false + dropzone = form_dropzone.dropzone( + autoDiscover: false + autoProcessQueue: false + url: form.attr('action') + # Rails uses a hidden input field for PUT + # http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails + method: method + clickable: true + uploadMultiple: false + paramName: "file" + maxFilesize: gon.max_file_size or 10 + parallelUploads: 1 + maxFiles: 1 + addRemoveLinks: true + previewsContainer: '.dropzone-previews' + headers: + "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") + + init: -> + this.on 'addedfile', (file) -> + $('.dropzone-alerts').html('').hide() + commit_message = form.find('#commit_message')[0] + + if /^Upload/.test(commit_message.placeholder) + commit_message.placeholder = 'Upload ' + file.name + + return + + this.on 'removedfile', (file) -> + commit_message = form.find('#commit_message')[0] + + if /^Upload/.test(commit_message.placeholder) + commit_message.placeholder = 'Upload new file' + + return + + this.on 'success', (header, response) -> + window.location.href = response.filePath + return + + this.on 'maxfilesexceeded', (file) -> + @removeFile file + return + + this.on 'sending', (file, xhr, formData) -> + formData.append('commit_message', form.find('#commit_message').val()) + return + + # Override behavior of adding error underneath preview + error: (file, errorMessage) -> + stripped = $("<div/>").html(errorMessage).text(); + $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show() + @removeFile file + return + ) + + submitButton = form.find('#submit-all')[0] + submitButton.addEventListener 'click', (e) -> + e.preventDefault() + e.stopPropagation() + alert "Please select a file" if dropzone[0].dropzone.getQueuedFiles().length == 0 + dropzone[0].dropzone.processQueue() + return false diff --git a/app/assets/javascripts/ci/Chart.min.js b/app/assets/javascripts/ci/Chart.min.js new file mode 100644 index 00000000000..ab635881087 --- /dev/null +++ b/app/assets/javascripts/ci/Chart.min.js @@ -0,0 +1,39 @@ +var Chart=function(s){function v(a,c,b){a=A((a-c.graphMin)/(c.steps*c.stepValue),1,0);return b*c.steps*a}function x(a,c,b,e){function h(){g+=f;var k=a.animation?A(d(g),null,0):1;e.clearRect(0,0,q,u);a.scaleOverlay?(b(k),c()):(c(),b(k));if(1>=g)D(h);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var f=a.animation?1/A(a.animationSteps,Number.MAX_VALUE,1):1,d=B[a.animationEasing],g=a.animation?0:1;"function"!==typeof c&&(c=function(){});D(h)}function C(a,c,b,e,h,f){var d;a= +Math.floor(Math.log(e-h)/Math.LN10);h=Math.floor(h/(1*Math.pow(10,a)))*Math.pow(10,a);e=Math.ceil(e/(1*Math.pow(10,a)))*Math.pow(10,a)-h;a=Math.pow(10,a);for(d=Math.round(e/a);d<b||d>c;)a=d<b?a/2:2*a,d=Math.round(e/a);c=[];z(f,c,d,h,a);return{steps:d,stepValue:a,graphMin:h,labels:c}}function z(a,c,b,e,h){if(a)for(var f=1;f<b+1;f++)c.push(E(a,{value:(e+h*f).toFixed(0!=h%1?h.toString().split(".")[1].length:0)}))}function A(a,c,b){return!isNaN(parseFloat(c))&&isFinite(c)&&a>c?c:!isNaN(parseFloat(b))&& +isFinite(b)&&a<b?b:a}function y(a,c){var b={},e;for(e in a)b[e]=a[e];for(e in c)b[e]=c[e];return b}function E(a,c){var b=!/\W/.test(a)?F[a]=F[a]||E(document.getElementById(a).innerHTML):new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c? +b(c):b}var r=this,B={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)? +0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1== +a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);e<Math.abs(1)?(e=1,c=b/4):c=b/(2* +Math.PI)*Math.asin(1/e);return-(e*Math.pow(2,10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b))},easeOutElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/e);return e*Math.pow(2,-10*a)*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInOutElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(2==(a/=0.5))return 1;b||(b=1*0.3*1.5);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/e);return 1>a?-0.5*e*Math.pow(2,10* +(a-=1))*Math.sin((1*a-c)*2*Math.PI/b):0.5*e*Math.pow(2,-10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=0.5)?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-B.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/=1)<1/2.75?1*7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)* +a+0.75):a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+0.9375):1*(7.5625*(a-=2.625/2.75)*a+0.984375)},easeInOutBounce:function(a){return 0.5>a?0.5*B.easeInBounce(2*a):0.5*B.easeOutBounce(2*a-1)+0.5}},q=s.canvas.width,u=s.canvas.height;window.devicePixelRatio&&(s.canvas.style.width=q+"px",s.canvas.style.height=u+"px",s.canvas.height=u*window.devicePixelRatio,s.canvas.width=q*window.devicePixelRatio,s.scale(window.devicePixelRatio,window.devicePixelRatio));this.PolarArea=function(a,c){r.PolarArea.defaults={scaleOverlay:!0, +scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce", +animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.PolarArea.defaults,c):r.PolarArea.defaults;return new G(a,b,s)};this.Radar=function(a,c){r.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)", +scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Radar.defaults,c):r.Radar.defaults;return new H(a,b,s)};this.Pie=function(a, +c){r.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.Pie.defaults,c):r.Pie.defaults;return new I(a,b,s)};this.Doughnut=function(a,c){r.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1, +onAnimationComplete:null};var b=c?y(r.Doughnut.defaults,c):r.Doughnut.defaults;return new J(a,b,s)};this.Line=function(a,c){r.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0, +pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Line.defaults,c):r.Line.defaults;return new K(a,b,s)};this.Bar=function(a,c){r.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'", +scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Bar.defaults,c):r.Bar.defaults;return new L(a,b,s)};var G=function(a,c,b){var e,h,f,d,g,k,j,l,m;g=Math.min.apply(Math,[q,u])/2;g-=Math.max.apply(Math,[0.5*c.scaleFontSize,0.5*c.scaleLineWidth]); +d=2*c.scaleFontSize;c.scaleShowLabelBackdrop&&(d+=2*c.scaleBackdropPaddingY,g-=1.5*c.scaleBackdropPaddingY);l=g;d=d?d:5;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.length;f++)a[f].value>e&&(e=a[f].value),a[f].value<h&&(h=a[f].value);f=Math.floor(l/(0.66*d));d=Math.floor(0.5*(l/d));m=c.scaleShowLabels?c.scaleLabel:null;c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(m,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(l,f,d,e,h, +m);k=g/j.steps;x(c,function(){for(var a=0;a<j.steps;a++)if(c.scaleShowLine&&(b.beginPath(),b.arc(q/2,u/2,k*(a+1),0,2*Math.PI,!0),b.strokeStyle=c.scaleLineColor,b.lineWidth=c.scaleLineWidth,b.stroke()),c.scaleShowLabels){b.textAlign="center";b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;var e=j.labels[a];if(c.scaleShowLabelBackdrop){var d=b.measureText(e).width;b.fillStyle=c.scaleBackdropColor;b.beginPath();b.rect(Math.round(q/2-d/2-c.scaleBackdropPaddingX),Math.round(u/2-k*(a+ +1)-0.5*c.scaleFontSize-c.scaleBackdropPaddingY),Math.round(d+2*c.scaleBackdropPaddingX),Math.round(c.scaleFontSize+2*c.scaleBackdropPaddingY));b.fill()}b.textBaseline="middle";b.fillStyle=c.scaleFontColor;b.fillText(e,q/2,u/2-k*(a+1))}},function(e){var d=-Math.PI/2,g=2*Math.PI/a.length,f=1,h=1;c.animation&&(c.animateScale&&(f=e),c.animateRotate&&(h=e));for(e=0;e<a.length;e++)b.beginPath(),b.arc(q/2,u/2,f*v(a[e].value,j,k),d,d+h*g,!1),b.lineTo(q/2,u/2),b.closePath(),b.fillStyle=a[e].color,b.fill(), +c.segmentShowStroke&&(b.strokeStyle=c.segmentStrokeColor,b.lineWidth=c.segmentStrokeWidth,b.stroke()),d+=h*g},b)},H=function(a,c,b){var e,h,f,d,g,k,j,l,m;a.labels||(a.labels=[]);g=Math.min.apply(Math,[q,u])/2;d=2*c.scaleFontSize;for(e=l=0;e<a.labels.length;e++)b.font=c.pointLabelFontStyle+" "+c.pointLabelFontSize+"px "+c.pointLabelFontFamily,h=b.measureText(a.labels[e]).width,h>l&&(l=h);g-=Math.max.apply(Math,[l,1.5*(c.pointLabelFontSize/2)]);g-=c.pointLabelFontSize;l=g=A(g,null,0);d=d?d:5;e=Number.MIN_VALUE; +h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(m=0;m<a.datasets[f].data.length;m++)a.datasets[f].data[m]>e&&(e=a.datasets[f].data[m]),a.datasets[f].data[m]<h&&(h=a.datasets[f].data[m]);f=Math.floor(l/(0.66*d));d=Math.floor(0.5*(l/d));m=c.scaleShowLabels?c.scaleLabel:null;c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(m,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(l,f,d,e,h,m);k=g/j.steps;x(c,function(){var e=2*Math.PI/ +a.datasets[0].data.length;b.save();b.translate(q/2,u/2);if(c.angleShowLineOut){b.strokeStyle=c.angleLineColor;b.lineWidth=c.angleLineWidth;for(var d=0;d<a.datasets[0].data.length;d++)b.rotate(e),b.beginPath(),b.moveTo(0,0),b.lineTo(0,-g),b.stroke()}for(d=0;d<j.steps;d++){b.beginPath();if(c.scaleShowLine){b.strokeStyle=c.scaleLineColor;b.lineWidth=c.scaleLineWidth;b.moveTo(0,-k*(d+1));for(var f=0;f<a.datasets[0].data.length;f++)b.rotate(e),b.lineTo(0,-k*(d+1));b.closePath();b.stroke()}c.scaleShowLabels&& +(b.textAlign="center",b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily,b.textBaseline="middle",c.scaleShowLabelBackdrop&&(f=b.measureText(j.labels[d]).width,b.fillStyle=c.scaleBackdropColor,b.beginPath(),b.rect(Math.round(-f/2-c.scaleBackdropPaddingX),Math.round(-k*(d+1)-0.5*c.scaleFontSize-c.scaleBackdropPaddingY),Math.round(f+2*c.scaleBackdropPaddingX),Math.round(c.scaleFontSize+2*c.scaleBackdropPaddingY)),b.fill()),b.fillStyle=c.scaleFontColor,b.fillText(j.labels[d],0,-k*(d+ +1)))}for(d=0;d<a.labels.length;d++){b.font=c.pointLabelFontStyle+" "+c.pointLabelFontSize+"px "+c.pointLabelFontFamily;b.fillStyle=c.pointLabelFontColor;var f=Math.sin(e*d)*(g+c.pointLabelFontSize),h=Math.cos(e*d)*(g+c.pointLabelFontSize);b.textAlign=e*d==Math.PI||0==e*d?"center":e*d>Math.PI?"right":"left";b.textBaseline="middle";b.fillText(a.labels[d],f,-h)}b.restore()},function(d){var e=2*Math.PI/a.datasets[0].data.length;b.save();b.translate(q/2,u/2);for(var g=0;g<a.datasets.length;g++){b.beginPath(); +b.moveTo(0,d*-1*v(a.datasets[g].data[0],j,k));for(var f=1;f<a.datasets[g].data.length;f++)b.rotate(e),b.lineTo(0,d*-1*v(a.datasets[g].data[f],j,k));b.closePath();b.fillStyle=a.datasets[g].fillColor;b.strokeStyle=a.datasets[g].strokeColor;b.lineWidth=c.datasetStrokeWidth;b.fill();b.stroke();if(c.pointDot){b.fillStyle=a.datasets[g].pointColor;b.strokeStyle=a.datasets[g].pointStrokeColor;b.lineWidth=c.pointDotStrokeWidth;for(f=0;f<a.datasets[g].data.length;f++)b.rotate(e),b.beginPath(),b.arc(0,d*-1* +v(a.datasets[g].data[f],j,k),c.pointDotRadius,2*Math.PI,!1),b.fill(),b.stroke()}b.rotate(e)}b.restore()},b)},I=function(a,c,b){for(var e=0,h=Math.min.apply(Math,[u/2,q/2])-5,f=0;f<a.length;f++)e+=a[f].value;x(c,null,function(d){var g=-Math.PI/2,f=1,j=1;c.animation&&(c.animateScale&&(f=d),c.animateRotate&&(j=d));for(d=0;d<a.length;d++){var l=j*a[d].value/e*2*Math.PI;b.beginPath();b.arc(q/2,u/2,f*h,g,g+l);b.lineTo(q/2,u/2);b.closePath();b.fillStyle=a[d].color;b.fill();c.segmentShowStroke&&(b.lineWidth= +c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());g+=l}},b)},J=function(a,c,b){for(var e=0,h=Math.min.apply(Math,[u/2,q/2])-5,f=h*(c.percentageInnerCutout/100),d=0;d<a.length;d++)e+=a[d].value;x(c,null,function(d){var k=-Math.PI/2,j=1,l=1;c.animation&&(c.animateScale&&(j=d),c.animateRotate&&(l=d));for(d=0;d<a.length;d++){var m=l*a[d].value/e*2*Math.PI;b.beginPath();b.arc(q/2,u/2,j*h,k,k+m,!1);b.arc(q/2,u/2,j*f,k+m,k,!0);b.closePath();b.fillStyle=a[d].color;b.fill();c.segmentShowStroke&& +(b.lineWidth=c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());k+=m}},b)},K=function(a,c,b){var e,h,f,d,g,k,j,l,m,t,r,n,p,s=0;g=u;b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;t=1;for(d=0;d<a.labels.length;d++)e=b.measureText(a.labels[d]).width,t=e>t?e:t;q/a.labels.length<t?(s=45,q/a.labels.length<Math.cos(s)*t?(s=90,g-=t):g-=Math.sin(s)*t):g-=c.scaleFontSize;d=c.scaleFontSize;g=g-5-d;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(l= +0;l<a.datasets[f].data.length;l++)a.datasets[f].data[l]>e&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]<h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily; +for(e=0;e<j.labels.length;e++)h=b.measureText(j.labels[e]).width,d=h>d?h:d;d+=10}r=q-d-t;m=Math.floor(r/(a.labels.length-1));n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0<s?(b.save(),b.textAlign="right"):b.textAlign="center";b.fillStyle=c.scaleFontColor;for(var d=0;d<a.labels.length;d++)b.save(),0<s?(b.translate(n+d*m,p+c.scaleFontSize),b.rotate(-(s*(Math.PI/180))),b.fillText(a.labels[d], +0,0),b.restore()):b.fillText(a.labels[d],n+d*m,p+c.scaleFontSize+3),b.beginPath(),b.moveTo(n+d*m,p+3),c.scaleShowGridLines&&0<d?(b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+d*m,5)):b.lineTo(n+d*m,p+3),b.stroke();b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(n,p+5);b.lineTo(n,5);b.stroke();b.textAlign="right";b.textBaseline="middle";for(d=0;d<j.steps;d++)b.beginPath(),b.moveTo(n-3,p-(d+1)*k),c.scaleShowGridLines?(b.lineWidth=c.scaleGridLineWidth, +b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+r+5,p-(d+1)*k)):b.lineTo(n-0.5,p-(d+1)*k),b.stroke(),c.scaleShowLabels&&b.fillText(j.labels[d],n-8,p-(d+1)*k)},function(d){function e(b,c){return p-d*v(a.datasets[b].data[c],j,k)}for(var f=0;f<a.datasets.length;f++){b.strokeStyle=a.datasets[f].strokeColor;b.lineWidth=c.datasetStrokeWidth;b.beginPath();b.moveTo(n,p-d*v(a.datasets[f].data[0],j,k));for(var g=1;g<a.datasets[f].data.length;g++)c.bezierCurve?b.bezierCurveTo(n+m*(g-0.5),e(f,g-1),n+m*(g-0.5), +e(f,g),n+m*g,e(f,g)):b.lineTo(n+m*g,e(f,g));b.stroke();c.datasetFill?(b.lineTo(n+m*(a.datasets[f].data.length-1),p),b.lineTo(n,p),b.closePath(),b.fillStyle=a.datasets[f].fillColor,b.fill()):b.closePath();if(c.pointDot){b.fillStyle=a.datasets[f].pointColor;b.strokeStyle=a.datasets[f].pointStrokeColor;b.lineWidth=c.pointDotStrokeWidth;for(g=0;g<a.datasets[f].data.length;g++)b.beginPath(),b.arc(n+m*g,p-d*v(a.datasets[f].data[g],j,k),c.pointDotRadius,0,2*Math.PI,!0),b.fill(),b.stroke()}}},b)},L=function(a, +c,b){var e,h,f,d,g,k,j,l,m,t,r,n,p,s,w=0;g=u;b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;t=1;for(d=0;d<a.labels.length;d++)e=b.measureText(a.labels[d]).width,t=e>t?e:t;q/a.labels.length<t?(w=45,q/a.labels.length<Math.cos(w)*t?(w=90,g-=t):g-=Math.sin(w)*t):g-=c.scaleFontSize;d=c.scaleFontSize;g=g-5-d;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(l=0;l<a.datasets[f].data.length;l++)a.datasets[f].data[l]>e&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]< +h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;for(e=0;e<j.labels.length;e++)h=b.measureText(j.labels[e]).width,d=h>d?h:d;d+=10}r=q-d-t;m= +Math.floor(r/a.labels.length);s=(m-2*c.scaleGridLineWidth-2*c.barValueSpacing-(c.barDatasetSpacing*a.datasets.length-1)-(c.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length;n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0<w?(b.save(),b.textAlign="right"):b.textAlign="center";b.fillStyle=c.scaleFontColor;for(var d=0;d<a.labels.length;d++)b.save(),0<w?(b.translate(n+ +d*m,p+c.scaleFontSize),b.rotate(-(w*(Math.PI/180))),b.fillText(a.labels[d],0,0),b.restore()):b.fillText(a.labels[d],n+d*m+m/2,p+c.scaleFontSize+3),b.beginPath(),b.moveTo(n+(d+1)*m,p+3),b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+(d+1)*m,5),b.stroke();b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(n,p+5);b.lineTo(n,5);b.stroke();b.textAlign="right";b.textBaseline="middle";for(d=0;d<j.steps;d++)b.beginPath(),b.moveTo(n-3,p-(d+1)* +k),c.scaleShowGridLines?(b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+r+5,p-(d+1)*k)):b.lineTo(n-0.5,p-(d+1)*k),b.stroke(),c.scaleShowLabels&&b.fillText(j.labels[d],n-8,p-(d+1)*k)},function(d){b.lineWidth=c.barStrokeWidth;for(var e=0;e<a.datasets.length;e++){b.fillStyle=a.datasets[e].fillColor;b.strokeStyle=a.datasets[e].strokeColor;for(var f=0;f<a.datasets[e].data.length;f++){var g=n+c.barValueSpacing+m*f+s*e+c.barDatasetSpacing*e+c.barStrokeWidth*e;b.beginPath(); +b.moveTo(g,p);b.lineTo(g,p-d*v(a.datasets[e].data[f],j,k)+c.barStrokeWidth/2);b.lineTo(g+s,p-d*v(a.datasets[e].data[f],j,k)+c.barStrokeWidth/2);b.lineTo(g+s,p);c.barShowStroke&&b.stroke();b.closePath();b.fill()}}},b)},D=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1E3/60)},F={}};
\ No newline at end of file diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee new file mode 100644 index 00000000000..05aa0f366bb --- /dev/null +++ b/app/assets/javascripts/ci/application.js.coffee @@ -0,0 +1,40 @@ +# This is a manifest file that'll be compiled into application.js, which will include all the files +# listed below. +# +# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +# +# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +# the compiled file. +# +# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +# GO AFTER THE REQUIRES BELOW. +# +#= require pager +#= require jquery_nested_form +#= require_tree . +# +$(document).on 'click', '.edit-runner-link', (event) -> + event.preventDefault() + + descr = $(this).closest('.runner-description').first() + descr.addClass('hide') + form = descr.next('.runner-description-form') + descrInput = form.find('input.description') + originalValue = descrInput.val() + form.removeClass('hide') + form.find('.cancel').on 'click', (event) -> + event.preventDefault() + + form.addClass('hide') + descrInput.val(originalValue) + descr.removeClass('hide') + +$(document).on 'click', '.assign-all-runner', -> + $(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..') + +window.unbindEvents = -> + $(document).unbind('scroll') + $(document).off('scroll') + +document.addEventListener("page:fetch", unbindEvents) diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee new file mode 100644 index 00000000000..c30859b484b --- /dev/null +++ b/app/assets/javascripts/ci/build.coffee @@ -0,0 +1,41 @@ +class CiBuild + @interval: null + + constructor: (build_url, build_status) -> + clearInterval(CiBuild.interval) + + if build_status == "running" || build_status == "pending" + # + # Bind autoscroll button to follow build output + # + $("#autoscroll-button").bind "click", -> + state = $(this).data("state") + if "enabled" is state + $(this).data "state", "disabled" + $(this).text "enable autoscroll" + else + $(this).data "state", "enabled" + $(this).text "disable autoscroll" + + # + # Check for new build output if user still watching build page + # Only valid for runnig build when output changes during time + # + CiBuild.interval = setInterval => + if window.location.href is build_url + $.ajax + url: build_url + dataType: "json" + success: (build) => + if build.status == "running" + $('#build-trace code').html build.trace_html + $('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>' + @checkAutoscroll() + else + Turbolinks.visit build_url + , 4000 + + checkAutoscroll: -> + $("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state") + +@CiBuild = CiBuild diff --git a/app/assets/javascripts/ci/projects.js.coffee b/app/assets/javascripts/ci/projects.js.coffee new file mode 100644 index 00000000000..7e028b4e115 --- /dev/null +++ b/app/assets/javascripts/ci/projects.js.coffee @@ -0,0 +1,6 @@ +$(document).on 'click', '.badge-codes-toggle', -> + $('.badge-codes-block').toggleClass("hide") + return false + +$(document).on 'click', '.sync-now', -> + $(this).find('i').addClass('fa-spin') diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index a0dcaa8c27a..6f789e668af 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -167,6 +167,7 @@ class @DropzoneInput dataType: "json" ).success (data) -> preview.html data.body + preview.syntaxHighlight() renderReferencedUsers data.references.users diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee index 176d9cabefa..c4d3e619f5e 100644 --- a/app/assets/javascripts/issuable_context.js.coffee +++ b/app/assets/javascripts/issuable_context.js.coffee @@ -11,12 +11,13 @@ class @IssuableContext $(this).submit() $('.issuable-details').waitForImages -> + $('.issuable-affix').on 'affix.bs.affix', -> + $(@).width($(@).outerWidth()) + .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> + $(@).width('') + $('.issuable-affix').affix offset: top: -> @top = ($('.issuable-affix').offset().top - 70) bottom: -> @bottom = $('.footer').outerHeight(true) - $('.issuable-affix').on 'affix.bs.affix', -> - $(@).width($(@).outerWidth()) - .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> - $(@).width('') diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 19a07b6a033..4e56791bde4 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -66,6 +66,11 @@ class @MergeRequestTabs @setCurrentAction(action) + scrollToElement: (container) -> + if window.location.hash + top = $(container + " " + window.location.hash).offset().top + $('body').scrollTo(top); + # Activate a tab based on the current action activateTab: (action) -> action = 'notes' if action == 'show' @@ -122,6 +127,7 @@ class @MergeRequestTabs document.getElementById('commits').innerHTML = data.html $('.js-timeago').timeago() @commitsLoaded = true + @scrollToElement(".commits") loadDiff: (source) -> return if @diffsLoaded @@ -131,6 +137,7 @@ class @MergeRequestTabs success: (data) => document.getElementById('diffs').innerHTML = data.html @diffsLoaded = true + @scrollToElement(".diffs") toggleLoading: -> $('.mr-loading-status .loading').toggle() diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 995a2f24093..3176e5a8965 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -15,11 +15,12 @@ class @MergeRequestWidget type: 'GET' url: $('.merge-request').data('url') success: (data) => - switch data.state - when 'merged' - location.reload() - else - setTimeout(merge_request_widget.mergeInProgress, 2000) + if data.state == "merged" + location.reload() + else if data.merge_error + $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>") + else + setTimeout(merge_request_widget.mergeInProgress, 2000) dataType: 'json' getMergeStatus: -> diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 0021d17d85e..4b9f0d68912 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -122,7 +122,9 @@ class @Notes # or skip if rendered if @isNewNote(note) @note_ids.push(note.id) - $('ul.main-notes-list').append(note.html) + $('ul.main-notes-list'). + append(note.html). + syntaxHighlight() @initTaskList() ### @@ -275,13 +277,15 @@ class @Notes Updates the current note field. ### - updateNote: (xhr, note, status) => - note_li = $(".note-row-" + note.id) - note_li.replaceWith(note.html) - note_li.find('.note-edit-form').hide() - note_li.find('.note-body > .note-text').show() - note_li.find('js-task-list-container').taskList('enable') - @enableTaskList() + updateNote: (_xhr, note, _status) => + # Convert returned HTML to a jQuery object so we can modify it further + $html = $(note.html) + $html.syntaxHighlight() + $html.find('.js-task-list-container').taskList('enable') + + # Find the note's `li` element by ID and replace it with the updated HTML + $note_li = $("#note_#{note.id}") + $note_li.replaceWith($html) ### Called in response to clicking the edit note link diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 39a433dfc91..0ea8fffce07 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -24,3 +24,19 @@ class @Project $.cookie('hide_no_password_message', 'false', { path: path }) $(@).parents('.no-password-message').remove() e.preventDefault() + + $('.update-notification').on 'click', (e) -> + e.preventDefault() + notification_level = $(@).data 'notification-level' + $('#notification_level').val(notification_level) + $('#notification-form').submit() + label = null + switch notification_level + when 0 then label = ' Disabled ' + when 1 then label = ' Participating ' + when 2 then label = ' Watching ' + when 3 then label = ' Global ' + when 4 then label = ' On Mention ' + $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") + $(@).parents('ul').find('li.active').removeClass 'active' + $(@).parent().addClass 'active'
\ No newline at end of file diff --git a/app/assets/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee index 510f15d1b49..980f0232d10 100644 --- a/app/assets/javascripts/syntax_highlight.coffee +++ b/app/assets/javascripts/syntax_highlight.coffee @@ -1,3 +1,5 @@ +# Syntax Highlighter +# # Applies a syntax highlighting color scheme CSS class to any element with the # `js-syntax-highlight` class # @@ -5,5 +7,14 @@ # # <div class="js-syntax-highlight"></div> # +$.fn.syntaxHighlight = -> + if $(this).hasClass('js-syntax-highlight') + # Given the element itself, apply highlighting + $(this).addClass(gon.user_color_scheme) + else + # Given a parent element, recurse to any of its applicable children + $children = $(this).find('.js-syntax-highlight') + $children.syntaxHighlight() if $children.length + $(document).on 'ready page:load', -> - $('.js-syntax-highlight').addClass(gon.user_color_scheme) + $('.js-syntax-highlight').syntaxHighlight() diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index d428db5b422..de8eebcd0b2 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -16,6 +16,9 @@ class @TreeView li = $("tr.tree-item") liSelected = null $('body').keydown (e) -> + if $("input:focus").length > 0 && (e.which == 38 || e.which == 40) + return false + if e.which is 40 if liSelected next = liSelected.next() @@ -38,4 +41,4 @@ class @TreeView $(liSelected).focus() else if e.which is 13 path = $('.tree-item.selected .tree-item-file-name a').attr('href') - Turbolinks.visit(path) + if path then Turbolinks.visit(path) diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee index 8a0564a9098..a1462cf3cae 100644 --- a/app/assets/javascripts/zen_mode.js.coffee +++ b/app/assets/javascripts/zen_mode.js.coffee @@ -38,6 +38,8 @@ class @ZenMode @active_checkbox = $(checkbox) @active_checkbox.prop('checked', true) @active_zen_area = @active_checkbox.parent().find('textarea') + # Prevent a user-resized textarea from persisting to fullscreen + @active_zen_area.removeAttr('style') @active_zen_area.focus() exitZenMode: => diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 1a5f11df7d1..d9ede637944 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -12,6 +12,7 @@ */ +@import "base/fonts"; @import "base/variables"; @import "base/mixins"; @import "base/layout"; @@ -60,3 +61,9 @@ * Styles for JS behaviors. */ @import "behaviors.scss"; + +/** + * CI specific styles: + */ +@import "ci/**/*"; + diff --git a/app/assets/stylesheets/base/fonts.scss b/app/assets/stylesheets/base/fonts.scss new file mode 100644 index 00000000000..e214567eca1 --- /dev/null +++ b/app/assets/stylesheets/base/fonts.scss @@ -0,0 +1,25 @@ +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf'); +} +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf'); +} +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf'); +} +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf'); +} diff --git a/app/assets/stylesheets/base/gl_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss index 21acbfa5e5a..eb8d23d6453 100644 --- a/app/assets/stylesheets/base/gl_bootstrap.scss +++ b/app/assets/stylesheets/base/gl_bootstrap.scss @@ -85,14 +85,14 @@ // Labels .label { padding: 2px 4px; - font-size: 12px; + font-size: 13px; font-style: normal; font-weight: normal; display: inline-block; &.label-gray { - background-color: #eee; - color: #999; + background-color: #f8fafc; + color: $gl-gray; text-shadow: none; } @@ -156,10 +156,16 @@ * Add some extra stuff to panels * */ + +.container-blank .panel .panel-heading { + font-size: 17px; + line-height: 38px; +} + .panel { - .panel-heading { - font-weight: bold; + box-shadow: none; + .panel-heading { .panel-head-actions { position: relative; top: -5px; @@ -182,6 +188,10 @@ .pagination { margin: 0; } + + .btn { + min-width: 124px; + } } &.panel-small { @@ -209,6 +219,12 @@ } } +.alert-help { + background-color: $background-color; + border: 1px solid $border-color; + color: $gl-gray; +} + // Typography ================================================================= .text-primary, diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/base/gl_variables.scss index 56f4c794e1b..7378d404008 100644 --- a/app/assets/stylesheets/base/gl_variables.scss +++ b/app/assets/stylesheets/base/gl_variables.scss @@ -22,6 +22,10 @@ $brand-info: $gl-info; $brand-warning: $gl-warning; $brand-danger: $gl-danger; +$border-radius-base: 3px !default; +$border-radius-large: 5px !default; +$border-radius-small: 2px !default; + //== Scaffolding // @@ -42,17 +46,18 @@ $font-size-base: $gl-font-size; // //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). -$padding-base-vertical: 6px; -$padding-base-horizontal: 14px; - +$padding-base-vertical: 9px; +$padding-base-horizontal: $gl-padding; +$component-active-color: #fff; +$component-active-bg: $brand-info; //== Forms // //## $input-color: $text-color; -$input-border: #DDD; -$input-border-focus: $brand-info; +$input-border: #e7e9ed; +$input-border-focus: #7F8FA4; $legend-color: $text-color; @@ -60,20 +65,20 @@ $legend-color: $text-color; // //## -$pagination-color: #fff; -$pagination-bg: $brand-success; +$pagination-color: $gl-gray; +$pagination-bg: $background-color; $pagination-border: transparent; $pagination-hover-color: #fff; -$pagination-hover-bg: darken($brand-success, 15%); +$pagination-hover-bg: $brand-info; $pagination-hover-border: transparent; $pagination-active-color: #fff; -$pagination-active-bg: darken($brand-success, 15%); +$pagination-active-bg: $brand-info; $pagination-active-border: transparent; -$pagination-disabled-color: #b4bcc2; -$pagination-disabled-bg: lighten($brand-success, 15%); +$pagination-disabled-color: #fff; +$pagination-disabled-bg: lighten($brand-info, 15%); $pagination-disabled-border: transparent; @@ -109,11 +114,12 @@ $alert-border-radius: 0; // //## -$panel-border-radius: 0; -$panel-default-text: $text-color; -$panel-default-border: $border-color; -$panel-default-heading-bg: $background-color; - +$panel-border-radius: 2px; +$panel-default-text: $text-color; +$panel-default-border: $border-color; +$panel-default-heading-bg: $background-color; +$panel-footer-bg: $background-color; +$panel-inner-border: $border-color; //== Wells // @@ -131,3 +137,22 @@ $code-bg: #f9f2f4; $kbd-color: #fff; $kbd-bg: #333; + +//== Buttons +// +//## +$btn-default-color: $gl-text-color; +$btn-default-bg: #fff; +$btn-default-border: #e7e9ed; + +//== Nav +// +//## +$nav-link-padding: 13px $gl-padding; + +//== Code +// +//## +$pre-bg: #f8fafc !default; +$pre-color: $gl-gray !default; +$pre-border-color: #e7e9ed; diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss index 734b95e26c0..b91c15d8910 100644 --- a/app/assets/stylesheets/base/layout.scss +++ b/app/assets/stylesheets/base/layout.scss @@ -21,7 +21,6 @@ html { margin-top: 30px; } - .container-limited { max-width: $fixed-layout-width; } diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss index 05f5bd79f91..c74a6d39824 100644 --- a/app/assets/stylesheets/base/mixins.scss +++ b/app/assets/stylesheets/base/mixins.scss @@ -55,8 +55,11 @@ } @mixin md-typography { - font-size: 15px; - line-height: 1.5; + color: $md-text-color; + + a { + color: $md-link-color; + } img { max-width: 100%; @@ -90,46 +93,88 @@ } h1 { - margin-top: 45px; - font-size: 2.5em; + font-size: 1.3em; + font-weight: 600; + margin: 24px 0 12px 0; + padding: 0 0 10px 0; + border-bottom: 1px solid #e7e9ed; + color: #313236; } h2 { - margin-top: 40px; - font-size: 2em; + font-size: 1.2em; + font-weight: 600; + margin: 24px 0 12px 0; + color: #313236; } h3 { - margin-top: 35px; - font-size: 1.5em; + margin: 24px 0 12px 0; + font-size: 1.25em; } h4 { - margin-top: 30px; - font-size: 1.2em; + margin: 24px 0 12px 0; + font-size: 1.1em; + } + + h5 { + margin: 24px 0 12px 0; + font-size: 1em; + } + + h6 { + margin: 24px 0 12px 0; + font-size: 0.90em; } blockquote { - color: #888; + padding: 8px 21px; + margin: 12px 0 12px; + border-left: 3px solid #e7e9ed; + } + + blockquote p { + color: #7f8fa4 !important; font-size: 15px; line-height: 1.5; } + p { + color:#5c5d5e; + margin:6px 0 0 0; + } + table { @extend .table; @extend .table-bordered; + margin: 12px 0 12px 0; + color: #5c5d5e; th { - background: #EEE; + background: #f8fafc; } } + pre { + margin: 12px 0 12px 0 !important; + background-color: #f8fafc !important; + font-size: 13px !important; + color: #5b6169 !important; + line-height: 1.6em !important; + @include border-radius(2px); + } + p > code { - font-size: inherit; font-weight: inherit; } + + ul { + color: #5c5d5e; + } + li { - line-height: 1.5; + line-height: 1.6em; } a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] { @@ -149,6 +194,7 @@ } } + @mixin str-truncated($max_width: 82%) { display: inline-block; overflow: hidden; @@ -166,7 +212,7 @@ padding: 0px; list-style: none; - li { + > li { padding: 10px 0; border-bottom: 1px solid #EEE; overflow: hidden; @@ -174,13 +220,13 @@ margin: 0px; &:last-child { - border:none + border-bottom: none; } &.active { background: #f9f9f9; a { - font-weight: bold; + font-weight: 600; } } @@ -190,8 +236,66 @@ &.light { a { - color: #777; + color: $gl-gray; } } } } + +@mixin input-big { + height: 36px; + padding: 5px 10px; + font-size: 16px; + line-height: 24px; + color: #7f8fa4; + background-color: #fff; + border-color: #e7e9ed; +} + +@mixin btn-big { + height: 36px; + padding: 5px 10px; + font-size: 16px; + line-height: 24px; +} + +@mixin nav-menu { + padding: 0; + margin: 0; + list-style: none; + margin-top: 5px; + height: 56px; + + li { + display: inline-block; + + a { + padding: 14px; + font-size: 17px; + line-height: 28px; + color: #7f8fa4; + border-bottom: 2px solid transparent; + + &:hover, &:active, &:focus { + text-decoration: none; + } + } + + &.active a { + color: #4c4e54; + border-bottom: 2px solid #1cacfc; + } + + .badge { + font-weight: normal; + background-color: #fff; + background-color: #eee; + color: #78a; + } + } +} + +.fa-align { + top: 20px; + position: relative; +} diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss index 26d0a1e5363..befd63832d5 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -1,27 +1,35 @@ -$style_color: #474D57; $hover: #FFFAF1; -$gl-text-color: #222222; -$gl-link-color: #446e9b; +$gl-text-color: #54565B; +$gl-text-green: #4A2; +$gl-text-red: #D12F19; +$gl-text-orange: #D90; +$gl-header-color: #4c4e54; +$gl-link-color: #333c48; +$md-text-color: #444; +$md-link-color: #3084bb; $nprogress-color: #c0392b; -$gl-font-size: 14px; +$gl-font-size: 15px; $list-font-size: 15px; -$sidebar_collapsed_width: 52px; +$sidebar_collapsed_width: 62px; $sidebar_width: 230px; $avatar_radius: 50%; $code_font_size: 13px; $code_line_height: 1.5; -$border-color: #E5E5E5; -$background-color: #f5f5f5; -$header-height: 50px; +$border-color: #dce0e6; +$background-color: #F7F8FA; +$header-height: 58px; $fixed-layout-width: 1200px; +$gl-gray: #7f8fa4; +$gl-padding: 16px; +$gl-avatar-size: 46px; /* * State colors: */ $gl-primary: #446e9b; -$gl-success: #019875; -$gl-info: #029ACF; +$gl-success: #44c679; +$gl-info: #00aaff; $gl-warning: #EB9532; $gl-danger: #d9534f; @@ -35,4 +43,4 @@ $deleted: #f77; * Fonts */ $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; -$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; +$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/ci/builds.scss b/app/assets/stylesheets/ci/builds.scss new file mode 100644 index 00000000000..a27dd0db581 --- /dev/null +++ b/app/assets/stylesheets/ci/builds.scss @@ -0,0 +1,76 @@ +.build-page { + pre.trace { + background: #111111; + color: #fff; + font-family: $monospace_font; + white-space: pre; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + overflow: auto; + overflow-y: hidden; + font-size: 12px; + + .fa-refresh { + font-size: 24px; + margin-left: 20px; + } + } + + .autoscroll-container { + position: fixed; + bottom: 10px; + right: 20px; + z-index: 100; + } + + .scroll-controls { + position: fixed; + bottom: 10px; + left: 250px; + z-index: 100; + + a { + display: block; + margin-bottom: 5px; + } + } + + .page-sidebar-collapsed { + .scroll-controls { + left: 70px; + } + } + + .build-widget { + padding: 10px; + background: $background-color; + margin-bottom: 20px; + border-radius: 4px; + + .title { + margin-top: 0; + color: #666; + line-height: 1.5; + } + .attr-name { + color: #777; + } + } + + .alert-disabled { + background: $background-color; + + a { + color: #3084bb !important; + } + } + + .build-top-menu { + margin-top: 0; + margin-bottom: 2px; + } +} + diff --git a/app/assets/stylesheets/ci/lint.scss b/app/assets/stylesheets/ci/lint.scss new file mode 100644 index 00000000000..6d2bd33b28b --- /dev/null +++ b/app/assets/stylesheets/ci/lint.scss @@ -0,0 +1,10 @@ +.ci-body { + .incorrect-syntax{ + font-size: 19px; + color: red; + } + .correct-syntax{ + font-size: 19px; + color: #47a447; + } +} diff --git a/app/assets/stylesheets/ci/projects.scss b/app/assets/stylesheets/ci/projects.scss new file mode 100644 index 00000000000..8c5273abcda --- /dev/null +++ b/app/assets/stylesheets/ci/projects.scss @@ -0,0 +1,59 @@ +.ci-body { + .project-title { + margin: 0; + color: #444; + font-size: 20px; + line-height: 1.5; + } + + .wide-table-holder { + margin-left: -$gl-padding; + margin-right: -$gl-padding; + } + + .builds, + .projects-table { + .light { + border-color: $border-color; + } + + th, td { + padding: 10px $gl-padding; + } + + td { + color: $gl-gray; + vertical-align: middle !important; + + a { + font-weight: normal; + text-decoration: none; + } + } + } + + .commit-info { + .attr-name { + margin-right: 5px; + } + + pre.commit-message { + background: none; + padding: 0; + margin: 0; + border: none; + margin: 20px 0; + border-radius: 0; + } + } + + .loading{ + font-size: 20px; + } + + .ci-charts { + fieldset { + margin-bottom: 16px; + } + } +} diff --git a/app/assets/stylesheets/ci/runners.scss b/app/assets/stylesheets/ci/runners.scss new file mode 100644 index 00000000000..2b15ab83129 --- /dev/null +++ b/app/assets/stylesheets/ci/runners.scss @@ -0,0 +1,36 @@ +.ci-body { + .runner-state { + padding: 6px 12px; + margin-right: 10px; + color: #FFF; + + &.runner-state-shared { + background: #32b186; + } + &.runner-state-specific { + background: #3498db; + } + } + + .runner-status-online { + color: green; + } + + .runner-status-offline { + color: gray; + } + + .runner-status-paused { + color: red; + } + + .runner { + .btn { + padding: 1px 6px; + } + + h4 { + font-weight: normal; + } + } +} diff --git a/app/assets/stylesheets/ci/status.scss b/app/assets/stylesheets/ci/status.scss new file mode 100644 index 00000000000..a7d3b2197f1 --- /dev/null +++ b/app/assets/stylesheets/ci/status.scss @@ -0,0 +1,37 @@ +.ci-status { + padding: 2px 7px; + margin-right: 5px; + border: 1px solid #EEE; + white-space: nowrap; + @include border-radius(4px); + + &:hover { + text-decoration: none; + } + + &.ci-failed { + color: $gl-danger; + border-color: $gl-danger; + } + + &.ci-success { + color: $gl-success; + border-color: $gl-success; + } + + &.ci-info { + color: $gl-info; + border-color: $gl-info; + } + + &.ci-disabled { + color: $gl-gray; + border-color: $gl-gray; + } + + &.ci-pending, + &.ci-running { + color: $gl-warning; + border-color: $gl-warning; + } +} diff --git a/app/assets/stylesheets/ci/xterm.scss b/app/assets/stylesheets/ci/xterm.scss new file mode 100644 index 00000000000..532dede0b23 --- /dev/null +++ b/app/assets/stylesheets/ci/xterm.scss @@ -0,0 +1,906 @@ +.ci-body { + // color codes are based on http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg + // see also: https://gist.github.com/jasonm23/2868981 + + $black: #000000; + $red: #cd0000; + $green: #00cd00; + $yellow: #cdcd00; + $blue: #0000ee; // according to wikipedia, this is the xterm standard + //$blue: #1e90ff; // this is used by all the terminals I tried (when configured with the xterm color profile) + $magenta: #cd00cd; + $cyan: #00cdcd; + $white: #e5e5e5; + $l-black: #7f7f7f; + $l-red: #ff0000; + $l-green: #00ff00; + $l-yellow: #ffff00; + $l-blue: #5c5cff; + $l-magenta: #ff00ff; + $l-cyan: #00ffff; + $l-white: #ffffff; + + .term-bold { + font-weight: bold; + } + .term-italic { + font-style: italic; + } + .term-conceal { + visibility: hidden; + } + .term-underline { + text-decoration: underline; + } + .term-cross { + text-decoration: line-through; + } + + .term-fg-black { + color: $black; + } + .term-fg-red { + color: $red; + } + .term-fg-green { + color: $green; + } + .term-fg-yellow { + color: $yellow; + } + .term-fg-blue { + color: $blue; + } + .term-fg-magenta { + color: $magenta; + } + .term-fg-cyan { + color: $cyan; + } + .term-fg-white { + color: $white; + } + .term-fg-l-black { + color: $l-black; + } + .term-fg-l-red { + color: $l-red; + } + .term-fg-l-green { + color: $l-green; + } + .term-fg-l-yellow { + color: $l-yellow; + } + .term-fg-l-blue { + color: $l-blue; + } + .term-fg-l-magenta { + color: $l-magenta; + } + .term-fg-l-cyan { + color: $l-cyan; + } + .term-fg-l-white { + color: $l-white; + } + + .term-bg-black { + background-color: $black; + } + .term-bg-red { + background-color: $red; + } + .term-bg-green { + background-color: $green; + } + .term-bg-yellow { + background-color: $yellow; + } + .term-bg-blue { + background-color: $blue; + } + .term-bg-magenta { + background-color: $magenta; + } + .term-bg-cyan { + background-color: $cyan; + } + .term-bg-white { + background-color: $white; + } + .term-bg-l-black { + background-color: $l-black; + } + .term-bg-l-red { + background-color: $l-red; + } + .term-bg-l-green { + background-color: $l-green; + } + .term-bg-l-yellow { + background-color: $l-yellow; + } + .term-bg-l-blue { + background-color: $l-blue; + } + .term-bg-l-magenta { + background-color: $l-magenta; + } + .term-bg-l-cyan { + background-color: $l-cyan; + } + .term-bg-l-white { + background-color: $l-white; + } + + + .xterm-fg-0 { + color: #000000; + } + .xterm-fg-1 { + color: #800000; + } + .xterm-fg-2 { + color: #008000; + } + .xterm-fg-3 { + color: #808000; + } + .xterm-fg-4 { + color: #000080; + } + .xterm-fg-5 { + color: #800080; + } + .xterm-fg-6 { + color: #008080; + } + .xterm-fg-7 { + color: #c0c0c0; + } + .xterm-fg-8 { + color: #808080; + } + .xterm-fg-9 { + color: #ff0000; + } + .xterm-fg-10 { + color: #00ff00; + } + .xterm-fg-11 { + color: #ffff00; + } + .xterm-fg-12 { + color: #0000ff; + } + .xterm-fg-13 { + color: #ff00ff; + } + .xterm-fg-14 { + color: #00ffff; + } + .xterm-fg-15 { + color: #ffffff; + } + .xterm-fg-16 { + color: #000000; + } + .xterm-fg-17 { + color: #00005f; + } + .xterm-fg-18 { + color: #000087; + } + .xterm-fg-19 { + color: #0000af; + } + .xterm-fg-20 { + color: #0000d7; + } + .xterm-fg-21 { + color: #0000ff; + } + .xterm-fg-22 { + color: #005f00; + } + .xterm-fg-23 { + color: #005f5f; + } + .xterm-fg-24 { + color: #005f87; + } + .xterm-fg-25 { + color: #005faf; + } + .xterm-fg-26 { + color: #005fd7; + } + .xterm-fg-27 { + color: #005fff; + } + .xterm-fg-28 { + color: #008700; + } + .xterm-fg-29 { + color: #00875f; + } + .xterm-fg-30 { + color: #008787; + } + .xterm-fg-31 { + color: #0087af; + } + .xterm-fg-32 { + color: #0087d7; + } + .xterm-fg-33 { + color: #0087ff; + } + .xterm-fg-34 { + color: #00af00; + } + .xterm-fg-35 { + color: #00af5f; + } + .xterm-fg-36 { + color: #00af87; + } + .xterm-fg-37 { + color: #00afaf; + } + .xterm-fg-38 { + color: #00afd7; + } + .xterm-fg-39 { + color: #00afff; + } + .xterm-fg-40 { + color: #00d700; + } + .xterm-fg-41 { + color: #00d75f; + } + .xterm-fg-42 { + color: #00d787; + } + .xterm-fg-43 { + color: #00d7af; + } + .xterm-fg-44 { + color: #00d7d7; + } + .xterm-fg-45 { + color: #00d7ff; + } + .xterm-fg-46 { + color: #00ff00; + } + .xterm-fg-47 { + color: #00ff5f; + } + .xterm-fg-48 { + color: #00ff87; + } + .xterm-fg-49 { + color: #00ffaf; + } + .xterm-fg-50 { + color: #00ffd7; + } + .xterm-fg-51 { + color: #00ffff; + } + .xterm-fg-52 { + color: #5f0000; + } + .xterm-fg-53 { + color: #5f005f; + } + .xterm-fg-54 { + color: #5f0087; + } + .xterm-fg-55 { + color: #5f00af; + } + .xterm-fg-56 { + color: #5f00d7; + } + .xterm-fg-57 { + color: #5f00ff; + } + .xterm-fg-58 { + color: #5f5f00; + } + .xterm-fg-59 { + color: #5f5f5f; + } + .xterm-fg-60 { + color: #5f5f87; + } + .xterm-fg-61 { + color: #5f5faf; + } + .xterm-fg-62 { + color: #5f5fd7; + } + .xterm-fg-63 { + color: #5f5fff; + } + .xterm-fg-64 { + color: #5f8700; + } + .xterm-fg-65 { + color: #5f875f; + } + .xterm-fg-66 { + color: #5f8787; + } + .xterm-fg-67 { + color: #5f87af; + } + .xterm-fg-68 { + color: #5f87d7; + } + .xterm-fg-69 { + color: #5f87ff; + } + .xterm-fg-70 { + color: #5faf00; + } + .xterm-fg-71 { + color: #5faf5f; + } + .xterm-fg-72 { + color: #5faf87; + } + .xterm-fg-73 { + color: #5fafaf; + } + .xterm-fg-74 { + color: #5fafd7; + } + .xterm-fg-75 { + color: #5fafff; + } + .xterm-fg-76 { + color: #5fd700; + } + .xterm-fg-77 { + color: #5fd75f; + } + .xterm-fg-78 { + color: #5fd787; + } + .xterm-fg-79 { + color: #5fd7af; + } + .xterm-fg-80 { + color: #5fd7d7; + } + .xterm-fg-81 { + color: #5fd7ff; + } + .xterm-fg-82 { + color: #5fff00; + } + .xterm-fg-83 { + color: #5fff5f; + } + .xterm-fg-84 { + color: #5fff87; + } + .xterm-fg-85 { + color: #5fffaf; + } + .xterm-fg-86 { + color: #5fffd7; + } + .xterm-fg-87 { + color: #5fffff; + } + .xterm-fg-88 { + color: #870000; + } + .xterm-fg-89 { + color: #87005f; + } + .xterm-fg-90 { + color: #870087; + } + .xterm-fg-91 { + color: #8700af; + } + .xterm-fg-92 { + color: #8700d7; + } + .xterm-fg-93 { + color: #8700ff; + } + .xterm-fg-94 { + color: #875f00; + } + .xterm-fg-95 { + color: #875f5f; + } + .xterm-fg-96 { + color: #875f87; + } + .xterm-fg-97 { + color: #875faf; + } + .xterm-fg-98 { + color: #875fd7; + } + .xterm-fg-99 { + color: #875fff; + } + .xterm-fg-100 { + color: #878700; + } + .xterm-fg-101 { + color: #87875f; + } + .xterm-fg-102 { + color: #878787; + } + .xterm-fg-103 { + color: #8787af; + } + .xterm-fg-104 { + color: #8787d7; + } + .xterm-fg-105 { + color: #8787ff; + } + .xterm-fg-106 { + color: #87af00; + } + .xterm-fg-107 { + color: #87af5f; + } + .xterm-fg-108 { + color: #87af87; + } + .xterm-fg-109 { + color: #87afaf; + } + .xterm-fg-110 { + color: #87afd7; + } + .xterm-fg-111 { + color: #87afff; + } + .xterm-fg-112 { + color: #87d700; + } + .xterm-fg-113 { + color: #87d75f; + } + .xterm-fg-114 { + color: #87d787; + } + .xterm-fg-115 { + color: #87d7af; + } + .xterm-fg-116 { + color: #87d7d7; + } + .xterm-fg-117 { + color: #87d7ff; + } + .xterm-fg-118 { + color: #87ff00; + } + .xterm-fg-119 { + color: #87ff5f; + } + .xterm-fg-120 { + color: #87ff87; + } + .xterm-fg-121 { + color: #87ffaf; + } + .xterm-fg-122 { + color: #87ffd7; + } + .xterm-fg-123 { + color: #87ffff; + } + .xterm-fg-124 { + color: #af0000; + } + .xterm-fg-125 { + color: #af005f; + } + .xterm-fg-126 { + color: #af0087; + } + .xterm-fg-127 { + color: #af00af; + } + .xterm-fg-128 { + color: #af00d7; + } + .xterm-fg-129 { + color: #af00ff; + } + .xterm-fg-130 { + color: #af5f00; + } + .xterm-fg-131 { + color: #af5f5f; + } + .xterm-fg-132 { + color: #af5f87; + } + .xterm-fg-133 { + color: #af5faf; + } + .xterm-fg-134 { + color: #af5fd7; + } + .xterm-fg-135 { + color: #af5fff; + } + .xterm-fg-136 { + color: #af8700; + } + .xterm-fg-137 { + color: #af875f; + } + .xterm-fg-138 { + color: #af8787; + } + .xterm-fg-139 { + color: #af87af; + } + .xterm-fg-140 { + color: #af87d7; + } + .xterm-fg-141 { + color: #af87ff; + } + .xterm-fg-142 { + color: #afaf00; + } + .xterm-fg-143 { + color: #afaf5f; + } + .xterm-fg-144 { + color: #afaf87; + } + .xterm-fg-145 { + color: #afafaf; + } + .xterm-fg-146 { + color: #afafd7; + } + .xterm-fg-147 { + color: #afafff; + } + .xterm-fg-148 { + color: #afd700; + } + .xterm-fg-149 { + color: #afd75f; + } + .xterm-fg-150 { + color: #afd787; + } + .xterm-fg-151 { + color: #afd7af; + } + .xterm-fg-152 { + color: #afd7d7; + } + .xterm-fg-153 { + color: #afd7ff; + } + .xterm-fg-154 { + color: #afff00; + } + .xterm-fg-155 { + color: #afff5f; + } + .xterm-fg-156 { + color: #afff87; + } + .xterm-fg-157 { + color: #afffaf; + } + .xterm-fg-158 { + color: #afffd7; + } + .xterm-fg-159 { + color: #afffff; + } + .xterm-fg-160 { + color: #d70000; + } + .xterm-fg-161 { + color: #d7005f; + } + .xterm-fg-162 { + color: #d70087; + } + .xterm-fg-163 { + color: #d700af; + } + .xterm-fg-164 { + color: #d700d7; + } + .xterm-fg-165 { + color: #d700ff; + } + .xterm-fg-166 { + color: #d75f00; + } + .xterm-fg-167 { + color: #d75f5f; + } + .xterm-fg-168 { + color: #d75f87; + } + .xterm-fg-169 { + color: #d75faf; + } + .xterm-fg-170 { + color: #d75fd7; + } + .xterm-fg-171 { + color: #d75fff; + } + .xterm-fg-172 { + color: #d78700; + } + .xterm-fg-173 { + color: #d7875f; + } + .xterm-fg-174 { + color: #d78787; + } + .xterm-fg-175 { + color: #d787af; + } + .xterm-fg-176 { + color: #d787d7; + } + .xterm-fg-177 { + color: #d787ff; + } + .xterm-fg-178 { + color: #d7af00; + } + .xterm-fg-179 { + color: #d7af5f; + } + .xterm-fg-180 { + color: #d7af87; + } + .xterm-fg-181 { + color: #d7afaf; + } + .xterm-fg-182 { + color: #d7afd7; + } + .xterm-fg-183 { + color: #d7afff; + } + .xterm-fg-184 { + color: #d7d700; + } + .xterm-fg-185 { + color: #d7d75f; + } + .xterm-fg-186 { + color: #d7d787; + } + .xterm-fg-187 { + color: #d7d7af; + } + .xterm-fg-188 { + color: #d7d7d7; + } + .xterm-fg-189 { + color: #d7d7ff; + } + .xterm-fg-190 { + color: #d7ff00; + } + .xterm-fg-191 { + color: #d7ff5f; + } + .xterm-fg-192 { + color: #d7ff87; + } + .xterm-fg-193 { + color: #d7ffaf; + } + .xterm-fg-194 { + color: #d7ffd7; + } + .xterm-fg-195 { + color: #d7ffff; + } + .xterm-fg-196 { + color: #ff0000; + } + .xterm-fg-197 { + color: #ff005f; + } + .xterm-fg-198 { + color: #ff0087; + } + .xterm-fg-199 { + color: #ff00af; + } + .xterm-fg-200 { + color: #ff00d7; + } + .xterm-fg-201 { + color: #ff00ff; + } + .xterm-fg-202 { + color: #ff5f00; + } + .xterm-fg-203 { + color: #ff5f5f; + } + .xterm-fg-204 { + color: #ff5f87; + } + .xterm-fg-205 { + color: #ff5faf; + } + .xterm-fg-206 { + color: #ff5fd7; + } + .xterm-fg-207 { + color: #ff5fff; + } + .xterm-fg-208 { + color: #ff8700; + } + .xterm-fg-209 { + color: #ff875f; + } + .xterm-fg-210 { + color: #ff8787; + } + .xterm-fg-211 { + color: #ff87af; + } + .xterm-fg-212 { + color: #ff87d7; + } + .xterm-fg-213 { + color: #ff87ff; + } + .xterm-fg-214 { + color: #ffaf00; + } + .xterm-fg-215 { + color: #ffaf5f; + } + .xterm-fg-216 { + color: #ffaf87; + } + .xterm-fg-217 { + color: #ffafaf; + } + .xterm-fg-218 { + color: #ffafd7; + } + .xterm-fg-219 { + color: #ffafff; + } + .xterm-fg-220 { + color: #ffd700; + } + .xterm-fg-221 { + color: #ffd75f; + } + .xterm-fg-222 { + color: #ffd787; + } + .xterm-fg-223 { + color: #ffd7af; + } + .xterm-fg-224 { + color: #ffd7d7; + } + .xterm-fg-225 { + color: #ffd7ff; + } + .xterm-fg-226 { + color: #ffff00; + } + .xterm-fg-227 { + color: #ffff5f; + } + .xterm-fg-228 { + color: #ffff87; + } + .xterm-fg-229 { + color: #ffffaf; + } + .xterm-fg-230 { + color: #ffffd7; + } + .xterm-fg-231 { + color: #ffffff; + } + .xterm-fg-232 { + color: #080808; + } + .xterm-fg-233 { + color: #121212; + } + .xterm-fg-234 { + color: #1c1c1c; + } + .xterm-fg-235 { + color: #262626; + } + .xterm-fg-236 { + color: #303030; + } + .xterm-fg-237 { + color: #3a3a3a; + } + .xterm-fg-238 { + color: #444444; + } + .xterm-fg-239 { + color: #4e4e4e; + } + .xterm-fg-240 { + color: #585858; + } + .xterm-fg-241 { + color: #626262; + } + .xterm-fg-242 { + color: #6c6c6c; + } + .xterm-fg-243 { + color: #767676; + } + .xterm-fg-244 { + color: #808080; + } + .xterm-fg-245 { + color: #8a8a8a; + } + .xterm-fg-246 { + color: #949494; + } + .xterm-fg-247 { + color: #9e9e9e; + } + .xterm-fg-248 { + color: #a8a8a8; + } + .xterm-fg-249 { + color: #b2b2b2; + } + .xterm-fg-250 { + color: #bcbcbc; + } + .xterm-fg-251 { + color: #c6c6c6; + } + .xterm-fg-252 { + color: #d0d0d0; + } + .xterm-fg-253 { + color: #dadada; + } + .xterm-fg-254 { + color: #e4e4e4; + } + .xterm-fg-255 { + color: #eeeeee; + } +} diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/generic/avatar.scss index 8595887c3b9..36e582d4854 100644 --- a/app/assets/stylesheets/generic/avatar.scss +++ b/app/assets/stylesheets/generic/avatar.scss @@ -23,8 +23,13 @@ &.s24 { width: 24px; height: 24px; margin-right: 8px; } &.s26 { width: 26px; height: 26px; margin-right: 8px; } &.s32 { width: 32px; height: 32px; margin-right: 10px; } + &.s36 { width: 36px; height: 36px; margin-right: 10px; } + &.s46 { width: 46px; height: 46px; margin-right: 15px; } + &.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; } + &.s110 { width: 110px; height: 110px; margin-right: 15px; } + &.s140 { width: 140px; height: 140px; margin-right: 20px; } &.s160 { width: 160px; height: 160px; margin-right: 20px; } } @@ -38,5 +43,7 @@ &.s32 { font-size: 22px; line-height: 32px; } &.s60 { font-size: 32px; line-height: 60px; } &.s90 { font-size: 36px; line-height: 90px; } - &.s160 { font-size: 96px; line-height: 1.33; } + &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; } + &.s140 { font-size: 72px; line-height: 140px; } + &.s160 { font-size: 96px; line-height: 160px; } } diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss index 3536a68f416..6ce34b5c3e8 100644 --- a/app/assets/stylesheets/generic/blocks.scss +++ b/app/assets/stylesheets/generic/blocks.scss @@ -1,19 +1,62 @@ .light-well { - background: #f9f9f9; + background-color: #f8fafc; padding: 15px; } .centered-light-block { text-align: center; - color: #888; + color: $gl-gray; margin: 20px; } .nothing-here-block { text-align: center; padding: 20px; - color: #666; + color: $gl-gray; font-weight: normal; font-size: 16px; line-height: 36px; } + +.gray-content-block { + margin: -$gl-padding; + background-color: $background-color; + padding: $gl-padding; + margin-bottom: 0px; + border-top: 1px solid $border-color; + border-bottom: 1px solid $border-color; + color: $gl-gray; + + &.top-block { + border-top: none; + } + + &.middle-block { + margin-top: 0; + margin-bottom: 0; + } + + &.clear-block { + margin-bottom: $gl-padding - 1px; + padding-bottom: $gl-padding; + } + + &.second-block { + margin-top: -1px; + margin-bottom: 0; + } + + &.footer-block { + margin-top: 0; + border-bottom: none; + margin-bottom: -$gl-padding; + } + + .title { + color: $gl-text-color; + } + + .oneline { + line-height: 42px; + } +} diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index cd6bf64c0ae..cf76f538e01 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -1,3 +1,6 @@ +body { + text-rendering: geometricPrecision; +} .btn { @extend .btn-default; @@ -10,7 +13,7 @@ } &.btn-save { - @extend .btn-primary; + @extend .btn-success; } &.btn-remove { @@ -72,3 +75,154 @@ } } } + +.btn-group-next { + .btn { + padding: 9px 0px; + font-size: 15px; + color: #7f8fa4; + border-color: #e7e9ed; + width: 140px; + + &.active { + border-color: $gl-info; + background: $gl-info; + color: #fff; + } + } +} + +@mixin btn-info { + @include border-radius(2px); + + border-width: 1px; + border-style: solid; + text-transform: uppercase; + font-size: 13px; + font-weight: 600; + line-height: 18px; + padding: 11px 16px; + letter-spacing: .4px; + + &:hover { + border-width: 1px; + border-style: solid; + } + + &:focus { + border-width: 1px; + border-style: solid; + } + + &:active { + @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); + border-width: 1px; + border-style: solid; + } +} + +@mixin btn-middle { + @include border-radius(2px); + + border-width: 1px; + border-style: solid; + text-transform: uppercase; + font-size: 13px; + font-weight: 600; + line-height: 18px; + padding: 11px 24px; + letter-spacing: .4px; + + &:hover { + border-width: 1px; + border-style: solid; + } + + &:focus { + border-width: 1px; + border-style: solid; + } + + &:active { + @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); + border-width: 1px; + border-style: solid; + } +} + + +@mixin btn-green { + background-color: #28b061; + border: 1px solid #26a65c; + color: #fff; + + &:hover { + background-color: #26ab5d; + border: 1px solid #229954; + color: #fff; + } + + &:focus { + background-color: #26ab5d; + border: 1px solid #229954; + color: #fff; + } + + &:active { + @include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12)); + + background-color: #23a158 !important; + border: 1px solid #229954 !important; + color: #fff !important; + } +} + +/*Butons*/ + +@mixin bnt-project { + background-color: #f0f2f5; + border-color: #dce0e5; + color: #313236; + + &:hover { + border-color:#dce0e5; + background-color: #ebeef2; + color: #313236; + } + + &:focus { + border-color: #dce0e5; + background-color: #ebeef2; + color: #313236; + } + + &:active { + @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); + + color: #313236 !important; + border-color: #c6cacf !important; + background-color: #e4e7ed !important; + } +} + +@mixin btn-remove { + background-color: #f72e60; + border-color: #ee295a; + + &:hover { + background-color: #e82757; + border-color: #e32555; + } + + &:focus { + background-color: #e82757; + border-color: #e32555; + } + + &:active { + @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); + background-color: #d42450 !important; + border-color: #e12554 !important; + } + +}
\ No newline at end of file diff --git a/app/assets/stylesheets/generic/callout.scss b/app/assets/stylesheets/generic/callout.scss new file mode 100644 index 00000000000..f1699d21c9b --- /dev/null +++ b/app/assets/stylesheets/generic/callout.scss @@ -0,0 +1,45 @@ +/* + * Callouts from Bootstrap3 docs + * + * Not quite alerts, but custom and helpful notes for folks reading the docs. + * Requires a base and modifier class. + */ + +/* Common styles for all types */ +.bs-callout { + margin: 20px 0; + padding: 20px; + border-left: 3px solid #eee; + color: #666; + background: #f9f9f9; +} +.bs-callout h4 { + margin-top: 0; + margin-bottom: 5px; +} +.bs-callout p:last-child { + margin-bottom: 0; +} + +/* Variations */ +.bs-callout-danger { + background-color: #fdf7f7; + border-color: #eed3d7; + color: #b94a48; +} +.bs-callout-warning { + background-color: #faf8f0; + border-color: #faebcc; + color: #8a6d3b; +} +.bs-callout-info { + background-color: #f4f8fa; + border-color: #bce8f1; + color: #34789a; +} +.bs-callout-success { + background-color: #dff0d8; + border-color: #5cA64d; + color: #3c763d; +} + diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index e5902597c4d..45e284542d2 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -1,12 +1,13 @@ /** COLORS **/ -.cgray { color: gray } +.cgray { color: $gl-gray; } .clgray { color: #BBB } -.cred { color: #D12F19 } -.cgreen { color: #4a2 } +.cred { color: $gl-text-red; } +.cgreen { color: $gl-text-green; } .cdark { color: #444 } /** COMMON CLASSES **/ .prepend-top-10 { margin-top:10px } +.prepend-top-default { margin-top: $gl-padding; } .prepend-top-20 { margin-top:20px } .prepend-left-10 { margin-left:10px } .prepend-left-20 { margin-left:20px } @@ -20,10 +21,10 @@ .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } -.light { color: #888 } +.light { color: $gl-gray; } .slead { - color: #666; + color: $gl-gray; font-size: 15px; margin-bottom: 12px; font-weight: normal; @@ -74,8 +75,6 @@ pre { color: $gl-link-color; } -.help li { color:$style_color; } - .back-link { font-size: 14px; } @@ -303,7 +302,7 @@ table { } .btn-sign-in { - margin-top: 7px; + margin-top: 8px; text-shadow: none; } @@ -314,7 +313,7 @@ table { } .wiki .highlight, .note-body .highlight { - margin-bottom: 9px; + margin: 12px 0 12px 0; } .wiki .code { @@ -355,14 +354,14 @@ table { } .description { - font-size: 16px; + font-size: $gl-font-size; color: #666; margin-top: 8px; } } .profiler-results { - top: 50px !important; + top: 73px !important; .profiler-button, .profiler-controls { @@ -371,21 +370,27 @@ table { } .center-top-menu { - list-style: none; + @include nav-menu; text-align: center; margin-top: 5px; - padding-bottom: 15px; - margin-bottom: 15px; - - li { - display: inline-block; + margin-bottom: $gl-padding; + height: 56px; + margin-top: -$gl-padding; + padding-top: $gl-padding; - a { - padding: 15px; - } + &.no-bottom { + margin-bottom: 0; + } - &.active a { - color: #666; - } + &.no-top { + margin-top: 0; } } + +.dropzone .dz-preview .dz-progress { + border-color: $border-color !important; +} + +.dropzone .dz-preview .dz-progress .dz-upload { + background: $gl-success !important; +} diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss index f845342c67b..9dd77747884 100644 --- a/app/assets/stylesheets/generic/files.scss +++ b/app/assets/stylesheets/generic/files.scss @@ -3,7 +3,11 @@ * */ .file-holder { - border: 1px solid $border-color; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border: none; + border-top: 1px solid #E7E9EE; + border-bottom: 1px solid #E7E9EE; margin-bottom: 1em; table { @@ -49,7 +53,7 @@ } &.wiki { - padding: 25px; + padding: $gl-padding; .highlight { margin-bottom: 9px; @@ -90,7 +94,7 @@ border-right: none; } background: #fff; - padding: 8px; + padding: 10px $gl-padding; } .lines { pre { @@ -100,6 +104,33 @@ border: none; } } + img.avatar { + border: 0 none; + float: none; + margin: 0; + padding: 0; + } + td.blame-commit { + background: #f9f9f9; + min-width: 350px; + + .commit-author-link { + color: #888; + } + } + td.blame-numbers { + pre { + color: #AAA; + white-space: pre; + } + background: #f1f1f1; + border-left: 1px solid #DDD; + } + td.lines { + code { + font-family: $monospace_font; + } + } } &.logs { diff --git a/app/assets/stylesheets/generic/filters.scss b/app/assets/stylesheets/generic/filters.scss index bd93a79722d..8e6922c9231 100644 --- a/app/assets/stylesheets/generic/filters.scss +++ b/app/assets/stylesheets/generic/filters.scss @@ -2,31 +2,6 @@ margin-right: 15px; } -.issues-state-filters { - li.active a { - border-color: #DDD !important; - - &, &:hover, &:active, &.active { - background: #f5f5f5 !important; - border-bottom: 1px solid #f5f5f5 !important; - } - } -} - -.issues-details-filters { - font-size: 13px; - background: #f5f5f5; - margin: -10px 0; - padding: 10px 15px; - margin-top: -15px; - border-left: 1px solid #DDD; - border-right: 1px solid #DDD; - - .btn { - font-size: 13px; - } -} - @media (min-width: 800px) { .issues-filters, .issues_bulk_update { diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 6a29b32e196..543ce41ab52 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -24,29 +24,27 @@ header { z-index: 100; margin-bottom: 0; min-height: $header-height; + background-color: #fff; border: none; - border-bottom: 1px solid #EEE; .container-fluid { - background: #FFF; width: 100% !important; filter: none; + padding: 0; .nav > li > a { - color: #888; - font-size: 14px; + color: #7f8fa4; + font-size: 18px; padding: 0; - background-color: #f5f5f5; margin: ($header-height - 28) / 2 0; margin-left: 10px; - border-radius: 40px; height: 28px; width: 28px; line-height: 28px; text-align: center; &:hover, &:focus, &:active { - background-color: #EEE; + background-color: #FFF; } } @@ -56,6 +54,7 @@ header { border-radius: 0; position: absolute; right: 2px; + top: 15px; &:hover { background-color: #EEE; @@ -70,16 +69,16 @@ header { .title { margin: 0; overflow: hidden; - font-size: 18px; + font-size: 19px; line-height: $header-height; - font-weight: bold; - color: #444; + font-weight: normal; + color: #4c4e54; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; a { - color: #444; + color: #4c4e54; &:hover { text-decoration: underline; } @@ -94,7 +93,7 @@ header { .search { margin-right: 10px; margin-left: 10px; - margin-top: ($header-height - 28) / 2; + margin-top: ($header-height - 36) / 2; form { margin: 0; @@ -105,13 +104,8 @@ header { width: 220px; background-image: image-url("icon-search.png"); background-repeat: no-repeat; - background-position: 10px; - height: inherit; - padding: 4px 6px; - padding-left: 25px; - font-size: 13px; - background-color: #f5f5f5; - border-color: #f5f5f5; + background-position: 195px; + @include input-big; &:focus { @include box-shadow(none); diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 869e586839b..b1fb87a6830 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -5,10 +5,13 @@ */ .issue-box { + @include border-radius(3px); + display: inline-block; - padding: 4px 13px; + padding: 10px $gl-padding; font-weight: normal; - margin-right: 5px; + margin-right: 10px; + font-size: $gl-font-size; &.issue-box-closed { background-color: $gl-danger; @@ -21,7 +24,7 @@ } &.issue-box-open { - background-color: $gl-success; + background-color: #019875; color: #FFF; } diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss index 4b7ff84de2b..3bfed8de772 100644 --- a/app/assets/stylesheets/generic/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -49,8 +49,6 @@ } } - .author { color: #999; } - .list-item-name { float: left; position: relative; @@ -71,15 +69,6 @@ font-size: $list-font-size; line-height: 18px; } - - .row_title { - color: $gray-dark; - - &:hover { - color: $text-color; - text-decoration: underline; - } - } } } @@ -109,3 +98,28 @@ ul.bordered-list { li.task-list-item { list-style-type: none; } + +ul.content-list { + @include basic-list; + + margin: 0; + padding: 0; + + > li { + padding: $gl-padding; + border-color: #f1f2f4; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + color: $gl-gray; + + .avatar { + margin-right: 15px; + } + + .controls { + padding-top: 10px; + float: right; + } + } +} + diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss index a4fc82e90bf..ed0333d2336 100644 --- a/app/assets/stylesheets/generic/markdown_area.scss +++ b/app/assets/stylesheets/generic/markdown_area.scss @@ -65,8 +65,11 @@ position: relative; } -.md-header ul { - float: left; +.md-header { + ul { + float: left; + margin-bottom: 1px; + } } .referenced-users { @@ -80,7 +83,7 @@ .md-preview-holder { background: #FFF; border: 1px solid #ddd; - min-height: 100px; + min-height: 169px; padding: 5px; box-shadow: none; } @@ -105,7 +108,7 @@ .markdown-area { background: #FFF; border: 1px solid #ddd; - min-height: 100px; + min-height: 140px; padding: 5px; box-shadow: none; width: 100%; diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index bb7b9356c70..36ae126f865 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -80,6 +80,23 @@ %ul.notes .note-role, .note-actions { display: none; } + + .center-top-menu { + height: 45px; + + li a { + font-size: 14px; + padding: 19px 10px; + } + } + + .projects-search-form { + margin: 0 -5px !important; + + .btn { + display: none; + } + } } @media (max-width: $screen-sm-max) { diff --git a/app/assets/stylesheets/generic/pagination.scss b/app/assets/stylesheets/generic/pagination.scss new file mode 100644 index 00000000000..6677f94dafd --- /dev/null +++ b/app/assets/stylesheets/generic/pagination.scss @@ -0,0 +1,34 @@ +.gl-pagination { + border-top: 1px solid $border-color; + background-color: $background-color; + margin: -$gl-padding; + margin-top: 0; + + .pagination { + padding: 0; + margin: 0; + display: block; + + li.first, + li.last, + li.next, + li.prev { + > a { + color: $link-color; + + &:hover { + color: #fff; + } + } + } + + li > a, + li > span { + border: none; + margin: 0; + @include border-radius(0 !important); + padding: 13px 19px; + border-right: 1px solid $border-color; + } + } +} diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss index d8e0dc028d1..f0860de1c49 100644 --- a/app/assets/stylesheets/generic/selects.scss +++ b/app/assets/stylesheets/generic/selects.scss @@ -3,9 +3,9 @@ .select2-choice { background: #FFF; border-color: #DDD; - height: 34px; - padding: 6px 14px; - font-size: 14px; + height: 42px; + padding: 8px $gl-padding; + font-size: $gl-font-size; line-height: 1.42857143; @include border-radius(4px); @@ -13,7 +13,7 @@ .select2-arrow { background: #FFF; border-left: none; - padding-top: 3px; + padding-top: 5px; } } } diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss index 320bdb1c765..c5ea3aca7ca 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -18,14 +18,27 @@ } .content-wrapper { + min-height: 100vh; width: 100%; padding: 20px; - background: #FFF; + background: #EAEBEC; + + .container-fluid { + background: #FFF; + padding: $gl-padding; + min-height: 90vh; + + &.container-blank { + background: none; + padding: 0; + border: none; + } + } } .nav-sidebar { - margin-top: 29 + $header-height; - margin-bottom: 50px; + margin-top: 14 + $header-height; + margin-bottom: 100px; transition-duration: .3s; list-style: none; overflow: hidden; @@ -43,13 +56,14 @@ } a { - padding: 8px 15px; - font-size: 13px; - line-height: 18px; + padding: 7px 15px; + font-size: $gl-font-size; + line-height: 24px; color: $gray; display: block; text-decoration: none; - padding-left: 16px; + padding-left: 22px; + font-weight: normal; &:hover { text-decoration: none; @@ -60,9 +74,9 @@ } i { - width: 20px; + width: 16px; color: $gray-light; - margin-right: 23px; + margin-right: 13px; } .count { @@ -108,41 +122,59 @@ } @mixin folded-sidebar { - padding-left: 50px; + padding-left: 60px; transition-duration: .3s; .sidebar-wrapper { width: $sidebar_collapsed_width; + .header-logo { + width: $sidebar_collapsed_width; + + a { + padding-left: 12px; + + .gitlab-text-container { + display: none; + } + } + } + .nav-sidebar { width: $sidebar_collapsed_width; li a { - padding-left: 16px; + span { + display: none; + } } } .collapse-nav a { - left: 0px; width: $sidebar_collapsed_width; } .sidebar-user { + padding-left: 12px; width: $sidebar_collapsed_width; + + .username { + display: none; + } } } } .collapse-nav a { + width: $sidebar_width; position: fixed; - top: $header-height; - left: 198px; + bottom: 0; + left: 0; font-size: 13px; background: transparent; - width: 32px; - height: 28px; + height: 40px; text-align: center; - line-height: 28px; + line-height: 40px; transition-duration: .3s; } @@ -176,16 +208,18 @@ } .sidebar-user { + padding: 9px 22px; position: fixed; - bottom: 0; + bottom: 40px; width: $sidebar_width; - padding: 10px; overflow: hidden; transition-duration: .3s; .username { - margin-top: 5px; + margin-left: 10px; width: $sidebar_width - 2 * 10px; + font-size: 16px; + line-height: 34px; } } @@ -202,7 +236,7 @@ float: left; height: $header-height; width: 100%; - padding: ($header-height - 36 ) / 2 8px; + padding: 10px 22px; overflow: hidden; img { @@ -219,8 +253,8 @@ float: left; margin: 0; margin-left: 14px; - font-size: 18px; - line-height: $header-height - 14; + font-size: 19px; + line-height: 41px; font-weight: normal; } } diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss index 97831eb7c27..74bbaabad39 100644 --- a/app/assets/stylesheets/generic/timeline.scss +++ b/app/assets/stylesheets/generic/timeline.scss @@ -1,119 +1,50 @@ .timeline { - list-style: none; - padding: 20px 0 20px; - position: relative; + @include basic-list; - &:before { - top: 0; - bottom: 0; - position: absolute; - content: " "; - width: 3px; - background-color: #eeeeee; - margin-left: 29px; - } + margin: 0; + padding: 0; .timeline-entry { - position: relative; - margin-top: 5px; - margin-left: 30px; - margin-bottom: 10px; - clear: both; - - - &:target { - .timeline-entry-inner .timeline-content { - -webkit-animation:target-note 2s linear; - background: $hover; - } + padding: $gl-padding; + border-color: #f1f2f4; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + color: $gl-gray; + border-bottom: 1px solid #f1f2f4; + border-right: 1px solid #f1f2f4; + + &:last-child { + border-bottom: none; } - .timeline-entry-inner { - position: relative; - margin-left: -20px; - - &:before, &:after { - content: " "; - display: table; - } - - .timeline-icon { - margin-top: 2px; - background: #fff; - color: #737881; - float: left; - @include border-radius($avatar_radius); - @include box-shadow(0 0 0 3px #EEE); - overflow: hidden; - - .avatar { - margin: 0; - padding: 0; - } - } - - .timeline-content { - position: relative; - background: $background-color; - padding: 10px 15px; - margin-left: 60px; - - img { - max-width: 100%; - } + .avatar { + margin-right: 15px; + } - &:after { - content: ''; - display: block; - position: absolute; - width: 0; - height: 0; - border-style: solid; - border-width: 9px 9px 9px 0; - border-color: transparent $background-color transparent transparent; - left: 0; - top: 10px; - margin-left: -9px; - } - } + .controls { + padding-top: 10px; + float: right; } } - .system-note .timeline-entry-inner { - .timeline-icon { - background: none; - margin-left: 12px; - margin-top: 0; - @include box-shadow(none); - - span { - margin: 0 2px; - font-size: 16px; - color: #eeeeee; - } + .note-text { + p:last-child { + margin-bottom: 0; } + } - .timeline-content { - background: none; - margin-left: 45px; - padding: 0px 15px; - - &:after { border: 0; } - - .note-header { - span { font-size: 12px; } - - .avatar { - margin-right: 5px; - } - } - - .note-text { - font-size: 12px; - margin-left: 20px; - } + .system-note { + .note-text { + color: $gl-gray !important; } } + + .diff-file { + border: 1px solid $border-color; + border-bottom: none; + margin-left: 0; + margin-right: 0; + } } @media (max-width: $screen-xs-max) { @@ -132,3 +63,8 @@ } } } + +.discussion .timeline-entry { + margin: 0; + border-right: none; +} diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index 34b4ee3e17e..6a3cb49baae 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -2,11 +2,29 @@ * Headers * */ +body { + text-rendering:optimizeLegibility; + -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px; +} + .page-title { margin-top: 0px; - line-height: 1.5; - font-weight: normal; - margin-bottom: 5px; + line-height: 1.3; + font-size: 1.25em; + font-weight: 600; +} + +.page-title-empty { + margin-top: 0px; + line-height: 1.3; + font-size: 1.25em; + font-weight: 600; + margin: 12px 7px 12px 7px; +} + +h1, h2, h3, h4, h5, h6 { + color: $gl-header-color; + font-weight: 500; } /** CODE **/ @@ -50,6 +68,7 @@ a > code { @include md-typography; word-wrap: break-word; + padding: 7px; /* Link to current header. */ h1, h2, h3, h4, h5, h6 { @@ -78,12 +97,19 @@ a > code { } } - ul { + ul,ol { padding: 0; - margin: 0 0 9px 25px !important; + margin: 6px 0 6px 18px !important; + } + ol { + color: #5c5d5e; } } +.md-area { + @include md-typography; +} + .md { @include md-typography; } @@ -96,6 +122,9 @@ textarea.js-gfm-input { font-family: $monospace_font; } +.md-preview { +} + .strikethrough { text-decoration: line-through; -} +}
\ No newline at end of file diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss index 7e86a0fe4b9..32e2c020e06 100644 --- a/app/assets/stylesheets/generic/zen.scss +++ b/app/assets/stylesheets/generic/zen.scss @@ -4,7 +4,7 @@ } .zen-enter-link { - color: #888; + color: $gl-gray; position: absolute; top: 0px; right: 4px; @@ -13,7 +13,7 @@ .zen-leave-link { display: none; - color: #888; + color: $gl-text-color; position: absolute; top: 10px; right: 10px; diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index e0edfb80b42..20a144ef952 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,19 +1,24 @@ /* https://github.com/aahan/pygments-github-style */ pre.code.highlight.white, .code.white { + background-color: #f8fafc; + font-size: 13px; + color: #5b6169; + line-height: 1.6em; - background-color: #fff; - color: #333; - - pre.highlight, .line-numbers, .line-numbers a { + background-color: $background-color !important; + color: $gl-gray !important; + } + + pre.highlight { background-color: #fff !important; color: #333 !important; } pre.code { - border-left: 1px solid #bbb; + border-left: 1px solid $border-color; } // highlight line via anchor diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index e7125c03993..fbd7c363de1 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -26,14 +26,6 @@ margin-top: 10px; } -.commit-stat-summary { - color: #666; - font-size: 14px; - font-weight: normal; - padding: 3px 0; - margin-bottom: 10px; -} - .commit-info-row { margin-bottom: 10px; .avatar { @@ -47,11 +39,6 @@ } .commit-box { - margin: 10px 0; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - padding: 20px 0; - .commit-title { margin: 0; } @@ -61,35 +48,34 @@ } } -.file-stats a { - color: $style_color; -} - .file-stats { + ul { + list-style: none; + margin: 0; + padding: 10px 0; + + li { + padding: 3px 0px; + } + } .new-file { a { - color: #090; - } - i { - color: #1BCF00; + color: $gl-text-green; } } .renamed-file { - i { - color: #FE9300; + a { + color: $gl-text-orange; } } .deleted-file { a { - color: #B00; - } - i { - color: #EE0000; + color: $gl-text-red; } } .edit-file{ - i{ - color: #555; + a { + color: $gl-text-color; } } } @@ -121,3 +107,16 @@ z-index: 2; } } + +.commit-ci-menu { + padding: 0; + margin: 0; + list-style: none; + margin-top: 5px; + height: 56px; + margin: -16px; + padding: 16px; + text-align: center; + margin-top: 0px; + margin-bottom: 2px; +} diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 359f4073e87..de2ae93df37 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -52,7 +52,7 @@ li.commit { } .commit-row-message { - color: #444; + color: $gl-link-color; &:hover { text-decoration: underline; @@ -88,12 +88,12 @@ li.commit { } .commit-row-info { - color: #777; + color: $gl-gray; line-height: 24px; font-size: 13px; a { - color: #777; + color: $gl-gray; } .committed_ago { diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index c1103a1c2e6..25a86cd0f94 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -2,7 +2,7 @@ .side { .panel { .panel-heading { - background: #EEE; + background: $background-color; border-top-left-radius: 0; } border-top-left-radius: 0; @@ -38,11 +38,11 @@ float: left; .avatar { - @include border-radius(0px); + @include border-radius(50%); } .identicon { - line-height: 40px; + line-height: 46px; } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1557c243db5..5e7e59a6af8 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1,12 +1,14 @@ .diff-file { - border: 1px solid $border-color; - margin-bottom: 1em; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border: none; + border-bottom: 1px solid #E7E9EE; .diff-header { position: relative; background: $background-color; border-bottom: 1px solid $border-color; - padding: 10px 15px; + padding: 10px 16px; color: #555; z-index: 10; @@ -45,7 +47,7 @@ overflow-y: hidden; background: #FFF; color: #333; - font-size: $code_font_size; + .old { span.idiff { background-color: #f8cbcb; @@ -82,7 +84,7 @@ border: none; margin: 0px; padding: 0px; - td { + .line_holder td { line-height: $code_line_height; font-size: $code_font_size; } @@ -367,3 +369,7 @@ white-space: pre-wrap; } +.inline-parallel-buttons { + float: right; + margin-top: -5px; +} diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 759ba6b1c22..1d565477dd4 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -9,6 +9,10 @@ width: 100%; } + .ace_gutter-cell { + background-color: $background-color; + } + .cancel-btn { color: #B94A48; &:hover { @@ -32,14 +36,12 @@ .file-title { @extend .monospace; - font-size: 14px; - padding: 5px; } .editor-ref { background: $background-color; padding: 11px 15px; - border-right: 1px solid #CCC; + border-right: 1px solid $border-color; display: inline-block; margin: -5px -5px; margin-right: 10px; @@ -50,5 +52,15 @@ display: inline-block; width: 200px; } + + .form-control { + margin-top: -3px; + } + } + + .form-actions { + margin: -$gl-padding; + margin-top: 0; + padding: $gl-padding } } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index d4af7506d5b..ca2ee455423 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -1,70 +1,58 @@ /** - * Events labels - * - */ -.event_label { - &.pushed { - padding: 0 2px; - } - - &.opened { - padding: 0 2px; - } - - &.closed { - padding: 0 2px; - } - - &.merged { - padding: 0 2px; - } - - &.left, - &.joined { - padding: 0 2px; - float: none; - } -} - -/** * Dashboard events feed * */ .event-item { - &:first-child { - padding-top: 0; - } + font-size: $gl-font-size; + padding: $gl-padding; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border-bottom: 1px solid #f1f2f4; + color: #7f8fa4; &.event-inline { .avatar { position: relative; top: -2px; } + + .event-title { + line-height: 44px; + } + + .event-item-timestamp { + line-height: 44px; + } + } + + a { + color: #4c4e54; + } + + .avatar { + margin-right: 15px; } - padding: 12px 0px; - border-bottom: 1px solid #eee; .event-title { - max-width: 70%; @include str-truncated(calc(100% - 174px)); - font-weight: 500; - font-size: 14px; + font-weight: 600; + .author_name { color: #333; } } + .event-body { - font-size: 13px; - margin-left: 35px; + margin-left: 63px; margin-right: 80px; - color: #777; .event-note { margin-top: 5px; word-wrap: break-word; .md { - font-size: 13px; + color: #7f8fa4; + font-size: $gl-font-size; iframe.twitter-share-button { vertical-align: bottom; @@ -94,7 +82,7 @@ .event-note-icon { color: #777; float: left; - font-size: 16px; + font-size: $gl-font-size; line-height: 16px; margin-right: 5px; } @@ -116,7 +104,7 @@ &:last-child { border:none } .event_commits { - margin-top: 5px; + margin-top: 9px; li { &.commit { @@ -125,10 +113,12 @@ padding-left: 0; border: none; .commit-row-title { - font-size: 12px; + font-size: $gl-font-size; } } + &.commits-stat { + margin-top: 3px; display: block; padding: 3px; padding-left: 0; @@ -142,7 +132,6 @@ .event-item-timestamp { float: right; - color: #999; line-height: 22px; } } @@ -186,12 +175,3 @@ } } } - -.event_filter { - li a { - font-size: 13px; - padding: 5px 10px; - background: $background-color; - margin-left: 4px; - } -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 2b1b747139a..07a38a19fad 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -10,3 +10,9 @@ .milestone-row { @include str-truncated(90%); } + +.dashboard .side .panel .panel-heading .input-group { + .form-control { + height: 42px; + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 586e7b5f8da..b5c61f7f91d 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -25,8 +25,6 @@ } .issuable-context-title { - font-size: 14px; - line-height: 1.4; margin-bottom: 5px; .avatar { @@ -34,14 +32,50 @@ } label { - color: #666; + color: $gl-gray; font-weight: normal; margin-right: 4px; } } -.issuable-affix .context { - font-size: 13px; +.project-issuable-filter { + .controls { + float: right; + margin-top: 7px; + } + + .center-top-menu { + text-align: left; + } +} - .btn { font-size: 13px; } +.issuable-details { + .page-title { + margin-top: -15px; + padding: 10px 0; + margin-bottom: 0; + color: $gl-gray; + font-size: 16px; + + .author { + color: $gl-gray; + } + + .issue-id { + font-size: 19px; + color: $gl-text-color; + } + } + + .issue-title { + margin: 0; + } + + .description { + margin-top: 6px; + + p:last-child { + margin-bottom: 0; + } + } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 3572f33e91f..4bf58cb4a59 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -1,6 +1,6 @@ .issues-list { .issue { - padding: 10px 15px; + padding: 10px $gl-padding; position: relative; .issue-title { @@ -10,8 +10,7 @@ } .issue-info { - color: #999; - font-size: 13px; + color: $gl-gray; } .issue-check { @@ -47,10 +46,6 @@ } } -.participants { - margin-bottom: 20px; -} - .issue-search-form { margin: 0; height: 24px; @@ -137,11 +132,6 @@ form.edit-issue { } } -h2.issue-title { - margin-top: 0; - font-weight: bold; -} - .issue-form .select2-container { width: 250px !important; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 10fce5b3daa..d8c8e5ad0a4 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -3,10 +3,10 @@ * */ .mr-state-widget { - background: #FAFAFA; + background: #f8fafc; margin-bottom: 20px; - color: #666; - border: 1px solid #e5e5e5; + color: $gl-gray; + border: 1px solid #eef0f2; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05)); @include border-radius(3px); @@ -29,6 +29,14 @@ padding: 5px; line-height: 20px; + &.right { + float: right; + padding-top: 12px; + a { + color: $gl-gray; + } + } + .remove_source_checkbox { margin: 0; } @@ -36,7 +44,7 @@ } .ci_widget { - border-bottom: 1px solid #EEE; + border-bottom: 1px solid #eef0f2; i { margin-right: 4px; @@ -89,20 +97,14 @@ } } -@media(min-width: $screen-sm-max) { - .merge-request .merge-request-tabs{ - li { - a { - padding: 15px 40px; - font-size: 14px; - } - } - } -} - .merge-request .merge-request-tabs{ - margin-top: 30px; - margin-bottom: 20px; + @include nav-menu; + margin: -$gl-padding; + padding: $gl-padding; + text-align: center; + border-top: 1px solid #e7e9ed; + margin-top: 18px; + margin-bottom: 3px; } .mr_source_commit, @@ -136,8 +138,7 @@ } .merge-request-info { - color: #999; - font-size: 13px; + color: $gl-gray; } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 15e3948e402..e80dc9e84a1 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -6,4 +6,8 @@ li.milestone { h4 { font-weight: bold; } + + .progress { + height: 6px; + } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 203f9374cee..fdc2c3332df 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -72,9 +72,13 @@ .common-note-form { margin: 0; - background: #F9F9F9; - padding: 5px; - border: 1px solid #DDD; + background: #f8fafc; + padding: $gl-padding; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border-right: 1px solid #f1f2f4; + border-top: 1px solid #f1f2f4; + margin-bottom: -$gl-padding; } .note-form-actions { @@ -105,7 +109,7 @@ .note-edit-form { display: none; - font-size: 13px; + font-size: 15px; .form-actions { padding-left: 20px; @@ -142,9 +146,9 @@ } .discussion-reply-holder { - background: #f9f9f9; + background: $background-color; padding: 10px 15px; - border-top: 1px solid #DDD; + border-top: 1px solid $border-color; } } @@ -166,6 +170,6 @@ background: #FFF; padding: 5px; margin-top: -11px; - border: 1px solid #DDD; + border: 1px solid $border-color; font-size: 13px; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 85c828ec1ad..2a77f065aed 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -14,6 +14,19 @@ ul.notes { margin: 0px; padding: 0px; + .system-note { + font-size: 14px; + padding-top: 10px; + padding-bottom: 10px; + background: #f8fafc; + + .timeline-icon { + .avatar { + visibility: hidden; + } + } + } + .discussion-header, .note-header { @extend .cgray; @@ -34,10 +47,8 @@ ul.notes { content: "\00b7"; } - font-size: 13px; - a { - @extend .cgray; + color: $gl-gray; &:hover { text-decoration: underline; @@ -45,8 +56,9 @@ ul.notes { } } .author { - color: #333; - font-weight: bold; + color: #4c4e54; + margin-right: 3px; + &:hover { color: $gl-link-color; } @@ -59,7 +71,7 @@ ul.notes { margin-top: 1px; border: 1px solid #bbb; background-color: transparent; - color: #999; + color: $gl-gray; } } @@ -133,8 +145,6 @@ ul.notes { } .diff-file .notes_holder { - font-size: 13px; - line-height: 18px; font-family: $regular_font; td { @@ -176,8 +186,7 @@ ul.notes { a { margin-left: 5px; - - color: #999; + color: $gl-gray; i.fa { font-size: 16px; @@ -226,8 +235,6 @@ ul.notes { filter: alpha(opacity=0); &:hover { - width: 38px; - font-size: 20px; background: $gl-info; color: #FFF; @include show-add-diff-note; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 488dded549e..818aa10aefe 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -1,3 +1,14 @@ +.alert_holder { + margin: -16px; + + .alert-link { + font-weight: normal; + } +} +.no-ssh-key-message { + background-color: #f28d35; + margin-bottom: 16px; +} .new_project, .edit_project { fieldset.features { @@ -16,9 +27,13 @@ .project-home-panel { text-align: center; + background: #f7f8fa; + margin: -$gl-padding; + padding: $gl-padding; + padding: 44px 0 17px 0; .project-identicon-holder { - margin-bottom: 15px; + margin-bottom: 16px; .avatar, .identicon { margin: 0 auto; @@ -36,22 +51,27 @@ .project-home-desc { h1 { + color: #313236; margin: 0; - margin-bottom: 10px; - font-size: 26px; - font-weight: bold; + margin-bottom: 6px; + font-size: 23px; + font-weight: normal; } p { - font-size: 18px; - color: #666; - display: inline; + color: #5c5d5e; } } .git-clone-holder { - max-width: 600px; - margin: 20px auto; + max-width: 498px; + + .form-control { + background: #FFF; + font-size: 14px; + height: 42px; + margin-left: -1px; + } } .visibility-level-label { @@ -60,29 +80,37 @@ color: inherit; } } + .input-group { + display: inline-table; + position: relative; + top: 17px; + margin-bottom: 44px; + } .project-repo-buttons { - margin-top: 25px; - margin-bottom: 25px; + margin-top: 12px; + margin-bottom: 0px; .btn { - @extend .btn-info; - - margin-left: 10px; - font-weight: bold; - font-size: 14px; - line-height: 16px; - padding: 8px 12px; + @include bnt-project; + @include btn-info; .count { - padding-left: 7px; display: inline-block; - margin-left: 7px; } } } } +.split-one { + display: inline-table; + margin-right: 12px; + + a { + margin: -1px !important; + } +} + .git-clone-holder { .project-home-dropdown + & { margin-right: 45px; @@ -92,11 +120,11 @@ cursor: auto; @extend .monospace; background: #FAFAFA; - width: 100%; + width: 101%; } .input-group-addon { - background: #FAFAFA; + background: #f7f8fa; &.git-protocols { padding: 0; @@ -104,11 +132,120 @@ .input-group-btn:last-child > .btn { @include border-radius-right(0); + + border-left: 1px solid #c6cacf; + margin-left: -2px !important; } } } } +.projects-search-form { + + .input-group .form-control { + height: 42px; + } +} + +.input-group-btn { + .btn { + @include bnt-project; + @include btn-middle; + + &:hover { + outline: none; + } + + &:focus { + outline: none; + } + + &:active { + outline: none; + } + } + + .active { + @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); + + border: 1px solid #c6cacf !important; + background-color: #e4e7ed !important; + } + + .btn-green { + @include btn-green + } + +} + +.split-repo-buttons { + display: inline-table; + margin: 0 12px 0 12px; + + .btn{ + @include bnt-project; + @include btn-info; + } + + .dropdown-toggle { + margin: -5px; + } +} + +#notification-form { + margin-left: 5px; +} + +.dropdown-new { + margin-left: -5px; +} + +.open > .dropdown-new.btn { + @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); + + border: 1px solid #c6cacf !important; + background-color: #e4e7ed !important; + text-transform: uppercase; + color: #313236 !important; + font-size: 13px; + font-weight: 600; +} + +.dropdown-menu { + @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px); + @include border-radius (0px); + + border: none; + padding: 16px 0; + font-size: 14px; + font-weight: 100; + + li a { + color: #5f697a; + line-height: 30px; + + &:hover { + background-color: #3084bb !important; + } + } + + .fa-fw { + margin-right: 8px; + } +} + +.fa-bell { + margin-right: 6px; +} + +.fa-angle-down { + margin-left: 6px; +} + +.project-home-panel .project-home-dropdown { + margin: 13px 0px 0; +} + .project-visibility-level-holder { .radio { margin-bottom: 10px; @@ -184,10 +321,10 @@ ul.nav.nav-projects-tabs { .breadcrumb.repo-breadcrumb { padding: 0; - line-height: 34px; - background: white; + line-height: 42px; + background: transparent; border: none; - font-size: 16px; + margin: 0; > li + li:before { padding: 0 3px; @@ -196,26 +333,18 @@ ul.nav.nav-projects-tabs { } .fork-namespaces { - .thumbnail { - - &.fork-exists-thumbnail { - border-color: #EEE; + .fork-thumbnail { + text-align: center; + margin-bottom: $gl-padding; - .caption { - color: #999; - } + .caption { + padding: $gl-padding 0; + min-height: 30px; } - &.fork-thumbnail { - border-color: #AAA; - - &:hover { - background-color: $hover; - } - } - - a { - text-decoration: none; + img { + @include border-radius(50%); + max-width: 100px; } } } @@ -233,17 +362,43 @@ table.table.protected-branches-list tr.no-border { .project-stats { text-align: center; + margin-top: 15px; + margin-bottom: 0; + padding-top: 10px; + padding-bottom: 4px; + + ul.nav-pills { + display:inline-block; + } + + .nav-pills li { + display:inline; + } + + .nav > li > a { + @include btn-info; + @include bnt-project; - ul.nav-pills { display:inline-block; } - li { display:inline; } - a { float:left; } + background-color: transparent; + border: 1px solid #f7f8fa; + margin-left: 12px; + } + + li { + display:inline; + } + + a { + float:left; + font-size: 17px; + } li.missing a { - color: #bbb; - border: 1px dashed #ccc; + color: #5a6069; + border: 1px dashed #dce0e5; &:hover { - background-color: #FAFAFA; + background-color: #f0f2f5; } } } @@ -253,41 +408,106 @@ pre.light-well { } .projects-search-form { - max-width: 600px; - margin: 0 auto; - margin-bottom: 20px; + margin: -$gl-padding; + background-color: #f8fafc; + padding: $gl-padding; + margin-bottom: 0px; + border-top: 1px solid #e7e9ed; + border-bottom: 1px solid #e7e9ed; +} + +.git-empty { + margin: 0 7px 0 7px; + + h5 { + color: #5c5d5e; + } + + .light-well { + @include border-radius (2px); + + color: #5b6169; + font-size: 13px; + line-height: 1.6em; + } +} + +.project-footer { + margin-top: 20px; + + .btn-remove { + @include btn-middle; + @include btn-remove; - input { - border-color: #BBB; + float: left !important; } } /* * Projects list rendered on dashboard and user page */ + .projects-list { @include basic-list; .project-row { + padding: $gl-padding; + border-color: #f1f2f4; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + + &.no-description { + .project { + line-height: 44px; + } + } + .project-full-name { @include str-truncated; - font-weight: bold; - font-size: 15px; + font-weight: 600; + color: #4c4e54; + } + + .project-controls { + float: right; + color: $gl-gray; + line-height: 45px; + color: #7f8fa4; + + a:hover { + text-decoration: none; + } } .project-description { - color: #888; - font-size: 13px; + color: #7f8fa4; p { @include str-truncated; margin-bottom: 0; - color: #888; + color: #7f8fa4; } } } + + .bottom { + padding-top: $gl-padding; + padding-bottom: 0; + } } .panel .projects-list li { padding: 10px 15px; + margin: 0; +} + +.project-show-activity { + .activity-filter-block { + margin-top: -1px; + } } + +.inline-form { + display: inline-block; +} + diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 81e2aa7bb9c..271cc547e2b 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -63,51 +63,21 @@ padding-right: 8px; .commit-author-name { - color: gray; + color: $gl-gray; } } .tree_commit { - color: gray; + color: $gl-gray; .tree-commit-link { - color: gray; + color: $gl-gray; &:hover { text-decoration: underline; } } } - - .blame { - img.avatar { - border: 0 none; - float: none; - margin: 0; - padding: 0; - } - td.blame-commit { - background: #f9f9f9; - min-width: 350px; - - .commit-author-link { - color: #888; - } - } - td.blame-numbers { - pre { - color: #AAA; - white-space: pre; - } - background: #f1f1f1; - border-left: 1px solid #DDD; - } - td.lines { - code { - font-family: $monospace_font; - } - } - } } .tree-ref-holder { @@ -132,20 +102,30 @@ list-style: none; margin: 0; padding: 0; - margin-bottom: 10px; + margin-bottom: 5px; .commit { - padding: 10px 15px; + padding: $gl-padding 0; .commit-row-title { - font-size: 13px; - .commit-row-message { font-weight: normal; - color: #555; } } } } #modal-remove-blob > .modal-dialog { width: 850px; } + +.blob-upload-dropzone-previews { + text-align: center; + border: 2px; + border-style: dashed; + border-color: $border-color; + min-height: 200px; +} + +.upload-link { + font-weight: normal; + color: $md-link-color; +} diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index 77b62c3153f..8d9a0aae568 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -9,15 +9,19 @@ @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { .page-with-sidebar { .header-logo { - background-color: $color-darker; - border-color: $color-darker; + background-color: $color; + border-color: $color; a { color: $color-light; + + h3 { + color: $color-light; + } } &:hover { - background-color: $color-dark; + background-color: $color-darker; a { color: #FFF; } @@ -83,7 +87,7 @@ } $theme-blue: #2980B9; -$theme-charcoal: #474D57; +$theme-charcoal: #333c47; $theme-graphite: #888888; $theme-gray: #373737; $theme-green: #019875; @@ -95,7 +99,7 @@ body { } &.ui_charcoal { - @include gitlab-theme(#979DA7, $theme-charcoal, #373D47, #24272D); + @include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272D); } &.ui_graphite { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index f38e07af84b..7c134d2ec9b 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -46,6 +46,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :gravatar_enabled, :twitter_sharing_enabled, :sign_in_text, + :help_page_text, :home_page_url, :after_sign_out_path, :max_attachment_size, diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb new file mode 100644 index 00000000000..3b070e65d0d --- /dev/null +++ b/app/controllers/admin/labels_controller.rb @@ -0,0 +1,58 @@ +class Admin::LabelsController < Admin::ApplicationController + before_action :set_label, only: [:show, :edit, :update, :destroy] + + def index + @labels = Label.templates.page(params[:page]).per(PER_PAGE) + end + + def show + end + + def new + @label = Label.new + end + + def edit + end + + def create + @label = Label.new(label_params) + @label.template = true + + if @label.save + redirect_to admin_labels_url, notice: "Label was created" + else + render :new + end + end + + def update + if @label.update(label_params) + redirect_to admin_labels_path, notice: 'label was successfully updated.' + else + render :edit + end + end + + def destroy + @label.destroy + @labels = Label.templates + + respond_to do |format| + format.html do + redirect_to(admin_labels_path, notice: 'Label was removed') + end + format.js + end + end + + private + + def set_label + @label = Label.find(params[:id]) + end + + def label_params + params[:label].permit(:title, :color) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 6092c79c254..00f41a10dd1 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -56,13 +56,19 @@ class Admin::UsersController < Admin::ApplicationController end def confirm - if user.confirm! + if user.confirm redirect_to :back, notice: "Successfully confirmed" else redirect_to :back, alert: "Error occurred. User was not confirmed" end end + def login_as + sign_in(user) + flash[:alert] = "Logged in as #{user.username}" + redirect_to root_path + end + def disable_two_factor user.disable_two_factor! redirect_to admin_user_path(user), diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cb1cf13d34d..527c9da0faa 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,5 @@ require 'gon' +require 'fogbugz' class ApplicationController < ActionController::Base include Gitlab::CurrentSettings @@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :abilities, :can?, :current_application_settings - helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :git_import_enabled? + helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -116,9 +117,14 @@ class ApplicationController < ActionController::Base redirect_to request.original_url.gsub(/\.git\Z/, '') and return end - @project = Project.find_with_namespace("#{namespace}/#{id}") + project_path = "#{namespace}/#{id}" + @project = Project.find_with_namespace(project_path) + if @project and can?(current_user, :read_project, @project) + if @project.path_with_namespace != project_path + redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return + end @project elsif current_user.nil? @project = nil @@ -133,9 +139,6 @@ class ApplicationController < ActionController::Base def repository @repository ||= project.repository - rescue Grit::NoSuchPathError => e - log_exception(e) - nil end def authorize_project!(action) @@ -337,6 +340,10 @@ class ApplicationController < ActionController::Base current_application_settings.import_sources.include?('google_code') end + def fogbugz_import_enabled? + current_application_settings.import_sources.include?('fogbugz') + end + def git_import_enabled? current_application_settings.import_sources.include?('git') end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 904d26a39f4..202e9da9eee 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -32,6 +32,7 @@ class AutocompleteController < ApplicationController @users ||= User.none @users = @users.search(params[:search]) if params[:search].present? @users = @users.active + @users = @users.reorder(:name) @users = @users.page(params[:page]).per(PER_PAGE) unless params[:search].present? diff --git a/app/controllers/ci/admin/application_controller.rb b/app/controllers/ci/admin/application_controller.rb new file mode 100644 index 00000000000..4ec2dc9c2cf --- /dev/null +++ b/app/controllers/ci/admin/application_controller.rb @@ -0,0 +1,10 @@ +module Ci + module Admin + class ApplicationController < Ci::ApplicationController + before_action :authenticate_user! + before_action :authenticate_admin! + + layout "ci/admin" + end + end +end diff --git a/app/controllers/ci/admin/application_settings_controller.rb b/app/controllers/ci/admin/application_settings_controller.rb new file mode 100644 index 00000000000..71e253fac67 --- /dev/null +++ b/app/controllers/ci/admin/application_settings_controller.rb @@ -0,0 +1,31 @@ +module Ci + class Admin::ApplicationSettingsController < Ci::Admin::ApplicationController + before_action :set_application_setting + + def show + end + + def update + if @application_setting.update_attributes(application_setting_params) + redirect_to ci_admin_application_settings_path, + notice: 'Application settings saved successfully' + else + render :show + end + end + + private + + def set_application_setting + @application_setting = Ci::ApplicationSetting.current + @application_setting ||= Ci::ApplicationSetting.create_from_defaults + end + + def application_setting_params + params.require(:application_setting).permit( + :all_broken_builds, + :add_pusher, + ) + end + end +end diff --git a/app/controllers/ci/admin/builds_controller.rb b/app/controllers/ci/admin/builds_controller.rb new file mode 100644 index 00000000000..38abfdeafbf --- /dev/null +++ b/app/controllers/ci/admin/builds_controller.rb @@ -0,0 +1,18 @@ +module Ci + class Admin::BuildsController < Ci::Admin::ApplicationController + def index + @scope = params[:scope] + @builds = Ci::Build.order('created_at DESC').page(params[:page]).per(30) + + @builds = + case @scope + when "pending" + @builds.pending + when "running" + @builds.running + else + @builds + end + end + end +end diff --git a/app/controllers/ci/admin/events_controller.rb b/app/controllers/ci/admin/events_controller.rb new file mode 100644 index 00000000000..5939efff980 --- /dev/null +++ b/app/controllers/ci/admin/events_controller.rb @@ -0,0 +1,9 @@ +module Ci + class Admin::EventsController < Ci::Admin::ApplicationController + EVENTS_PER_PAGE = 50 + + def index + @events = Ci::Event.admin.order('created_at DESC').page(params[:page]).per(EVENTS_PER_PAGE) + end + end +end diff --git a/app/controllers/ci/admin/projects_controller.rb b/app/controllers/ci/admin/projects_controller.rb new file mode 100644 index 00000000000..5bbd0ce7396 --- /dev/null +++ b/app/controllers/ci/admin/projects_controller.rb @@ -0,0 +1,19 @@ +module Ci + class Admin::ProjectsController < Ci::Admin::ApplicationController + def index + @projects = Ci::Project.ordered_by_last_commit_date.page(params[:page]).per(30) + end + + def destroy + project.destroy + + redirect_to ci_projects_url + end + + protected + + def project + @project ||= Ci::Project.find(params[:id]) + end + end +end diff --git a/app/controllers/ci/admin/runner_projects_controller.rb b/app/controllers/ci/admin/runner_projects_controller.rb new file mode 100644 index 00000000000..e7de6eb12ca --- /dev/null +++ b/app/controllers/ci/admin/runner_projects_controller.rb @@ -0,0 +1,34 @@ +module Ci + class Admin::RunnerProjectsController < Ci::Admin::ApplicationController + layout 'ci/project' + + def index + @runner_projects = project.runner_projects.all + @runner_project = project.runner_projects.new + end + + def create + @runner = Ci::Runner.find(params[:runner_project][:runner_id]) + + if @runner.assign_to(project, current_user) + redirect_to ci_admin_runner_path(@runner) + else + redirect_to ci_admin_runner_path(@runner), alert: 'Failed adding runner to project' + end + end + + def destroy + rp = Ci::RunnerProject.find(params[:id]) + runner = rp.runner + rp.destroy + + redirect_to ci_admin_runner_path(runner) + end + + private + + def project + @project ||= Ci::Project.find(params[:project_id]) + end + end +end diff --git a/app/controllers/ci/admin/runners_controller.rb b/app/controllers/ci/admin/runners_controller.rb new file mode 100644 index 00000000000..9a68add9083 --- /dev/null +++ b/app/controllers/ci/admin/runners_controller.rb @@ -0,0 +1,72 @@ +module Ci + class Admin::RunnersController < Ci::Admin::ApplicationController + before_action :runner, except: :index + + def index + @runners = Ci::Runner.order('id DESC') + @runners = @runners.search(params[:search]) if params[:search].present? + @runners = @runners.page(params[:page]).per(30) + @active_runners_cnt = Ci::Runner.where("contacted_at > ?", 1.minutes.ago).count + end + + def show + @builds = @runner.builds.order('id DESC').first(30) + @projects = Ci::Project.all + if params[:search].present? + @gl_projects = ::Project.search(params[:search]) + @projects = @projects.where(gitlab_id: @gl_projects.select(:id)) + end + @projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any? + @projects = @projects.page(params[:page]).per(30) + end + + def update + @runner.update_attributes(runner_params) + + respond_to do |format| + format.js + format.html { redirect_to ci_admin_runner_path(@runner) } + end + end + + def destroy + @runner.destroy + + redirect_to ci_admin_runners_path + end + + def resume + if @runner.update_attributes(active: true) + redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.' + else + redirect_to ci_admin_runners_path, alert: 'Runner was not updated.' + end + end + + def pause + if @runner.update_attributes(active: false) + redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.' + else + redirect_to ci_admin_runners_path, alert: 'Runner was not updated.' + end + end + + def assign_all + Ci::Project.unassigned(@runner).all.each do |project| + @runner.assign_to(project, current_user) + end + + redirect_to ci_admin_runner_path(@runner), notice: "Runner was assigned to all projects" + end + + private + + def runner + @runner ||= Ci::Runner.find(params[:id]) + end + + def runner_params + params.require(:runner).permit(:token, :description, :tag_list, :contacted_at, :active) + end + end +end diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb new file mode 100644 index 00000000000..9be470660e6 --- /dev/null +++ b/app/controllers/ci/application_controller.rb @@ -0,0 +1,74 @@ +module Ci + class ApplicationController < ::ApplicationController + def self.railtie_helpers_paths + "app/helpers/ci" + end + + helper_method :gl_project + + private + + def authenticate_public_page! + unless project.public + authenticate_user! + + return access_denied! unless can?(current_user, :read_project, gl_project) + end + end + + def authenticate_token! + unless project.valid_token?(params[:token]) + return head(403) + end + end + + def authorize_access_project! + unless can?(current_user, :read_project, gl_project) + return page_404 + end + end + + def authorize_manage_builds! + unless can?(current_user, :manage_builds, gl_project) + return page_404 + end + end + + def authenticate_admin! + return render_404 unless current_user.is_admin? + end + + def authorize_manage_project! + unless can?(current_user, :admin_project, gl_project) + return page_404 + end + end + + def page_404 + render file: "#{Rails.root}/public/404.html", status: 404, layout: false + end + + def default_headers + headers['X-Frame-Options'] = 'DENY' + headers['X-XSS-Protection'] = '1; mode=block' + end + + # JSON for infinite scroll via Pager object + def pager_json(partial, count) + html = render_to_string( + partial, + layout: false, + formats: [:html] + ) + + render json: { + html: html, + count: count + } + end + + def gl_project + ::Project.find(@project.gitlab_id) + end + end +end diff --git a/app/controllers/ci/builds_controller.rb b/app/controllers/ci/builds_controller.rb new file mode 100644 index 00000000000..b0b8b62fced --- /dev/null +++ b/app/controllers/ci/builds_controller.rb @@ -0,0 +1,52 @@ +module Ci + class BuildsController < Ci::ApplicationController + before_action :authenticate_user!, except: [:status] + before_action :project + before_action :authorize_access_project!, except: [:status] + before_action :authorize_manage_project!, except: [:status, :retry, :cancel] + before_action :authorize_manage_builds!, only: [:retry, :cancel] + before_action :build + + def retry + if @build.commands.blank? + return page_404 + end + + build = Ci::Build.retry(@build) + + if params[:return_to] + redirect_to URI.parse(params[:return_to]).path + else + redirect_to build_path(build) + end + end + + def status + render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha) + end + + def cancel + @build.cancel + + redirect_to build_path(@build) + end + + protected + + def project + @project = Ci::Project.find(params[:project_id]) + end + + def build + @build ||= project.builds.unscoped.find_by!(id: params[:id]) + end + + def commit_by_sha + @project.commits.find_by(sha: params[:id]) + end + + def build_path(build) + namespace_project_build_path(build.gl_project.namespace, build.gl_project, build) + end + end +end diff --git a/app/controllers/ci/commits_controller.rb b/app/controllers/ci/commits_controller.rb new file mode 100644 index 00000000000..7e6705c9702 --- /dev/null +++ b/app/controllers/ci/commits_controller.rb @@ -0,0 +1,32 @@ +module Ci + class CommitsController < Ci::ApplicationController + before_action :authenticate_user!, except: [:status, :show] + before_action :authenticate_public_page!, only: :show + before_action :project + before_action :authorize_access_project!, except: [:status, :show, :cancel] + before_action :authorize_manage_builds!, only: [:cancel] + + def status + commit = Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id]) + render json: commit.to_json(only: [:id, :sha], methods: [:status, :coverage]) + rescue ActiveRecord::RecordNotFound + render json: { status: "not_found" } + end + + def cancel + commit.builds.running_or_pending.each(&:cancel) + + redirect_to namespace_project_commit_path(commit.gl_project.namespace, commit.gl_project, commit.sha) + end + + private + + def project + @project ||= Ci::Project.find(params[:project_id]) + end + + def commit + @commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id]) + end + end +end diff --git a/app/controllers/ci/events_controller.rb b/app/controllers/ci/events_controller.rb new file mode 100644 index 00000000000..89b784a1e89 --- /dev/null +++ b/app/controllers/ci/events_controller.rb @@ -0,0 +1,21 @@ +module Ci + class EventsController < Ci::ApplicationController + EVENTS_PER_PAGE = 50 + + before_action :authenticate_user! + before_action :project + before_action :authorize_manage_project! + + layout 'ci/project' + + def index + @events = project.events.order("created_at DESC").page(params[:page]).per(EVENTS_PER_PAGE) + end + + private + + def project + @project ||= Ci::Project.find(params[:project_id]) + end + end +end diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb new file mode 100644 index 00000000000..24dd1b5c93a --- /dev/null +++ b/app/controllers/ci/lints_controller.rb @@ -0,0 +1,26 @@ +module Ci + class LintsController < Ci::ApplicationController + before_action :authenticate_user! + + def show + end + + def create + if params[:content].blank? + @status = false + @error = "Please provide content of .gitlab-ci.yml" + else + @config_processor = Ci::GitlabCiYamlProcessor.new params[:content] + @stages = @config_processor.stages + @builds = @config_processor.builds + @status = true + end + rescue Ci::GitlabCiYamlProcessor::ValidationError => e + @error = e.message + @status = false + rescue Exception + @error = "Undefined error" + @status = false + end + end +end diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb new file mode 100644 index 00000000000..adb913a783f --- /dev/null +++ b/app/controllers/ci/projects_controller.rb @@ -0,0 +1,56 @@ +module Ci + class ProjectsController < Ci::ApplicationController + before_action :authenticate_user!, except: [:build, :badge, :show] + before_action :authenticate_public_page!, only: :show + before_action :project, only: [:build, :show, :badge, :toggle_shared_runners, :dumped_yaml] + before_action :authorize_access_project!, except: [:build, :badge, :show, :new] + before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml] + before_action :authenticate_token!, only: [:build] + before_action :no_cache, only: [:badge] + protect_from_forgery except: :build + + layout 'ci/project', except: [:index] + + def show + @ref = params[:ref] + + @commits = @project.commits.reverse_order + if @ref + # unscope is required, because of default_scope defined in Ci::Build + builds = @project.builds.unscope(:select, :order).where(ref: @ref).select(:commit_id).distinct + @commits = @commits.where(id: builds) + end + @commits = @commits.page(params[:page]).per(20) + end + + # Project status badge + # Image with build status for sha or ref + def badge + image = Ci::ImageForBuildService.new.execute(@project, params) + + send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" + end + + def toggle_shared_runners + project.toggle!(:shared_runners_enabled) + + redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project) + end + + def dumped_yaml + send_data @project.generated_yaml_config, filename: '.gitlab-ci.yml' + end + + protected + + def project + @project ||= Ci::Project.find(params[:id]) + end + + def no_cache + response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" + response.headers["Pragma"] = "no-cache" + response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" + end + end +end diff --git a/app/controllers/ci/runner_projects_controller.rb b/app/controllers/ci/runner_projects_controller.rb new file mode 100644 index 00000000000..97f01d40af5 --- /dev/null +++ b/app/controllers/ci/runner_projects_controller.rb @@ -0,0 +1,36 @@ +module Ci + class RunnerProjectsController < Ci::ApplicationController + before_action :authenticate_user! + before_action :project + before_action :authorize_manage_project! + + layout 'ci/project' + + def create + @runner = Ci::Runner.find(params[:runner_project][:runner_id]) + + return head(403) unless current_user.ci_authorized_runners.include?(@runner) + + path = runners_path(@project.gl_project) + + if @runner.assign_to(project, current_user) + redirect_to path + else + redirect_to path, alert: 'Failed adding runner to project' + end + end + + def destroy + runner_project = project.runner_projects.find(params[:id]) + runner_project.destroy + + redirect_to runners_path(@project.gl_project) + end + + private + + def project + @project ||= Ci::Project.find(params[:project_id]) + end + end +end diff --git a/app/controllers/ci/services_controller.rb b/app/controllers/ci/services_controller.rb new file mode 100644 index 00000000000..52c96a34ce8 --- /dev/null +++ b/app/controllers/ci/services_controller.rb @@ -0,0 +1,59 @@ +module Ci + class ServicesController < Ci::ApplicationController + before_action :authenticate_user! + before_action :project + before_action :authorize_access_project! + before_action :authorize_manage_project! + before_action :service, only: [:edit, :update, :test] + + respond_to :html + + layout 'ci/project' + + def index + @project.build_missing_services + @services = @project.services.reload + end + + def edit + end + + def update + if @service.update_attributes(service_params) + redirect_to edit_ci_project_service_path(@project, @service.to_param) + else + render 'edit' + end + end + + def test + last_build = @project.builds.last + + if @service.execute(last_build) + message = { notice: 'We successfully tested the service' } + else + message = { alert: 'We tried to test the service but error occurred' } + end + + redirect_to :back, message + end + + private + + def project + @project = Ci::Project.find(params[:project_id]) + end + + def service + @service ||= @project.services.find { |service| service.to_param == params[:id] } + end + + def service_params + params.require(:service).permit( + :type, :active, :webhook, :notify_only_broken_builds, + :email_recipients, :email_only_broken_builds, :email_add_pusher, + :hipchat_token, :hipchat_room, :hipchat_server + ) + end + end +end diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index da96171e885..58e9049f158 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -1,10 +1,26 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController before_action :event_filter + def index + @projects = current_user.authorized_projects.sorted_by_activity.non_archived + @projects = @projects.includes(:namespace) + @last_push = current_user.recent_push + + respond_to do |format| + format.html + format.atom do + event_filter + load_events + render layout: false + end + end + end + def starred @projects = current_user.starred_projects @projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.sort(@sort = params[:sort]) + @last_push = current_user.recent_push @groups = [] respond_to do |format| diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb new file mode 100644 index 00000000000..f4354c6d8ca --- /dev/null +++ b/app/controllers/dashboard/snippets_controller.rb @@ -0,0 +1,10 @@ +class Dashboard::SnippetsController < Dashboard::ApplicationController + def index + @snippets = SnippetsFinder.new.execute(current_user, + filter: :by_user, + user: current_user, + scope: params[:scope] + ) + @snippets = @snippets.page(params[:page]).per(PER_PAGE) + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index d745131694b..4ebb3d7276e 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,23 +1,8 @@ class DashboardController < Dashboard::ApplicationController - before_action :load_projects before_action :event_filter, only: :activity respond_to :html - def show - @projects = @projects.includes(:namespace) - @last_push = current_user.recent_push - - respond_to do |format| - format.html - format.atom do - event_filter - load_events - render layout: false - end - end - end - def merge_requests @merge_requests = get_merge_requests_collection @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @@ -50,12 +35,15 @@ class DashboardController < Dashboard::ApplicationController protected - def load_projects - @projects = current_user.authorized_projects.sorted_by_activity.non_archived - end - def load_events - @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) + project_ids = + if params[:filter] == "starred" + current_user.starred_projects + else + current_user.authorized_projects + end.pluck(:id) + + @events = Event.in_projects(project_ids) @events = @event_filter.apply_filter(@events).with_associations @events = @events.limit(20).offset(params[:offset] || 0) end diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb index 4b275033d26..461fc059a3c 100644 --- a/app/controllers/explore/application_controller.rb +++ b/app/controllers/explore/application_controller.rb @@ -1,3 +1,5 @@ class Explore::ApplicationController < ApplicationController + skip_before_action :authenticate_user!, :reject_blocked + layout 'explore' end diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index 55cda0cff17..9575a87ee41 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,7 +1,4 @@ class Explore::GroupsController < Explore::ApplicationController - skip_before_action :authenticate_user!, - :reject_blocked, :set_current_user_for_observers - def index @groups = GroupsFinder.new.execute(current_user) @groups = @groups.search(params[:search]) if params[:search].present? diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 6c733c1ae4d..a5aeaed66c5 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,7 +1,4 @@ class Explore::ProjectsController < Explore::ApplicationController - skip_before_action :authenticate_user!, - :reject_blocked - def index @projects = ProjectsFinder.new.execute(current_user) @tags = @projects.tags_on(:tags) diff --git a/app/controllers/explore/snippets_controller.rb b/app/controllers/explore/snippets_controller.rb new file mode 100644 index 00000000000..b70ac51d06e --- /dev/null +++ b/app/controllers/explore/snippets_controller.rb @@ -0,0 +1,6 @@ +class Explore::SnippetsController < Explore::ApplicationController + def index + @snippets = SnippetsFinder.new.execute(current_user, filter: :all) + @snippets = @snippets.page(params[:page]).per(PER_PAGE) + end +end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 279c6ef0f4d..524218290c6 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -4,7 +4,7 @@ class GroupsController < Groups::ApplicationController before_action :group, except: [:new, :create] # Authorize - before_action :authorize_read_group!, except: [:new, :create] + before_action :authorize_read_group!, except: [:show, :new, :create] before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects] before_action :authorize_create_group!, only: [:new, :create] @@ -14,6 +14,10 @@ class GroupsController < Groups::ApplicationController layout :determine_layout + def index + redirect_to(current_user ? dashboard_groups_path : explore_groups_path) + end + def new @group = Group.new end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 71831c5380d..55050615473 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -1,7 +1,14 @@ class HelpController < ApplicationController + skip_before_action :authenticate_user!, :reject_blocked + layout 'help' def index + @help_index = File.read(Rails.root.join('doc', 'README.md')) + + # Prefix Markdown links with `help/` unless they already have been + # See http://rubular.com/r/nwwhzH6Z8X + @help_index.gsub!(/(\]\()(?!help\/)([^\)\(]+)(\))/, '\1help/\2\3') end def show diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb new file mode 100644 index 00000000000..849646cd665 --- /dev/null +++ b/app/controllers/import/fogbugz_controller.rb @@ -0,0 +1,104 @@ +class Import::FogbugzController < Import::BaseController + before_action :verify_fogbugz_import_enabled + before_action :user_map, only: [:new_user_map, :create_user_map] + + rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized + + def new + + end + + def callback + begin + res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys) + rescue + # If the URI is invalid various errors can occur + return redirect_to new_import_fogbugz_path, alert: 'Could not connect to FogBugz, check your URL' + end + session[:fogbugz_token] = res.get_token + session[:fogbugz_uri] = params[:uri] + + redirect_to new_user_map_import_fogbugz_path + end + + def new_user_map + + end + + def create_user_map + user_map = params[:users] + + unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? } + flash.now[:alert] = 'All users must have a name.' + + render 'new_user_map' and return + end + + session[:fogbugz_user_map] = user_map + + flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.' + + redirect_to status_import_fogbugz_path + end + + def status + unless client.valid? + return redirect_to new_import_fogbugz_path + end + + @repos = client.repos + + @already_added_projects = current_user.created_projects.where(import_type: 'fogbugz') + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.reject! { |repo| already_added_projects_names.include? repo.name } + end + + def jobs + jobs = current_user.created_projects.where(import_type: 'fogbugz').to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id] + repo = client.repo(@repo_id) + fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] } + @target_namespace = current_user.namespace + @project_name = repo.name + + namespace = @target_namespace + + umap = session[:fogbugz_user_map] || client.user_map + + @project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, namespace, current_user, umap).execute + end + + private + + def client + @client ||= Gitlab::FogbugzImport::Client.new(token: session[:fogbugz_token], uri: session[:fogbugz_uri]) + end + + def user_map + @user_map ||= begin + user_map = client.user_map + + stored_user_map = session[:fogbugz_user_map] + user_map.update(stored_user_map) if stored_user_map + + user_map + end + end + + def fogbugz_unauthorized(exception) + redirect_to new_import_fogbugz_path, alert: exception.message + end + + def import_params + params.permit(:uri, :email, :password) + end + + def verify_fogbugz_import_enabled + not_found! unless fogbugz_import_enabled? + end +end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index eb3c8233530..8ef10a17f55 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -24,7 +24,7 @@ class InvitesController < ApplicationController path = if current_user - dashboard_path + dashboard_projects_path else new_user_session_path end @@ -73,7 +73,7 @@ class InvitesController < ApplicationController path = group_path(group) else label = "who knows what" - path = dashboard_path + path = dashboard_projects_path end [label, path] diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb index 83eec1bf4a2..282012c60a1 100644 --- a/app/controllers/namespaces_controller.rb +++ b/app/controllers/namespaces_controller.rb @@ -14,7 +14,7 @@ class NamespacesController < ApplicationController if user redirect_to user_path(user) - elsif group && can?(current_user, :read_group, group) + elsif group redirect_to group_path(group) elsif current_user.nil? authenticate_user! diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb index fc31118124b..dc22101cd5e 100644 --- a/app/controllers/oauth/applications_controller.rb +++ b/app/controllers/oauth/applications_controller.rb @@ -1,7 +1,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController include Gitlab::CurrentSettings include PageLayoutHelper - + before_action :verify_user_oauth_applications_enabled before_action :authenticate_user! diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 523264b8ea9..f809fa7500a 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -71,7 +71,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return end end - rescue Gitlab::OAuth::SignupDisabledError => e + rescue Gitlab::OAuth::SignupDisabledError label = Gitlab::OAuth::Provider.label_for(oauth['provider']) message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed." @@ -80,7 +80,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController end flash[:notice] = message - + redirect_to new_user_session_path end diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 8450ba31021..2025158d065 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -1,41 +1,7 @@ class PasswordsController < Devise::PasswordsController - - def create - email = resource_params[:email] - resource_found = resource_class.find_by_email(email) - if resource_found && resource_found.ldap_user? - flash[:alert] = "Cannot reset password for LDAP user." - respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) and return - end - - self.resource = resource_class.send_reset_password_instructions(resource_params) - if successfully_sent?(resource) - respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) - else - respond_with(resource) - end - end - - # After a user resets their password, prompt for 2FA code if enabled instead - # of signing in automatically - # - # See http://git.io/vURrI - def update - super do |resource| - # TODO (rspeicher): In Devise master (> 3.4.1), we can set - # `Devise.sign_in_after_reset_password = false` and avoid this mess. - if resource.errors.empty? && resource.try(:two_factor_enabled?) - resource.unlock_access! if unlockable?(resource) - - # Since we are not signing this user in, we use the :updated_not_active - # message which only contains "Your password was changed successfully." - set_flash_message(:notice, :updated_not_active) if is_flashing_format? - - # Redirect to sign in so they can enter 2FA code - respond_with(resource, location: new_session_path(resource)) and return - end - end - end + before_action :resource_from_email, only: [:create] + before_action :prevent_ldap_reset, only: [:create] + before_action :throttle_reset, only: [:create] def edit super @@ -56,4 +22,25 @@ class PasswordsController < Devise::PasswordsController end end end + + protected + + def resource_from_email + email = resource_params[:email] + self.resource = resource_class.find_by_email(email) + end + + def prevent_ldap_reset + return unless resource && resource.ldap_user? + + redirect_to after_sending_reset_password_instructions_path_for(resource_name), + alert: "Cannot reset password for LDAP user." + end + + def throttle_reset + return unless resource && resource.recently_sent_password_reset? + + redirect_to new_password_path(resource_name), + alert: I18n.t('devise.passwords.recently_reset') + end end diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index f83b4abd1e2..a9a06ecc808 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -31,6 +31,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController def preferences_params params.require(:user).permit( :color_scheme_id, + :layout, :dashboard, :project_view, :theme_id diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index f9af0871cf1..e6b99be37fb 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -9,7 +9,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController end def create - if current_user.valid_otp?(params[:pin_code]) + if current_user.validate_and_consume_otp!(params[:pin_code]) current_user.two_factor_enabled = true @codes = current_user.generate_otp_backup_codes! current_user.save! diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index ee88d49b400..519d6d6127e 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -25,4 +25,14 @@ class Projects::ApplicationController < ApplicationController ) end end + + private + + def ci_enabled + return render_404 unless @project.gitlab_ci? + end + + def ci_project + @ci_project ||= @project.ensure_gitlab_ci_project + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 100d3d3b317..8776721d243 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -26,10 +26,16 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) + respond_to do |format| + format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } + format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } } + end else flash[:alert] = result[:message] - render :new + respond_to do |format| + format.html { render :new } + format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } } + end end end @@ -45,10 +51,16 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to after_edit_path + respond_to do |format| + format.html { redirect_to after_edit_path } + format.json { render json: { message: "success", filePath: after_edit_path } } + end else flash[:alert] = result[:message] - render :edit + respond_to do |format| + format.html { render :edit } + format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } } + end end end @@ -146,11 +158,19 @@ class Projects::BlobController < Projects::ApplicationController @file_path = if action_name.to_s == 'create' + if params[:file].present? + params[:file_name] = params[:file].original_filename + end File.join(@path, File.basename(params[:file_name])) else @path end + if params[:file].present? + params[:content] = Base64.encode64(params[:file].read) + params[:encoding] = 'base64' + end + @commit_params = { file_path: @file_path, current_branch: @current_branch, diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb new file mode 100644 index 00000000000..76c7f31f61b --- /dev/null +++ b/app/controllers/projects/builds_controller.rb @@ -0,0 +1,25 @@ +class Projects::BuildsController < Projects::ApplicationController + before_action :ci_project + before_action :build + + layout "project" + + def show + @builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') + @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20) + @commit = @build.commit + + respond_to do |format| + format.html + format.json do + render json: @build.to_json(methods: :trace_html) + end + end + end + + private + + def build + @build ||= ci_project.builds.unscoped.find_by!(id: params[:id]) + end +end diff --git a/app/controllers/projects/ci_settings_controller.rb b/app/controllers/projects/ci_settings_controller.rb new file mode 100644 index 00000000000..a263242a850 --- /dev/null +++ b/app/controllers/projects/ci_settings_controller.rb @@ -0,0 +1,36 @@ +class Projects::CiSettingsController < Projects::ApplicationController + before_action :ci_project + before_action :authorize_admin_project! + + layout "project_settings" + + def edit + end + + def update + if ci_project.update_attributes(project_params) + Ci::EventService.new.change_project_settings(current_user, ci_project) + + redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.' + else + render action: "edit" + end + end + + def destroy + ci_project.destroy + Ci::EventService.new.remove_project(current_user, ci_project) + project.gitlab_ci_service.update_attributes(active: false) + + redirect_to project_path(project), notice: "CI was disabled for this project" + end + + protected + + def project_params + params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build, + :polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients, + :email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token, + { variables_attributes: [:id, :key, :value, :_destroy] }) + end +end diff --git a/app/controllers/projects/ci_web_hooks_controller.rb b/app/controllers/projects/ci_web_hooks_controller.rb new file mode 100644 index 00000000000..7f40ddcb3f3 --- /dev/null +++ b/app/controllers/projects/ci_web_hooks_controller.rb @@ -0,0 +1,45 @@ +class Projects::CiWebHooksController < Projects::ApplicationController + before_action :ci_project + before_action :authorize_admin_project! + + layout "project_settings" + + def index + @web_hooks = @ci_project.web_hooks + @web_hook = Ci::WebHook.new + end + + def create + @web_hook = @ci_project.web_hooks.new(web_hook_params) + @web_hook.save + + if @web_hook.valid? + redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project) + else + @web_hooks = @ci_project.web_hooks.select(&:persisted?) + render :index + end + end + + def test + Ci::TestHookService.new.execute(hook, current_user) + + redirect_to :back + end + + def destroy + hook.destroy + + redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project) + end + + private + + def hook + @web_hook ||= @ci_project.web_hooks.find(params[:id]) + end + + def web_hook_params + params.require(:web_hook).permit(:url) + end +end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 78d42d695b6..1938c63c10c 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -22,6 +22,8 @@ class Projects::CommitController < Projects::ApplicationController commit_id: @commit.id } + @ci_commit = project.ci_commit(commit.sha) + respond_to do |format| format.html format.diff { render text: @commit.to_diff } @@ -29,6 +31,13 @@ class Projects::CommitController < Projects::ApplicationController end end + def ci + @ci_commit = @project.ci_commit(@commit.sha) + @builds = @ci_commit.builds if @ci_commit + @notes_count = @commit.notes.count + @ci_project = @project.gitlab_ci_project + end + def branches @branches = @project.repository.branch_names_contains(commit.id) @tags = @project.repository.tag_names_contains(commit.id) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index d9b3adae95b..d15004f93a6 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -16,10 +16,12 @@ class Projects::CompareController < Projects::ApplicationController compare_result = CompareService.new. execute(@project, head_ref, @project, base_ref) - @commits = compare_result.commits - @diffs = compare_result.diffs - @commit = @commits.last - @line_notes = [] + if compare_result + @commits = compare_result.commits + @diffs = compare_result.diffs + @commit = @commits.last + @line_notes = [] + end end def create diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 9e72597ea87..8a785076bb7 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -13,10 +13,14 @@ class Projects::ForksController < Projects::ApplicationController @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute if @forked_project.saved? && @forked_project.forked? - redirect_to( - namespace_project_path(@forked_project.namespace, @forked_project), - notice: 'Project was successfully forked.' - ) + if @forked_project.import_in_progress? + redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project) + else + redirect_to( + namespace_project_path(@forked_project.namespace, @forked_project), + notice: 'Project was successfully forked.' + ) + end else render :error end diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 0b6f7f5c91e..418b92040bc 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -5,6 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController before_action :require_non_empty_project before_action :assign_ref_vars before_action :authorize_download_code! + before_action :ci_enabled, only: :ci def show respond_to do |format| @@ -23,6 +24,16 @@ class Projects::GraphsController < Projects::ApplicationController @commits_per_month = @commits_graph.commits_per_month end + def ci + ci_project = @project.gitlab_ci_project + + @charts = {} + @charts[:week] = Ci::Charts::WeekChart.new(ci_project) + @charts[:month] = Ci::Charts::MonthChart.new(ci_project) + @charts[:year] = Ci::Charts::YearChart.new(ci_project) + @charts[:build_times] = Ci::Charts::BuildTime.new(ci_project) + end + private def fetch_graph diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index f3054881daf..7570934e727 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits] before_action :validates_merge_request, only: [:show, :diffs, :commits] before_action :define_show_vars, only: [:show, :diffs, :commits] + before_action :ensure_ref_fetched, only: [:show, :commits, :diffs] # Allow read any merge_request before_action :authorize_read_merge_request! @@ -149,6 +150,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController return access_denied! unless @merge_request.can_be_merged_by?(current_user) if @merge_request.mergeable? + @merge_request.update(merge_error: nil) MergeWorker.perform_async(@merge_request.id, current_user.id, params) @status = true else @@ -257,8 +259,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commits = @merge_request.commits @merge_request_diff = @merge_request.merge_request_diff - @source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name) - + if @merge_request.locked_long_ago? @merge_request.unlock_mr @merge_request.close @@ -277,4 +278,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController :state_event, :description, :task_num, label_ids: [] ) end + + # Make sure merge requests created before 8.0 + # have head file in refs/merge-requests/ + def ensure_ref_fetched + @merge_request.ensure_ref_fetched + end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 9efe9704d1e..86f4a02a6e9 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -66,12 +66,7 @@ class Projects::MilestonesController < Projects::ApplicationController def destroy return access_denied! unless can?(current_user, :admin_milestone, @project) - update_params = { milestone: nil } - @milestone.issues.each do |issue| - Issues::UpdateService.new(@project, current_user, update_params).execute(issue) - end - - @milestone.destroy + Milestones::DestroyService.new(project, current_user).execute(milestone) respond_to do |format| format.html { redirect_to namespace_project_milestones_path } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index b82b6f45d59..cf73bc01c8f 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -78,7 +78,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project.project_members.find_by(user_id: current_user).destroy respond_to do |format| - format.html { redirect_to dashboard_path } + format.html { redirect_to dashboard_projects_path } format.js { render nothing: true } end end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 647c1454078..5f6fbce795e 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -17,8 +17,7 @@ class Projects::RawController < Projects::ApplicationController send_data( @blob.data, type: type, - disposition: 'inline', - filename: @blob.name + disposition: 'inline' ) else not_found! @@ -30,6 +29,8 @@ class Projects::RawController < Projects::ApplicationController def get_blob_type if @blob.text? 'text/plain; charset=utf-8' + elsif @blob.image? + @blob.content_type else 'application/octet-stream' end diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb new file mode 100644 index 00000000000..6cb6e3ef6d4 --- /dev/null +++ b/app/controllers/projects/runners_controller.rb @@ -0,0 +1,65 @@ +class Projects::RunnersController < Projects::ApplicationController + before_action :ci_project + before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show] + before_action :authorize_admin_project! + + layout 'project_settings' + + def index + @runners = @ci_project.runners.order('id DESC') + @specific_runners = + Ci::Runner.specific.includes(:runner_projects). + where(Ci::RunnerProject.table_name => { project_id: current_user.authorized_projects } ). + where.not(id: @runners).order("#{Ci::Runner.table_name}.id DESC").page(params[:page]).per(20) + @shared_runners = Ci::Runner.shared.active + @shared_runners_count = @shared_runners.count(:all) + end + + def edit + end + + def update + if @runner.update_attributes(runner_params) + redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' + else + redirect_to runner_path(@runner), alert: 'Runner was not updated.' + end + end + + def destroy + if @runner.only_for?(@ci_project) + @runner.destroy + end + + redirect_to runners_path(@project) + end + + def resume + if @runner.update_attributes(active: true) + redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' + else + redirect_to runner_path(@runner), alert: 'Runner was not updated.' + end + end + + def pause + if @runner.update_attributes(active: false) + redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' + else + redirect_to runner_path(@runner), alert: 'Runner was not updated.' + end + end + + def show + end + + protected + + def set_runner + @runner ||= @ci_project.runners.find(params[:id]) + end + + def runner_params + params.require(:runner).permit(:description, :tag_list, :contacted_at, :active) + end +end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index b0cf5866d41..3047ee8a1ff 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -2,7 +2,7 @@ class Projects::ServicesController < Projects::ApplicationController ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain, :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, - :build_key, :server, :teamcity_url, :build_type, + :build_key, :server, :teamcity_url, :drone_url, :build_type, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :colorize_messages, :channels, :push_events, :issues_events, :merge_requests_events, :tag_push_events, @@ -58,6 +58,8 @@ class Projects::ServicesController < Projects::ApplicationController end def service_params - params.require(:service).permit(ALLOWED_PARAMS) + service_params = params.require(:service).permit(ALLOWED_PARAMS) + service_params.delete("password") if service_params["password"].blank? + service_params end end diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb new file mode 100644 index 00000000000..782ebd01b05 --- /dev/null +++ b/app/controllers/projects/triggers_controller.rb @@ -0,0 +1,35 @@ +class Projects::TriggersController < Projects::ApplicationController + before_action :ci_project + before_action :authorize_admin_project! + + layout 'project_settings' + + def index + @triggers = @ci_project.triggers + @trigger = Ci::Trigger.new + end + + def create + @trigger = @ci_project.triggers.new + @trigger.save + + if @trigger.valid? + redirect_to namespace_project_triggers_path(@project.namespace, @project) + else + @triggers = @ci_project.triggers.select(&:persisted?) + render :index + end + end + + def destroy + trigger.destroy + + redirect_to namespace_project_triggers_path(@project.namespace, @project) + end + + private + + def trigger + @trigger ||= @ci_project.triggers.find(params[:id]) + end +end diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb new file mode 100644 index 00000000000..d6561a45a70 --- /dev/null +++ b/app/controllers/projects/variables_controller.rb @@ -0,0 +1,25 @@ +class Projects::VariablesController < Projects::ApplicationController + before_action :ci_project + before_action :authorize_admin_project! + + layout 'project_settings' + + def show + end + + def update + if ci_project.update_attributes(project_params) + Ci::EventService.new.change_project_settings(current_user, ci_project) + + redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.' + else + render action: 'show' + end + end + + private + + def project_params + params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] }) + end +end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 50512cb6dc3..88fccfed509 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -5,7 +5,6 @@ class Projects::WikisController < Projects::ApplicationController before_action :authorize_create_wiki!, only: [:edit, :create, :history] before_action :authorize_admin_wiki!, only: :destroy before_action :load_project_wiki - include WikiHelper def pages @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE) @@ -99,7 +98,7 @@ class Projects::WikisController < Projects::ApplicationController # Call #wiki to make sure the Wiki Repo is initialized @project_wiki.wiki - rescue ProjectWiki::CouldNotCreateWikiError => ex + rescue ProjectWiki::CouldNotCreateWikiError flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." redirect_to project_path(@project) return false diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index dafc11d0707..213c2a7173b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -10,6 +10,10 @@ class ProjectsController < ApplicationController layout :determine_layout + def index + redirect_to(current_user ? root_path : explore_root_path) + end + def new @project = Project.new end @@ -82,6 +86,10 @@ class ProjectsController < ApplicationController if @project.empty_repo? render 'projects/empty' else + if current_user + @membership = @project.project_member_by_id(current_user.id) + end + render :show end else @@ -105,7 +113,7 @@ class ProjectsController < ApplicationController if request.referer.include?('/admin') redirect_to admin_namespaces_projects_path else - redirect_to dashboard_path + redirect_to dashboard_projects_path end rescue Projects::DestroyService::DestroyError => ex redirect_to edit_project_path(@project), alert: ex.message diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index fdfe00dc135..ad04c646e1b 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -6,10 +6,10 @@ # # For users who haven't customized the setting, we simply delegate to # `DashboardController#show`, which is the default. -class RootController < DashboardController - before_action :redirect_to_custom_dashboard, only: [:show] +class RootController < Dashboard::ProjectsController + before_action :redirect_to_custom_dashboard, only: [:index] - def show + def index super end @@ -20,7 +20,12 @@ class RootController < DashboardController case current_user.dashboard when 'stars' + flash.keep redirect_to starred_dashboard_projects_path + when 'project_activity' + redirect_to activity_dashboard_path + when 'starred_project_activity' + redirect_to activity_dashboard_path(filter: 'starred') else return end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8389f07a3bd..1b60d3e27d0 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -8,6 +8,8 @@ class SessionsController < Devise::SessionsController def new if Gitlab.config.ldap.enabled @ldap_servers = Gitlab::LDAP::Config.servers + else + @ldap_servers = [] end super @@ -97,7 +99,7 @@ class SessionsController < Devise::SessionsController end def valid_otp_attempt?(user) - user.valid_otp?(user_params[:otp_attempt]) || + user.validate_and_consume_otp!(user_params[:otp_attempt]) || user.invalidate_otp_backup_code!(user_params[:otp_attempt]) end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 8e7e45c781f..9f9f9a92f11 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -24,13 +24,9 @@ class SnippetsController < ApplicationController scope: params[:scope] }). page(params[:page]).per(PER_PAGE) - if @user == current_user - render 'current_user_index' - else - render 'user_index' - end + render 'index' else - @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(PER_PAGE) + redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path) end end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index ab89aa2c53a..6aa16673d63 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -39,7 +39,7 @@ class IssuableFinder items = by_assignee(items) items = by_author(items) items = by_label(items) - items = sort(items) + sort(items) end def group diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb index f3f4d461efa..9ea342cb26d 100644 --- a/app/finders/trending_projects_finder.rb +++ b/app/finders/trending_projects_finder.rb @@ -2,21 +2,12 @@ class TrendingProjectsFinder def execute(current_user, start_date = nil) start_date ||= Date.today - 1.month + projects = projects_for(current_user) + # Determine trending projects based on comments count # for period of time - ex. month - trending_project_ids = Note. - select("notes.project_id, count(notes.project_id) as pcount"). - where('notes.created_at > ?', start_date). - group("project_id"). - reorder("pcount DESC"). - map(&:project_id) - - sql_order_ids = trending_project_ids.reverse. - map { |project_id| "id = #{project_id}" }.join(", ") - - # Get list of projects that user allowed to see - projects = projects_for(current_user) - projects.where(id: trending_project_ids).reorder(sql_order_ids) + projects.joins(:notes).where('notes.created_at > ?', start_date). + group("projects.id").reorder("count(notes.id) DESC") end private diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a803b66c502..cab2278adb7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -13,7 +13,9 @@ module ApplicationHelper # current_controller?(:commits) # => false # current_controller?(:commits, :tree) # => true def current_controller?(*args) - args.any? { |v| v.to_s.downcase == controller.controller_name } + args.any? do |v| + v.to_s.downcase == controller.controller_name || v.to_s.downcase == controller.controller_path + end end # Check if a particular action is the current one @@ -33,7 +35,7 @@ module ApplicationHelper def project_icon(project_id, options = {}) project = if project_id.is_a?(Project) - project = project_id + project_id else Project.find_with_namespace(project_id) end @@ -82,7 +84,7 @@ module ApplicationHelper end def default_avatar - image_path('no_avatar.png') + 'no_avatar.png' end def last_commit(project) @@ -201,7 +203,7 @@ module ApplicationHelper class: "#{html_class} js-timeago", datetime: time.getutc.iso8601, title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'), - data: { toggle: 'tooltip', placement: placement } + data: { toggle: 'tooltip', placement: placement, container: 'body' } element += javascript_tag "$('.js-timeago').timeago()" unless skip_js @@ -312,4 +314,8 @@ module ApplicationHelper html.html_safe end + + def truncate_first_line(message, length = 50) + truncate(message.each_line.first.chomp, length: length) if message + end end diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 0e7a37b4cc6..cd99a232403 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,6 +1,6 @@ module AuthHelper PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze - FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos'].freeze + FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze def ldap_enabled? Gitlab.config.ldap.enabled @@ -26,6 +26,10 @@ module AuthHelper auth_providers.select { |provider| form_based_provider?(provider) } end + def crowd_enabled? + auth_providers.include? :crowd + end + def button_based_providers auth_providers.reject { |provider| form_based_provider?(provider) } end @@ -36,7 +40,7 @@ module AuthHelper if provider_has_icon?(provider) file_name = "#{provider.to_s.split('_').first}_#{size}.png" - image_tag(image_path("auth_buttons/#{file_name}"), alt: label, title: "Sign in with #{label}") + image_tag("auth_buttons/#{file_name}", alt: label, title: "Sign in with #{label}") else label end diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb new file mode 100644 index 00000000000..1b5a2c31d74 --- /dev/null +++ b/app/helpers/builds_helper.rb @@ -0,0 +1,13 @@ +module BuildsHelper + def build_ref_link build + gitlab_ref_link build.project, build.ref + end + + def build_commit_link build + gitlab_commit_link build.project, build.short_sha + end + + def build_url(build) + namespace_project_build_path(build.gl_project, build.project, build) + end +end diff --git a/app/helpers/ci/gitlab_helper.rb b/app/helpers/ci/gitlab_helper.rb new file mode 100644 index 00000000000..baddbc806f2 --- /dev/null +++ b/app/helpers/ci/gitlab_helper.rb @@ -0,0 +1,36 @@ +module Ci + module GitlabHelper + def no_turbolink + { :"data-no-turbolink" => "data-no-turbolink" } + end + + def gitlab_ref_link project, ref + gitlab_url = project.gitlab_url.dup + gitlab_url << "/commits/#{ref}" + link_to ref, gitlab_url, no_turbolink + end + + def gitlab_compare_link project, before, after + gitlab_url = project.gitlab_url.dup + gitlab_url << "/compare/#{before}...#{after}" + + link_to "#{before}...#{after}", gitlab_url, no_turbolink + end + + def gitlab_commit_link project, sha + gitlab_url = project.gitlab_url.dup + gitlab_url << "/commit/#{sha}" + link_to Ci::Commit.truncate_sha(sha), gitlab_url, no_turbolink + end + + def yaml_web_editor_link(project) + commits = project.commits + + if commits.any? && commits.last.ci_yaml_file + "#{project.gitlab_url}/edit/master/.gitlab-ci.yml" + else + "#{project.gitlab_url}/new/master" + end + end + end +end diff --git a/app/helpers/ci/projects_helper.rb b/app/helpers/ci/projects_helper.rb new file mode 100644 index 00000000000..fd991a4165a --- /dev/null +++ b/app/helpers/ci/projects_helper.rb @@ -0,0 +1,36 @@ +module Ci + module ProjectsHelper + def ref_tab_class ref = nil + 'active' if ref == @ref + end + + def success_ratio(success_builds, failed_builds) + failed_builds = failed_builds.count(:all) + success_builds = success_builds.count(:all) + + return 100 if failed_builds.zero? + + ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100 + ratio.to_i + end + + def markdown_badge_code(project, ref) + url = status_ci_project_url(project, ref: ref, format: 'png') + "[![build status](#{url})](#{ci_project_url(project, ref: ref)})" + end + + def html_badge_code(project, ref) + url = status_ci_project_url(project, ref: ref, format: 'png') + "<a href='#{ci_project_url(project, ref: ref)}'><img src='#{url}' /></a>" + end + + def project_uses_specific_runner?(project) + project.runners.any? + end + + def no_runners_for_project?(project) + project.runners.blank? && + Ci::Runner.shared.blank? + end + end +end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb new file mode 100644 index 00000000000..dbd1e26fa79 --- /dev/null +++ b/app/helpers/ci_status_helper.rb @@ -0,0 +1,45 @@ +module CiStatusHelper + def ci_status_path(ci_commit) + project = ci_commit.gl_project + ci_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + end + + def ci_status_icon(ci_commit) + ci_icon_for_status(ci_commit.status) + end + + def ci_status_color(ci_commit) + case ci_commit.status + when 'success' + 'green' + when 'failed' + 'red' + when 'running', 'pending' + 'yellow' + else + 'gray' + end + end + + def ci_status_with_icon(status) + content_tag :span, class: "ci-status ci-#{status}" do + ci_icon_for_status(status) + ' '.html_safe + status + end + end + + def ci_icon_for_status(status) + icon_name = + case status + when 'success' + 'check' + when 'failed' + 'close' + when 'running', 'pending' + 'clock-o' + else + 'circle' + end + + icon(icon_name) + end +end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index d13d80be293..9df20c9fce5 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -135,7 +135,7 @@ module CommitsHelper # size: size of the avatar image in px def commit_person_link(commit, options = {}) user = commit.send(options[:source]) - + source_name = clean(commit.send "#{options[:source]}_name".to_sym) source_email = clean(commit.send "#{options[:source]}_email".to_sym) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 1bd3ec5e0e0..b896fba3704 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -137,7 +137,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do + link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do 'Inline' end end @@ -148,7 +148,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do + link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do 'Side-by-side' end end @@ -167,4 +167,23 @@ module DiffHelper content_tag(:span, commit_id, class: 'monospace'), ].join(' ').html_safe end + + def commit_for_diff(diff) + if diff.deleted_file + @merge_request ? @merge_request.commits.last : @commit.parents.first + else + @commit + end + end + + def diff_file_html_data(project, diff_commit, diff_file) + { + blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, + tree_join(diff_commit.id, diff_file.file_path)) + } + end + + def editable_diff?(diff) + !diff.deleted_file && @merge_request && @merge_request.source_project + end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 8428281f8f6..6f69c2a9f32 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -27,16 +27,13 @@ module EventsHelper key = key.to_s active = 'active' if @event_filter.active?(key) link_opts = { - class: 'event_filter_link', + class: "event-filter-link btn btn-default #{active}", id: "#{key}_event_filter", title: "Filter by #{tooltip.downcase}", - data: { toggle: 'tooltip', placement: 'top' } } - content_tag :li, class: "filter_icon #{active}" do - link_to request.path, link_opts do - icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) - end + link_to request.path, link_opts do + content_tag(:span, ' ' + tooltip) end end @@ -49,6 +46,14 @@ module EventsHelper } end + def event_preposition(event) + if event.push? || event.commented? || event.target + "at" + elsif event.milestone? + "in" + end + end + def event_feed_title(event) words = [] words << event.author_name @@ -65,8 +70,11 @@ module EventsHelper words << "##{truncate event.note_target_iid}" end words << "at" + elsif event.milestone? + words << "##{event.target_iid}" if event.target_iid + words << "in" elsif event.target - words << "##{event.target_iid}:" + words << "##{event.target_iid}:" words << event.target.title if event.target.respond_to?(:title) words << "at" end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 0d175e1ea18..40161c5a641 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -21,7 +21,7 @@ module GitlabMarkdownHelper gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user) - fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body) + fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body) if fragment.children.size == 1 && fragment.children[0].name == 'a' # Fragment has only one node, and it's a link generated by `gfm`. # Replace it with our requested link. @@ -171,7 +171,7 @@ module GitlabMarkdownHelper # and return true. Otherwise return false. def truncate_if_block(node, truncated) if node.element? && node.description.block? && !truncated - node.content = "#{node.content}..." if node.next_sibling + node.inner_html = "#{node.inner_html}..." if node.next_sibling true else truncated diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index d0fae255a04..4d9da6ff837 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -17,6 +17,14 @@ module GitlabRoutingHelper namespace_project_path(project.namespace, project, *args) end + def project_files_path(project, *args) + namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref) + end + + def project_commits_path(project, *args) + namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) + end + def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end @@ -25,6 +33,14 @@ module GitlabRoutingHelper edit_namespace_project_path(project.namespace, project, *args) end + def runners_path(project, *args) + namespace_project_runners_path(project.namespace, project, *args) + end + + def runner_path(runner, *args) + namespace_project_runner_path(@project.namespace, @project, runner, *args) + end + def issue_path(entity, *args) namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) end diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index e1dda20de85..1e372d5631d 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -1,7 +1,10 @@ module GraphHelper def get_refs(repo, commit) refs = "" - refs << commit.ref_names(repo).join(' ') + # Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX) + # so anything leftover is internally used by GitLab + commit_refs = commit.ref_names(repo).reject{ |name| name.starts_with?('refs/') } + refs << commit_refs.join(' ') # append note count refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index b067cb54a43..1d36969cd62 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -27,7 +27,16 @@ module GroupsHelper if group && group.avatar.present? group.avatar.url else - image_path('no_group_avatar.png') + 'no_group_avatar.png' + end + end + + def group_title(group, name = nil, url = nil) + full_title = link_to(simple_sanitize(group.name), group_path(group)) + full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name + + content_tag :span do + full_title end end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index f8169b4f288..81773e7afcf 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -71,4 +71,17 @@ module MergeRequestsHelper merge_request.source_branch end end + + def format_mr_branch_names(merge_request) + source_path = merge_request.source_project_path + target_path = merge_request.target_project_path + source_branch = merge_request.source_branch + target_branch = merge_request.target_branch + + if source_path == target_path + [source_branch, target_branch] + else + ["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"] + end + end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 2f8e64c375f..cf11f8e5320 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -12,4 +12,49 @@ module NotificationsHelper icon('circle-o', class: 'ns-default') end end + + def notification_list_item(notification_level, user_membership) + case notification_level + when Notification::N_DISABLED + content_tag(:li, class: active_level_for(user_membership, Notification::N_DISABLED)) do + link_to '#', class: 'update-notification', data: { notification_level: Notification::N_DISABLED } do + icon('microphone-slash fw', text: 'Disabled') + end + end + when Notification::N_PARTICIPATING + content_tag(:li, class: active_level_for(user_membership, Notification::N_PARTICIPATING)) do + link_to '#', class: 'update-notification', data: { notification_level: Notification::N_PARTICIPATING } do + icon('volume-up fw', text: 'Participate') + end + end + when Notification::N_WATCH + content_tag(:li, class: active_level_for(user_membership, Notification::N_WATCH)) do + link_to '#', class: 'update-notification', data: { notification_level: Notification::N_WATCH } do + icon('eye fw', text: 'Watch') + end + end + when Notification::N_MENTION + content_tag(:li, class: active_level_for(user_membership, Notification::N_MENTION)) do + link_to '#', class: 'update-notification', data: { notification_level: Notification::N_MENTION } do + icon('at fw', text: 'On mention') + end + end + when Notification::N_GLOBAL + content_tag(:li, class: active_level_for(user_membership, Notification::N_GLOBAL)) do + link_to '#', class: 'update-notification', data: { notification_level: Notification::N_GLOBAL } do + icon('globe fw', text: 'Global') + end + end + else + # do nothing + end + end + + def notification_label(user_membership) + Notification.new(user_membership).to_s + end + + def active_level_for(user_membership, level) + 'active' if user_membership.notification_level == level + end end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 8473d6d75d0..775cf5a3dd4 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -26,9 +26,31 @@ module PageLayoutHelper def fluid_layout(enabled = false) if @fluid_layout.nil? - @fluid_layout = enabled + @fluid_layout = (current_user && current_user.layout == "fluid") || enabled else @fluid_layout end end + + def blank_container(enabled = false) + if @blank_container.nil? + @blank_container = enabled + else + @blank_container + end + end + + def container_class + css_class = "container-fluid" + + unless fluid_layout + css_class += " container-limited" + end + + if blank_container + css_class += " container-blank" + end + + css_class + end end diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index 7f1b6a69926..4710171ebaa 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -1,9 +1,18 @@ # Helper methods for per-User preferences module PreferencesHelper + def layout_choices + [ + ['Fixed', :fixed], + ['Fluid', :fluid] + ] + end + # Maps `dashboard` values to more user-friendly option text DASHBOARD_CHOICES = { projects: 'Your Projects (default)', - stars: 'Starred Projects' + stars: 'Starred Projects', + project_activity: "Your Projects' Activity", + starred_project_activity: "Starred Projects' Activity" }.with_indifferent_access.freeze # Returns an Array usable by a select field for more user-friendly option text diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index ab9b068de05..a0220af4c30 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -43,24 +43,22 @@ module ProjectsHelper end end - def project_title(project) - if project.group - content_tag :span do - link_to( - simple_sanitize(project.group.name), group_path(project.group) - ) + ' / ' + - link_to(simple_sanitize(project.name), - project_path(project)) - end - else - owner = project.namespace.owner - content_tag :span do - link_to( - simple_sanitize(owner.name), user_path(owner) - ) + ' / ' + - link_to(simple_sanitize(project.name), - project_path(project)) + def project_title(project, name = nil, url = nil) + namespace_link = + if project.group + link_to(simple_sanitize(project.group.name), group_path(project.group)) + else + owner = project.namespace.owner + link_to(simple_sanitize(owner.name), user_path(owner)) end + + project_link = link_to(simple_sanitize(project.name), project_path(project)) + + full_title = namespace_link + ' / ' + project_link + full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name + + content_tag :span do + full_title end end @@ -158,8 +156,8 @@ module ProjectsHelper end end - def repository_size(project = nil) - "#{(project || @project).repository_size} MB" + def repository_size(project = @project) + "#{project.repository_size} MB" rescue # In order to prevent 500 error # when application cannot allocate memory @@ -298,7 +296,7 @@ module ProjectsHelper def readme_cache_key sha = @project.commit.try(:sha) || 'nil' - [@project.id, sha, "readme"].join('-') + [@project.path_with_namespace, sha, "readme"].join('-') end def round_commit_count(project) @@ -315,6 +313,10 @@ module ProjectsHelper end end + def current_ref + @ref || @repository.try(:root_ref) + end + private def filename_path(project, filename) diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb new file mode 100644 index 00000000000..5d7d06c8490 --- /dev/null +++ b/app/helpers/runners_helper.rb @@ -0,0 +1,20 @@ +module RunnersHelper + def runner_status_icon(runner) + unless runner.contacted_at + return content_tag :i, nil, + class: "fa fa-warning-sign", + title: "New runner. Has not connected yet" + end + + status = + if runner.active? + runner.contacted_at > 3.hour.ago ? :online : :offline + else + :paused + end + + content_tag :i, nil, + class: "fa fa-circle runner-status-#{status}", + title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago" + end +end diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb new file mode 100644 index 00000000000..8142f733e76 --- /dev/null +++ b/app/helpers/time_helper.rb @@ -0,0 +1,27 @@ +module TimeHelper + def duration_in_words(finished_at, started_at) + if finished_at && started_at + interval_in_seconds = finished_at.to_i - started_at.to_i + elsif started_at + interval_in_seconds = Time.now.to_i - started_at.to_i + end + + time_interval_in_words(interval_in_seconds) + end + + def time_interval_in_words(interval_in_seconds) + minutes = interval_in_seconds / 60 + seconds = interval_in_seconds - minutes * 60 + + if minutes >= 1 + "#{pluralize(minutes, "minute")} #{pluralize(seconds, "second")}" + else + "#{pluralize(seconds, "second")}" + end + end + + + def date_from_to(from, to) + "#{from.to_s(:short)} - #{to.to_s(:short)}" + end +end diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb new file mode 100644 index 00000000000..2a3a7e80fca --- /dev/null +++ b/app/helpers/triggers_helper.rb @@ -0,0 +1,5 @@ +module TriggersHelper + def ci_build_trigger_url(project_id, ref_name) + "#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger" + end +end diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index f64d730b448..a674564c4ec 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -1,6 +1,6 @@ module VersionCheckHelper def version_status_badge - if Rails.env.production? + if Rails.env.production? && current_application_settings.version_check_enabled image_tag VersionCheck.new.url end end diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb deleted file mode 100644 index f8a96516e61..00000000000 --- a/app/helpers/wiki_helper.rb +++ /dev/null @@ -1,24 +0,0 @@ -module WikiHelper - # Rails v4.1.9+ escapes all model IDs, converting slashes into %2F. The - # only way around this is to implement our own path generators. - def namespace_project_wiki_path(namespace, project, wiki_page, *args) - slug = - case wiki_page - when Symbol - wiki_page - when String - wiki_page - else - wiki_page.slug - end - namespace_project_path(namespace, project) + "/wikis/#{slug}" - end - - def edit_namespace_project_wiki_path(namespace, project, wiki_page, *args) - namespace_project_wiki_path(namespace, project, wiki_page) + '/edit' - end - - def history_namespace_project_wiki_path(namespace, project, wiki_page, *args) - namespace_project_wiki_path(namespace, project, wiki_page) + '/history' - end -end diff --git a/app/mailers/ci/emails/builds.rb b/app/mailers/ci/emails/builds.rb new file mode 100644 index 00000000000..6fb4fba85e5 --- /dev/null +++ b/app/mailers/ci/emails/builds.rb @@ -0,0 +1,17 @@ +module Ci + module Emails + module Builds + def build_fail_email(build_id, to) + @build = Ci::Build.find(build_id) + @project = @build.project + mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha)) + end + + def build_success_email(build_id, to) + @build = Ci::Build.find(build_id) + @project = @build.project + mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha)) + end + end + end +end diff --git a/app/mailers/ci/notify.rb b/app/mailers/ci/notify.rb new file mode 100644 index 00000000000..404842cf213 --- /dev/null +++ b/app/mailers/ci/notify.rb @@ -0,0 +1,46 @@ +module Ci + class Notify < ActionMailer::Base + include Ci::Emails::Builds + + add_template_helper Ci::GitlabHelper + + default_url_options[:host] = Gitlab.config.gitlab.host + default_url_options[:protocol] = Gitlab.config.gitlab.protocol + default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? + default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root + + default from: Gitlab.config.gitlab.email_from + + # Just send email with 3 seconds delay + def self.delay + delay_for(2.seconds) + end + + private + + # Formats arguments into a String suitable for use as an email subject + # + # extra - Extra Strings to be inserted into the subject + # + # Examples + # + # >> subject('Lorem ipsum') + # => "GitLab-CI | Lorem ipsum" + # + # # Automatically inserts Project name when @project is set + # >> @project = Project.last + # => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...> + # >> subject('Lorem ipsum') + # => "GitLab-CI | Ruby on Rails | Lorem ipsum " + # + # # Accepts multiple arguments + # >> subject('Lorem ipsum', 'Dolor sit amet') + # => "GitLab-CI | Lorem ipsum | Dolor sit amet" + def subject(*extra) + subject = "GitLab-CI" + subject << (@project ? " | #{@project.name}" : "") + subject << " | " + extra.join(' | ') if extra.present? + subject + end + end +end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 63d4aca61af..87ba94a583d 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -12,7 +12,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@commit.title} (#{@commit.short_id})")) - SentNotification.record(@commit, recipient_id, reply_key) + SentNotification.record_note(@note, recipient_id, reply_key) end def note_issue_email(recipient_id, note_id) @@ -27,7 +27,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@issue.title} (##{@issue.iid})")) - SentNotification.record(@issue, recipient_id, reply_key) + SentNotification.record_note(@note, recipient_id, reply_key) end def note_merge_request_email(recipient_id, note_id) @@ -43,7 +43,7 @@ module Emails to: recipient(recipient_id), subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) - SentNotification.record(@merge_request, recipient_id, reply_key) + SentNotification.record_note(@note, recipient_id, reply_key) end end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 4a6e18e6a74..caba63006da 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -50,10 +50,11 @@ module Emails subject: subject("Invitation declined")) end - def project_was_moved_email(project_id, user_id) + def project_was_moved_email(project_id, user_id, old_path_with_namespace) @current_user = @user = User.find user_id @project = Project.find project_id @target_url = namespace_project_url(@project.namespace, @project) + @old_path_with_namespace = old_path_with_namespace mail(to: @user.notification_email, subject: subject("Project was moved")) end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 5717c89e61d..50a409c3754 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -100,7 +100,7 @@ class Notify < BaseMailer def mail_thread(model, headers = {}) if @project - headers['X-GitLab-Project'] = @project.name + headers['X-GitLab-Project'] = @project.name headers['X-GitLab-Project-Id'] = @project.id headers['X-GitLab-Project-Path'] = @project.path_with_namespace end @@ -110,7 +110,7 @@ class Notify < BaseMailer if reply_key headers['X-GitLab-Reply-Key'] = reply_key - address = Mail::Address.new(Gitlab::ReplyByEmail.reply_address(reply_key)) + address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key)) address.display_name = @project.name_with_namespace headers['Reply-To'] = address @@ -140,7 +140,7 @@ class Notify < BaseMailer # * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID' # def mail_answer_thread(model, headers = {}) - headers['Message-ID'] = SecureRandom.hex + headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>" headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) @@ -150,6 +150,6 @@ class Notify < BaseMailer end def reply_key - @reply_key ||= Gitlab::ReplyByEmail.reply_key + @reply_key ||= SentNotification.reply_key end end diff --git a/app/models/ability.rb b/app/models/ability.rb index f8e5afa9b01..a020b24a550 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -149,6 +149,7 @@ class Ability :admin_merge_request, :create_merge_request, :create_wiki, + :manage_builds, :push_code ] end diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index c8c39db11bc..89b3116b9f2 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -1,9 +1,21 @@ +# == Schema Information +# +# Table name: abuse_reports +# +# id :integer not null, primary key +# reporter_id :integer +# user_id :integer +# message :text +# created_at :datetime +# updated_at :datetime +# + class AbuseReport < ActiveRecord::Base - belongs_to :reporter, class_name: "User" + belongs_to :reporter, class_name: 'User' belongs_to :user validates :reporter, presence: true validates :user, presence: true validates :message, presence: true - validates :user_id, uniqueness: { scope: :reporter_id } + validates :user_id, uniqueness: true end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 8f27e35d723..c8841178e93 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -83,7 +83,7 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], - import_sources: ['github','bitbucket','gitlab','gitorious','google_code','git'] + import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'] ) end diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb new file mode 100644 index 00000000000..0cf496f7d81 --- /dev/null +++ b/app/models/ci/application_setting.rb @@ -0,0 +1,27 @@ +# == Schema Information +# +# Table name: application_settings +# +# id :integer not null, primary key +# all_broken_builds :boolean +# add_pusher :boolean +# created_at :datetime +# updated_at :datetime +# + +module Ci + class ApplicationSetting < ActiveRecord::Base + extend Ci::Model + + def self.current + Ci::ApplicationSetting.last + end + + def self.create_from_defaults + create( + all_broken_builds: Settings.gitlab_ci['all_broken_builds'], + add_pusher: Settings.gitlab_ci['add_pusher'], + ) + end + end +end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb new file mode 100644 index 00000000000..5d17f4418ed --- /dev/null +++ b/app/models/ci/build.rb @@ -0,0 +1,309 @@ +# == Schema Information +# +# Table name: builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# commit_id :integer +# coverage :float +# commands :text +# job_id :integer +# name :string(255) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# deploy :boolean default(FALSE) +# trigger_request_id :integer +# + +module Ci + class Build < ActiveRecord::Base + extend Ci::Model + + LAZY_ATTRIBUTES = ['trace'] + + belongs_to :commit, class_name: 'Ci::Commit' + belongs_to :runner, class_name: 'Ci::Runner' + belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' + belongs_to :user + + serialize :options + + validates :commit, presence: true + validates :status, presence: true + validates :coverage, numericality: true, allow_blank: true + validates_presence_of :ref + + scope :running, ->() { where(status: "running") } + scope :pending, ->() { where(status: "pending") } + scope :success, ->() { where(status: "success") } + scope :failed, ->() { where(status: "failed") } + scope :unstarted, ->() { where(runner_id: nil) } + scope :running_or_pending, ->() { where(status:[:running, :pending]) } + scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) } + scope :ignore_failures, ->() { where(allow_failure: false) } + scope :for_ref, ->(ref) { where(ref: ref) } + scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) } + + acts_as_taggable + + # To prevent db load megabytes of data from trace + default_scope -> { select(Ci::Build.columns_without_lazy) } + + class << self + def columns_without_lazy + (column_names - LAZY_ATTRIBUTES).map do |column_name| + "#{table_name}.#{column_name}" + end + end + + def last_month + where('created_at > ?', Date.today - 1.month) + end + + def first_pending + pending.unstarted.order('created_at ASC').first + end + + def create_from(build) + new_build = build.dup + new_build.status = :pending + new_build.runner_id = nil + new_build.save + end + + def retry(build) + new_build = Ci::Build.new(status: :pending) + new_build.ref = build.ref + new_build.tag = build.tag + new_build.options = build.options + new_build.commands = build.commands + new_build.tag_list = build.tag_list + new_build.commit_id = build.commit_id + new_build.name = build.name + new_build.allow_failure = build.allow_failure + new_build.stage = build.stage + new_build.stage_idx = build.stage_idx + new_build.trigger_request = build.trigger_request + new_build.save + new_build + end + end + + state_machine :status, initial: :pending do + event :run do + transition pending: :running + end + + event :drop do + transition running: :failed + end + + event :success do + transition running: :success + end + + event :cancel do + transition [:pending, :running] => :canceled + end + + after_transition pending: :running do |build, transition| + build.update_attributes started_at: Time.now + end + + after_transition any => [:success, :failed, :canceled] do |build, transition| + build.update_attributes finished_at: Time.now + project = build.project + + if project.web_hooks? + Ci::WebHookService.new.build_end(build) + end + + if build.commit.should_create_next_builds?(build) + build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request) + end + + project.execute_services(build) + + if project.coverage_enabled? + build.update_coverage + end + end + + state :pending, value: 'pending' + state :running, value: 'running' + state :failed, value: 'failed' + state :success, value: 'success' + state :canceled, value: 'canceled' + end + + delegate :sha, :short_sha, :project, :gl_project, + to: :commit, prefix: false + + def before_sha + Gitlab::Git::BLANK_SHA + end + + def trace_html + html = Ci::Ansi2html::convert(trace) if trace.present? + html || '' + end + + def started? + !pending? && !canceled? && started_at + end + + def active? + running? || pending? + end + + def complete? + canceled? || success? || failed? + end + + def ignored? + failed? && allow_failure? + end + + def timeout + project.timeout + end + + def variables + yaml_variables + project_variables + trigger_variables + end + + def duration + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.now - started_at + end + end + + def project + commit.project + end + + def project_id + commit.project.id + end + + def project_name + project.name + end + + def project_recipients + recipients = project.email_recipients.split(' ') + + if project.email_add_pusher? && user.present? && user.notification_email.present? + recipients << user.notification_email + end + + recipients.uniq + end + + def repo_url + project.repo_url_with_auth + end + + def allow_git_fetch + project.allow_git_fetch + end + + def update_coverage + coverage = extract_coverage(trace, project.coverage_regex) + + if coverage.is_a? Numeric + update_attributes(coverage: coverage) + end + end + + def extract_coverage(text, regex) + begin + matches = text.gsub(Regexp.new(regex)).to_a.last + coverage = matches.gsub(/\d+(\.\d+)?/).first + + if coverage.present? + coverage.to_f + end + rescue + # if bad regex or something goes wrong we dont want to interrupt transition + # so we just silentrly ignore error for now + end + end + + def raw_trace + if File.exist?(path_to_trace) + File.read(path_to_trace) + else + # backward compatibility + read_attribute :trace + end + end + + def trace + trace = raw_trace + if project && trace.present? + trace.gsub(project.token, 'xxxxxx') + else + trace + end + end + + def trace=(trace) + unless Dir.exists? dir_to_trace + FileUtils.mkdir_p dir_to_trace + end + + File.write(path_to_trace, trace) + end + + def dir_to_trace + File.join( + Settings.gitlab_ci.builds_path, + created_at.utc.strftime("%Y_%m"), + project.id.to_s + ) + end + + def path_to_trace + "#{dir_to_trace}/#{id}.log" + end + + private + + def yaml_variables + if commit.config_processor + commit.config_processor.variables.map do |key, value| + { key: key, value: value, public: true } + end + else + [] + end + end + + def project_variables + project.variables.map do |variable| + { key: variable.key, value: variable.value, public: false } + end + end + + def trigger_variables + if trigger_request && trigger_request.variables + trigger_request.variables.map do |key, value| + { key: key, value: value, public: false } + end + else + [] + end + end + end +end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb new file mode 100644 index 00000000000..fde754a92a1 --- /dev/null +++ b/app/models/ci/commit.rb @@ -0,0 +1,246 @@ +# == Schema Information +# +# Table name: commits +# +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# + +module Ci + class Commit < ActiveRecord::Base + extend Ci::Model + + belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id + has_many :builds, dependent: :destroy, class_name: 'Ci::Build' + has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + + validates_presence_of :sha + validate :valid_commit_sha + + def self.truncate_sha(sha) + sha[0...8] + end + + def to_param + sha + end + + def project + @project ||= gl_project.ensure_gitlab_ci_project + end + + def project_id + project.id + end + + def last_build + builds.order(:id).last + end + + def retry + builds_without_retry.each do |build| + Ci::Build.retry(build) + end + end + + def valid_commit_sha + if self.sha == Ci::Git::BLANK_SHA + self.errors.add(:sha, " cant be 00000000 (branch removal)") + end + end + + def git_author_name + commit_data.author_name if commit_data + end + + def git_author_email + commit_data.author_email if commit_data + end + + def git_commit_message + commit_data.message if commit_data + end + + def short_sha + Ci::Commit.truncate_sha(sha) + end + + def commit_data + @commit ||= gl_project.commit(sha) + rescue + nil + end + + def stage + running_or_pending = builds_without_retry.running_or_pending + running_or_pending.limit(1).pluck(:stage).first + end + + def create_builds(ref, tag, user, trigger_request = nil) + return if skip_ci? && trigger_request.blank? + return unless config_processor + config_processor.stages.any? do |stage| + CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? + end + end + + def create_next_builds(ref, tag, user, trigger_request) + return if skip_ci? && trigger_request.blank? + return unless config_processor + + stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage) + + config_processor.stages.any? do |stage| + unless stages.include?(stage) + CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? + end + end + end + + def refs + builds.group(:ref).pluck(:ref) + end + + def last_ref + builds.latest.first.try(:ref) + end + + def builds_without_retry + builds.latest + end + + def builds_without_retry_for_ref(ref) + builds.for_ref(ref).latest + end + + def retried_builds + @retried_builds ||= (builds.order(id: :desc) - builds_without_retry) + end + + def status + if skip_ci? + return 'skipped' + elsif yaml_errors.present? + return 'failed' + elsif builds.none? + return 'skipped' + elsif success? + 'success' + elsif pending? + 'pending' + elsif running? + 'running' + elsif canceled? + 'canceled' + else + 'failed' + end + end + + def pending? + builds_without_retry.all? do |build| + build.pending? + end + end + + def running? + builds_without_retry.any? do |build| + build.running? || build.pending? + end + end + + def success? + builds_without_retry.all? do |build| + build.success? || build.ignored? + end + end + + def failed? + status == 'failed' + end + + def canceled? + builds_without_retry.all? do |build| + build.canceled? + end + end + + def duration + @duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i + end + + def duration_for_ref(ref) + builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i + end + + def finished_at + @finished_at ||= builds.order('finished_at DESC').first.try(:finished_at) + end + + def coverage + if project.coverage_enabled? + coverage_array = builds_without_retry.map(&:coverage).compact + if coverage_array.size >= 1 + '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) + end + end + end + + def matrix_for_ref?(ref) + builds_without_retry_for_ref(ref).pluck(:id).size > 1 + end + + def config_processor + @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file) + rescue Ci::GitlabCiYamlProcessor::ValidationError => e + save_yaml_error(e.message) + nil + rescue Exception => e + logger.error e.message + "\n" + e.backtrace.join("\n") + save_yaml_error("Undefined yaml error") + nil + end + + def ci_yaml_file + gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data + rescue + nil + end + + def skip_ci? + return false if builds.any? + git_commit_message =~ /(\[ci skip\])/ if git_commit_message + end + + def update_committed! + update!(committed_at: DateTime.now) + end + + def should_create_next_builds?(build) + # don't create other builds if this one is retried + other_builds = builds.similar(build).latest + return false unless other_builds.include?(build) + + other_builds.all? do |build| + build.success? || build.ignored? + end + end + + private + + def save_yaml_error(error) + return if self.yaml_errors? + self.yaml_errors = error + save + end + end +end diff --git a/app/models/ci/event.rb b/app/models/ci/event.rb new file mode 100644 index 00000000000..cac3a7a49c1 --- /dev/null +++ b/app/models/ci/event.rb @@ -0,0 +1,27 @@ +# == Schema Information +# +# Table name: events +# +# id :integer not null, primary key +# project_id :integer +# user_id :integer +# is_admin :integer +# description :text +# created_at :datetime +# updated_at :datetime +# + +module Ci + class Event < ActiveRecord::Base + extend Ci::Model + + belongs_to :project, class_name: 'Ci::Project' + + validates :description, + presence: true, + length: { in: 5..200 } + + scope :admin, ->(){ where(is_admin: true) } + scope :project_wide, ->(){ where(is_admin: false) } + end +end diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb new file mode 100644 index 00000000000..88ba933a434 --- /dev/null +++ b/app/models/ci/project.rb @@ -0,0 +1,215 @@ +# == Schema Information +# +# Table name: projects +# +# id :integer not null, primary key +# name :string(255) not null +# timeout :integer default(3600), not null +# created_at :datetime +# updated_at :datetime +# token :string(255) +# default_ref :string(255) +# path :string(255) +# always_build :boolean default(FALSE), not null +# polling_interval :integer +# public :boolean default(FALSE), not null +# ssh_url_to_repo :string(255) +# gitlab_id :integer +# allow_git_fetch :boolean default(TRUE), not null +# email_recipients :string(255) default(""), not null +# email_add_pusher :boolean default(TRUE), not null +# email_only_broken_builds :boolean default(TRUE), not null +# skip_refs :string(255) +# coverage_regex :string(255) +# shared_runners_enabled :boolean default(FALSE) +# generated_yaml_config :text +# + +module Ci + class Project < ActiveRecord::Base + extend Ci::Model + + include Ci::ProjectStatus + + belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id + + has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' + has_many :runners, through: :runner_projects, class_name: 'Ci::Runner' + has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook' + has_many :events, dependent: :destroy, class_name: 'Ci::Event' + has_many :variables, dependent: :destroy, class_name: 'Ci::Variable' + has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger' + + # Project services + has_many :services, dependent: :destroy, class_name: 'Ci::Service' + has_one :hip_chat_service, dependent: :destroy, class_name: 'Ci::HipChatService' + has_one :slack_service, dependent: :destroy, class_name: 'Ci::SlackService' + has_one :mail_service, dependent: :destroy, class_name: 'Ci::MailService' + + accepts_nested_attributes_for :variables, allow_destroy: true + + delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project + + # + # Validations + # + validates_presence_of :timeout, :token, :default_ref, :gitlab_id + + validates_uniqueness_of :gitlab_id + + validates :polling_interval, + presence: true, + if: ->(project) { project.always_build.present? } + + before_validation :set_default_values + + class << self + include Ci::CurrentSettings + + def base_build_script + <<-eos + git submodule update --init + ls -la + eos + end + + def parse(project) + params = { + gitlab_id: project.id, + default_ref: project.default_branch || 'master', + email_add_pusher: current_application_settings.add_pusher, + email_only_broken_builds: current_application_settings.all_broken_builds, + } + + project = Ci::Project.new(params) + project.build_missing_services + project + end + + def already_added?(project) + where(gitlab_id: project.id).any? + end + + def unassigned(runner) + joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \ + "AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}"). + where("#{Ci::RunnerProject.table_name}.project_id" => nil) + end + + def ordered_by_last_commit_date + last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)" + joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id"). + order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC") + end + end + + def name + name_with_namespace + end + + def path + path_with_namespace + end + + def gitlab_url + web_url + end + + def any_runners? + if runners.active.any? + return true + end + + shared_runners_enabled && Ci::Runner.shared.active.any? + end + + def set_default_values + self.token = SecureRandom.hex(15) if self.token.blank? + self.default_ref ||= 'master' + end + + def tracked_refs + @tracked_refs ||= default_ref.split(",").map { |ref| ref.strip } + end + + def valid_token? token + self.token && self.token == token + end + + def no_running_builds? + # Get running builds not later than 3 days ago to ignore hangs + builds.running.where("updated_at > ?", 3.days.ago).empty? + end + + def email_notification? + email_add_pusher || email_recipients.present? + end + + def web_hooks? + web_hooks.any? + end + + def services? + services.any? + end + + def timeout_in_minutes + timeout / 60 + end + + def timeout_in_minutes=(value) + self.timeout = value.to_i * 60 + end + + def coverage_enabled? + coverage_regex.present? + end + + # Build a clone-able repo url + # using http and basic auth + def repo_url_with_auth + auth = "gitlab-ci-token:#{token}@" + http_url_to_repo.sub(/^https?:\/\//) do |prefix| + prefix + auth + end + end + + def available_services_names + %w(slack mail hip_chat) + end + + def build_missing_services + available_services_names.each do |service_name| + service = services.find { |service| service.to_param == service_name } + + # If service is available but missing in db + # we should create an instance. Ex `create_gitlab_ci_service` + self.send :"create_#{service_name}_service" if service.nil? + end + end + + def execute_services(data) + services.each do |service| + + # Call service hook only if it is active + begin + service.execute(data) if service.active && service.can_execute?(data) + rescue => e + logger.error(e) + end + end + end + + def setup_finished? + commits.any? + end + + def commits + gl_project.ci_commits + end + + def builds + gl_project.ci_builds + end + end +end diff --git a/app/models/ci/project_status.rb b/app/models/ci/project_status.rb new file mode 100644 index 00000000000..b66f1212f23 --- /dev/null +++ b/app/models/ci/project_status.rb @@ -0,0 +1,35 @@ +module Ci + module ProjectStatus + def status + last_commit.status if last_commit + end + + def broken? + last_commit.failed? if last_commit + end + + def success? + last_commit.success? if last_commit + end + + def broken_or_success? + broken? || success? + end + + def last_commit + @last_commit ||= commits.last if commits.any? + end + + def last_commit_date + last_commit.try(:created_at) + end + + def human_status + status + end + + def last_commit_for_ref(ref) + commits.where(ref: ref).last + end + end +end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb new file mode 100644 index 00000000000..6838ccfaaab --- /dev/null +++ b/app/models/ci/runner.rb @@ -0,0 +1,84 @@ +# == Schema Information +# +# Table name: runners +# +# id :integer not null, primary key +# token :string(255) +# created_at :datetime +# updated_at :datetime +# description :string(255) +# contacted_at :datetime +# active :boolean default(TRUE), not null +# is_shared :boolean default(FALSE) +# name :string(255) +# version :string(255) +# revision :string(255) +# platform :string(255) +# architecture :string(255) +# + +module Ci + class Runner < ActiveRecord::Base + extend Ci::Model + + has_many :builds, class_name: 'Ci::Build' + has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' + has_many :projects, through: :runner_projects, class_name: 'Ci::Project' + + has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' + + before_validation :set_default_values + + scope :specific, ->() { where(is_shared: false) } + scope :shared, ->() { where(is_shared: true) } + scope :active, ->() { where(active: true) } + scope :paused, ->() { where(active: false) } + + acts_as_taggable + + def self.search(query) + where('LOWER(ci_runners.token) LIKE :query OR LOWER(ci_runners.description) like :query', + query: "%#{query.try(:downcase)}%") + end + + def gl_projects_ids + projects.select(:gitlab_id) + end + + def set_default_values + self.token = SecureRandom.hex(15) if self.token.blank? + end + + def assign_to(project, current_user = nil) + self.is_shared = false if shared? + self.save + project.runner_projects.create!(runner_id: self.id) + end + + def display_name + return token unless !description.blank? + + description + end + + def shared? + is_shared + end + + def belongs_to_one_project? + runner_projects.count == 1 + end + + def specific? + !shared? + end + + def only_for?(project) + projects == [project] + end + + def short_sha + token[0...10] + end + end +end diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb new file mode 100644 index 00000000000..44453ee4b41 --- /dev/null +++ b/app/models/ci/runner_project.rb @@ -0,0 +1,21 @@ +# == Schema Information +# +# Table name: runner_projects +# +# id :integer not null, primary key +# runner_id :integer not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +module Ci + class RunnerProject < ActiveRecord::Base + extend Ci::Model + + belongs_to :runner, class_name: 'Ci::Runner' + belongs_to :project, class_name: 'Ci::Project' + + validates_uniqueness_of :runner_id, scope: :project_id + end +end diff --git a/app/models/ci/service.rb b/app/models/ci/service.rb new file mode 100644 index 00000000000..ed5e3f940b6 --- /dev/null +++ b/app/models/ci/service.rb @@ -0,0 +1,105 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +# To add new service you should build a class inherited from Service +# and implement a set of methods +module Ci + class Service < ActiveRecord::Base + extend Ci::Model + + serialize :properties, JSON + + default_value_for :active, false + + after_initialize :initialize_properties + + belongs_to :project, class_name: 'Ci::Project' + + validates :project_id, presence: true + + def activated? + active + end + + def category + :common + end + + def initialize_properties + self.properties = {} if properties.nil? + end + + def title + # implement inside child + end + + def description + # implement inside child + end + + def help + # implement inside child + end + + def to_param + # implement inside child + end + + def fields + # implement inside child + [] + end + + def can_test? + project.builds.any? + end + + def can_execute?(build) + true + end + + def execute(build) + # implement inside child + end + + # Provide convenient accessor methods + # for each serialized property. + def self.prop_accessor(*args) + args.each do |arg| + class_eval %{ + def #{arg} + (properties || {})['#{arg}'] + end + + def #{arg}=(value) + self.properties ||= {} + self.properties['#{arg}'] = value + end + } + end + end + + def self.boolean_accessor(*args) + self.prop_accessor(*args) + + args.each do |arg| + class_eval %{ + def #{arg}? + ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg}) + end + } + end + end + end +end diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb new file mode 100644 index 00000000000..fe224b7dc70 --- /dev/null +++ b/app/models/ci/trigger.rb @@ -0,0 +1,39 @@ +# == Schema Information +# +# Table name: triggers +# +# id :integer not null, primary key +# token :string(255) +# project_id :integer not null +# deleted_at :datetime +# created_at :datetime +# updated_at :datetime +# + +module Ci + class Trigger < ActiveRecord::Base + extend Ci::Model + + acts_as_paranoid + + belongs_to :project, class_name: 'Ci::Project' + has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + + validates_presence_of :token + validates_uniqueness_of :token + + before_validation :set_default_values + + def set_default_values + self.token = SecureRandom.hex(15) if self.token.blank? + end + + def last_trigger_request + trigger_requests.last + end + + def short_token + token[0...10] + end + end +end diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb new file mode 100644 index 00000000000..29cd9553394 --- /dev/null +++ b/app/models/ci/trigger_request.rb @@ -0,0 +1,23 @@ +# == Schema Information +# +# Table name: trigger_requests +# +# id :integer not null, primary key +# trigger_id :integer not null +# variables :text +# created_at :datetime +# updated_at :datetime +# commit_id :integer +# + +module Ci + class TriggerRequest < ActiveRecord::Base + extend Ci::Model + + belongs_to :trigger, class_name: 'Ci::Trigger' + belongs_to :commit, class_name: 'Ci::Commit' + has_many :builds, class_name: 'Ci::Build' + + serialize :variables + end +end diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb new file mode 100644 index 00000000000..7a542802fa6 --- /dev/null +++ b/app/models/ci/variable.rb @@ -0,0 +1,25 @@ +# == Schema Information +# +# Table name: variables +# +# id :integer not null, primary key +# project_id :integer not null +# key :string(255) +# value :text +# encrypted_value :text +# encrypted_value_salt :string(255) +# encrypted_value_iv :string(255) +# + +module Ci + class Variable < ActiveRecord::Base + extend Ci::Model + + belongs_to :project, class_name: 'Ci::Project' + + validates_presence_of :key + validates_uniqueness_of :key, scope: :project_id + + attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + end +end diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb new file mode 100644 index 00000000000..8f03b0625da --- /dev/null +++ b/app/models/ci/web_hook.rb @@ -0,0 +1,44 @@ +# == Schema Information +# +# Table name: web_hooks +# +# id :integer not null, primary key +# url :string(255) not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +module Ci + class WebHook < ActiveRecord::Base + extend Ci::Model + + include HTTParty + + belongs_to :project, class_name: 'Ci::Project' + + # HTTParty timeout + default_timeout 10 + + validates :url, presence: true, + format: { with: URI::regexp(%w(http https)), message: "should be a valid url" } + + def execute(data) + parsed_url = URI.parse(url) + if parsed_url.userinfo.blank? + Ci::WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) + else + post_url = url.gsub("#{parsed_url.userinfo}@", "") + auth = { + username: URI.decode(parsed_url.user), + password: URI.decode(parsed_url.password), + } + Ci::WebHook.post(post_url, + body: data.to_json, + headers: { "Content-Type" => "application/json" }, + verify: false, + basic_auth: auth) + end + end + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 40642dc63ba..4db4ffb2e79 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -140,6 +140,12 @@ module Issuable { object_kind: self.class.name.underscore, user: user.hook_attrs, + repository: { + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url + }, object_attributes: hook_attrs } end diff --git a/app/models/event.rb b/app/models/event.rb index 78f16c6304e..47600c57e35 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -27,6 +27,7 @@ class Event < ActiveRecord::Base MERGED = 7 JOINED = 8 # User joined project LEFT = 9 # User left project + DESTROYED = 10 delegate :name, :email, to: :author, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true @@ -48,6 +49,7 @@ class Event < ActiveRecord::Base scope :code_push, -> { where(action: PUSHED) } scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent } scope :with_associations, -> { includes(project: :namespace) } + scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) } class << self def reset_event_cache_for(target) @@ -71,7 +73,7 @@ class Event < ActiveRecord::Base elsif created_project? true else - (issue? || merge_request? || note? || milestone?) && target + ((issue? || merge_request? || note?) && target) || milestone? end end @@ -115,6 +117,10 @@ class Event < ActiveRecord::Base action == LEFT end + def destroyed? + action == DESTROYED + end + def commented? action == COMMENTED end @@ -124,7 +130,7 @@ class Event < ActiveRecord::Base end def created_project? - created? && !target + created? && !target && target_type.nil? end def created_target? @@ -180,6 +186,8 @@ class Event < ActiveRecord::Base 'joined' elsif left? 'left' + elsif destroyed? + 'destroyed' elsif commented? "commented on" elsif created_project? diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 9a8251bdad5..a078accbdbd 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -25,7 +25,7 @@ class WebHook < ActiveRecord::Base default_value_for :note_events, false default_value_for :merge_requests_events, false default_value_for :tag_push_events, false - default_value_for :enable_ssl_verification, false + default_value_for :enable_ssl_verification, true # HTTParty timeout default_timeout Gitlab.config.gitlab.webhook_timeout diff --git a/app/models/issue.rb b/app/models/issue.rb index 2456b7d0dc1..fc7e9abe29e 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -2,19 +2,20 @@ # # Table name: issues # -# id :integer not null, primary key -# title :string(255) -# assignee_id :integer -# author_id :integer -# project_id :integer -# created_at :datetime -# updated_at :datetime -# position :integer default(0) -# branch_name :string(255) -# description :text -# milestone_id :integer -# state :string(255) -# iid :integer +# id :integer not null, primary key +# title :string(255) +# assignee_id :integer +# author_id :integer +# project_id :integer +# created_at :datetime +# updated_at :datetime +# position :integer default(0) +# branch_name :string(255) +# description :text +# milestone_id :integer +# state :string(255) +# iid :integer +# updated_by_id :integer # require 'carrierwave/orm/activerecord' diff --git a/app/models/label.rb b/app/models/label.rb index 230631b5180..4a22bd53400 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -24,7 +24,7 @@ class Label < ActiveRecord::Base validates :color, format: { with: /\A#[0-9A-Fa-f]{6}\Z/ }, allow_blank: false - validates :project, presence: true + validates :project, presence: true, unless: Proc.new { |service| service.template? } # Don't allow '?', '&', and ',' for label titles validates :title, @@ -34,6 +34,8 @@ class Label < ActiveRecord::Base default_scope { order(title: :asc) } + scope :templates, -> { where(template: true) } + alias_attribute :name, :title def self.reference_prefix @@ -78,4 +80,8 @@ class Label < ActiveRecord::Base def open_issues_count issues.opened.count end + + def template? + template + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 467b90861f9..eb468c6cd53 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -19,6 +19,7 @@ # description :text # position :integer default(0) # locked_at :datetime +# updated_by_id :integer # require Rails.root.join("app/models/commit") @@ -432,10 +433,22 @@ class MergeRequest < ActiveRecord::Base target_project.repository.fetch_ref( source_project.repository.path_to_repo, "refs/heads/#{source_branch}", - "refs/merge-requests/#{iid}/head" + ref_path ) end + def ref_path + "refs/merge-requests/#{iid}/head" + end + + def ref_is_fetched? + File.exists?(File.join(project.repository.path_to_repo, ref_path)) + end + + def ensure_ref_fetched + fetch_ref unless ref_is_fetched? + end + def in_locked_state begin lock_mr diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index e317c8eac4d..c9ef8023aea 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -123,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base if new_diffs.any? if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES self.state = :overflow_diff_files_limit - new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES] + new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES) end if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES self.state = :overflow_diff_lines_limit - new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES] + new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES) end end @@ -144,12 +144,10 @@ class MergeRequestDiff < ActiveRecord::Base # Collect array of Git::Diff objects # between target and source branches def unmerged_diffs - diffs = compare_result.diffs - diffs ||= [] - diffs - rescue Gitlab::Git::Diff::TimeoutError => ex + compare_result.diffs || [] + rescue Gitlab::Git::Diff::TimeoutError self.state = :timeout - diffs = [] + [] end def repository diff --git a/app/models/milestone.rb b/app/models/milestone.rb index c6aff6f709f..d979a35084b 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -61,7 +61,7 @@ class Milestone < ActiveRecord::Base false end end - + def open_items_count self.issues.opened.count + self.merge_requests.opened.count end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 161a16ca61c..bc8525df5a5 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -137,7 +137,9 @@ class Namespace < ActiveRecord::Base end def send_update_instructions - projects.each(&:send_move_instructions) + projects.each do |project| + project.send_move_instructions("#{path_was}/#{project.path}") + end end def kind diff --git a/app/models/note.rb b/app/models/note.rb index 36cad8f583d..de3b6df88f7 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -15,6 +15,7 @@ # noteable_id :integer # system :boolean default(FALSE), not null # st_diff :text +# updated_by_id :integer # require 'carrierwave/orm/activerecord' @@ -365,6 +366,6 @@ class Note < ActiveRecord::Base end def editable? - !read_attribute(:system) + !system? end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 1395274173d..171b8df45c2 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -12,7 +12,7 @@ class Notification class << self def notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION] + [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH] end def options_with_labels @@ -26,7 +26,7 @@ class Notification end def project_notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION] + [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH, N_GLOBAL] end end @@ -57,4 +57,21 @@ class Notification def level target.notification_level end + + def to_s + case level + when N_DISABLED + 'Disabled' + when N_PARTICIPATING + 'Participating' + when N_WATCH + 'Watching' + when N_MENTION + 'On mention' + when N_GLOBAL + 'Global' + else + # do nothing + end + end end diff --git a/app/models/project.rb b/app/models/project.rb index 69f9af91c51..bb47b9abb03 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -39,10 +39,13 @@ class Project < ActiveRecord::Base include Gitlab::VisibilityLevel include Referable include Sortable + include AfterCommitQueue extend Gitlab::ConfigHelper extend Enumerize + UNKNOWN_IMPORT_URL = 'http://unknown.git' + default_value_for :archived, false default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :issues_enabled, gitlab_config_features.issues @@ -73,6 +76,7 @@ class Project < ActiveRecord::Base has_many :services has_one :gitlab_ci_service, dependent: :destroy has_one :campfire_service, dependent: :destroy + has_one :drone_ci_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy has_one :irker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy @@ -114,8 +118,11 @@ class Project < ActiveRecord::Base has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy has_many :starrers, through: :users_star_projects, source: :user + has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id + has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" + has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true @@ -141,7 +148,7 @@ class Project < ActiveRecord::Base validates_uniqueness_of :path, scope: :namespace_id validates :import_url, format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' }, - if: :import? + if: :external_import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, @@ -187,7 +194,7 @@ class Project < ActiveRecord::Base state :finished state :failed - after_transition any => :started, do: :add_import_job + after_transition any => :started, do: :schedule_add_import_job after_transition any => :finished, do: :clear_import_data end @@ -231,10 +238,10 @@ class Project < ActiveRecord::Base return nil unless id.include?('/') id = id.split('/') - namespace = Namespace.find_by(path: id.first) + namespace = Namespace.by_path(id.first) return nil unless namespace - where(namespace_id: namespace.id).find_by(path: id.second) + where(namespace_id: namespace.id).where("LOWER(projects.path) = :path", path: id.second.downcase).first end def visibility_levels @@ -271,8 +278,18 @@ class Project < ActiveRecord::Base id && persisted? end + def schedule_add_import_job + run_after_commit(:add_import_job) + end + def add_import_job - RepositoryImportWorker.perform_in(2.seconds, id) + if forked? + unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path) + import_fail + end + else + RepositoryImportWorker.perform_async(id) + end end def clear_import_data @@ -280,6 +297,10 @@ class Project < ActiveRecord::Base end def import? + external_import? || forked? + end + + def external_import? import_url.present? end @@ -316,7 +337,7 @@ class Project < ActiveRecord::Base end def web_url - Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self) + Gitlab::Application.routes.url_helpers.namespace_project_url(self.namespace, self) end def web_url_without_protocol @@ -392,7 +413,7 @@ class Project < ActiveRecord::Base if template.nil? # If no template, we should create an instance. Ex `create_gitlab_ci_service` - service = self.send :"create_#{service_name}_service" + self.send :"create_#{service_name}_service" else Service.create_from_template(self.id, template) end @@ -400,12 +421,21 @@ class Project < ActiveRecord::Base end end + def create_labels + Label.templates.each do |label| + label = label.dup + label.template = nil + label.project_id = self.id + label.save + end + end + def find_service(list, name) list.find { |service| service.to_param == name } end def gitlab_ci? - gitlab_ci_service && gitlab_ci_service.active + gitlab_ci_service && gitlab_ci_service.active && gitlab_ci_project.present? end def ci_services @@ -433,7 +463,7 @@ class Project < ActiveRecord::Base if avatar.present? [gitlab_config.url, avatar.url].join elsif avatar_in_git - Rails.application.routes.url_helpers.namespace_project_avatar_url(namespace, self) + Gitlab::Application.routes.url_helpers.namespace_project_avatar_url(namespace, self) end end @@ -451,8 +481,8 @@ class Project < ActiveRecord::Base end end - def send_move_instructions - NotificationService.new.project_was_moved(self) + def send_move_instructions(old_path_with_namespace) + NotificationService.new.project_was_moved(self, old_path_with_namespace) end def owner @@ -594,7 +624,7 @@ class Project < ActiveRecord::Base # So we basically we mute exceptions in next actions begin gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") - send_move_instructions + send_move_instructions(old_path_with_namespace) reset_events_cache rescue # Returning false does not rollback after_* transaction but gives @@ -613,6 +643,7 @@ class Project < ActiveRecord::Base name: name, ssh_url: ssh_url_to_repo, http_url: http_url_to_repo, + web_url: web_url, namespace: namespace.name, visibility_level: visibility_level } @@ -689,14 +720,8 @@ class Project < ActiveRecord::Base end def create_repository - if forked? - if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path) - true - else - errors.add(:base, 'Failed to fork repository via gitlab-shell') - false - end - else + # Forked import is handled asynchronously + unless forked? if gitlab_shell.add_repository(path_with_namespace) true else @@ -713,8 +738,26 @@ class Project < ActiveRecord::Base def create_wiki ProjectWiki.new(self, self.owner).wiki true - rescue ProjectWiki::CouldNotCreateWikiError => ex + rescue ProjectWiki::CouldNotCreateWikiError errors.add(:base, 'Failed create wiki') false end + + def ci_commit(sha) + ci_commits.find_by(sha: sha) + end + + def ensure_ci_commit(sha) + ci_commit(sha) || ci_commits.create(sha: sha) + end + + def ensure_gitlab_ci_project + gitlab_ci_project || create_gitlab_ci_project + end + + def enable_ci + service = gitlab_ci_service || create_gitlab_ci_service + service.active = true + service.save + end end diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 9e5da6f45d2..40058b53df5 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -69,14 +69,6 @@ class BuildkiteService < CiService "#{project_url}/builds?commit=#{sha}" end - def builds_path - "#{project_url}/builds?branch=#{project.default_branch}" - end - - def status_img_path - "#{buildkite_endpoint('badge')}/#{status_token}.svg" - end - def title 'Buildkite' end diff --git a/app/models/project_services/ci/hip_chat_message.rb b/app/models/project_services/ci/hip_chat_message.rb new file mode 100644 index 00000000000..cbf325cc525 --- /dev/null +++ b/app/models/project_services/ci/hip_chat_message.rb @@ -0,0 +1,73 @@ +module Ci + class HipChatMessage + include Gitlab::Application.routes.url_helpers + + attr_reader :build + + def initialize(build) + @build = build + end + + def to_s + lines = Array.new + lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ") + lines.push("<a href=\"#{ci_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}\">Commit ##{commit.id}</a></br>") + lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>") + lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).") + lines.join('') + end + + def status_color(build_or_commit=nil) + build_or_commit ||= commit_status + case build_or_commit + when :success + 'green' + when :failed, :canceled + 'red' + else # :pending, :running or unknown + 'yellow' + end + end + + def notify? + [:failed, :canceled].include?(commit_status) + end + + + private + + def commit + build.commit + end + + def project + commit.project + end + + def build_status + build.status.to_sym + end + + def commit_status + commit.status.to_sym + end + + def humanized_status(build_or_commit=nil) + build_or_commit ||= commit_status + case build_or_commit + when :pending + "Pending" + when :running + "Running" + when :failed + "Failed" + when :success + "Successful" + when :canceled + "Canceled" + else + "Unknown" + end + end + end +end diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb new file mode 100644 index 00000000000..0e6e97394bc --- /dev/null +++ b/app/models/project_services/ci/hip_chat_service.rb @@ -0,0 +1,93 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +module Ci + class HipChatService < Ci::Service + prop_accessor :hipchat_token, :hipchat_room, :hipchat_server + boolean_accessor :notify_only_broken_builds + validates :hipchat_token, presence: true, if: :activated? + validates :hipchat_room, presence: true, if: :activated? + default_value_for :notify_only_broken_builds, true + + def title + "HipChat" + end + + def description + "Private group chat, video chat, instant messaging for teams" + end + + def help + end + + def to_param + 'hip_chat' + end + + def fields + [ + { type: 'text', name: 'hipchat_token', label: 'Token', placeholder: '' }, + { type: 'text', name: 'hipchat_room', label: 'Room', placeholder: '' }, + { type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' }, + { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' } + ] + end + + def can_execute?(build) + return if build.allow_failure? + + commit = build.commit + return unless commit + return unless commit.builds_without_retry.include? build + + case commit.status.to_sym + when :failed + true + when :success + true unless notify_only_broken_builds? + else + false + end + end + + def execute(build) + msg = Ci::HipChatMessage.new(build) + opts = default_options.merge( + token: hipchat_token, + room: hipchat_room, + server: server_url, + color: msg.status_color, + notify: msg.notify? + ) + Ci::HipChatNotifierWorker.perform_async(msg.to_s, opts) + end + + private + + def default_options + { + service_name: 'GitLab CI', + message_format: 'html' + } + end + + def server_url + if hipchat_server.blank? + 'https://api.hipchat.com' + else + hipchat_server + end + end + end +end diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb new file mode 100644 index 00000000000..11a2743f969 --- /dev/null +++ b/app/models/project_services/ci/mail_service.rb @@ -0,0 +1,84 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +module Ci + class MailService < Ci::Service + delegate :email_recipients, :email_recipients=, + :email_add_pusher, :email_add_pusher=, + :email_only_broken_builds, :email_only_broken_builds=, to: :project, prefix: false + + before_save :update_project + + default_value_for :active, true + + def title + 'Mail' + end + + def description + 'Email notification' + end + + def to_param + 'mail' + end + + def fields + [ + { type: 'text', name: 'email_recipients', label: 'Recipients', help: 'Whitespace-separated list of recipient addresses' }, + { type: 'checkbox', name: 'email_add_pusher', label: 'Add pusher to recipients list' }, + { type: 'checkbox', name: 'email_only_broken_builds', label: 'Notify only broken builds' } + ] + end + + def can_execute?(build) + return if build.allow_failure? + + # it doesn't make sense to send emails for retried builds + commit = build.commit + return unless commit + return unless commit.builds_without_retry.include?(build) + + case build.status.to_sym + when :failed + true + when :success + true unless email_only_broken_builds + else + false + end + end + + def execute(build) + build.project_recipients.each do |recipient| + case build.status.to_sym + when :success + mailer.build_success_email(build.id, recipient) + when :failed + mailer.build_fail_email(build.id, recipient) + end + end + end + + private + + def update_project + project.save! + end + + def mailer + Ci::Notify.delay + end + end +end diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb new file mode 100644 index 00000000000..5ac8907ecd0 --- /dev/null +++ b/app/models/project_services/ci/slack_message.rb @@ -0,0 +1,92 @@ +require 'slack-notifier' + +module Ci + class SlackMessage + include Gitlab::Application.routes.url_helpers + + def initialize(commit) + @commit = commit + end + + def pretext + '' + end + + def color + attachment_color + end + + def fallback + format(attachment_message) + end + + def attachments + fields = [] + + commit.builds_without_retry.each do |build| + next if build.allow_failure? + next unless build.failed? + fields << { + title: build.name, + value: "Build <#{namespace_project_build_url(build.gl_project.namespace, build.gl_project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)." + } + end + + [{ + text: attachment_message, + color: attachment_color, + fields: fields + }] + end + + private + + attr_reader :commit + + def attachment_message + out = "<#{ci_project_url(project)}|#{project_name}>: " + out << "Commit <#{ci_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> " + out << "(<#{commit_sha_link}|#{commit.short_sha}>) " + out << "of <#{commit_ref_link}|#{commit.ref}> " + out << "by #{commit.git_author_name} " if commit.git_author_name + out << "#{commit_status} in " + out << "#{commit.duration} second(s)" + end + + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def project + commit.project + end + + def project_name + project.name + end + + def commit_sha_link + "#{project.gitlab_url}/commit/#{commit.sha}" + end + + def commit_ref_link + "#{project.gitlab_url}/commits/#{commit.ref}" + end + + def attachment_color + if commit.success? + 'good' + else + 'danger' + end + end + + def commit_status + if commit.success? + 'succeeded' + else + 'failed' + end + end + end +end diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb new file mode 100644 index 00000000000..76db573dc17 --- /dev/null +++ b/app/models/project_services/ci/slack_service.rb @@ -0,0 +1,81 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +module Ci + class SlackService < Ci::Service + prop_accessor :webhook + boolean_accessor :notify_only_broken_builds + validates :webhook, presence: true, if: :activated? + + default_value_for :notify_only_broken_builds, true + + def title + 'Slack' + end + + def description + 'A team communication tool for the 21st century' + end + + def to_param + 'slack' + end + + def help + 'Visit https://www.slack.com/services/new/incoming-webhook. Then copy link and save project!' unless webhook.present? + end + + def fields + [ + { type: 'text', name: 'webhook', label: 'Webhook URL', placeholder: '' }, + { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' } + ] + end + + def can_execute?(build) + return if build.allow_failure? + + commit = build.commit + return unless commit + return unless commit.builds_without_retry.include?(build) + + case commit.status.to_sym + when :failed + true + when :success + true unless notify_only_broken_builds? + else + false + end + end + + def execute(build) + message = Ci::SlackMessage.new(build.commit) + options = default_options.merge( + color: message.color, + fallback: message.fallback, + attachments: message.attachments + ) + Ci::SlackNotifierWorker.perform_async(webhook, message.pretext, options) + end + + private + + def default_options + { + username: 'GitLab CI' + } + end + end +end diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 803402c83ee..88186113c68 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -25,12 +25,24 @@ class CiService < Service def category :ci end - + + def valid_token?(token) + self.respond_to?(:token) && self.token.present? && self.token == token + end + def supported_events %w(push) end - # Return complete url to build page + def merge_request_page(iid, sha, ref) + commit_page(sha, ref) + end + + def commit_page(sha, ref) + build_page(sha, ref) + end + + # Return complete url to merge_request page # # Ex. # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c @@ -45,10 +57,27 @@ class CiService < Service # # # Ex. - # @service.commit_status('13be4ac') + # @service.merge_request_status(9, '13be4ac', 'dev') + # # => 'success' + # + # @service.merge_request_status(10, '2abe4ac', 'dev) + # # => 'running' + # + # + def merge_request_status(iid, sha, ref) + commit_status(sha, ref) + end + + # Return string with build status or :error symbol + # + # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' + # + # + # Ex. + # @service.commit_status('13be4ac', 'master') # # => 'success' # - # @service.commit_status('2abe4ac') + # @service.commit_status('2abe4ac', 'dev') # # => 'running' # # diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb new file mode 100644 index 00000000000..c73c4b058a1 --- /dev/null +++ b/app/models/project_services/drone_ci_service.rb @@ -0,0 +1,176 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +class DroneCiService < CiService + + prop_accessor :drone_url, :token, :enable_ssl_verification + validates :drone_url, + presence: true, + format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated? + validates :token, + presence: true, + if: :activated? + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join + hook.enable_ssl_verification = enable_ssl_verification + hook.save + end + + def execute(data) + case data[:object_kind] + when 'push' + service_hook.execute(data) if push_valid?(data) + when 'merge_request' + service_hook.execute(data) if merge_request_valid?(data) + when 'tag_push' + service_hook.execute(data) if tag_push_valid?(data) + end + end + + def allow_target_ci? + true + end + + def supported_events + %w(push merge_request tag_push) + end + + def merge_request_status_path(iid, sha = nil, ref = nil) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}", + "?access_token=#{token}"] + + URI.join(*url).to_s + end + + def commit_status_path(sha, ref) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}", + "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"] + + URI.join(*url).to_s + end + + def merge_request_status(iid, sha, ref) + response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification) + + if response.code == 200 and response['status'] + case response['status'] + when 'killed' + :canceled + when 'failure', 'error' + # Because drone return error if some test env failed + :failed + else + response["status"] + end + else + :error + end + rescue Errno::ECONNREFUSED + :error + end + + def commit_status(sha, ref) + response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification) + + if response.code == 200 and response['status'] + case response['status'] + when 'killed' + :canceled + when 'failure', 'error' + # Because drone return error if some test env failed + :failed + else + response["status"] + end + else + :error + end + rescue Errno::ECONNREFUSED + :error + end + + def merge_request_page(iid, sha, ref) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"] + + URI.join(*url).to_s + end + + def commit_page(sha, ref) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}", + "?branch=#{URI::encode(ref.to_s)}"] + + URI.join(*url).to_s + end + + def commit_coverage(sha, ref) + nil + end + + def build_page(sha, ref) + commit_page(sha, ref) + end + + def title + 'Drone CI' + end + + def description + 'Drone is a Continuous Integration platform built on Docker, written in Go' + end + + def to_param + 'drone_ci' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: 'Drone CI project specific token' }, + { type: 'text', name: 'drone_url', placeholder: 'http://drone.example.com' }, + { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } + ] + end + + private + + def tag_push_valid?(data) + data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after]) + end + + def push_valid?(data) + opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id, + source_branch: Gitlab::Git.ref_name(data[:ref])) + + opened_merge_requests.empty? && data[:total_commits_count] > 0 && + !Gitlab::Git.blank_ref?(data[:after]) + end + + def merge_request_valid?(data) + ['opened', 'reopened'].include?(data[:object_attributes][:state]) && + data[:object_attributes][:merge_status] == 'unchecked' + end +end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index acbbc9935b6..4dcd16ede3a 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -19,25 +19,20 @@ # class GitlabCiService < CiService - API_PREFIX = "api/v1" - - prop_accessor :project_url, :token, :enable_ssl_verification - validates :project_url, - presence: true, - format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated? - validates :token, - presence: true, - format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated? + include Gitlab::Application.routes.url_helpers after_save :compose_service_hook, if: :activated? + after_save :ensure_gitlab_ci_project, if: :activated? def compose_service_hook hook = service_hook || build_service_hook - hook.url = [project_url, "/build", "?token=#{token}"].join("") - hook.enable_ssl_verification = enable_ssl_verification hook.save end + def ensure_gitlab_ci_project + project.ensure_gitlab_ci_project + end + def supported_events %w(push tag_push) end @@ -45,81 +40,39 @@ class GitlabCiService < CiService def execute(data) return unless supported_events.include?(data[:object_kind]) - sha = data[:checkout_sha] - - if sha.present? - file = ci_yaml_file(sha) - - if file && file.data - data.merge!(ci_yaml_file: file.data) - end + ci_project = project.gitlab_ci_project + if ci_project + current_user = User.find_by(id: data[:user_id]) + Ci::CreateCommitService.new.execute(ci_project, current_user, data) end - - service_hook.execute(data) end - def commit_status_path(sha, ref) - URI::encode(project_url + "/refs/#{ref}/commits/#{sha}/status.json?token=#{token}") + def token + if project.gitlab_ci_project.present? + project.gitlab_ci_project.token + end end - def get_ci_build(sha, ref) - @ci_builds ||= {} - @ci_builds[sha] ||= HTTParty.get(commit_status_path(sha, ref), verify: false) + def get_ci_commit(sha, ref) + Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha) end def commit_status(sha, ref) - response = get_ci_build(sha, ref) - - if response.code == 200 and response["status"] - response["status"] - else - :error - end - rescue Errno::ECONNREFUSED + get_ci_commit(sha, ref).status + rescue ActiveRecord::RecordNotFound :error end - def fork_registration(new_project, private_token) - params = { - id: new_project.id, - name_with_namespace: new_project.name_with_namespace, - path_with_namespace: new_project.path_with_namespace, - web_url: new_project.web_url, - default_branch: new_project.default_branch, - ssh_url_to_repo: new_project.ssh_url_to_repo - } - - HTTParty.post( - fork_registration_path, - body: { - project_id: project.id, - project_token: token, - private_token: private_token, - data: params }, - verify: false - ) - end - def commit_coverage(sha, ref) - response = get_ci_build(sha, ref) - - if response.code == 200 and response["coverage"] - response["coverage"] - end - rescue Errno::ECONNREFUSED - nil + get_ci_commit(sha, ref).coverage + rescue ActiveRecord::RecordNotFound + :error end def build_page(sha, ref) - URI::encode(project_url + "/refs/#{ref}/commits/#{sha}") - end - - def builds_path - project_url + "?ref=" + project.default_branch - end - - def status_img_path - project_url + "/status.png?ref=" + project.default_branch + if project.gitlab_ci_project.present? + ci_namespace_project_commit_url(project.namespace, project, sha) + end end def title @@ -135,24 +88,6 @@ class GitlabCiService < CiService end def fields - [ - { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' }, - { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' }, - { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } - ] - end - - private - - def ci_yaml_file(sha) - repository.blob_at(sha, '.gitlab-ci.yml') - end - - def fork_registration_path - project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks") - end - - def repository - project.repository + [] end end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 0ebc0a3ba1a..9558292fea3 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -19,7 +19,7 @@ # class GitlabIssueTrackerService < IssueTrackerService - include Rails.application.routes.url_helpers + include Gitlab::Application.routes.url_helpers prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 7a15a861abc..af2840a57f0 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -85,17 +85,16 @@ class HipchatService < Service def create_message(data) object_kind = data[:object_kind] - message = \ - case object_kind - when "push", "tag_push" - create_push_message(data) - when "issue" - create_issue_message(data) unless is_update?(data) - when "merge_request" - create_merge_request_message(data) unless is_update?(data) - when "note" - create_note_message(data) - end + case object_kind + when "push", "tag_push" + create_push_message(data) + when "issue" + create_issue_message(data) unless is_update?(data) + when "merge_request" + create_merge_request_message(data) unless is_update?(data) + when "note" + create_note_message(data) + end end def create_push_message(push) @@ -167,8 +166,6 @@ class HipchatService < Service obj_attr = data[:object_attributes] obj_attr = HashWithIndifferentAccess.new(obj_attr) merge_request_id = obj_attr[:iid] - source_branch = obj_attr[:source_branch] - target_branch = obj_attr[:target_branch] state = obj_attr[:state] description = obj_attr[:description] title = obj_attr[:title] @@ -194,8 +191,6 @@ class HipchatService < Service data = HashWithIndifferentAccess.new(data) user_name = data[:user][:name] - repo_attr = HashWithIndifferentAccess.new(data[:repository]) - obj_attr = HashWithIndifferentAccess.new(data[:object_attributes]) note = obj_attr[:note] note_url = obj_attr[:url] diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index bfa8fc7b860..35e30b1cb0b 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -19,7 +19,7 @@ # class JiraService < IssueTrackerService - include Rails.application.routes.url_helpers + include Gitlab::Application.routes.url_helpers prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 36d9874edd3..7cd5e892507 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -34,6 +34,12 @@ class SlackService < Service 'slack' end + def help + 'This service sends notifications to your Slack channel.<br/> + To setup this Service you need to create a new <b>"Incoming webhook"</b> in your Slack integration panel, + and enter the Webhook URL below.' + end + def fields [ { type: 'text', name: 'webhook', diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 56e49af2324..f602a965364 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -135,6 +135,10 @@ class ProjectTeam !!find_member(user_id) end + def human_max_access(user_id) + Gitlab::Access.options.key max_member_access(user_id) + end + def max_member_access(user_id) access = [] access << project.project_members.find_by(user_id: user_id).try(:access_field) diff --git a/app/models/repository.rb b/app/models/repository.rb index 79b48ebfedf..2c5ab62d22c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -549,7 +549,7 @@ class Repository # Run GitLab post receive hook post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo) - status = post_receive_hook.trigger(gl_id, oldrev, newrev, ref) + post_receive_hook.trigger(gl_id, oldrev, newrev, ref) else # Remove tmp ref and return error to user rugged.references.delete(tmp_ref) diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 460ca40be3f..3eed5c16e45 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -1,3 +1,17 @@ +# == Schema Information +# +# Table name: sent_notifications +# +# id :integer not null, primary key +# project_id :integer +# noteable_id :integer +# noteable_type :string(255) +# recipient_id :integer +# commit_id :string(255) +# line_code :string(255) +# reply_key :string(255) not null +# + class SentNotification < ActiveRecord::Base belongs_to :project belongs_to :noteable, polymorphic: true @@ -8,13 +22,20 @@ class SentNotification < ActiveRecord::Base validates :noteable_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? + validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true class << self + def reply_key + return nil unless Gitlab::IncomingEmail.enabled? + + SecureRandom.hex(16) + end + def for(reply_key) find_by(reply_key: reply_key) end - def record(noteable, recipient_id, reply_key) + def record(noteable, recipient_id, reply_key, params = {}) return unless reply_key noteable_id = nil @@ -25,7 +46,7 @@ class SentNotification < ActiveRecord::Base noteable_id = noteable.id end - create( + params.reverse_merge!( project: noteable.project, noteable_type: noteable.class.name, noteable_id: noteable_id, @@ -33,6 +54,14 @@ class SentNotification < ActiveRecord::Base recipient_id: recipient_id, reply_key: reply_key ) + + create(params) + end + + def record_note(note, recipient_id, reply_key, params = {}) + params[:line_code] = note.line_code + + record(note.noteable, recipient_id, reply_key, params) end end diff --git a/app/models/service.rb b/app/models/service.rb index dcef2866c3b..60fcc9d2857 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -135,6 +135,7 @@ class Service < ActiveRecord::Base buildkite campfire custom_issue_tracker + drone_ci emails_on_push external_wiki flowdock diff --git a/app/models/user.rb b/app/models/user.rb index 48e0e6ed48b..889d2d3b867 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,62 +2,59 @@ # # Table name: users # -# id :integer not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(255) default(""), not null -# reset_password_token :string(255) -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) -# created_at :datetime -# updated_at :datetime -# name :string(255) -# admin :boolean default(FALSE), not null -# projects_limit :integer default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) -# theme_id :integer default(1), not null -# bio :string(255) -# failed_attempts :integer default(0) -# locked_at :datetime -# username :string(255) -# can_create_group :boolean default(TRUE), not null -# can_create_team :boolean default(TRUE), not null -# state :string(255) -# color_scheme_id :integer default(1), not null -# notification_level :integer default(1), not null -# password_expires_at :datetime -# created_by_id :integer -# last_credential_check_at :datetime -# avatar :string(255) -# confirmation_token :string(255) -# confirmed_at :datetime -# confirmation_sent_at :datetime -# unconfirmed_email :string(255) -# hide_no_ssh_key :boolean default(FALSE) -# website_url :string(255) default(""), not null -# github_access_token :string(255) -# gitlab_access_token :string(255) -# notification_email :string(255) -# hide_no_password :boolean default(FALSE) -# password_automatically_set :boolean default(FALSE) -# bitbucket_access_token :string(255) -# bitbucket_access_token_secret :string(255) -# location :string(255) -# encrypted_otp_secret :string(255) -# encrypted_otp_secret_iv :string(255) -# encrypted_otp_secret_salt :string(255) -# otp_required_for_login :boolean default(FALSE), not null -# otp_backup_codes :text -# public_email :string(255) default(""), not null -# dashboard :integer default(0) -# project_view :integer default(0) +# id :integer not null, primary key +# email :string(255) default(""), not null +# encrypted_password :string(255) default(""), not null +# reset_password_token :string(255) +# reset_password_sent_at :datetime +# remember_created_at :datetime +# sign_in_count :integer default(0) +# current_sign_in_at :datetime +# last_sign_in_at :datetime +# current_sign_in_ip :string(255) +# last_sign_in_ip :string(255) +# created_at :datetime +# updated_at :datetime +# name :string(255) +# admin :boolean default(FALSE), not null +# projects_limit :integer default(10) +# skype :string(255) default(""), not null +# linkedin :string(255) default(""), not null +# twitter :string(255) default(""), not null +# authentication_token :string(255) +# theme_id :integer default(1), not null +# bio :string(255) +# failed_attempts :integer default(0) +# locked_at :datetime +# username :string(255) +# can_create_group :boolean default(TRUE), not null +# can_create_team :boolean default(TRUE), not null +# state :string(255) +# color_scheme_id :integer default(1), not null +# notification_level :integer default(1), not null +# password_expires_at :datetime +# created_by_id :integer +# last_credential_check_at :datetime +# avatar :string(255) +# confirmation_token :string(255) +# confirmed_at :datetime +# confirmation_sent_at :datetime +# unconfirmed_email :string(255) +# hide_no_ssh_key :boolean default(FALSE) +# website_url :string(255) default(""), not null +# notification_email :string(255) +# hide_no_password :boolean default(FALSE) +# password_automatically_set :boolean default(FALSE) +# location :string(255) +# encrypted_otp_secret :string(255) +# encrypted_otp_secret_iv :string(255) +# encrypted_otp_secret_salt :string(255) +# otp_required_for_login :boolean default(FALSE), not null +# otp_backup_codes :text +# public_email :string(255) default(""), not null +# dashboard :integer default(0) +# project_view :integer default(0) +# layout :integer default(0) # require 'carrierwave/orm/activerecord' @@ -133,6 +130,8 @@ class User < ActiveRecord::Base has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy + has_one :abuse_report, dependent: :destroy + has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build' # @@ -174,9 +173,12 @@ class User < ActiveRecord::Base after_create :post_create_hook after_destroy :post_destroy_hook + # User's Layout preference + enum layout: [:fixed, :fluid] + # User's Dashboard preference # Note: When adding an option, it MUST go on the end of the array. - enum dashboard: [:projects, :stars] + enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity] # User's Project preference # Note: When adding an option, it MUST go on the end of the array. @@ -331,6 +333,10 @@ class User < ActiveRecord::Base @reset_token end + def recently_sent_password_reset? + reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago + end + def disable_two_factor! update_attributes( two_factor_enabled: false, @@ -704,13 +710,7 @@ class User < ActiveRecord::Base end def manageable_namespaces - @manageable_namespaces ||= - begin - namespaces = [] - namespaces << namespace - namespaces += owned_groups - namespaces += masters_groups - end + @manageable_namespaces ||= [namespace] + owned_groups + masters_groups end def namespaces @@ -757,4 +757,13 @@ class User < ActiveRecord::Base def can_be_removed? !solo_owned_groups.present? end + + def ci_authorized_projects + @ci_authorized_projects ||= Ci::Project.where(gitlab_id: authorized_projects) + end + + def ci_authorized_runners + Ci::Runner.specific.includes(:runner_projects). + where(ci_runner_projects: { project_id: ci_authorized_projects } ) + end end diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb new file mode 100644 index 00000000000..c420f3268fd --- /dev/null +++ b/app/services/ci/create_builds_service.rb @@ -0,0 +1,27 @@ +module Ci + class CreateBuildsService + def execute(commit, stage, ref, tag, user, trigger_request) + builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag) + + builds_attrs.map do |build_attrs| + # don't create the same build twice + unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) + build_attrs.slice!(:name, + :commands, + :tag_list, + :options, + :allow_failure, + :stage, + :stage_idx) + + build_attrs.merge!(ref: ref, + tag: tag, + trigger_request: trigger_request, + user: user) + + commit.builds.create!(build_attrs) + end + end + end + end +end diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb new file mode 100644 index 00000000000..fc1ae5774d5 --- /dev/null +++ b/app/services/ci/create_commit_service.rb @@ -0,0 +1,26 @@ +module Ci + class CreateCommitService + def execute(project, user, params) + sha = params[:checkout_sha] || params[:after] + origin_ref = params[:ref] + + unless origin_ref && sha.present? + return false + end + + ref = origin_ref.gsub(/\Arefs\/(tags|heads)\//, '') + + # Skip branch removal + if sha == Ci::Git::BLANK_SHA + return false + end + + tag = origin_ref.start_with?('refs/tags/') + commit = project.gl_project.ensure_ci_commit(sha) + commit.update_committed! + commit.create_builds(ref, tag, user) + + commit + end + end +end diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb new file mode 100644 index 00000000000..4b86cb0a1f5 --- /dev/null +++ b/app/services/ci/create_trigger_request_service.rb @@ -0,0 +1,22 @@ +module Ci + class CreateTriggerRequestService + def execute(project, trigger, ref, variables = nil) + commit = project.gl_project.commit(ref) + return unless commit + + # check if ref is tag + tag = project.gl_project.repository.find_tag(ref).present? + + ci_commit = project.gl_project.ensure_ci_commit(commit.sha) + + trigger_request = trigger.trigger_requests.create!( + variables: variables, + commit: ci_commit, + ) + + if ci_commit.create_builds(ref, tag, nil, trigger_request) + trigger_request + end + end + end +end diff --git a/app/services/ci/event_service.rb b/app/services/ci/event_service.rb new file mode 100644 index 00000000000..3f4e02dd26c --- /dev/null +++ b/app/services/ci/event_service.rb @@ -0,0 +1,31 @@ +module Ci + class EventService + def remove_project(user, project) + create( + description: "Project \"#{project.name}\" has been removed by #{user.username}", + user_id: user.id, + is_admin: true + ) + end + + def create_project(user, project) + create( + description: "Project \"#{project.name}\" has been created by #{user.username}", + user_id: user.id, + is_admin: true + ) + end + + def change_project_settings(user, project) + create( + project_id: project.id, + user_id: user.id, + description: "User \"#{user.username}\" updated projects settings" + ) + end + + def create(*args) + Ci::Event.create!(*args) + end + end +end diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb new file mode 100644 index 00000000000..b95835ba093 --- /dev/null +++ b/app/services/ci/image_for_build_service.rb @@ -0,0 +1,31 @@ +module Ci + class ImageForBuildService + def execute(project, params) + image_name = + if params[:sha] + commit = project.commits.find_by(sha: params[:sha]) + image_for_commit(commit) + elsif params[:ref] + commit = project.last_commit_for_ref(params[:ref]) + image_for_commit(commit) + else + 'build-unknown.svg' + end + + image_path = Rails.root.join('public/ci', image_name) + + OpenStruct.new( + path: image_path, + name: image_name + ) + end + + private + + def image_for_commit(commit) + return 'build-unknown.svg' unless commit + + 'build-' + commit.status + ".svg" + end + end +end diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb new file mode 100644 index 00000000000..71b61bbe389 --- /dev/null +++ b/app/services/ci/register_build_service.rb @@ -0,0 +1,40 @@ +module Ci + # This class responsible for assigning + # proper pending build to runner on runner API request + class RegisterBuildService + def execute(current_runner) + builds = Ci::Build.pending.unstarted + + builds = + if current_runner.shared? + # don't run projects which have not enables shared runners + builds.joins(commit: { gl_project: :gitlab_ci_project }).where(ci_projects: { shared_runners_enabled: true }) + else + # do run projects which are only assigned to this runner + builds.joins(:commit).where(ci_commits: { gl_project_id: current_runner.gl_projects_ids }) + end + + builds = builds.order('created_at ASC') + + build = builds.find do |build| + (build.tag_list - current_runner.tag_list).empty? + end + + + if build + # In case when 2 runners try to assign the same build, second runner will be declined + # with StateMachine::InvalidTransition in run! method. + build.with_lock do + build.runner_id = current_runner.id + build.save! + build.run! + end + end + + build + + rescue StateMachine::InvalidTransition + nil + end + end +end diff --git a/app/services/ci/test_hook_service.rb b/app/services/ci/test_hook_service.rb new file mode 100644 index 00000000000..3a17596aaeb --- /dev/null +++ b/app/services/ci/test_hook_service.rb @@ -0,0 +1,7 @@ +module Ci + class TestHookService + def execute(hook, current_user) + Ci::WebHookService.new.build_end(hook.project.commits.last.last_build) + end + end +end diff --git a/app/services/ci/web_hook_service.rb b/app/services/ci/web_hook_service.rb new file mode 100644 index 00000000000..92e6df442b4 --- /dev/null +++ b/app/services/ci/web_hook_service.rb @@ -0,0 +1,35 @@ +module Ci + class WebHookService + def build_end(build) + execute_hooks(build.project, build_data(build)) + end + + def execute_hooks(project, data) + project.web_hooks.each do |web_hook| + async_execute_hook(web_hook, data) + end + end + + def async_execute_hook(hook, data) + Sidekiq::Client.enqueue(Ci::WebHookWorker, hook.id, data) + end + + def build_data(build) + project = build.project + data = {} + data.merge!({ + build_id: build.id, + build_name: build.name, + build_status: build.status, + build_started_at: build.started_at, + build_finished_at: build.finished_at, + project_id: project.id, + project_name: project.name, + gitlab_url: project.gitlab_url, + ref: build.ref, + before_sha: build.before_sha, + sha: build.sha, + }) + end + end +end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 70f642baaaa..bfe6a3dc4be 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -4,7 +4,10 @@ require 'securerandom' # and return Gitlab::CompareResult object that responds to commits and diffs class CompareService def execute(source_project, source_branch, target_project, target_branch) - source_sha = source_project.commit(source_branch).sha + source_commit = source_project.commit(source_branch) + return unless source_commit + + source_sha = source_commit.sha # If compare with other project we need to fetch ref first unless target_project == source_project diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index 103d6b0a08b..07fc77001a5 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -46,6 +46,10 @@ class EventCreateService create_record_event(milestone, current_user, Event::REOPENED) end + def destroy_milestone(milestone, current_user) + create_record_event(milestone, current_user, Event::DESTROYED) + end + def leave_note(note, current_user) create_record_event(note, current_user, Event::COMMENTED) end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 7aecee217d8..008833eed80 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -21,7 +21,7 @@ module Files create_target_branch end - if sha = commit + if commit success else error("Something went wrong. Your changes were not committed") diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 91d715b2d63..ffbb5993279 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -19,10 +19,12 @@ module Files end unless project.empty_repo? + @file_path.slice!(0) if @file_path.start_with?('/') + blob = repository.blob_at_branch(@current_branch, @file_path) if blob - raise_error("Your changes could not be committed, because file with such name exists") + raise_error("Your changes could not be committed because a file with the same name already exists") end end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 0a73244774a..f9a8265d2d4 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -55,6 +55,12 @@ class GitPushService @push_data = build_push_data(oldrev, newrev, ref) + # If CI was disabled but .gitlab-ci.yml file was pushed + # we enable CI automatically + if !project.gitlab_ci? && gitlab_ci_yaml?(newrev) + project.enable_ci + end + EventCreateService.new.push(project, user, @push_data) project.execute_hooks(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks) @@ -143,4 +149,10 @@ class GitPushService def commit_user(commit) commit.author || user end + + def gitlab_ci_yaml?(sha) + @project.repository.blob_at(sha, '.gitlab-ci.yml') + rescue Rugged::ReferenceError + nil + end end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 98a67c0bc99..fcc0f2a6a8d 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -38,6 +38,10 @@ module MergeRequests } repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options) + rescue Exception => e + merge_request.update(merge_error: "Something went wrong during merge") + Rails.logger.error(e.message) + return false end def after_merge diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb new file mode 100644 index 00000000000..2414966505b --- /dev/null +++ b/app/services/milestones/destroy_service.rb @@ -0,0 +1,27 @@ +module Milestones + class DestroyService < Milestones::BaseService + def execute(milestone) + + Milestone.transaction do + update_params = { milestone: nil } + + milestone.issues.each do |issue| + Issues::UpdateService.new(project, current_user, update_params).execute(issue) + end + + milestone.merge_requests.each do |merge_request| + MergeRequests::UpdateService.new(project, current_user, update_params).execute(merge_request) + end + + event_service.destroy_milestone(milestone, current_user) + + Event.for_milestone_id(milestone.id).each do |event| + event.target_id = nil + event.save + end + + milestone.destroy + end + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index e294b23bc23..a6b22348650 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -183,12 +183,12 @@ class NotificationService mailer.group_access_granted_email(group_member.id) end - def project_was_moved(project) + def project_was_moved(project, old_path_with_namespace) recipients = project.team.members recipients = reject_muted_users(recipients, project) recipients.each do |recipient| - mailer.project_was_moved_email(project.id, recipient.id) + mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace) end end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index b35aed005da..faf1ee008e7 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -55,16 +55,14 @@ module Projects @project.save if @project.persisted? && !@project.import? - unless @project.create_repository - raise 'Failed to create repository' - end + raise 'Failed to create repository' unless @project.create_repository end end after_create_actions if @project.persisted? @project - rescue => ex + rescue @project.errors.add(:base, "Can't save project. Please try again later") @project end @@ -87,6 +85,8 @@ module Projects @project.build_missing_services + @project.create_labels + event_service.create_project(@project, current_user) system_hook_service.execute_hooks_for(@project, :create) diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb new file mode 100644 index 00000000000..99f22293d0d --- /dev/null +++ b/app/services/projects/download_service.rb @@ -0,0 +1,43 @@ +module Projects + class DownloadService < BaseService + + WHITELIST = [ + /^[^.]+\.fogbugz.com$/ + ] + + def initialize(project, url) + @project, @url = project, url + end + + def execute + return nil unless valid_url?(@url) + + uploader = FileUploader.new(@project) + uploader.download!(@url) + uploader.store! + + filename = uploader.image? ? uploader.file.basename : uploader.file.filename + + { + 'alt' => filename, + 'url' => uploader.secure_url, + 'is_image' => uploader.image? + } + end + + private + + def valid_url?(url) + url && http?(url) && valid_domain?(url) + end + + def http?(url) + url =~ /\A#{URI::regexp(['http', 'https'])}\z/ + end + + def valid_domain?(url) + host = URI.parse(url).host + WHITELIST.any? { |entry| entry === host } + end + end +end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 50f208b11d1..46374a3909a 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -18,7 +18,13 @@ module Projects if new_project.persisted? if @project.gitlab_ci? - ForkRegistrationWorker.perform_async(@project.id, new_project.id, @current_user.private_token) + new_project.enable_ci + + settings = @project.gitlab_ci_project.attributes.select do |attr_name, value| + ["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name + end + + new_project.gitlab_ci_project.update(settings) end end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 550ed6897dd..c327c244f0d 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -38,7 +38,7 @@ module Projects project.save! # Notifications - project.send_move_instructions + project.send_move_instructions(old_path) # Move main repository unless gitlab_shell.mv_repository(old_path, new_path) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 330b8bbf9d2..a36ae0b766c 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -118,6 +118,11 @@ .col-sm-10 = f.text_area :sign_in_text, class: 'form-control', rows: 4 .help-block Markdown enabled + .form-group + = f.label :help_page_text, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :help_page_text, class: 'form-control', rows: 4 + .help-block Markdown enabled .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml index fc921a966f3..f8cd98f0ec4 100644 --- a/app/views/admin/applications/index.html.haml +++ b/app/views/admin/applications/index.html.haml @@ -2,7 +2,7 @@ %h3.page-title System OAuth applications %p.light - System OAuth application does not belong to certain user and can be managed only by admins + System OAuth applications don't belong to any user and can only be managed by admins %hr %p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success' %table.table.table-striped diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 54191aadda6..8657d2c71fe 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -58,7 +58,7 @@ %p Reply by email %span.light.pull-right - = boolean_to_icon Gitlab::ReplyByEmail.enabled? + = boolean_to_icon Gitlab::IncomingEmail.enabled? .col-md-4 %h4 Components diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml new file mode 100644 index 00000000000..ad58a3837f6 --- /dev/null +++ b/app/views/admin/labels/_form.html.haml @@ -0,0 +1,35 @@ += form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| + -if @label.errors.any? + .row + .col-sm-offset-2.col-sm-10 + .alert.alert-danger + - @label.errors.full_messages.each do |msg| + %span= msg + %br + + .form-group + = f.label :title, class: 'control-label' + .col-sm-10 + = f.text_field :title, class: "form-control", required: true + .form-group + = f.label :color, "Background Color", class: 'control-label' + .col-sm-10 + .input-group + .input-group-addon.label-color-preview + = f.color_field :color, class: "form-control" + .help-block + Choose any color. + %br + Or you can choose one of suggested colors below + + .suggest-colors + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do + + + .form-actions + = f.submit 'Save', class: 'btn btn-save js-save-button' + = link_to "Cancel", admin_labels_path, class: 'btn btn-cancel' + +:coffeescript + new Labels diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml new file mode 100644 index 00000000000..596e06243dd --- /dev/null +++ b/app/views/admin/labels/_label.html.haml @@ -0,0 +1,5 @@ +%li{id: dom_id(label)} + = render_colored_label(label) + .pull-right + = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm' + = link_to 'Remove', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} diff --git a/app/views/admin/labels/destroy.js.haml b/app/views/admin/labels/destroy.js.haml new file mode 100644 index 00000000000..9d51762890f --- /dev/null +++ b/app/views/admin/labels/destroy.js.haml @@ -0,0 +1,2 @@ +- if @labels.size == 0 + $('.labels').load(document.URL + ' .light-well').hide().fadeIn(1000) diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml new file mode 100644 index 00000000000..45c62a76259 --- /dev/null +++ b/app/views/admin/labels/edit.html.haml @@ -0,0 +1,9 @@ +- page_title "Edit", @label.name, "Labels" +%h3 + Edit label + %span.light #{@label.name} +.back-link + = link_to admin_labels_path do + ← To labels list +%hr += render 'form' diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml new file mode 100644 index 00000000000..d67454c03e7 --- /dev/null +++ b/app/views/admin/labels/index.html.haml @@ -0,0 +1,16 @@ +- page_title "Labels" += link_to new_admin_label_path, class: "pull-right btn btn-new" do + New label +%h3.page-title + Labels +%hr + +.labels + - if @labels.present? + %ul.bordered-list.manage-labels-list + = render @labels + = paginate @labels, theme: 'gitlab' + - else + .light-well + .nothing-here-block There are no labels yet + diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml new file mode 100644 index 00000000000..8d298ad20f7 --- /dev/null +++ b/app/views/admin/labels/new.html.haml @@ -0,0 +1,7 @@ +- page_title "New Label" +%h3 New label +.back-link + = link_to admin_labels_path do + ← To labels list +%hr += render 'form' diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index 9d5e934c8ba..4245d0f1eda 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -6,6 +6,8 @@ %span.cred (Admin) .pull-right + - unless @user == current_user + = link_to 'Log in as this user', login_as_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info" = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 5e40d95d1c5..e3698ac1c46 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -97,5 +97,5 @@ - if user.access_locked? = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success", data: { confirm: 'Are you sure?' } - if user.can_be_removed? - = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" + = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" = paginate @users, theme: "gitlab" diff --git a/app/views/ci/admin/application_settings/_form.html.haml b/app/views/ci/admin/application_settings/_form.html.haml new file mode 100644 index 00000000000..634c9daa477 --- /dev/null +++ b/app/views/ci/admin/application_settings/_form.html.haml @@ -0,0 +1,24 @@ += form_for @application_setting, url: ci_admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| + - if @application_setting.errors.any? + #error_explanation + .alert.alert-danger + - @application_setting.errors.full_messages.each do |msg| + %p= msg + + %fieldset + %legend Default Project Settings + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :all_broken_builds do + = f.check_box :all_broken_builds + Send emails only on broken builds + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :add_pusher do + = f.check_box :add_pusher + Add pusher to recipients list + + .form-actions + = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/ci/admin/application_settings/show.html.haml b/app/views/ci/admin/application_settings/show.html.haml new file mode 100644 index 00000000000..7ef0aa89ed6 --- /dev/null +++ b/app/views/ci/admin/application_settings/show.html.haml @@ -0,0 +1,3 @@ +%h3.page-title Settings +%hr += render 'form' diff --git a/app/views/ci/admin/builds/_build.html.haml b/app/views/ci/admin/builds/_build.html.haml new file mode 100644 index 00000000000..2df58713214 --- /dev/null +++ b/app/views/ci/admin/builds/_build.html.haml @@ -0,0 +1,34 @@ +- gl_project = build.project.gl_project +- if build.commit && build.project + %tr.build + %td.build-link + = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do + %strong #{build.id} + + %td.status + = ci_status_with_icon(build.status) + + %td.commit-link + = link_to ci_status_path(build.commit) do + %strong #{build.commit.short_sha} + + %td.runner + - if build.runner + = link_to build.runner.id, ci_admin_runner_path(build.runner) + + %td.build-project + = truncate build.project.name, length: 30 + + %td.build-message + %span= truncate(build.commit.git_commit_message, length: 30) + + %td.build-branch + %span= truncate(build.ref, length: 25) + + %td.duration + - if build.duration + #{duration_in_words(build.finished_at, build.started_at)} + + %td.timestamp + - if build.finished_at + %span #{time_ago_in_words build.finished_at} ago diff --git a/app/views/ci/admin/builds/index.html.haml b/app/views/ci/admin/builds/index.html.haml new file mode 100644 index 00000000000..d23119162cc --- /dev/null +++ b/app/views/ci/admin/builds/index.html.haml @@ -0,0 +1,28 @@ +%ul.nav.nav-tabs.append-bottom-20 + %li{class: ("active" if @scope.nil?)} + = link_to 'All builds', ci_admin_builds_path + + %li{class: ("active" if @scope == "pending")} + = link_to "Pending", ci_admin_builds_path(scope: :pending) + + %li{class: ("active" if @scope == "running")} + = link_to "Running", ci_admin_builds_path(scope: :running) + + +%table.builds + %thead + %tr + %th Build + %th Status + %th Commit + %th Runner + %th Project + %th Message + %th Branch + %th Duration + %th Finished at + + - @builds.each do |build| + = render "ci/admin/builds/build", build: build + += paginate @builds diff --git a/app/views/ci/admin/events/index.html.haml b/app/views/ci/admin/events/index.html.haml new file mode 100644 index 00000000000..f9ab0994304 --- /dev/null +++ b/app/views/ci/admin/events/index.html.haml @@ -0,0 +1,17 @@ +%table.table + %thead + %tr + %th User ID + %th Description + %th When + - @events.each do |event| + %tr + %td + = event.user_id + %td + = event.description + %td.light + = time_ago_in_words event.updated_at + ago + += paginate @events
\ No newline at end of file diff --git a/app/views/ci/admin/projects/_project.html.haml b/app/views/ci/admin/projects/_project.html.haml new file mode 100644 index 00000000000..a342d6e1cf0 --- /dev/null +++ b/app/views/ci/admin/projects/_project.html.haml @@ -0,0 +1,29 @@ +- last_commit = project.commits.last +%tr + %td + = project.id + %td + = link_to [:ci, project] do + %strong= project.name + %td + - if last_commit + = ci_status_with_icon(last_commit.status) + - if project.last_commit_date + · + = time_ago_in_words project.last_commit_date + ago + - else + No builds yet + %td + - if project.public + %i.fa.fa-globe + Public + - else + %i.fa.fa-lock + Private + %td + = project.commits.count + %td + = link_to [:ci, :admin, project], method: :delete, class: 'btn btn-danger btn-sm' do + %i.fa.fa-remove + Remove diff --git a/app/views/ci/admin/projects/index.html.haml b/app/views/ci/admin/projects/index.html.haml new file mode 100644 index 00000000000..dc7b041473b --- /dev/null +++ b/app/views/ci/admin/projects/index.html.haml @@ -0,0 +1,15 @@ +%table.table + %thead + %tr + %th ID + %th Name + %th Last build + %th Access + %th Builds + %th + + - @projects.each do |project| + = render "ci/admin/projects/project", project: project + += paginate @projects + diff --git a/app/views/ci/admin/runner_projects/index.html.haml b/app/views/ci/admin/runner_projects/index.html.haml new file mode 100644 index 00000000000..f049b4f4c4e --- /dev/null +++ b/app/views/ci/admin/runner_projects/index.html.haml @@ -0,0 +1,57 @@ +%p.lead + To register new runner visit #{link_to 'this page ', ci_runners_path} + +.row + .col-md-8 + %h5 Activated: + %table.table + %tr + %th Runner ID + %th Runner Description + %th Last build + %th Builds Stats + %th Registered + %th + + - @runner_projects.each do |runner_project| + - runner = runner_project.runner + - builds = runner.builds.where(project_id: @project.id) + %tr + %td + %span.badge.badge-info= runner.id + %td + = runner.display_name + %td + - last_build = builds.last + - if last_build + = link_to last_build.short_sha, [last_build.project, last_build] + - else + unknown + %td + %span.badge.badge-success + #{builds.success.count} + %span / + %span.badge.badge-important + #{builds.failed.count} + %td + #{time_ago_in_words(runner_project.created_at)} ago + %td + = link_to 'Disable', [:ci, @project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm right' + .col-md-4 + %h5 Available + %table.table + %tr + %th ID + %th Token + %th + + - (Ci::Runner.all - @project.runners).each do |runner| + %tr + %td + = runner.id + %td + = runner.token + %td + = form_for [:ci, @project, @runner_project] do |f| + = f.hidden_field :runner_id, value: runner.id + = f.submit 'Add', class: 'btn btn-sm' diff --git a/app/views/ci/admin/runners/_runner.html.haml b/app/views/ci/admin/runners/_runner.html.haml new file mode 100644 index 00000000000..701782d26bb --- /dev/null +++ b/app/views/ci/admin/runners/_runner.html.haml @@ -0,0 +1,48 @@ +%tr{id: dom_id(runner)} + %td + - if runner.shared? + %span.label.label-success shared + - else + %span.label.label-info specific + - unless runner.active? + %span.label.label-danger paused + + %td + = link_to ci_admin_runner_path(runner) do + = runner.short_sha + %td + .runner-description + = runner.description + %span (#{link_to 'edit', '#', class: 'edit-runner-link'}) + .runner-description-form.hide + = form_for [:ci, :admin, runner], remote: true, html: { class: 'form-inline' } do |f| + .form-group + = f.text_field :description, class: 'form-control' + = f.submit 'Save', class: 'btn' + %span (#{link_to 'cancel', '#', class: 'cancel'}) + %td + - if runner.shared? + \- + - else + = runner.projects.count(:all) + %td + #{runner.builds.count(:all)} + %td + - runner.tag_list.each do |tag| + %span.label.label-primary + = tag + %td + - if runner.contacted_at + #{time_ago_in_words(runner.contacted_at)} ago + - else + Never + %td + .pull-right + = link_to 'Edit', ci_admin_runner_path(runner), class: 'btn btn-sm' + + - if runner.active? + = link_to 'Pause', [:pause, :ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm' + - else + = link_to 'Resume', [:resume, :ci, :admin, runner], method: :get, class: 'btn btn-success btn-sm' + = link_to 'Remove', [:ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' + diff --git a/app/views/ci/admin/runners/index.html.haml b/app/views/ci/admin/runners/index.html.haml new file mode 100644 index 00000000000..b9d6703ff41 --- /dev/null +++ b/app/views/ci/admin/runners/index.html.haml @@ -0,0 +1,52 @@ +%p.lead + %span To register new runner you should enter the following registration token. With this token the runner will request a unique runner token and use that for future communication. + %code #{GitlabCi::REGISTRATION_TOKEN} + +.bs-callout + %p + A 'runner' is a process which runs a build. + You can setup as many runners as you need. + %br + Runners can be placed on separate users, servers, and even on your local machine. + %br + + %div + %span Each runner can be in one of the following states: + %ul + %li + %span.label.label-success shared + \- run builds from all unassigned projects + %li + %span.label.label-info specific + \- run builds from assigned projects + %li + %span.label.label-danger paused + \- runner will not receive any new build + +.append-bottom-20.clearfix + .pull-left + = form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do + .form-group + = search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token' + = submit_tag 'Search', class: 'btn' + + .pull-right.light + Runners with last contact less than a minute ago: #{@active_runners_cnt} + +%br + +%table.table + %thead + %tr + %th Type + %th Runner token + %th Description + %th Projects + %th Builds + %th Tags + %th Last contact + %th + + - @runners.each do |runner| + = render "ci/admin/runners/runner", runner: runner += paginate @runners diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/ci/admin/runners/show.html.haml new file mode 100644 index 00000000000..5bb442cbf92 --- /dev/null +++ b/app/views/ci/admin/runners/show.html.haml @@ -0,0 +1,124 @@ += content_for :title do + %h3.project-title + Runner ##{@runner.id} + .pull-right + - if @runner.shared? + %span.runner-state.runner-state-shared + Shared + - else + %span.runner-state.runner-state-specific + Specific + + + +- if @runner.shared? + .bs-callout.bs-callout-success + %h4 This runner will process build from ALL UNASSIGNED projects + %p + If you want runners to build only specific projects, enable them in the table below. + Keep in mind that this is a one way transition. +- else + .bs-callout.bs-callout-info + %h4 This runner will process build only from ASSIGNED projects + %p You can't make this a shared runner. +%hr += form_for @runner, url: ci_admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f| + .form-group + = label_tag :token, class: 'control-label' do + Token + .col-sm-10 + = f.text_field :token, class: 'form-control', readonly: true + .form-group + = label_tag :description, class: 'control-label' do + Description + .col-sm-10 + = f.text_field :description, class: 'form-control' + .form-group + = label_tag :tag_list, class: 'control-label' do + Tags + .col-sm-10 + = f.text_field :tag_list, class: 'form-control' + .help-block You can setup builds to only use runners with specific tags + .form-actions + = f.submit 'Save', class: 'btn btn-save' + +.row + .col-md-6 + %h4 Restrict projects for this runner + - if @runner.projects.any? + %table.table + %thead + %tr + %th Assigned projects + %th + - @runner.runner_projects.each do |runner_project| + - project = runner_project.project + %tr.alert-info + %td + %strong + = project.name + %td + .pull-right + = link_to 'Disable', [:ci, :admin, project, runner_project], method: :delete, class: 'btn btn-danger btn-xs' + + %table.table + %thead + %tr + %th Project + %th + .pull-right + = link_to 'Assign to all', assign_all_ci_admin_runner_path(@runner), + class: 'btn btn-sm assign-all-runner', + title: 'Assign runner to all projects', + method: :put + + %tr + %td + = form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do + .form-group + = search_field_tag :search, params[:search], class: 'form-control' + = submit_tag 'Search', class: 'btn' + + %td + - @projects.each do |project| + %tr + %td + = project.name + %td + .pull-right + = form_for [:ci, :admin, project, project.runner_projects.new] do |f| + = f.hidden_field :runner_id, value: @runner.id + = f.submit 'Enable', class: 'btn btn-xs' + = paginate @projects + + .col-md-6 + %h4 Recent builds served by this runner + %table.builds.runner-builds + %thead + %tr + %th Build ID + %th Status + %th Project + %th Commit + %th Finished at + + - @builds.each do |build| + %tr.build + %td.id + - gl_project = build.project.gl_project + = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do + = build.id + + %td.status + = ci_status_with_icon(build.status) + + %td.status + = build.project.name + + %td.build-link + = link_to ci_status_path(build.commit) do + %strong #{build.commit.short_sha} + + %td.timestamp + - if build.finished_at + %span #{time_ago_in_words build.finished_at} ago diff --git a/app/views/ci/admin/runners/update.js.haml b/app/views/ci/admin/runners/update.js.haml new file mode 100644 index 00000000000..2b7d3067e20 --- /dev/null +++ b/app/views/ci/admin/runners/update.js.haml @@ -0,0 +1,2 @@ +:plain + $("#runner_#{@runner.id}").replaceWith("#{escape_javascript(render(@runner))}") diff --git a/app/views/ci/commits/_commit.html.haml b/app/views/ci/commits/_commit.html.haml new file mode 100644 index 00000000000..b24a3b826cf --- /dev/null +++ b/app/views/ci/commits/_commit.html.haml @@ -0,0 +1,33 @@ +%tr.build + %td.status + = ci_status_with_icon(commit.status) + - if commit.running? + · + = commit.stage + + + %td.build-link + = link_to ci_status_path(commit) do + %strong #{commit.short_sha} + + %td.build-message + %span= truncate_first_line(commit.git_commit_message) + + %td.build-branch + - unless @ref + %span + - commit.refs.each do |ref| + = link_to truncate(ref, length: 25), ci_project_path(@project, ref: ref) + + %td.duration + - if commit.duration > 0 + #{time_interval_in_words commit.duration} + + %td.timestamp + - if commit.finished_at + %span #{time_ago_in_words commit.finished_at} ago + + - if commit.project.coverage_enabled? + %td.coverage + - if commit.coverage + #{commit.coverage}% diff --git a/app/views/ci/errors/show.haml b/app/views/ci/errors/show.haml new file mode 100644 index 00000000000..2788112c835 --- /dev/null +++ b/app/views/ci/errors/show.haml @@ -0,0 +1,2 @@ +%h3.error Error += @error diff --git a/app/views/ci/events/index.html.haml b/app/views/ci/events/index.html.haml new file mode 100644 index 00000000000..779f49b3d3a --- /dev/null +++ b/app/views/ci/events/index.html.haml @@ -0,0 +1,19 @@ +%h3.page-title Events + +%table.table + %thead + %tr + %th User ID + %th Description + %th When + - @events.each do |event| + %tr + %td + = event.user_id + %td + = event.description + %td.light + = time_ago_in_words event.updated_at + ago + += paginate @events
\ No newline at end of file diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml new file mode 100644 index 00000000000..e2179e60f3e --- /dev/null +++ b/app/views/ci/lints/_create.html.haml @@ -0,0 +1,39 @@ +- if @status + %p + %b Status: + syntax is correct + %i.fa.fa-ok.correct-syntax + + %table.table.table-bordered + %thead + %tr + %th Parameter + %th Value + %tbody + - @stages.each do |stage| + - @builds.select { |build| build[:stage] == stage }.each do |build| + %tr + %td #{stage.capitalize} Job - #{build[:name]} + %td + %pre + = simple_format build[:script] + + %br + %b Tag list: + = build[:tags] + %br + %b Refs only: + = build[:only] && build[:only].join(", ") + %br + %b Refs except: + = build[:except] && build[:except].join(", ") + +-else + %p + %b Status: + syntax is incorrect + %i.fa.fa-remove.incorrect-syntax + %b Error: + = @error + + diff --git a/app/views/ci/lints/create.js.haml b/app/views/ci/lints/create.js.haml new file mode 100644 index 00000000000..a96c0b11b6e --- /dev/null +++ b/app/views/ci/lints/create.js.haml @@ -0,0 +1,2 @@ +:plain + $(".results").html("#{escape_javascript(render "create")}")
\ No newline at end of file diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml new file mode 100644 index 00000000000..a9b954771c5 --- /dev/null +++ b/app/views/ci/lints/show.html.haml @@ -0,0 +1,25 @@ +%h2 Check your .gitlab-ci.yml +%hr + += form_tag ci_lint_path, method: :post, remote: true do + .control-group + = label_tag :content, "Content of .gitlab-ci.yml", class: 'control-label' + .controls + = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true + + .control-group.clearfix + .controls.pull-left.prepend-top-10 + = submit_tag "Validate", class: 'btn btn-success submit-yml' + + +%p.text-center.loading + %i.fa.fa-refresh.fa-spin + +.results.prepend-top-20 + +:coffeescript + $(".loading").hide() + $('form').bind 'ajax:beforeSend', -> + $(".loading").show() + $('form').bind 'ajax:complete', -> + $(".loading").hide() diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml new file mode 100644 index 00000000000..69689a75022 --- /dev/null +++ b/app/views/ci/notify/build_fail_email.html.haml @@ -0,0 +1,19 @@ +- content_for :header do + %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} + GitLab CI (build failed) +%h3 + Project: + = link_to ci_project_url(@project) do + = @project.name + +%p + Commit link: #{gitlab_commit_link(@project, @build.commit.short_sha)} +%p + Author: #{@build.commit.git_author_name} +%p + Branch: #{@build.ref} +%p + Message: #{@build.commit.git_commit_message} + +%p + Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} diff --git a/app/views/ci/notify/build_fail_email.text.erb b/app/views/ci/notify/build_fail_email.text.erb new file mode 100644 index 00000000000..6de5dc10f17 --- /dev/null +++ b/app/views/ci/notify/build_fail_email.text.erb @@ -0,0 +1,9 @@ +Build failed for <%= @project.name %> + +Status: <%= @build.status %> +Commit: <%= @build.commit.short_sha %> +Author: <%= @build.commit.git_author_name %> +Branch: <%= @build.ref %> +Message: <%= @build.commit.git_commit_message %> + +Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %> diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml new file mode 100644 index 00000000000..4e3015a356b --- /dev/null +++ b/app/views/ci/notify/build_success_email.html.haml @@ -0,0 +1,20 @@ +- content_for :header do + %h1{style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} + GitLab CI (build successful) + +%h3 + Project: + = link_to ci_project_url(@project) do + = @project.name + +%p + Commit link: #{gitlab_commit_link(@project, @build.commit.short_sha)} +%p + Author: #{@build.commit.git_author_name} +%p + Branch: #{@build.ref} +%p + Message: #{@build.commit.git_commit_message} + +%p + Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} diff --git a/app/views/ci/notify/build_success_email.text.erb b/app/views/ci/notify/build_success_email.text.erb new file mode 100644 index 00000000000..d0a43ae1c12 --- /dev/null +++ b/app/views/ci/notify/build_success_email.text.erb @@ -0,0 +1,9 @@ +Build successful for <%= @project.name %> + +Status: <%= @build.status %> +Commit: <%= @build.commit.short_sha %> +Author: <%= @build.commit.git_author_name %> +Branch: <%= @build.ref %> +Message: <%= @build.commit.git_commit_message %> + +Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %> diff --git a/app/views/ci/projects/_info.html.haml b/app/views/ci/projects/_info.html.haml new file mode 100644 index 00000000000..1888e1bde93 --- /dev/null +++ b/app/views/ci/projects/_info.html.haml @@ -0,0 +1,2 @@ +- if no_runners_for_project?(@project) + = render 'no_runners' diff --git a/app/views/ci/projects/_no_runners.html.haml b/app/views/ci/projects/_no_runners.html.haml new file mode 100644 index 00000000000..c0a296fb17d --- /dev/null +++ b/app/views/ci/projects/_no_runners.html.haml @@ -0,0 +1,8 @@ +.alert.alert-danger + %p + There are NO runners to build this project. + %br + You can add Specific runner for this project on Runners page + + - if current_user.is_admin + or add Shared runner for whole application in admin are. diff --git a/app/views/ci/projects/show.html.haml b/app/views/ci/projects/show.html.haml new file mode 100644 index 00000000000..888b1ea41d5 --- /dev/null +++ b/app/views/ci/projects/show.html.haml @@ -0,0 +1,60 @@ += render 'ci/shared/guide' unless @project.setup_finished? + +- if current_user && can?(current_user, :manage_project, gl_project) && !@project.any_runners? + .alert.alert-danger + Builds for this project wont be served unless you configure runners on + = link_to "Runners page", runners_path(@project.gl_project) + +%ul.nav.nav-tabs.append-bottom-20 + %li{class: ref_tab_class} + = link_to 'All commits', ci_project_path(@project) + - @project.tracked_refs.each do |ref| + %li{class: ref_tab_class(ref)} + = link_to ref, ci_project_path(@project, ref: ref) + + - if @ref && !@project.tracked_refs.include?(@ref) + %li{class: 'active'} + = link_to @ref, ci_project_path(@project, ref: @ref) + + %li.pull-right + = link_to 'Go to project', project_path(gl_project), class: 'btn btn-sm' + +- if @ref + %p + Paste build status image for #{@ref} with next link + = link_to '#', class: 'badge-codes-toggle btn btn-default btn-xs' do + Status Badge + .badge-codes-block.bs-callout.bs-callout-info.hide + %p + Status badge for + %span.label.label-info #{@ref} + branch + %div + %label Markdown: + = text_field_tag 'badge_md', markdown_badge_code(@project, @ref), readonly: true, class: 'form-control' + %label Html: + = text_field_tag 'badge_html', html_badge_code(@project, @ref), readonly: true, class: 'form-control' + + + + +%table.table.builds + %thead + %tr + %th Status + %th Commit + %th Message + %th Branch + %th Total duration + %th Finished at + - if @project.coverage_enabled? + %th Coverage + + = render @commits + += paginate @commits + +- if @commits.empty? + .bs-callout + %h4 No commits yet + diff --git a/app/views/ci/services/_form.html.haml b/app/views/ci/services/_form.html.haml new file mode 100644 index 00000000000..9110aaa0528 --- /dev/null +++ b/app/views/ci/services/_form.html.haml @@ -0,0 +1,57 @@ +%h3.page-title + = @service.title + = boolean_to_icon @service.activated? + +%p= @service.description + +.back-link + = link_to ci_project_services_path(@project) do + ← to services + +%hr + += form_for(@service, as: :service, url: ci_project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f| + - if @service.errors.any? + .alert.alert-danger + %ul + - @service.errors.full_messages.each do |msg| + %li= msg + + - if @service.help.present? + .bs-callout + = @service.help + + .form-group + = f.label :active, "Active", class: "control-label" + .col-sm-10 + = f.check_box :active + + - @service.fields.each do |field| + - name = field[:name] + - label = field[:label] || name + - value = @service.send(name) + - type = field[:type] + - placeholder = field[:placeholder] + - choices = field[:choices] + - default_choice = field[:default_choice] + - help = field[:help] + + .form-group + = f.label label, class: "control-label" + .col-sm-10 + - if type == 'text' + = f.text_field name, class: "form-control", placeholder: placeholder + - elsif type == 'textarea' + = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder + - elsif type == 'checkbox' + = f.check_box name + - elsif type == 'select' + = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + - if help + .light #{help} + + .form-actions + = f.submit 'Save', class: 'btn btn-save' + + - if @service.valid? && @service.activated? && @service.can_test? + = link_to 'Test settings', test_ci_project_service_path(@project, @service.to_param), class: 'btn' diff --git a/app/views/ci/services/edit.html.haml b/app/views/ci/services/edit.html.haml new file mode 100644 index 00000000000..bcc5832792f --- /dev/null +++ b/app/views/ci/services/edit.html.haml @@ -0,0 +1 @@ += render 'form' diff --git a/app/views/ci/services/index.html.haml b/app/views/ci/services/index.html.haml new file mode 100644 index 00000000000..37e5723b541 --- /dev/null +++ b/app/views/ci/services/index.html.haml @@ -0,0 +1,22 @@ +%h3.page-title Project services +%p.light Project services allow you to integrate GitLab CI with other applications + +%table.table + %thead + %tr + %th + %th Service + %th Desription + %th Last edit + - @services.sort_by(&:title).each do |service| + %tr + %td + = boolean_to_icon service.activated? + %td + = link_to edit_ci_project_service_path(@project, service.to_param) do + %strong= service.title + %td + = service.description + %td.light + = time_ago_in_words service.updated_at + ago diff --git a/app/views/ci/shared/_guide.html.haml b/app/views/ci/shared/_guide.html.haml new file mode 100644 index 00000000000..db2d7f2f4b6 --- /dev/null +++ b/app/views/ci/shared/_guide.html.haml @@ -0,0 +1,15 @@ +.bs-callout.help-callout + %h4 How to setup CI for this project + + %ol + %li + Add at least one runner to the project. + Go to #{link_to 'Runners page', runners_path(@project.gl_project), target: :blank} for instructions. + %li + Put the .gitlab-ci.yml in the root of your repository. Examples can be found in #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}. + You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} + %li + Visit #{link_to 'GitLab project settings', @project.gitlab_url + "/services/gitlab_ci/edit", target: :blank} + and press the "Test settings" button. + %li + Return to this page and refresh it, it should show a new build. diff --git a/app/views/ci/shared/_no_runners.html.haml b/app/views/ci/shared/_no_runners.html.haml new file mode 100644 index 00000000000..f56c37d9b37 --- /dev/null +++ b/app/views/ci/shared/_no_runners.html.haml @@ -0,0 +1,7 @@ +.alert.alert-danger + %p + Now you need Runners to process your builds. + %span + Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it + + diff --git a/app/views/ci/user_sessions/new.html.haml b/app/views/ci/user_sessions/new.html.haml new file mode 100644 index 00000000000..308b217ea78 --- /dev/null +++ b/app/views/ci/user_sessions/new.html.haml @@ -0,0 +1,8 @@ +.login-block + %h2 Login using GitLab account + %p.light + Make sure you have account on GitLab server + = link_to GitlabCi.config.gitlab_server.url, GitlabCi.config.gitlab_server.url, no_turbolink + %hr + = link_to "Login with GitLab", auth_ci_user_sessions_path(state: params[:state]), no_turbolink.merge( class: 'btn btn-login btn-success' ) + diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index 213b5d65b3c..19d919f9b6a 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,13 +1,13 @@ .hidden-xs = render "events/event_last_push", event: @last_push +.gray-content-block - if current_user %ul.nav.nav-pills.event_filter.pull-right %li.pull-right - = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do + = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do %i.fa.fa-rss - = render 'shared/event_filter' - %hr + .content_list = spinner diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml new file mode 100644 index 00000000000..9f4be025bf2 --- /dev/null +++ b/app/views/dashboard/_activity_head.html.haml @@ -0,0 +1,7 @@ +%ul.center-top-menu + %li{ class: ("active" unless params[:filter]) } + = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do + Your Projects + %li{ class: ("active" if params[:filter] == 'starred') } + = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do + Starred Projects diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 8a397a84e0e..64bd356f546 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -1,7 +1,7 @@ %ul.center-top-menu - = nav_link(page: [dashboard_groups_path]) do + = nav_link(page: dashboard_groups_path) do = link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do Your Groups - = nav_link(page: [explore_groups_path]) do - = link_to explore_groups_path, title: 'Explore groups', data: {toggle: 'tooltip', placement: 'bottom'} do + = nav_link(page: explore_groups_path) do + = link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do Explore Groups diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index f7be194c696..ed480b8caf8 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,10 +1,10 @@ %ul.center-top-menu - = nav_link(path: ['dashboard#show', 'root#show']) do - = link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do + = nav_link(path: ['projects#index', 'root#index']) do + = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do Your Projects = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do Starred Projects - = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do - = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do + = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do + = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do Explore Projects diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml new file mode 100644 index 00000000000..0ae62d6f1b6 --- /dev/null +++ b/app/views/dashboard/_snippets_head.html.haml @@ -0,0 +1,7 @@ +%ul.center-top-menu + = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do + = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do + Your Snippets + = nav_link(page: explore_snippets_path) do + = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do + Explore Snippets diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index 7a5a093add5..aa57df14c23 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -1,6 +1,11 @@ = content_for :meta_tags do - if current_user - = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity") + = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity") + +- page_title "Activity" +- header_title "Activity", activity_dashboard_path + += render 'dashboard/activity_head' %section.activities = render 'activities' diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index fbe523b4b66..c249f5cacec 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -1,14 +1,17 @@ - page_title "Groups" +- header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' -.slead - Group members have access to all group projects. +.gray-content-block - if current_user.can_create_group? %span.pull-right.hidden-xs - = link_to new_group_path, class: "btn btn-new btn-sm" do + = link_to new_group_path, class: "btn btn-new" do %i.fa.fa-plus New Group -%ul.bordered-list + .title Welcome to the groups! + Group members have access to all group projects. + +%ul.content-list - @group_members.each do |group_member| - group = group_member.group = render 'shared/groups/group', group: group, group_member: group_member diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 94318d1bcf5..cd602e897b7 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,21 +1,17 @@ - page_title "Issues" +- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues") -%h3.page-title - Issues - -%p.light - List all issues from all projects you have access to. -%hr .append-bottom-20 .pull-right - if current_user - .hidden-xs.pull-left - = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do + .hidden-xs.pull-left.prepend-top-20 + = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do %i.fa.fa-rss = render 'shared/issuable/filter', type: :issues + = render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 90611d562b0..d1f332fa0d3 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,11 +1,6 @@ - page_title "Merge Requests" -%h3.page-title - Merge Requests +- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) - -%p.light - List all merge requests from all projects you have access to. -%hr .append-bottom-20 = render 'shared/issuable/filter', type: :merge_requests = render 'shared/merge_requests' diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml index d6f3e029a38..55080d6b3fe 100644 --- a/app/views/dashboard/milestones/_milestone.html.haml +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -1,20 +1,22 @@ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - %h4 - = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + %strong + = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) + .col-sm-6 + .pull-right.light #{milestone.percent_complete}% complete .row .col-sm-6 = link_to issues_dashboard_path(milestone_title: milestone.title) do = pluralize milestone.issue_count, 'Issue' - + · = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do = pluralize milestone.merge_requests_count, 'Merge Request' - - %span.light #{milestone.percent_complete}% complete - .col-sm-6 = milestone_progress_bar(milestone) - %div - - milestone.milestones.each do |milestone| - = link_to milestone_path(milestone) do - %span.label.label-gray - = milestone.project.name_with_namespace + .row + .col-sm-6 + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name_with_namespace diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 9a9a5e139a4..21b25c3986e 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -1,21 +1,19 @@ - page_title "Milestones" -%h3.page-title - Milestones - %span.pull-right #{@dashboard_milestones.count} milestones +- header_title "Milestones", dashboard_milestones_path -%p.light - List all milestones from all projects you have access to. - -%hr = render 'shared/milestones_filter' + +.gray-content-block + .oneline + List all milestones from all projects you have access to. + .milestones - .panel.panel-default - %ul.well-list - - if @dashboard_milestones.blank? - %li - .nothing-here-block No milestones to show - - else - - @dashboard_milestones.each do |milestone| - = render 'milestone', milestone: milestone + %ul.content-list + - if @dashboard_milestones.blank? + %li + .nothing-here-block No milestones to show + - else + - @dashboard_milestones.each do |milestone| + = render 'milestone', milestone: milestone = paginate @dashboard_milestones, theme: "gitlab" diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml index ef9b9ce756a..e09e032a7f1 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/projects/_projects.html.haml @@ -4,7 +4,7 @@ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' - if current_user.can_create_project? %span.input-group-btn - = link_to new_project_path, class: 'btn btn-success' do + = link_to new_project_path, class: 'btn btn-green' do New project - = render 'shared/projects/list', projects: @projects + = render 'shared/projects/list', projects: @projects, ci: true diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index 4e7d6639727..4e7d6639727 100644 --- a/app/views/dashboard/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/projects/index.atom.builder index e9a612231d5..d2c51486841 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/projects/index.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Activity" - xml.link href: dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" - xml.link href: dashboard_url, rel: "alternate", type: "text/html" - xml.id dashboard_url + xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" + xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html" + xml.id dashboard_projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/projects/index.html.haml index 4cf2feb9aa6..7a16b811f6b 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -1,6 +1,9 @@ = content_for :meta_tags do - if current_user - = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity") + = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity") + +- page_title "Projects" +- header_title "Projects", root_path = render 'dashboard/projects_head' diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 19f3975e530..f75f2e0a32a 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,24 +1,13 @@ - page_title "Starred Projects" -= render 'dashboard/projects_head' - -- if @projects.any? - = render 'shared/show_aside' +- header_title "Projects", projects_path - .dashboard.row - %section.activities.col-md-7 - = render 'dashboard/activities' - %aside.col-md-5 - .panel.panel-default.projects-list-holder - .panel-heading.clearfix - .input-group - = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' - - if current_user.can_create_project? - %span.input-group-btn - = link_to new_project_path, class: 'btn btn-success' do - New project += render 'dashboard/projects_head' - = render 'shared/projects/list', projects: @projects, projects_limit: 20 +- if @last_push + = render "events/event_last_push", event: @last_push +- if @projects.any? + = render 'projects' - else %h3 You don't have starred projects yet %p.slead Visit project page and press on star icon and it will appear on this page. diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml new file mode 100644 index 00000000000..d3908062f43 --- /dev/null +++ b/app/views/dashboard/snippets/index.html.haml @@ -0,0 +1,38 @@ +- page_title "Snippets" +- header_title "Snippets", dashboard_snippets_path + += render 'dashboard/snippets_head' + +.gray-content-block + .pull-right + = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do + Add new snippet + + .oneline + Share code pastes with others out of git repository + +%ul.nav.nav-tabs.prepend-top-20 + = nav_tab :scope, nil do + = link_to dashboard_snippets_path do + All + %span.badge + = current_user.snippets.count + = nav_tab :scope, 'are_private' do + = link_to dashboard_snippets_path(scope: 'are_private') do + Private + %span.badge + = current_user.snippets.are_private.count + = nav_tab :scope, 'are_internal' do + = link_to dashboard_snippets_path(scope: 'are_internal') do + Internal + %span.badge + = current_user.snippets.are_internal.count + = nav_tab :scope, 'are_public' do + = link_to dashboard_snippets_path(scope: 'are_public') do + Public + %span.badge + = current_user.snippets.are_public.count + +.my-snippets + = render 'snippets/snippets' + diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 29ffe8a8be3..535e85869e5 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -6,7 +6,7 @@ .devise-errors = devise_error_messages! .clearfix.append-bottom-20 - = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email] + = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email], autofocus: true .clearfix = f.submit "Reset password", class: "btn-primary btn" diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml new file mode 100644 index 00000000000..4974bb7f7fb --- /dev/null +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -0,0 +1,9 @@ += form_tag(user_omniauth_authorize_path("crowd"), id: 'new_crowd_user' ) do + = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} + = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} + - if devise_mapping.rememberable? + .remember-me.checkbox + %label{for: "remember_me"} + = check_box_tag :remember_me, '1', false, id: 'remember_me' + %span Remember me + = button_tag "Sign in", class: "btn-save btn"
\ No newline at end of file diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index bb5e479697d..41ad2c231d4 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -8,15 +8,21 @@ .login-body - if form_based_providers.any? %ul.nav.nav-tabs + - if crowd_enabled? + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero?)} + %li{class: (:active if i.zero? && !crowd_enabled?)} = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - if signin_enabled? %li = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' .tab-content + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} = render 'devise/sessions/new_ldap', server: server - if signin_enabled? %div#tab-signin.tab-pane diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 742b74a67c7..ad63841ccf3 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -1,5 +1,5 @@ %li.commit .commit-row-title = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: '' - + · = gfm event_commit_title(commit[:message]), project: project diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 0faab4458e9..9aacc79d686 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,8 +3,8 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache [event, "v1"] do - = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' + = cache [event, "v2.1"] do + = image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:'' - if event.created_project? = render "events/event/created_project", event: event - elsif event.push? diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 6a0c6cba41b..ffc37ad6178 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -1,14 +1,14 @@ - if show_last_push_widget?(event) - .event-last-push - .event-last-push-text - %span You pushed to - = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do - %strong= event.ref_name - %span at - %strong= link_to_project event.project - #{time_ago_with_tooltip(event.created_at)} + .gray-content-block.clear-block + .event-last-push + .event-last-push-text + %span You pushed to + = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do + %strong= event.ref_name + %span at + %strong= link_to_project event.project + #{time_ago_with_tooltip(event.created_at)} - .pull-right - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do - Create Merge Request - %hr + .pull-right + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do + Create Merge Request diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index a39e62e9dac..4ecf1c33d2a 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -5,13 +5,14 @@ - if event.target %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target] - at + + = event_preposition(event) - if event.project = link_to_project event.project - else = event.project_name - + - if event.target.respond_to?(:title) .event-body .event-note diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 07bec1697f5..830fec0b4ab 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -4,7 +4,7 @@ = event.action_name = event_note_title_html(event) at - + - if event.project = link_to_project event.project - else @@ -13,7 +13,6 @@ .event-body .event-note .md - %i.fa.fa-comment-o.event-note-icon = event_note(event.target.note, project: event.project) - note = event.target - if note.attachment.url diff --git a/app/views/explore/_head.html.haml b/app/views/explore/_head.html.haml new file mode 100644 index 00000000000..d8a57560788 --- /dev/null +++ b/app/views/explore/_head.html.haml @@ -0,0 +1,6 @@ +.explore-title + %h3 + Explore GitLab + %p.lead + Discover projects, groups and snippets. Share your projects with others + %br diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 80acb914365..83d4d321c83 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,14 +1,19 @@ -- page_title "Groups" +- page_title "Groups" +- header_title "Groups", dashboard_groups_path + - if current_user = render 'dashboard/groups_head' -.clearfix.append-bottom-10 +- else + = render 'explore/head' + +.gray-content-block.clearfix .pull-left = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| = hidden_field_tag :sort, @sort .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search" .form-group - = button_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-default" .pull-right .dropdown.inline @@ -30,7 +35,7 @@ = link_to explore_groups_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated -%ul.bordered-list +%ul.content-list - @groups.each do |group| = render 'shared/groups/group', group: group - unless @groups.present? diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 4b91291caf4..5a3d689d1e5 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -3,7 +3,7 @@ .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search" .form-group - = button_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-success" .pull-right.hidden-sm.hidden-xs - if current_user diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 0cfdf5cfd15..67e38ca3127 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,8 +1,12 @@ -- page_title "Projects" +- page_title "Projects" +- header_title "Projects", root_path + - if current_user = render 'dashboard/projects_head' -.clearfix +- else + = render 'explore/head' + +.gray-content-block.clearfix = render 'filter' -%br = render 'projects', projects: @projects = paginate @projects, theme: "gitlab" diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index 4a9fcae4bed..596cb0a96cd 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,11 +1,17 @@ -- page_title "Starred Projects" +- page_title "Projects" +- header_title "Projects", root_path + - if current_user = render 'dashboard/projects_head' +- else + = render 'explore/head' + .explore-trending-block - .lead - %i.fa.fa-star - See most starred projects + .gray-content-block .pull-right = render 'explore/projects/dropdown' + .oneline + %i.fa.fa-star + See most starred projects = render 'projects', projects: @starred_projects = paginate @starred_projects, theme: 'gitlab' diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 4c7e7d44733..5ea6d81c5b9 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,16 +1,16 @@ -- page_title "Trending Projects" +- page_title "Projects" +- header_title "Projects", root_path + - if current_user = render 'dashboard/projects_head' -.explore-title - %h3 - Explore GitLab - %p.lead - Discover projects and groups. Share your projects with others -%hr +- else + = render 'explore/head' + .explore-trending-block - .lead - %i.fa.fa-comments-o - See most discussed projects for last month + .gray-content-block .pull-right = render 'explore/projects/dropdown' + .oneline + %i.fa.fa-comments-o + See most discussed projects for last month = render 'projects', projects: @trending_projects diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml new file mode 100644 index 00000000000..7e4fa7d4873 --- /dev/null +++ b/app/views/explore/snippets/index.html.haml @@ -0,0 +1,18 @@ +- page_title "Snippets" +- header_title "Snippets", snippets_path + +- if current_user + = render 'dashboard/snippets_head' +- else + = render 'explore/head' + +.gray-content-block + - if current_user + .pull-right + = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do + Add new snippet + + .oneline + Public snippets created by you and other users are listed here + += render 'snippets/snippets' diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index b2e32ced5e0..2b27a88794d 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -4,7 +4,7 @@ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' - if can? current_user, :create_projects, @group %span.input-group-btn - = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do + = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do New project - = render 'shared/projects/list', projects: @projects, projects_limit: 20 + = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 2ff4b7e23ea..ae8fc9f85f0 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,3 +1,6 @@ +- header_title group_title(@group, "Settings", edit_group_path(@group)) +- @blank_container = true + .panel.panel-default .panel-heading %strong= @group.name diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index dba395cc8fa..3a6d07ebddf 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,15 +1,13 @@ - page_title "Members" +- header_title group_title(@group, "Members", group_group_members_path(@group)) - show_roles = should_user_see_group_roles?(current_user, @group) -%h3.page-title - Group members - if show_roles %p.light Members of group have access to all group projects. Read more about permissions %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" -%hr .clearfix.js-toggle-container = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index f0d90782556..08d97e418a3 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,25 +1,24 @@ - page_title "Issues" +- header_title group_title(@group, "Issues", issues_group_path(@group)) = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") -%h3.page-title - Issues -%p.light - Only issues from - %strong #{@group.name} - group are listed here. - - if current_user - To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. -%hr -.append-bottom-20 += render 'shared/issuable/filter', type: :issues +.gray-content-block.second-block .pull-right - if current_user .hidden-xs.pull-left - = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do + = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do %i.fa.fa-rss + %div + Only issues from + %strong #{@group.name} + group are listed here. + - if current_user + To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. - = render 'shared/issuable/filter', type: :issues -= render 'shared/issues' +.prepend-top-default + = render 'shared/issues' diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index ca85a158707..425ad8331bf 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,14 +1,13 @@ - page_title "Merge Requests" -%h3.page-title - Merge Requests +- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group)) -%p.light - Only merge requests from - %strong #{@group.name} - group are listed here. - - if current_user - To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. -%hr -.append-bottom-20 - = render 'shared/issuable/filter', type: :merge_requests -= render 'shared/merge_requests' += render 'shared/issuable/filter', type: :merge_requests +.gray-content-block.second-block + %div + Only merge requests from + %strong #{@group.name} + group are listed here. + - if current_user + To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. +.prepend-top-default + = render 'shared/merge_requests' diff --git a/app/views/groups/milestones/_header_title.html.haml b/app/views/groups/milestones/_header_title.html.haml new file mode 100644 index 00000000000..d7fabf53587 --- /dev/null +++ b/app/views/groups/milestones/_header_title.html.haml @@ -0,0 +1 @@ +- header_title group_title(@group, "Milestones", group_milestones_path(@group)) diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml index ba30e6e07c6..41dffdd2fb8 100644 --- a/app/views/groups/milestones/_milestone.html.haml +++ b/app/views/groups/milestones/_milestone.html.haml @@ -1,25 +1,29 @@ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - .pull-right - - if can?(current_user, :admin_group, @group) - - if milestone.closed? - = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" - - else - = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - %h4 - = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + %strong + = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) + .col-sm-6 + .pull-right.light #{milestone.percent_complete}% complete .row .col-sm-6 = link_to issues_group_path(@group, milestone_title: milestone.title) do = pluralize milestone.issue_count, 'Issue' - + · = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do = pluralize milestone.merge_requests_count, 'Merge Request' - - %span.light #{milestone.percent_complete}% complete .col-sm-6 = milestone_progress_bar(milestone) - %div - - milestone.milestones.each do |milestone| - = link_to milestone_path(milestone) do - %span.label.label-gray - = milestone.project.name + .row + .col-sm-6 + %div + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name + .col-sm-6 + - if can?(current_user, :admin_group, @group) + - if milestone.closed? + = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen" + - else + = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close" diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 385222fa5b7..2bbcad5fdfb 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,23 +1,17 @@ - page_title "Milestones" -%h3.page-title - Milestones - %span.pull-right #{@group_milestones.count} milestones +- header_title group_title(@group, "Milestones", group_milestones_path(@group)) -%p.light += render 'shared/milestones_filter' +.gray-content-block Only milestones from %strong #{@group.name} group are listed here. - -%hr - -= render 'shared/milestones_filter' .milestones - .panel.panel-default - %ul.well-list - - if @group_milestones.blank? - %li - .nothing-here-block No milestones to show - - else - - @group_milestones.each do |milestone| - = render 'milestone', milestone: milestone + %ul.content-list + - if @group_milestones.blank? + %li + .nothing-here-block No milestones to show + - else + - @group_milestones.each do |milestone| + = render 'milestone', milestone: milestone = paginate @group_milestones, theme: "gitlab" diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 8f2decb851f..0c213f42186 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -1,4 +1,6 @@ - page_title @group_milestone.title, "Milestones" += render "header_title" + %h4.page-title .issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } - if @group_milestone.closed? diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index d06cfa7ff9f..f1d507a50c7 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,4 +1,6 @@ - page_title "Projects" +- header_title group_title(@group, "Projects", projects_group_path(@group)) + .panel.panel-default .panel-heading %strong= @group.name diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 0577f4ec142..a9ba9d2ba10 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,3 +1,6 @@ +- unless can?(current_user, :read_group, @group) + - @disable_search_panel = true + = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") @@ -16,22 +19,25 @@ = render 'shared/show_aside' - .row - %section.activities.col-md-7 - .hidden-xs - - if current_user - = render "events/event_last_push", event: @last_push - + - if can?(current_user, :read_group, @group) + .row + %section.activities.col-md-7 + .hidden-xs - if current_user + = render "events/event_last_push", event: @last_push + %ul.nav.nav-pills.event_filter.pull-right %li = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do %i.fa.fa-rss - = render 'shared/event_filter' - %hr + = render 'shared/event_filter' + %hr - .content_list - = spinner - %aside.side.col-md-5 - = render "projects", projects: @projects + .content_list + = spinner + %aside.side.col-md-5 + = render "projects", projects: @projects + - else + %p + This group does not have public projects diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index bf4b7234b21..57bc91ea5a9 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -1,14 +1,15 @@ %div %h1 GitLab - %span= Gitlab::VERSION - %small= Gitlab::REVISION - - if current_application_settings.version_check_enabled + Community Edition + - if user_signed_in? + %span= Gitlab::VERSION + %small= Gitlab::REVISION = version_status_badge %p.slead GitLab is open source software to collaborate on code. %br - Manage git repositories with fine grained access controls that keep your code secure. + Manage git repositories with fine-grained access controls that keep your code secure. %br Perform code reviews and enhance collaboration with merge requests. %br @@ -17,6 +18,9 @@ Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}. + - if current_application_settings.help_page_text.present? + %hr + = markdown(current_application_settings.help_page_text) %hr @@ -24,29 +28,14 @@ .col-md-8 .documentation-index = preserve do - - readme_text = File.read(Rails.root.join("doc", "README.md")) - - text = readme_text.dup - - readme_text.scan(/\]\(([^(]+)\)/) { |match| text.gsub!(match.first, "help/#{match.first}") } - = markdown text - + = markdown(@help_index) .col-md-4 .panel.panel-default .panel-heading Quick help %ul.well-list - %li - See our website for - = link_to 'getting help', promo_url + '/getting-help/' - %li - Use the - = link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)' - on the top of this page - %li - Use - = link_to 'shortcuts', '#', onclick: 'Shortcuts.showHelp(event)' - %li - Get a support - = link_to 'subscription', 'https://about.gitlab.com/pricing/' - %li - = link_to 'Compare', 'https://about.gitlab.com/features/#compare' - GitLab editions + %li= link_to 'See our website for getting help', promo_url + '/getting-help/' + %li= link_to 'Use the search bar on the top of this page', '#', onclick: 'Shortcuts.focusSearch(event)' + %li= link_to 'Use shortcuts', '#', onclick: 'Shortcuts.showHelp(event)' + %li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/' + %li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare' diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index 8551496b98a..0398afb4c1d 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,3 @@ - page_title @file.humanize, *@category.split("/").reverse.map(&:humanize) .documentation.wiki - = markdown @markdown.gsub('$your_email', current_user.email) + = markdown @markdown.gsub('$your_email', current_user.try(:email) || "email@example.com") diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml new file mode 100644 index 00000000000..e1bb88ca4ed --- /dev/null +++ b/app/views/import/fogbugz/new.html.haml @@ -0,0 +1,25 @@ +- page_title "FogBugz Import" +%h3.page-title + %i.fa.fa-bug + Import projects from FogBugz +%hr + += form_tag callback_import_fogbugz_path, class: 'form-horizontal' do + %p + To get started you enter your FogBugz URL and login information below. + In the next steps, you'll be able to map users and select the projects + you want to import. + .form-group + = label_tag :uri, 'FogBugz URL', class: 'control-label' + .col-sm-4 + = text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control' + .form-group + = label_tag :email, 'FogBugz Email', class: 'control-label' + .col-sm-4 + = text_field_tag :email, nil, class: 'form-control' + .form-group + = label_tag :password, 'FogBugz Password', class: 'control-label' + .col-sm-4 + = password_field_tag :password, nil, class: 'form-control' + .form-actions + = submit_tag 'Continue to the next step', class: 'btn btn-create' diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml new file mode 100644 index 00000000000..25cebfb3665 --- /dev/null +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -0,0 +1,49 @@ +- page_title 'User map', 'FogBugz import' +%h3.page-title + %i.fa.fa-bug + Import projects from FogBugz +%hr + += form_tag create_user_map_import_fogbugz_path, class: 'form-horizontal' do + %p + Customize how FogBugz email addresses and usernames are imported into GitLab. + In the next step, you'll be able to select the projects you want to import. + %p + The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames wil be imported into GitLab. You can change this by populating the table below. + %ul + %li + %strong Default: Map a FogBugz account ID to a full name + %p + An empty GitLab User field will add the FogBugz user's full name + (e.g. "By John Smith") in the description of all issues and comments. + It will also associate and/or assign these issues and comments with + the project creator. + %li + %strong Map a FogBugz account ID to a GitLab user + %p + Selecting a GitLab user will add a link to the GitLab user in the descriptions + of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also + associate and/or assign these issues and comments with the selected user. + + %table.table + %thead + %tr + %th ID + %th Name + %th Email + %th GitLab User + %tbody + - @user_map.each do |id, user| + %tr + %td= id + %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control' + %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control' + %td + = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control', + scope: :all, email_user: true, selected: user[:gitlab_user]) + + .form-actions + = submit_tag 'Continue to the next step', class: 'btn btn-create' + +:coffeescript + new UsersSelect() diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml new file mode 100644 index 00000000000..f179ece402d --- /dev/null +++ b/app/views/import/fogbugz/status.html.haml @@ -0,0 +1,51 @@ +- page_title "FogBugz import" +%h3.page-title + %i.fa.fa-bug + Import projects from FogBugz + +- if @repos.any? + %p.light + Select projects you want to import. + %p.light + Optionally, you can + = link_to 'customize', new_user_map_import_fogbugz_path + how FogBugz email addresses and usernames are imported into GitLab. + %hr + %p + = button_tag 'Import all projects', class: 'btn btn-success js-import-all' + +%table.table.import-jobs + %thead + %tr + %th From FogBugz + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td + = project.import_source + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + %td.job-status + - if project.import_status == 'finished' + %span + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo.id}"} + %td + = repo.name + %td.import-target + = "#{current_user.username}/#{repo.name}" + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}") diff --git a/app/views/kaminari/gitlab/_first_page.html.haml b/app/views/kaminari/gitlab/_first_page.html.haml index 41c9c0b3af6..ada7306d98d 100644 --- a/app/views/kaminari/gitlab/_first_page.html.haml +++ b/app/views/kaminari/gitlab/_first_page.html.haml @@ -5,5 +5,5 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.first +%li.first = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote diff --git a/app/views/kaminari/gitlab/_last_page.html.haml b/app/views/kaminari/gitlab/_last_page.html.haml index b03a206224c..3431d029bcc 100644 --- a/app/views/kaminari/gitlab/_last_page.html.haml +++ b/app/views/kaminari/gitlab/_last_page.html.haml @@ -5,5 +5,5 @@ -# num_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%span.last +%li.last = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote} diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml index 4f7996e4996..2f645186921 100644 --- a/app/views/kaminari/gitlab/_paginator.html.haml +++ b/app/views/kaminari/gitlab/_paginator.html.haml @@ -7,11 +7,16 @@ -# paginator: the paginator that renders the pagination tags inside = paginator.render do %div.gl-pagination - %ul.pagination - = prev_page_tag unless current_page.first? + %ul.pagination.clearfix + - unless current_page.first? + = first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages + = prev_page_tag - each_page do |page| - if page.left_outer? || page.right_outer? || page.inside_window? = page_tag page - elsif !page.was_truncated? = gap_tag - = next_page_tag unless current_page.last? + - unless current_page.last? + = next_page_tag + = last_page_tag unless num_pages < 5 + diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 397649dacf8..74174a72f5a 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -3,6 +3,7 @@ %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} %meta{content: "GitLab Community Edition", name: "description"} + %meta{name: 'referrer', content: 'origin-when-cross-origin'} %title= page_title diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 3c58f10e759..035fe0056d3 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,3 +1,4 @@ +- project = @target_project || @project :javascript - GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(@project.namespace, @project, type: @noteable.class, type_id: params[:id])}" + GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}" GitLab.GfmAutoComplete.setup(); diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 0104d7198df..2468687b56d 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -6,20 +6,24 @@ = brand_header_logo .gitlab-text-container %h3 GitLab + - if defined?(sidebar) && sidebar = render "layouts/nav/#{sidebar}" - elsif current_user = render 'layouts/nav/dashboard' + - else + = render 'layouts/nav/explore' + .collapse-nav = render partial: 'layouts/collapse_button' - if current_user = link_to current_user, class: 'sidebar-user' do - = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32' + = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36' .username = current_user.username .content-wrapper - %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" } + = render "layouts/flash" + %div{ class: container_class } .content - = render "layouts/flash" .clearfix = yield diff --git a/app/views/layouts/ci/_info.html.haml b/app/views/layouts/ci/_info.html.haml new file mode 100644 index 00000000000..24c68a6dbf5 --- /dev/null +++ b/app/views/layouts/ci/_info.html.haml @@ -0,0 +1,2 @@ +- if current_user && current_user.is_admin? && Ci::Runner.count.zero? + = render 'ci/shared/no_runners' diff --git a/app/views/layouts/ci/_nav_admin.html.haml b/app/views/layouts/ci/_nav_admin.html.haml new file mode 100644 index 00000000000..af2545a22d8 --- /dev/null +++ b/app/views/layouts/ci/_nav_admin.html.haml @@ -0,0 +1,33 @@ +%ul.nav.nav-sidebar + = nav_link do + = link_to admin_root_path, title: 'Back to admin', data: {placement: 'right'}, class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Back to admin + + %li.separate-item + = nav_link path: 'projects#index' do + = link_to ci_admin_projects_path do + = icon('list-alt fw') + Projects + = nav_link path: 'events#index' do + = link_to ci_admin_events_path do + = icon('book fw') + Events + = nav_link path: ['runners#index', 'runners#show'] do + = link_to ci_admin_runners_path do + = icon('cog fw') + Runners + %small.pull-right + = Ci::Runner.count(:all) + = nav_link path: 'builds#index' do + = link_to ci_admin_builds_path do + = icon('link fw') + Builds + %small.pull-right + = Ci::Build.count(:all) + = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do + = link_to ci_admin_application_settings_path do + = icon('cogs fw') + %span + Settings diff --git a/app/views/layouts/ci/_nav_project.html.haml b/app/views/layouts/ci/_nav_project.html.haml new file mode 100644 index 00000000000..545abc23d99 --- /dev/null +++ b/app/views/layouts/ci/_nav_project.html.haml @@ -0,0 +1,23 @@ +%ul.nav.nav-sidebar + = nav_link do + = link_to project_path(@project.gl_project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do + = icon('caret-square-o-left fw') + %span + Back to project + %li.separate-item + = nav_link path: ['projects#show', 'commits#show', 'builds#show'] do + = link_to ci_project_path(@project) do + = icon('list-alt fw') + %span + Commits + %span.count= @project.commits.count + = nav_link path: ['services#index', 'services#edit'] do + = link_to ci_project_services_path(@project) do + = icon('share fw') + %span + Services + = nav_link path: 'events#index' do + = link_to ci_project_events_path(@project) do + = icon('book fw') + %span + Events diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml new file mode 100644 index 00000000000..bb5ec727bff --- /dev/null +++ b/app/views/layouts/ci/_page.html.haml @@ -0,0 +1,27 @@ +.page-with-sidebar{ class: nav_sidebar_class } + = render "layouts/broadcast" + .sidebar-wrapper.nicescroll + .header-logo + = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do + = brand_header_logo + .gitlab-text-container + %h3 GitLab + + - if defined?(sidebar) && sidebar + = render "layouts/ci/#{sidebar}" + - elsif current_user + = render 'layouts/nav/dashboard' + .collapse-nav + = render partial: 'layouts/collapse_button' + - if current_user + = link_to current_user, class: 'sidebar-user' do + = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36' + .username + = current_user.username + .content-wrapper + = render "layouts/flash" + = render 'layouts/ci/info' + %div{ class: container_class } + .content + .clearfix + = yield diff --git a/app/views/layouts/ci/admin.html.haml b/app/views/layouts/ci/admin.html.haml new file mode 100644 index 00000000000..c8cb185d28c --- /dev/null +++ b/app/views/layouts/ci/admin.html.haml @@ -0,0 +1,11 @@ +!!! 5 +%html{ lang: "en"} + = render 'layouts/head' + %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page} + - header_title = "Admin area" + - if current_user + = render "layouts/header/default", title: header_title + - else + = render "layouts/header/public", title: header_title + + = render 'layouts/ci/page', sidebar: 'nav_admin' diff --git a/app/views/layouts/ci/application.html.haml b/app/views/layouts/ci/application.html.haml new file mode 100644 index 00000000000..38023468d0b --- /dev/null +++ b/app/views/layouts/ci/application.html.haml @@ -0,0 +1,11 @@ +!!! 5 +%html{ lang: "en"} + = render 'layouts/head' + %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page} + - header_title = "Continuous Integration" + - if current_user + = render "layouts/header/default", title: header_title + - else + = render "layouts/header/public", title: header_title + + = render 'layouts/ci/page' diff --git a/app/views/layouts/ci/notify.html.haml b/app/views/layouts/ci/notify.html.haml new file mode 100644 index 00000000000..270b206df5e --- /dev/null +++ b/app/views/layouts/ci/notify.html.haml @@ -0,0 +1,19 @@ +%html{lang: "en"} + %head + %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"} + %title + GitLab CI + + %body + = yield :header + + %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"} + %tr + %td{align: "left", style: "margin: 0; padding: 10px;"} + = yield + %br + %tr + %td{align: "left", style: "margin: 0; padding: 10px;"} + %p{style: "font-size:small;color:#777"} + - if @project + You're receiving this notification because you are the one who triggered a build on the #{@project.name} project. diff --git a/app/views/layouts/ci/project.html.haml b/app/views/layouts/ci/project.html.haml new file mode 100644 index 00000000000..15478c3f5bc --- /dev/null +++ b/app/views/layouts/ci/project.html.haml @@ -0,0 +1,11 @@ +!!! 5 +%html{ lang: "en"} + = render 'layouts/head' + %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page} + - header_title @project.name, ci_project_path(@project) + - if current_user + = render "layouts/header/default", title: header_title + - else + = render "layouts/header/public", title: header_title + + = render 'layouts/ci/page', sidebar: 'nav_project' diff --git a/app/views/layouts/dashboard.html.haml b/app/views/layouts/dashboard.html.haml index c72eca10bf4..cb96bcc2cf4 100644 --- a/app/views/layouts/dashboard.html.haml +++ b/app/views/layouts/dashboard.html.haml @@ -1,5 +1,5 @@ - page_title "Dashboard" -- header_title "Dashboard", root_path +- header_title "Dashboard", root_path unless header_title - sidebar "dashboard" = render template: "layouts/application" diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 1987bf1592a..95e077c339f 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -31,5 +31,5 @@ .container .footer-links = link_to "Explore", explore_root_path - = link_to "Documentation", "http://doc.gitlab.com/" + = link_to "Help", help_path = link_to "About GitLab", "https://about.gitlab.com/" diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml index 17fee9c510d..df65792be73 100644 --- a/app/views/layouts/explore.html.haml +++ b/app/views/layouts/explore.html.haml @@ -1,8 +1,5 @@ - page_title "Explore" -- if current_user - - header_title "Dashboard", root_path -- else +- unless current_user - header_title "Explore GitLab", explore_root_path -- sidebar "dashboard" = render template: "layouts/application" diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index db7dbf9bfe3..31888c5580e 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,5 +1,5 @@ - page_title @group.name -- header_title @group.name, group_path(@group) -- sidebar "group" unless sidebar +- header_title group_title(@group) unless header_title +- sidebar "group" unless sidebar = render template: "layouts/application" diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml index e303a561628..a1a1fc2f858 100644 --- a/app/views/layouts/group_settings.html.haml +++ b/app/views/layouts/group_settings.html.haml @@ -1,4 +1,5 @@ - page_title "Settings" +- header_title group_title(@group, "Settings", edit_group_path(@group)) - sidebar "group_settings" = render template: "layouts/group" diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 0b630b55c70..c31b1cbe9a8 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,5 +1,5 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" } + %div{ class: fluid_layout ? "container-fluid" : "container-fluid" } .header-content %button.navbar-toggle{type: 'button'} %span.sr-only Toggle navigation @@ -7,8 +7,9 @@ .navbar-collapse.collapse %ul.nav.navbar-nav.pull-right - %li.hidden-sm.hidden-xs - = render 'layouts/search' + - unless @disable_search_panel + %li.hidden-sm.hidden-xs + = render 'layouts/search' %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('search') @@ -17,7 +18,7 @@ = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('wrench fw') - if current_user.can_create_project? - %li.hidden-xs + %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('plus fw') %li diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml index af4b9ba58f6..a6a26518a0e 100644 --- a/app/views/layouts/header/_public.html.haml +++ b/app/views/layouts/header/_public.html.haml @@ -1,9 +1,9 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" } + %div{ class: fluid_layout ? "container-fluid" : "container-fluid" } .header-content - unless current_controller?('sessions') .pull-right - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success btn-sm' + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' %h1.title= title diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 2065be3828a..2079feeeab6 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -4,7 +4,7 @@ = icon('dashboard fw') %span Overview - = nav_link(controller: :projects) do + = nav_link(controller: [:admin, :projects]) do = link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do = icon('cube fw') %span @@ -24,6 +24,11 @@ = icon('key fw') %span Deploy Keys + = nav_link do + = link_to ci_admin_projects_path, title: 'Continuous Integration', data: {placement: 'right'} do + = icon('building fw') + %span + Continuous Integration = nav_link(controller: :logs) do = link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do = icon('file-text fw') @@ -57,6 +62,12 @@ %span Service Templates + = nav_link(controller: :labels) do + = link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do + = icon('tags fw') + %span + Labels + = nav_link(controller: :abuse_reports) do = link_to admin_abuse_reports_path, title: "Abuse reports" do = icon('exclamation-circle fw') diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 0cf1c3d5d27..b1a1d531846 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,31 +1,30 @@ %ul.nav.nav-sidebar - = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do - = link_to (current_user ? root_path : explore_root_path), title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do + = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do + = link_to dashboard_projects_path, title: 'Projects', data: {placement: 'right'} do = icon('home fw') %span Projects = nav_link(path: 'dashboard#activity') do - = link_to activity_dashboard_path, title: 'Activity', data: {placement: 'right'} do + = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity', data: {placement: 'right'} do = icon('dashboard fw') %span Activity = nav_link(controller: :groups) do - = link_to (current_user ? dashboard_groups_path : explore_groups_path), title: 'Groups', data: {placement: 'right'} do + = link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do = icon('group fw') %span Groups - - if current_user - = nav_link(controller: :milestones) do - = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do - = icon('clock-o fw') - %span - Milestones - = nav_link(path: 'dashboard#issues') do - = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do - = icon('exclamation-circle fw') - %span - Issues - %span.count= current_user.assigned_issues.opened.count + = nav_link(controller: :milestones) do + = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do + = icon('clock-o fw') + %span + Milestones + = nav_link(path: 'dashboard#issues') do + = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do + = icon('exclamation-circle fw') + %span + Issues + %span.count= current_user.assigned_issues.opened.count = nav_link(path: 'dashboard#merge_requests') do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do = icon('tasks fw') @@ -33,18 +32,19 @@ Merge Requests %span.count= current_user.assigned_merge_requests.opened.count = nav_link(controller: :snippets) do - = link_to (current_user ? user_snippets_path(current_user) : snippets_path), title: 'Your snippets', data: {placement: 'right'} do + = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do = icon('clipboard fw') %span Snippets - - if current_user - = nav_link(controller: :profile) do - = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('user fw') - %span - Profile Settings = nav_link(controller: :help) do = link_to help_path, title: 'Help', data: {placement: 'right'} do = icon('question-circle fw') %span Help + + %li.separate-item + = nav_link(controller: :profile) do + = link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do + = icon('user fw') + %span + Profile Settings diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml new file mode 100644 index 00000000000..21e565972a7 --- /dev/null +++ b/app/views/layouts/nav/_explore.html.haml @@ -0,0 +1,21 @@ +%ul.nav.nav-sidebar + = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do + = link_to explore_root_path, title: 'Projects', data: {placement: 'right'} do + = icon('home fw') + %span + Projects + = nav_link(controller: :groups) do + = link_to explore_groups_path, title: 'Groups', data: {placement: 'right'} do + = icon('group fw') + %span + Groups + = nav_link(controller: :snippets) do + = link_to explore_snippets_path, title: 'Snippets', data: {placement: 'right'} do + = icon('clipboard fw') + %span + Snippets + = nav_link(controller: :help) do + = link_to help_path, title: 'Help', data: {placement: 'right'} do + = icon('question-circle fw') + %span + Help diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 695ce68a201..eb35af22b93 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -3,7 +3,7 @@ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do = icon('caret-square-o-left fw') %span - Back to Dashboard + Back to dashboard %li.separate-item @@ -12,34 +12,35 @@ = icon('dashboard fw') %span Group - - if current_user - = nav_link(controller: [:group, :milestones]) do - = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do - = icon('clock-o fw') + - if can?(current_user, :read_group, @group) + - if current_user + = nav_link(controller: [:group, :milestones]) do + = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do + = icon('clock-o fw') + %span + Milestones + = nav_link(path: 'groups#issues') do + = link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do + = icon('exclamation-circle fw') %span - Milestones - = nav_link(path: 'groups#issues') do - = link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do - = icon('exclamation-circle fw') - %span - Issues - - if current_user - %span.count= Issue.opened.of_group(@group).count - = nav_link(path: 'groups#merge_requests') do - = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do - = icon('tasks fw') - %span - Merge Requests - - if current_user - %span.count= MergeRequest.opened.of_group(@group).count - = nav_link(controller: [:group_members]) do - = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do - = icon('users fw') - %span - Members - - if can?(current_user, :admin_group, @group) - = nav_link(html_options: { class: "separate-item" }) do - = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do - = icon ('cogs fw') + Issues + - if current_user + %span.count= Issue.opened.of_group(@group).count + = nav_link(path: 'groups#merge_requests') do + = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do + = icon('tasks fw') + %span + Merge Requests + - if current_user + %span.count= MergeRequest.opened.of_group(@group).count + = nav_link(controller: [:group_members]) do + = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do + = icon('users fw') %span - Settings + Members + - if can?(current_user, :admin_group, @group) + = nav_link(html_options: { class: "separate-item" }) do + = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do + = icon ('cogs fw') + %span + Settings diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 33fd5fcef6c..5a47b8e6db2 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -3,7 +3,7 @@ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do = icon('caret-square-o-left fw') %span - Back to Dashboard + Back to dashboard %li.separate-item @@ -11,7 +11,7 @@ = link_to profile_path, title: 'Profile', data: {placement: 'right'} do = icon('user fw') %span - Profile + Profile Settings = nav_link(controller: [:accounts, :two_factor_auths]) do = link_to profile_account_path, title: 'Account', data: {placement: 'right'} do = icon('gear fw') diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 5e7b902622b..a218ec7486c 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -4,13 +4,13 @@ = link_to group_path(@project.group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do = icon('caret-square-o-left fw') %span - Back to Group + Back to group - else = nav_link do = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do = icon('caret-square-o-left fw') %span - Back to Dashboard + Back to dashboard %li.separate-item @@ -26,28 +26,28 @@ Activity - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do - = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do + = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do = icon('files-o fw') %span Files - if project_nav_tab? :commits = nav_link(controller: %w(commit commits compare repositories tags branches)) do - = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do + = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do = icon('history fw') %span Commits - if project_nav_tab? :network = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do + = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do = icon('code-fork fw') %span Network - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do - = link_to namespace_project_graph_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do + = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do = icon('area-chart fw') %span Graphs @@ -76,6 +76,13 @@ Merge Requests %span.count.merge_counter= @project.merge_requests.opened.count + - if @project.gitlab_ci? + = nav_link(controller: [:ci, :project]) do + = link_to ci_project_path(@project.gitlab_ci_project), title: 'Continuous Integration', data: {placement: 'right'} do + = icon('building fw') + %span + Continuous Integration + - if project_nav_tab? :settings = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 857fb199957..9279a846623 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -34,3 +34,29 @@ %span Protected Branches + - if @project.gitlab_ci? + = nav_link(controller: :runners) do + = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do + = icon('cog fw') + %span + Runners + = nav_link(controller: :variables) do + = link_to namespace_project_variables_path(@project.namespace, @project) do + = icon('code fw') + %span + Variables + = nav_link path: 'triggers#index' do + = link_to namespace_project_triggers_path(@project.namespace, @project) do + = icon('retweet fw') + %span + Triggers + = nav_link path: 'ci_web_hooks#index' do + = link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do + = icon('link fw') + %span + CI Web Hooks + = nav_link path: 'ci_settings#edit' do + = link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do + = icon('building fw') + %span + CI Settings diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index ec209c38eed..2f7d7e86f56 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -42,5 +42,3 @@ - else #{link_to "View it on GitLab", @target_url} = email_action @target_url - - if @project && !@disable_footer - You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team. diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 77d2ccbf762..dfa6cc5702e 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,5 +1,5 @@ - page_title "Profile Settings" -- header_title "Profile Settings", profile_path +- header_title "Profile Settings", profile_path unless header_title - sidebar "profile" = render template: "layouts/application" diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 44afa33dfe5..abf73bcc709 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -1,12 +1,13 @@ - page_title @project.name_with_namespace -- header_title project_title(@project) -- sidebar "project" unless sidebar +- header_title project_title(@project) unless header_title +- sidebar "project" unless sidebar - content_for :scripts_body_top do + - project = @target_project || @project - if current_user :javascript - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; - window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}"; + window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}"; + window.markdown_preview_path = "#{markdown_preview_namespace_project_path(project.namespace, project)}"; - content_for :scripts_body do = render "layouts/init_auto_complete" if current_user diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index 43401668334..59ce38f67bb 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -1,4 +1,5 @@ - page_title "Settings" +- header_title project_title(@project, "Settings", edit_project_path(@project)) - sidebar "project_settings" = render template: "layouts/project" diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml index b77fe09fc2a..02ca3ee7a28 100644 --- a/app/views/layouts/snippets.html.haml +++ b/app/views/layouts/snippets.html.haml @@ -1,8 +1,3 @@ -- page_title 'Snippets' -- if current_user - - header_title "Dashboard", root_path -- else - - header_title 'Snippets', snippets_path -- sidebar "dashboard" +- header_title "Snippets", snippets_path = render template: "layouts/application" diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml index 9db75bdb19e..34dbc60e19b 100644 --- a/app/views/notify/merged_merge_request_email.text.haml +++ b/app/views/notify/merged_merge_request_email.text.haml @@ -1,6 +1,6 @@ = "Merge Request ##{@merge_request.iid} was merged" -Merge Request Url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} +Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} = merge_path_description(@merge_request, 'to') diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml index 4feacdaacff..6b9b42dcf37 100644 --- a/app/views/notify/new_user_email.html.haml +++ b/app/views/notify/new_user_email.html.haml @@ -13,4 +13,4 @@ %p = link_to "Click here to set your password", edit_password_url(@user, reset_password_token: @token) %p - = reset_token_expire_message + = raw reset_token_expire_message diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml index 3cd759f1f57..87b3ff7f0b3 100644 --- a/app/views/notify/project_was_moved_email.html.haml +++ b/app/views/notify/project_was_moved_email.html.haml @@ -1,5 +1,5 @@ %p - Project was moved to another location + Project #{@old_path_with_namespace} was moved to another location %p The project is now located under = link_to namespace_project_url(@project.namespace, @project) do diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb index b3f18b35a4d..d8a23dabf49 100644 --- a/app/views/notify/project_was_moved_email.text.erb +++ b/app/views/notify/project_was_moved_email.text.erb @@ -1,4 +1,4 @@ -Project was moved to another location +Project #{@old_path_with_namespace} was moved to another location The project is now located under <%= namespace_project_url(@project.namespace, @project) %> diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 767fe2e0e9a..cd7b1b0fe03 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,9 +1,7 @@ - page_title "Account" -%h3.page-title - = page_title -%p.light - Change your username and basic account settings. -%hr +- header_title page_title, profile_account_path +- @blank_container = true + - if current_user.ldap_user? .alert.alert-info Some options are unavailable for LDAP accounts @@ -69,7 +67,7 @@ - button_based_providers.each do |provider| .btn-group = link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: "btn btn-lg #{'active' if auth_active?(provider)}", "data-no-turbolink" => "true" - + - if auth_active?(provider) = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do = icon('close') diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index 3a3e6e1b1c4..2342936a5d5 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -1,13 +1,12 @@ - page_title "Applications" -%h3.page-title - = page_title -%p.light +- header_title page_title, applications_profile_path + +.gray-content-block.top-block - if user_oauth_applications? - Manage applications that can use GitLab as an OAuth provider, + Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account. - else Manage applications that you've authorized to use your account. -%hr - if user_oauth_applications? .oauth-applications diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index 698d6037428..8fdba45b193 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,5 +1,8 @@ - page_title "Audit Log" -%h3.page-title Audit Log -%p.light History of authentications +- header_title page_title, audit_log_profile_path -= render 'event_table', events: @events
\ No newline at end of file +.gray-content-block.top-block + History of authentications + +.prepend-top-default += render 'event_table', events: @events diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 66812872c41..1d140347a5f 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,12 +1,10 @@ - page_title "Emails" -%h3.page-title - = page_title -%p.light - Control emails linked to your account -%hr +- header_title page_title, profile_emails_path +.gray-content-block.top-block + Control emails linked to your account -%ul +%ul.prepend-top-default %li Your %b Primary Email diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 06655f7ba3a..14adba1c797 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,11 +1,12 @@ - page_title "SSH Keys" -%h3.page-title - = page_title +- header_title page_title, profile_keys_path + +.gray-content-block.top-block .pull-right = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" -%p.light - Before you can add an SSH key you need to - = link_to "generate it.", help_page_path("ssh", "README") -%hr + .oneline + Before you can add an SSH key you need to + = link_to "generate it.", help_page_path("ssh", "README") +.prepend-top-default = render 'key_table' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index db7fa2eabe3..8eebd96b674 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,10 +1,10 @@ - page_title "Notifications" -%h3.page-title - = page_title -%p.light +- header_title page_title, profile_notifications_path + +.gray-content-block.top-block These are your global notification settings. -%hr +.prepend-top-default = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f| -if @user.errors.any? %div.alert.alert-danger @@ -33,7 +33,7 @@ = f.label :notification_level, value: Notification::N_MENTION do = f.radio_button :notification_level, Notification::N_MENTION .level-title - Mention + On Mention %p You will receive notifications only for comments in which you were @mentioned .radio diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 399ae98adf9..fab7c45c9b2 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,13 +1,13 @@ - page_title "Password" -%h3.page-title - = page_title -%p.light +- header_title page_title, edit_profile_password_path + +.gray-content-block.top-block - if @user.password_automatically_set? Set your password. - else Change your password or recover your current one. -%hr -.update-password + +.update-password.prepend-top-default = form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f| %div %p.slead diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index 9c6204963e0..d165f758c81 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -12,7 +12,7 @@ %ul - @user.errors.full_messages.each do |msg| %li= msg - + - unless @user.password_automatically_set? .form-group = f.label :current_password, class: 'control-label' diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index aa0361a0a1b..01e285a8dfa 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,11 +1,11 @@ - page_title 'Preferences' -%h3.page-title - = page_title -%p.light +- header_title page_title, profile_preferences_path +- @blank_container = true + +.alert.alert-help These settings allow you to customize the appearance and behavior of the site. They are saved with your account and will persist to any device you use to access the site. -%hr = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f| .panel.panel-default.application-theme @@ -33,6 +33,13 @@ Behavior .panel-body .form-group + = f.label :layout, class: 'control-label' do + Layout width + .col-sm-10 + = f.select :layout, layout_choices, {}, class: 'form-control' + .help-block + Choose between fixed (max. 1200px) and fluid (100%) application layout + .form-group = f.label :dashboard, class: 'control-label' do Default Dashboard = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank') diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb index 6c4b0ce757d..4433cab7782 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -2,6 +2,13 @@ $('body').removeClass('<%= Gitlab::Themes.body_classes %>') $('body').addClass('<%= user_application_theme %>') +// Toggle container-fluid class +if ('<%= current_user.layout %>' === 'fluid') { + $('.content-wrapper').find('.container-fluid').removeClass('container-limited') +} else { + $('.content-wrapper').find('.container-fluid').addClass('container-limited') +} + // Re-enable the "Save" button $('input[type=submit]').enable() diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index c519e52e596..47412e2ef0c 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,14 +1,9 @@ -- page_title "Profile" -%h3.page-title - = page_title -%p.light +.gray-content-block.top-block This information will appear on your profile. - if current_user.ldap_user? Some options are unavailable for LDAP accounts -%hr - - +.prepend-top-default = form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f| -if @user.errors.any? %div.alert.alert-danger diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index ee02b7f6a6c..1261f6254d7 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,5 +1,5 @@ = render 'projects/last_push' -.hidden-xs +.gray-content-block.activity-filter-block - if current_user %ul.nav.nav-pills.event_filter.pull-right %li @@ -7,7 +7,6 @@ %i.fa.fa-rss = render 'shared/event_filter' - %hr .content_list{:"data-href" => activity_project_path(@project)} = spinner diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index b93036e78e6..8c0980369fd 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -7,24 +7,28 @@ - if @project.description.present? = markdown(@project.description, pipeline: :description) - - .project-repo-buttons - = render 'projects/buttons/star' - - - unless empty_repo - = render 'projects/buttons/fork' - - - if forked_from_project = @project.forked_from_project - = link_to project_path(forked_from_project), class: 'btn' do - = icon("code-fork fw") - Forked from + - if forked_from_project = @project.forked_from_project + %p + Forked from + = link_to project_path(forked_from_project) do = forked_from_project.namespace.try(:name) - - if can? current_user, :download_code, @project - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do - = icon('download fw') - Download - = render 'projects/buttons/dropdown' - = render "shared/clone_panel" + .project-repo-buttons + .split-one + = render 'projects/buttons/star' + + - unless empty_repo + = render 'projects/buttons/fork' + + = render "shared/clone_panel" + .split-repo-buttons + - unless empty_repo + - if can? current_user, :download_code, @project + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do + = icon('download fw') + + = render 'projects/buttons/dropdown' + = render 'projects/buttons/notifications' + diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index 30622d8a910..f0a3e416db7 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,14 +1,15 @@ - if event = last_push_event - if show_last_push_widget?(event) - .hidden-xs.center - .slead - %span You pushed to - = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do - %strong= event.ref_name - branch - #{time_ago_with_tooltip(event.created_at)} - %div - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do - Create Merge Request - %hr + .gray-content-block.top-block.clear-block.hidden-xs + .event-last-push + .event-last-push-text + %span You pushed to + = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do + %strong= event.ref_name + branch + #{time_ago_with_tooltip(event.created_at)} + + .pull-right + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do + Create Merge Request diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index b7bca6dae09..507757f6a2b 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -1,6 +1,6 @@ .md-area .md-header.clearfix - %ul.nav.nav-tabs + %ul.center-top-menu %li.active = link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do Write @@ -14,7 +14,7 @@ You are about to add %strong %span.js-referenced-users-count 0 - people + people to the discussion. Proceed with caution. %div diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index 5038edb95ed..5bc1999ec9d 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -5,7 +5,7 @@ - if can?(current_user, :push_code, @project) = link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do - %i.fa.fa-pencil + %i.fa-align.fa.fa-pencil .wiki = cache(readme_cache_key) do = render_readme(readme) diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index 65674913bb0..555ed76426d 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -1 +1,4 @@ +- page_title "Activity" +- header_title project_title(@project, "Activity", activity_project_path(@project)) + = render 'projects/activity' diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index c1ec42aefca..6518c4173e1 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,4 +1,6 @@ - page_title "Blame", @blob.path, @ref +- header_title project_title(@project, "Files", project_files_path(@project)) + %h3.page-title Blame view #tree-holder.tree-holder diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 13f8271b979..373b3a0c5b0 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -17,6 +17,6 @@ tree_join(@commit.sha, @path)), class: 'btn btn-sm' - if allowed_tree_edit? - = button_tag class: 'remove-blob btn btn-sm btn-remove', - 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do - Remove + .btn-group{ role: "group" } + %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace + %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Remove diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 65c3ab10e02..b4c7d8b9b71 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -15,7 +15,7 @@ - else = link_to title, '#' -%ul.blob-commit-info.well.hidden-xs +%ul.blob-commit-info.hidden-xs - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path) = render blob_commit, project: @project diff --git a/app/views/projects/blob/_header_title.html.haml b/app/views/projects/blob/_header_title.html.haml new file mode 100644 index 00000000000..78c5ef20a5f --- /dev/null +++ b/app/views/projects/blob/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Files", project_files_path(@project)) diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml new file mode 100644 index 00000000000..1a1df127703 --- /dev/null +++ b/app/views/projects/blob/_upload.html.haml @@ -0,0 +1,28 @@ +#modal-upload-blob.modal + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3.page-title #{title} + %p.light + From branch + %strong= @ref + .modal-body + = form_tag form_path, method: method, class: 'blob-file-upload-form-js form-horizontal' do + .dropzone + .dropzone-previews.blob-upload-dropzone-previews + %p.dz-message.light + Attach a file by drag & drop or + = link_to 'click to upload', '#', class: "markdown-selector" + %br + .dropzone-alerts{class: "alert alert-danger data", style: "display:none"} + = render 'shared/commit_message_container', params: params, + placeholder: placeholder + .form-group + .col-sm-offset-2.col-sm-10 + = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all' + = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" + +:coffeescript + disableButtonIfEmptyField $('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file' + new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}') diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index a12cd660fc1..a811adc5094 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,6 +1,8 @@ - page_title "Edit", @blob.path, @ref += render "header_title" + .file-editor - %ul.nav.nav-tabs.js-edit-mode + %ul.center-top-menu.no-bottom.js-edit-mode %li.active = link_to '#editor' do %i.fa.fa-edit diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 7c2a4fece94..1950586b112 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,5 +1,14 @@ -- page_title "New File", @ref -%h3.page-title New file +- page_title "New File", @path.presence, @ref += render "header_title" + +.gray-content-block.top-block + Create a new file or + = link_to 'upload', '#modal-upload-blob', + { class: 'upload-link', 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} + an existing one + += render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post + .file-editor = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do = render 'projects/blob/editor', ref: @ref diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index bd2fc43633c..fa4be4a1bc4 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,4 +1,5 @@ - page_title @blob.path, @ref += render "header_title" = render 'projects/last_push' @@ -10,3 +11,8 @@ - if allowed_tree_edit? = render 'projects/blob/remove' + + - title = "Replace #{@blob.name}" + = render 'projects/blob/upload', title: title, placeholder: title, + button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), + method: :put diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index a693c4b282f..cc0ec9483d2 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,20 +1,20 @@ - commit = @repository.commit(branch.target) %li(class="js-branch-#{branch.name}") - %h4 + %div = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do %strong.str-truncated= branch.name + - if branch.name == @repository.root_ref - %span.label.label-info default + %span.label.label-primary default - elsif @repository.merged_to_root_ref? branch.name - %span.label.label-primary.has_tooltip(title="Merged into #{@repository.root_ref}") - %i.fa.fa-check + %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}") merged - if @project.protected_branch? branch.name %span.label.label-success %i.fa.fa-lock protected - .pull-right + .controls.hidden-xs - if create_mr_button?(@repository.root_ref, branch.name) = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do = icon('plus') @@ -30,8 +30,7 @@ = icon("trash-o") - if commit - %ul.list-unstyled - = render 'projects/commits/inline_commit', commit: commit, project: @project + = render 'projects/branches/commit', commit: commit, project: @project - else %p Cant find HEAD commit for this branch diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml new file mode 100644 index 00000000000..68326e65d85 --- /dev/null +++ b/app/views/projects/branches/_commit.html.haml @@ -0,0 +1,7 @@ +.branch-commit.light + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" + · + %span.str-truncated + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + · + #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 80acc937908..03ade02a0c8 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,7 +1,7 @@ - page_title "Branches" += render "projects/commits/header_title" = render "projects/commits/head" -%h3.page-title - Branches +.gray-content-block .pull-right - if can? current_user, :push_code, @project = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do @@ -24,9 +24,10 @@ = sort_title_recently_updated = link_to namespace_project_branches_path(sort: 'last_updated') do = sort_title_oldest_updated -%hr + .oneline + Protected branches can be managed in project settings - unless @branches.empty? - %ul.bordered-list.top-list.all-branches + %ul.content-list.all-branches - @branches.each do |branch| = render "projects/branches/branch", branch: branch = paginate @branches, theme: 'gitlab' diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 29e82b93883..f5577042ca4 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -1,4 +1,6 @@ - page_title "New Branch" += render "projects/commits/header_title" + - if @error .alert.alert-danger %button{ type: "button", class: "close", "data-dismiss" => "alert"} × diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml new file mode 100644 index 00000000000..21c543b38dd --- /dev/null +++ b/app/views/projects/builds/_build.html.haml @@ -0,0 +1,50 @@ +- gl_project = build.project.gl_project +%tr.build + %td.status + = ci_status_with_icon(build.status) + + %td.build-link + = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do + %strong Build ##{build.id} + + - if defined?(ref) + %td + = build.ref + + %td + = build.stage + + %td + = build.name + .pull-right + - if build.tags.any? + - build.tag_list.each do |tag| + %span.label.label-primary + = tag + - if build.trigger_request + %span.label.label-info triggered + - if build.allow_failure + %span.label.label-danger allowed to fail + + %td.duration + - if build.duration + #{duration_in_words(build.finished_at, build.started_at)} + + %td.timestamp + - if build.finished_at + %span #{time_ago_in_words build.finished_at} ago + + - if build.project.coverage_enabled? + %td.coverage + - if build.coverage + #{build.coverage}% + + %td + - if defined?(controls) && current_user && can?(current_user, :manage_builds, gl_project) + .pull-right + - if build.active? + = link_to cancel_ci_project_build_path(build.project, build, return_to: request.original_url), title: 'Cancel build' do + %i.fa.fa-remove.cred + - elsif build.commands.present? + = link_to retry_ci_project_build_path(build.project, build, return_to: request.original_url), method: :post, title: 'Retry build' do + %i.fa.fa-repeat diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml new file mode 100644 index 00000000000..93cd4dcfd93 --- /dev/null +++ b/app/views/projects/builds/show.html.haml @@ -0,0 +1,159 @@ +.build-page + .gray-content-block + Build for commit + %strong.monospace + = link_to @build.commit.short_sha, ci_status_path(@build.commit) + from + %code #{@build.ref} + + #up-build-trace + - if @commit.matrix_for_ref?(@build.ref) + %ul.center-top-menu.build-top-menu + - @commit.builds_without_retry_for_ref(@build.ref).each do |build| + %li{class: ('active' if build == @build) } + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + + + - unless @commit.builds_without_retry_for_ref(@build.ref).include?(@build) + %li.active + %a + Build ##{@build.id} + · + %i.fa.fa-warning-sign + This build was retried. + + .gray-content-block.second-block + .build-head + .clearfix + = ci_status_with_icon(@build.status) + - if @build.duration + %span + %i.fa.fa-time + #{duration_in_words(@build.finished_at, @build.started_at)} + .pull-right + = @build.updated_at.stamp('19:00 Aug 27') + + .row.prepend-top-default + .col-md-9 + .clearfix + - if @build.active? + .autoscroll-container + %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll + .clearfix + .scroll-controls + = link_to '#up-build-trace', class: 'btn' do + %i.fa.fa-angle-up + = link_to '#down-build-trace', class: 'btn' do + %i.fa.fa-angle-down + + %pre.trace#build-trace + %code.bash + = preserve do + = raw @build.trace_html + %div#down-build-trace + + .col-md-3 + - if @build.coverage + .build-widget + %h4.title + Test coverage + %h1 #{@build.coverage}% + + + .build-widget + %h4.title + Build + - if current_user && can?(current_user, :manage_builds, @project) + .pull-right + - if @build.active? + = link_to "Cancel", cancel_ci_project_build_path(@ci_project, @build), class: 'btn btn-sm btn-danger' + - elsif @build.commands.present? + = link_to "Retry", retry_ci_project_build_path(@ci_project, @build), class: 'btn btn-sm btn-primary', method: :post + + - if @build.duration + %p + %span.attr-name Duration: + #{duration_in_words(@build.finished_at, @build.started_at)} + %p + %span.attr-name Created: + #{time_ago_in_words(@build.created_at)} ago + - if @build.finished_at + %p + %span.attr-name Finished: + #{time_ago_in_words(@build.finished_at)} ago + %p + %span.attr-name Runner: + - if @build.runner && current_user && current_user.admin + \#{link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id)} + - elsif @build.runner + \##{@build.runner.id} + + - if @build.trigger_request + .build-widget + %h4.title + Trigger + + %p + %span.attr-name Token: + #{@build.trigger_request.trigger.short_token} + + - if @build.trigger_request.variables + %p + %span.attr-name Variables: + + %code + - @build.trigger_request.variables.each do |key, value| + #{key}=#{value} + + .build-widget + %h4.title + Commit + .pull-right + %small #{build_commit_link @build} + %p + %span.attr-name Branch: + #{build_ref_link @build} + %p + %span.attr-name Author: + #{@build.commit.git_author_name} + %p + %span.attr-name Message: + #{@build.commit.git_commit_message} + + - if @build.tags.any? + .build-widget + %h4.title + Tags + - @build.tag_list.each do |tag| + %span.label.label-primary + = tag + + - if @builds.present? + .build-widget + %h4.title #{pluralize(@builds.count, "other build")} for #{@build.short_sha}: + %table.table.builds + - @builds.each_with_index do |build, i| + %tr.build + %td + = ci_icon_for_status(build.status) + %td + = link_to namespace_project_build_path(@project.namespace, @project, @build) do + - if build.name + = build.name + - else + %span ##{build.id} + + %td.status= build.status + + + = paginate @builds + + + :javascript + new CiBuild("#{namespace_project_build_path(@project.namespace, @project, @build)}", "#{@build.status}") diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index bc7625e8989..4580c912692 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,6 +1,6 @@ - if current_user %span.dropdown - %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"} + %a.dropdown-new.btn.btn-new{href: '#', "data-toggle" => "dropdown"} = icon('plus') %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - if can?(current_user, :create_issue, @project) diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 854c154824d..8f2f631eb7d 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -8,6 +8,5 @@ - else = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do = icon('code-fork fw') - Fork %span.count = @project.forks_count diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml new file mode 100644 index 00000000000..4b69a6d7a6f --- /dev/null +++ b/app/views/projects/buttons/_notifications.html.haml @@ -0,0 +1,14 @@ +- return unless @membership + += form_tag profile_notifications_path, method: :put, remote: true, class: 'inline-form', id: 'notification-form' do + = hidden_field_tag :notification_type, 'project' + = hidden_field_tag :notification_id, @membership.id + = hidden_field_tag :notification_level + %span.dropdown + %a.dropdown-new.btn.btn-new#notifications-button{href: '#', "data-toggle" => "dropdown"} + = icon('bell') + = notification_label(@membership) + = icon('angle-down') + %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown + - Notification.project_notification_levels.each do |level| + = notification_list_item(level, @membership) diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 5d7df5ae099..3501dddefbe 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,10 +1,6 @@ - if current_user = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do = icon('star fw') - - if current_user.starred?(@project) - Unstar - - else - Star %span.count = @project.star_count @@ -17,6 +13,5 @@ - else = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do = icon('star fw') - Star %span.count = @project.star_count diff --git a/app/views/projects/ci_settings/_form.html.haml b/app/views/projects/ci_settings/_form.html.haml new file mode 100644 index 00000000000..9f891f557a9 --- /dev/null +++ b/app/views/projects/ci_settings/_form.html.haml @@ -0,0 +1,103 @@ +%h3.page-title + CI settings +%hr +.bs-callout.help-callout + %p + If you want to test your .gitlab-ci.yml, you can use special tool - #{link_to "Lint", ci_lint_path} + %p + Edit your + #{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@ci_project)} + += nested_form_for @ci_project, url: namespace_project_ci_settings_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f| + - if @ci_project.errors.any? + #error_explanation + %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:" + .alert.alert-error + %ul + - @ci_project.errors.full_messages.each do |msg| + %li= msg + + %fieldset + %legend Build settings + .form-group + = label_tag nil, class: 'control-label' do + Get code + .col-sm-10 + %p Get recent application code using the following command: + .radio + = label_tag do + = f.radio_button :allow_git_fetch, 'false' + %strong git clone + .light Slower but makes sure you have a clean dir before every build + .radio + = label_tag do + = f.radio_button :allow_git_fetch, 'true' + %strong git fetch + .light Faster + .form-group + = f.label :timeout_in_minutes, 'Timeout', class: 'control-label' + .col-sm-10 + = f.number_field :timeout_in_minutes, class: 'form-control', min: '0' + .light per build in minutes + + + %fieldset + %legend Build Schedule + .form-group + = f.label :always_build, 'Schedule build', class: 'control-label' + .col-sm-10 + .checkbox + = f.label :always_build do + = f.check_box :always_build + %span.light Repeat last build after X hours if no builds + .form-group + = f.label :polling_interval, "Build interval", class: 'control-label' + .col-sm-10 + = f.number_field :polling_interval, placeholder: '5', min: '0', class: 'form-control' + .light In hours + + %fieldset + %legend Project settings + .form-group + = f.label :default_ref, "Make tabs for the following branches", class: 'control-label' + .col-sm-10 + = f.text_field :default_ref, class: 'form-control', placeholder: 'master, stable' + .light You will be able to filter builds by the following branches + .form-group + = f.label :public, 'Public mode', class: 'control-label' + .col-sm-10 + .checkbox + = f.label :public do + = f.check_box :public + %span.light Anyone can see project and builds + .form-group + = f.label :coverage_regex, "Test coverage parsing", class: 'control-label' + .col-sm-10 + .input-group + %span.input-group-addon / + = f.text_field :coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered' + %span.input-group-addon / + .light We will use this regular expression to find test coverage output in build trace. Leave blank if you want to disable this feature + .bs-callout.bs-callout-info + %p Below are examples of regex for existing tools: + %ul + %li + Simplecov (Ruby) - + %code \(\d+.\d+\%\) covered + %li + pytest-cov (Python) - + %code \d+\%$ + + + + %fieldset + %legend Advanced settings + .form-group + = f.label :token, "CI token", class: 'control-label' + .col-sm-10 + = f.text_field :token, class: 'form-control', placeholder: 'xEeFCaDAB89' + + .form-actions + = f.submit 'Save changes', class: 'btn btn-save' + - unless @ci_project.new_record? + = link_to 'Remove Project', ci_project_path(@ci_project), method: :delete, data: { confirm: 'Project will be removed. Are you sure?' }, class: 'btn btn-danger pull-right' diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml new file mode 100644 index 00000000000..e9040fe4337 --- /dev/null +++ b/app/views/projects/ci_settings/edit.html.haml @@ -0,0 +1,21 @@ +- if @ci_project.generated_yaml_config + %p.alert.alert-danger + CI Jobs are deprecated now, you can #{link_to "download", dumped_yaml_ci_project_path(@ci_project)} + or + %a.preview-yml{:href => "#yaml-content", "data-toggle" => "modal"} preview + yaml file which is based on your old jobs. + Put this file to the root of your project and name it .gitlab-ci.yml + += render 'form' + +- if @ci_project.generated_yaml_config + #yaml-content.modal.fade{"aria-hidden" => "true", "aria-labelledby" => ".gitlab-ci.yml", :role => "dialog", :tabindex => "-1"} + .modal-dialog + .modal-content + .modal-header + %button.close{"aria-hidden" => "true", "data-dismiss" => "modal", :type => "button"} × + %h4.modal-title Content of .gitlab-ci.yml + .modal-body + = text_area_tag :yaml, @ci_project.generated_yaml_config, size: "70x25", class: "form-control" + .modal-footer + %button.btn.btn-default{"data-dismiss" => "modal", :type => "button"} Close diff --git a/app/views/projects/ci_web_hooks/index.html.haml b/app/views/projects/ci_web_hooks/index.html.haml new file mode 100644 index 00000000000..6aebd7cfc4d --- /dev/null +++ b/app/views/projects/ci_web_hooks/index.html.haml @@ -0,0 +1,92 @@ +%h3.page-title + CI Web hooks + +%p.light + Web Hooks can be used for binding events when build completed. + +%hr.clearfix + += form_for @web_hook, url: namespace_project_ci_web_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f| + -if @web_hook.errors.any? + .alert.alert-danger + - @web_hook.errors.full_messages.each do |msg| + %p= msg + .form-group + = f.label :url, "URL", class: 'control-label' + .col-sm-10 + = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json' + .form-actions + = f.submit "Add Web Hook", class: "btn btn-create" + +-if @web_hooks.any? + %h4 Activated web hooks (#{@web_hooks.count}) + %table.table + - @web_hooks.each do |hook| + %tr + %td + .clearfix + %span.monospace= hook.url + %td + .pull-right + - if @ci_project.commits.any? + = link_to 'Test Hook', test_namespace_project_ci_web_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped" + = link_to 'Remove', namespace_project_ci_web_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" + +%h4 Web Hook data example + +:erb + <pre> + <code> + { + "build_id": 2, + "build_name":"rspec_linux" + "build_status": "failed", + "build_started_at": "2014-05-05T18:01:02.563Z", + "build_finished_at": "2014-05-05T18:01:07.611Z", + "project_id": 1, + "project_name": "Brightbox \/ Brightbox Cli", + "gitlab_url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli", + "ref": "master", + "sha": "a26cf5de9ed9827746d4970872376b10d9325f40", + "before_sha": "34f57f6ba3ed0c21c5e361bbb041c3591411176c", + "push_data": { + "before": "34f57f6ba3ed0c21c5e361bbb041c3591411176c", + "after": "a26cf5de9ed9827746d4970872376b10d9325f40", + "ref": "refs\/heads\/master", + "user_id": 1, + "user_name": "Administrator", + "project_id": 5, + "repository": { + "name": "Brightbox Cli", + "url": "dzaporozhets@localhost:brightbox\/brightbox-cli.git", + "description": "Voluptatibus quae error consectetur voluptas dolores vel excepturi possimus.", + "homepage": "http:\/\/localhost:3000\/brightbox\/brightbox-cli" + }, + "commits": [ + { + "id": "a26cf5de9ed9827746d4970872376b10d9325f40", + "message": "Release v1.2.2", + "timestamp": "2014-04-22T16:46:42+03:00", + "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/a26cf5de9ed9827746d4970872376b10d9325f40", + "author": { + "name": "Paul Thornthwaite", + "email": "tokengeek@gmail.com" + } + }, + { + "id": "34f57f6ba3ed0c21c5e361bbb041c3591411176c", + "message": "Fix server user data update\n\nIncorrect condition was being used so Base64 encoding option was having\nopposite effect from desired.", + "timestamp": "2014-04-11T18:17:26+03:00", + "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/34f57f6ba3ed0c21c5e361bbb041c3591411176c", + "author": { + "name": "Paul Thornthwaite", + "email": "tokengeek@gmail.com" + } + } + ], + "total_commits_count": 2, + "ci_yaml_file":"rspec_linux:\r\n script: ls\r\n" + } + } + </code> + </pre> diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml new file mode 100644 index 00000000000..a634ae5dfda --- /dev/null +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -0,0 +1,7 @@ +%ul.center-top-menu.commit-ci-menu + = nav_link(path: 'commit#show') do + = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do + Changes + = nav_link(path: 'commit#ci') do + = link_to ci_namespace_project_commit_path(@project.namespace, @project, @commit.id) do + Builds diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 3f645b81397..fbf0a9ec0c3 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -38,10 +38,17 @@ - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent) +- if @ci_commit + .pull-right + = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do + = ci_status_icon(@ci_commit) + build: + = @ci_commit.status + .commit-info-row.branches %i.fa.fa-spinner.fa-spin -.commit-box +.commit-box.gray-content-block.middle-block %h3.commit-title = gfm escape_once(@commit.title) - if @commit.description.present? diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml new file mode 100644 index 00000000000..f4382e88046 --- /dev/null +++ b/app/views/projects/commit/ci.html.haml @@ -0,0 +1,62 @@ +- page_title "#{@commit.title} (#{@commit.short_id})", "Commits" += render "projects/commits/header_title" += render "commit_box" += render "ci_menu" + +- if @ci_project && current_user && can?(current_user, :manage_builds, @project) + .pull-right + - if @ci_commit.builds.running_or_pending.any? + = link_to "Cancel", cancel_ci_project_commits_path(@ci_project, @ci_commit), class: 'btn btn-sm btn-danger' + + +- if @ci_commit.yaml_errors.present? + .bs-callout.bs-callout-danger + %h4 Found errors in your .gitlab-ci.yml: + %ul + - @ci_commit.yaml_errors.split(",").each do |error| + %li= error + +- unless @ci_commit.ci_yaml_file + .bs-callout.bs-callout-warning + \.gitlab-ci.yml not found in this commit + +- @ci_commit.refs.each do |ref| + .gray-content-block.second-block + Builds for #{ref} + - if @ci_commit.duration_for_ref(ref) > 0 + %small.pull-right + %i.fa.fa-time + #{time_interval_in_words @ci_commit.duration_for_ref(ref)} + + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Stage + %th Name + %th Duration + %th Finished at + - if @ci_project && @ci_project.coverage_enabled? + %th Coverage + %th + = render partial: "projects/builds/build", collection: @ci_commit.builds_without_retry.for_ref(ref), controls: true + +- if @ci_commit.retried_builds.any? + %h3 + Retried builds + + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Ref + %th Stage + %th Name + %th Duration + %th Finished at + - if @ci_project && @ci_project.coverage_enabled? + %th Coverage + %th + = render partial: "projects/builds/build", collection: @ci_commit.retried_builds, ref: true diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 60b112e67d4..30a3973828f 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,4 +1,6 @@ - page_title "#{@commit.title} (#{@commit.short_id})", "Commits" += render "projects/commits/header_title" = render "commit_box" += render "ci_menu" if @ci_commit = render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/notes/notes_with_form", view: params[:view] diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 74f8d8b15cf..cddd5aa3a83 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -4,7 +4,11 @@ - notes = commit.notes - note_count = notes.user.count -= cache [project.id, commit.id, note_count] do +- ci_commit = project.ci_commit(commit.sha) +- cache_key = [project.path_with_namespace, commit.id, note_count] +- cache_key.push(ci_commit.status) if ci_commit + += cache(cache_key) do %li.commit.js-toggle-container .commit-row-title %strong.str-truncated @@ -13,6 +17,10 @@ %a.text-expander.js-toggle-button ... .pull-right + - if ci_commit + = link_to ci_status_path(ci_commit), class: "c#{ci_status_color(ci_commit)}" do + = ci_status_icon(ci_commit) + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" .notes_count diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index e3d8cd0fdd5..a849bf84698 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,22 +1,18 @@ -%ul.nav.nav-tabs +%ul.center-top-menu = nav_link(controller: [:commit, :commits]) do - = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do - = icon("history") + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do Commits %span.badge= number_with_delimiter(@repository.commit_count) = nav_link(controller: :compare) do - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref) do - = icon("exchange") + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do Compare = nav_link(html_options: {class: branches_tab_class}) do = link_to namespace_project_branches_path(@project.namespace, @project) do - = icon("code-fork") Branches %span.badge.js-totalbranch-count= @repository.branches.size = nav_link(controller: :tags) do = link_to namespace_project_tags_path(@project.namespace, @project) do - = icon("tags") Tags %span.badge.js-totaltags-count= @repository.tags.length diff --git a/app/views/projects/commits/_header_title.html.haml b/app/views/projects/commits/_header_title.html.haml new file mode 100644 index 00000000000..e4385893dd9 --- /dev/null +++ b/app/views/projects/commits/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Commits", project_commits_path(@project)) diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 55054a31977..2dd99cc8215 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -1,26 +1,28 @@ - page_title "Commits", @ref += render "header_title" = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") = render "head" -.tree-ref-holder - = render 'shared/ref_switcher', destination: 'commits' +.gray-content-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'commits' -.commits-feed-holder.hidden-xs.hidden-sm - - if create_mr_button?(@repository.root_ref, @ref) - = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do - = icon('plus') - Create Merge Request + .commits-feed-holder.hidden-xs.hidden-sm + - if create_mr_button?(@repository.root_ref, @ref) + = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do + = icon('plus') + Create Merge Request - - if current_user && current_user.private_token - = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do - = icon("rss") + - if current_user && current_user.private_token + = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do + = icon("rss") -%ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs %div{id: dom_id(@project)} #commits-list= render "commits", project: @project diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 3019893d12c..efc25eda26b 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,5 +1,5 @@ = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do - .clearfix.append-bottom-20 + .clearfix - if params[:to] && params[:from] = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} .form-group diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index d1e579a2ede..02be5a2d07f 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,9 +1,8 @@ - page_title "Compare" += render "projects/commits/header_title" = render "projects/commits/head" -%h3.page-title - Compare View -%p.slead +.gray-content-block Compare branches, tags or commit ranges. %br Fill input field with commit id like @@ -14,4 +13,5 @@ %br Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field. -= render "form" +.prepend-top-20 + = render "form" diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 3670dd5c13b..39755efd2fd 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,16 +1,17 @@ - page_title "#{params[:from]}...#{params[:to]}" += render "projects/commits/header_title" = render "projects/commits/head" -%h3.page-title - Compare View -= render "form" +.gray-content-block + = render "form" - if @commits.present? - = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diffs: @diffs, project: @project + .prepend-top-20 + = render "projects/commits/commit_list" + = render "projects/diffs/diffs", diffs: @diffs, project: @project - else - .light-well + .light-well.prepend-top-20 .center %h4 There isn't anything to compare. diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 30943f49bba..4f1965bfb39 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,21 +1,26 @@ - if params[:view] == 'parallel' - fluid_layout true -.prepend-top-20.append-bottom-20 - .pull-right +- diff_files = safe_diff_files(diffs) + +.gray-content-block.second-block + .inline-parallel-buttons .btn-group = inline_diff_btn = parallel_diff_btn - = render 'projects/diffs/stats', diffs: diffs - -- diff_files = safe_diff_files(diffs) + = render 'projects/diffs/stats', diff_files: diff_files - if diff_files.count < diffs.size = render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count .files - diff_files.each_with_index do |diff_file, index| - = render 'projects/diffs/file', diff_file: diff_file, i: index, project: project + - diff_commit = commit_for_diff(diff_file.diff) + - blob = project.repository.blob_for_diff(diff_commit, diff_file.diff) + - next unless blob + + = render 'projects/diffs/file', i: index, project: project, + diff_file: diff_file, diff_commit: diff_commit, blob: blob - if @diff_timeout .alert.alert-danger diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 99ee23a1ddc..4617b188150 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -1,24 +1,18 @@ -- blob = project.repository.blob_for_diff(@commit, diff_file.diff) -- return unless blob -- blob_diff_path = namespace_project_blob_diff_path(project.namespace, project, tree_join(@commit.id, diff_file.file_path)) -.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }} +.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)} .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"} - - if diff_file.deleted_file - %span="#{diff_file.old_path} deleted" - - .diff-btn-group - - if @commit.parent_ids.present? - = view_file_btn(@commit.parent_id, diff_file, project) - - elsif diff_file.diff.submodule? + - if diff_file.diff.submodule? %span - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) = submodule_link(submodule_item, @commit.id, project.repository) - else %span - - if diff_file.renamed_file + - if diff_file.deleted_file + = "#{diff_file.old_path} deleted" + - elsif diff_file.renamed_file = "#{diff_file.old_path} renamed to #{diff_file.new_path}" - else = diff_file.new_path + - if diff_file.mode_changed? %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" @@ -28,12 +22,12 @@ %i.fa.fa-comments - - if @merge_request && @merge_request.source_project + - if editable_diff?(diff_file) = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, after: ' ', from_merge_request_id: @merge_request.id) - = view_file_btn(@commit.id, diff_file, project) + = view_file_btn(diff_commit.id, diff_file, project) .diff-content.diff-wrap-lines -# Skipp all non non-supported blobs diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 1625930615a..ea2a3e01277 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -2,37 +2,35 @@ .commit-stat-summary Showing = link_to '#', class: 'js-toggle-button' do - %strong #{pluralize(diffs.count, "changed file")} - - if current_controller?(:commit) - - unless @commit.has_zero_stats? - with - %strong.cgreen #{@commit.stats.additions} additions - and - %strong.cred #{@commit.stats.deletions} deletions + %strong #{pluralize(diff_files.count, "changed file")} + with + %strong.cgreen #{diff_files.sum(&:added_lines)} additions + and + %strong.cred #{diff_files.sum(&:removed_lines)} deletions .file-stats.js-toggle-content.hide - %ul.bordered-list - - diffs.each_with_index do |diff, i| + %ul + - diff_files.each_with_index do |diff_file, i| %li - - if diff.deleted_file + - if diff_file.deleted_file %span.deleted-file %a{href: "#diff-#{i}"} %i.fa.fa-minus - = diff.old_path - - elsif diff.renamed_file + = diff_file.old_path + - elsif diff_file.renamed_file %span.renamed-file %a{href: "#diff-#{i}"} %i.fa.fa-minus - = diff.old_path + = diff_file.old_path → - = diff.new_path - - elsif diff.new_file + = diff_file.new_path + - elsif diff_file.new_file %span.new-file %a{href: "#diff-#{i}"} %i.fa.fa-plus - = diff.new_path + = diff_file.new_path - else %span.edit-file %a{href: "#diff-#{i}"} %i.fa.fa-adjust - = diff.new_path + = diff_file.new_path diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index e8e65d87f47..90dce739992 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1,10 +1,11 @@ +- @blank_container = true + .project-edit-container .project-edit-errors .project-edit-content - %div - %h3.page-title + .panel.panel-default + .panel-heading Project settings - %hr .panel-body = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal fieldset-form" }, authenticity_token: true do |f| diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index e577d35d560..e06454fd148 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,10 +1,11 @@ -- if current_user && can?(current_user, :download_code, @project) - = render 'shared/no_ssh' - = render 'shared/no_password' - +.alert_holder + - if current_user && can?(current_user, :download_code, @project) + = render 'shared/no_ssh' + = render 'shared/no_password' + = render "home_panel" -.center.light-well +.gray-content-block.center %h3.page-title The repository for this project is empty %p @@ -15,38 +16,39 @@ file to this project. .prepend-top-20 -%h3.page-title - Command line instructions -%div.git-empty - %fieldset - %h5 Git global setup - %pre.light-well - :preserve - git config --global user.name "#{git_user_name}" - git config --global user.email "#{git_user_email}" +.empty_wrapper + %h3.page-title-empty + Command line instructions + %div.git-empty + %fieldset + %h5 Git global setup + %pre.light-well + :preserve + git config --global user.name "#{h git_user_name}" + git config --global user.email "#{h git_user_email}" - %fieldset - %h5 Create a new repository - %pre.light-well - :preserve - git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')} - cd #{@project.path} - touch README.md - git add README.md - git commit -m "add README" - git push -u origin master + %fieldset + %h5 Create a new repository + %pre.light-well + :preserve + git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')} + cd #{h @project.path} + touch README.md + git add README.md + git commit -m "add README" + git push -u origin master - %fieldset - %h5 Existing folder or Git repository - %pre.light-well - :preserve - cd existing_folder - git init - git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} - git add . - git commit - git push -u origin master + %fieldset + %h5 Existing folder or Git repository + %pre.light-well + :preserve + cd existing_folder + git init + git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} + git add . + git commit + git push -u origin master -- if can? current_user, :remove_project, @project - .prepend-top-20 - = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" + - if can? current_user, :remove_project, @project + .prepend-top-20 + = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index b7a2ed68e25..cd5f3a5d39e 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -10,21 +10,22 @@ - group.each do |namespace| .col-md-2.col-sm-3 - if fork = namespace.find_fork_of(@project) - .thumbnail.fork-exists-thumbnail + .fork-thumbnail = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do - = image_tag namespace_icon(namespace, 200) + = image_tag namespace_icon(namespace, 100) .caption - %h4=namespace.human_name - %p - = namespace.path + %strong + = namespace.human_name + %div.text-primary + Already forked + - else - .thumbnail.fork-thumbnail + .fork-thumbnail = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do - = image_tag namespace_icon(namespace, 200) + = image_tag namespace_icon(namespace, 100) .caption - %h4=namespace.human_name - %p - = namespace.path + %strong + = namespace.human_name %p.light Fork is a copy of a project repository. diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index 9383df13305..bbfaf422a82 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -3,3 +3,7 @@ = link_to 'Contributors', namespace_project_graph_path = nav_link(action: :commits) do = link_to 'Commits', commits_namespace_project_graph_path + - if @project.gitlab_ci? + = nav_link(action: :ci) do + = link_to ci_namespace_project_graph_path do + Continuous Integration diff --git a/app/views/projects/graphs/_header_title.html.haml b/app/views/projects/graphs/_header_title.html.haml new file mode 100644 index 00000000000..1e2f61cd22b --- /dev/null +++ b/app/views/projects/graphs/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Graphs", namespace_project_graph_path(@project.namespace, @project, current_ref)) diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml new file mode 100644 index 00000000000..4f69cc64f7c --- /dev/null +++ b/app/views/projects/graphs/ci.html.haml @@ -0,0 +1,7 @@ +- page_title "Continuous Integration", "Graphs" += render "header_title" += render 'head' +#charts.ci-charts + = render 'projects/graphs/ci/builds' + = render 'projects/graphs/ci/build_times' += render 'projects/graphs/ci/overall' diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/graphs/ci/_build_times.haml new file mode 100644 index 00000000000..c3c2f572414 --- /dev/null +++ b/app/views/projects/graphs/ci/_build_times.haml @@ -0,0 +1,21 @@ +%fieldset + %legend + Commit duration in minutes for last 30 commits + + %canvas#build_timesChart.padded{width: 800, height: 300} + +:javascript + var data = { + labels : #{@charts[:build_times].labels.to_json}, + datasets : [ + { + fillColor : "#4A3", + strokeColor : "rgba(151,187,205,1)", + pointColor : "rgba(151,187,205,1)", + pointStrokeColor : "#fff", + data : #{@charts[:build_times].build_times.to_json} + } + ] + } + var ctx = $("#build_timesChart").get(0).getContext("2d"); + new Chart(ctx).Line(data,{"scaleOverlay": true}); diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml new file mode 100644 index 00000000000..1b0039fb834 --- /dev/null +++ b/app/views/projects/graphs/ci/_builds.haml @@ -0,0 +1,41 @@ +%fieldset + %legend + Builds chart for last week + (#{date_from_to(Date.today - 7.days, Date.today)}) + + %canvas#weekChart.padded{width: 800, height: 200} + +%fieldset + %legend + Builds chart for last month + (#{date_from_to(Date.today - 30.days, Date.today)}) + + %canvas#monthChart.padded{width: 800, height: 300} + +%fieldset + %legend Builds chart for last year + %canvas#yearChart.padded{width: 800, height: 400} + +- [:week, :month, :year].each do |scope| + :javascript + var data = { + labels : #{@charts[scope].labels.to_json}, + datasets : [ + { + fillColor : "rgba(220,220,220,0.5)", + strokeColor : "rgba(220,220,220,1)", + pointColor : "rgba(220,220,220,1)", + pointStrokeColor : "#EEE", + data : #{@charts[scope].total.to_json} + }, + { + fillColor : "#4A3", + strokeColor : "rgba(151,187,205,1)", + pointColor : "rgba(151,187,205,1)", + pointStrokeColor : "#fff", + data : #{@charts[scope].success.to_json} + } + ] + } + var ctx = $("##{scope}Chart").get(0).getContext("2d"); + new Chart(ctx).Line(data,{"scaleOverlay": true}); diff --git a/app/views/projects/graphs/ci/_overall.haml b/app/views/projects/graphs/ci/_overall.haml new file mode 100644 index 00000000000..9550d719471 --- /dev/null +++ b/app/views/projects/graphs/ci/_overall.haml @@ -0,0 +1,22 @@ +- ci_project = @project.gitlab_ci_project +%fieldset + %legend Overall + %p + Total: + %strong= pluralize ci_project.builds.count(:all), 'build' + %p + Successful: + %strong= pluralize ci_project.builds.success.count(:all), 'build' + %p + Failed: + %strong= pluralize ci_project.builds.failed.count(:all), 'build' + + %p + Success ratio: + %strong + #{success_ratio(ci_project.builds.success, ci_project.builds.failed)}% + + %p + Commits covered: + %strong + = ci_project.commits.count(:all) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index a357736bf52..112be875b6b 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -1,4 +1,5 @@ -- page_title "Commit statistics" +- page_title "Commits", "Graphs" += render "header_title" .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs_commits' = render 'head' @@ -31,61 +32,55 @@ %div %p.slead Commits per day of month - %canvas#month-chart{width: 800, height: 400} + %canvas#month-chart .row .col-md-6 %div %p.slead Commits per day hour (UTC) - %canvas#hour-chart{width: 800, height: 400} + %canvas#hour-chart .col-md-6 %div %p.slead Commits per weekday - %canvas#weekday-chart{width: 800, height: 400} + %canvas#weekday-chart :coffeescript - data = { - labels : #{@commits_per_time.keys.to_json}, - datasets : [{ - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data : #{@commits_per_time.values.to_json} - }] - } + responsiveChart = (selector, data) -> + options = { "scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2, maintainAspectRatio: false } - ctx = $("#hour-chart").get(0).getContext("2d"); - new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + # get selector by context + ctx = selector.get(0).getContext("2d") + # pointing parent container to make chart.js inherit its width + container = $(selector).parent() - data = { - labels : #{@commits_per_week_days.keys.to_json}, - datasets : [{ - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data : #{@commits_per_week_days.values.to_json} - }] - } + generateChart = -> + selector.attr('width', $(container).width()) + new Chart(ctx).Bar(data, options) + + # enabling auto-resizing + $(window).resize( generateChart ) - ctx = $("#weekday-chart").get(0).getContext("2d"); - new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + generateChart() - data = { - labels : #{@commits_per_month.keys.to_json}, - datasets : [{ - fillColor : "rgba(220,220,220,0.5)", - strokeColor : "rgba(220,220,220,1)", - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data : #{@commits_per_month.values.to_json} - }] + chartData = (keys, values) -> + data = { + labels : keys, + datasets : [{ + fillColor : "rgba(220,220,220,0.5)", + strokeColor : "rgba(220,220,220,1)", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data : values + }] } - ctx = $("#month-chart").get(0).getContext("2d"); - new Chart(ctx).Bar(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + hourData = chartData(#{@commits_per_time.keys.to_json}, #{@commits_per_time.values.to_json}) + responsiveChart($('#hour-chart'), hourData) + + dayData = chartData(#{@commits_per_week_days.keys.to_json}, #{@commits_per_week_days.values.to_json}) + responsiveChart($('#weekday-chart'), dayData) + + monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json}) + responsiveChart($('#month-chart'), monthData) diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index ecdd0eaf52f..bd342911e49 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,4 +1,5 @@ -- page_title "Contributor statistics" +- page_title "Contributors", "Graphs" += render "header_title" .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs' = render 'head' diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml index 39fe0fc1c4f..06886d215a3 100644 --- a/app/views/projects/imports/show.html.haml +++ b/app/views/projects/imports/show.html.haml @@ -3,8 +3,12 @@ .center %h2 %i.fa.fa-spinner.fa-spin - Import in progress. - %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)} + - if @project.forked? + Forking in progress. + - else + Import in progress. + - unless @project.forked? + %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)} %p Please wait while we import the repository for you. Refresh at will. :javascript new ProjectImport(); diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index f61ae957208..d4a98eca473 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -7,21 +7,24 @@ = render 'shared/show_aside' +.gray-content-block.second-block + .row + .col-md-9 + .votes-holder.pull-right + #votes= render 'votes/votes_block', votable: @issue + .participants + %span= pluralize(@participants.count, 'participant') + - @participants.each do |participant| + = link_to_member(@project, participant, name: false, size: 24) + .col-md-3 + %span.slead.has_tooltip{title: 'Cross-project reference'} + = cross_project_reference(@project, @issue) + .row %section.col-md-9 - .votes-holder.pull-right - #votes= render 'votes/votes_block', votable: @issue - .participants - %span= pluralize(@participants.count, 'participant') - - @participants.each do |participant| - = link_to_member(@project, participant, name: false, size: 24) .voting_notes#notes= render 'projects/notes/notes_with_form' %aside.col-md-3 .issuable-affix - .clearfix - %span.slead.has_tooltip{title: 'Cross-project reference'} - = cross_project_reference(@project, @issue) - %hr .context = render 'shared/issuable/context', issuable: @issue diff --git a/app/views/projects/issues/_header_title.html.haml b/app/views/projects/issues/_header_title.html.haml new file mode 100644 index 00000000000..99f03549c44 --- /dev/null +++ b/app/views/projects/issues/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project)) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index b6910c8f796..55ce912829d 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -41,4 +41,4 @@ = issue.task_status .pull-right.issue-updated-at - %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} + %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 5d243adb5fe..a3399c57aa2 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,9 +1,8 @@ -.panel.panel-default - %ul.well-list.issues-list - = render @issues - - if @issues.blank? - %li - .nothing-here-block No issues to show +%ul.content-list.issues-list + = render @issues + - if @issues.blank? + %li + .nothing-here-block No issues to show - if @issues.present? .pull-right diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index d06225f5488..d6260ab2900 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,10 +1,12 @@ - page_title "Issues" += render "header_title" + = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") -.append-bottom-10 - .pull-right +.project-issuable-filter + .controls .pull-left - if current_user .hidden-xs.pull-left diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml index da6edd5c2d2..153447baa1b 100644 --- a/app/views/projects/issues/new.html.haml +++ b/app/views/projects/issues/new.html.haml @@ -1,2 +1,4 @@ - page_title "New Issue" += render "header_title" + = render "form" diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index e7b14e7582c..5cb814c9ea8 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,15 +1,18 @@ - page_title "#{@issue.title} (##{@issue.iid})", "Issues" += render "header_title" + .issue .issue-details.issuable-details - %h4.page-title + .page-title .issue-box{ class: issue_box_class(@issue) } - if @issue.closed? Closed - else Open - Issue ##{@issue.iid} - %small.creator - · created by #{link_to_member(@project, @issue.author)} + %span.issue-id Issue ##{@issue.iid} + %span.creator + · created by #{link_to_member(@project, @issue.author, size: 24)} + · = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago') - if @issue.updated_at != @issue.created_at %span @@ -32,18 +35,17 @@ = icon('pencil-square-o') Edit - %hr - %h2.issue-title - = gfm escape_once(@issue.title) - %div - - if @issue.description.present? - .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} - .wiki - = preserve do - = markdown(@issue.description) - %textarea.hidden.js-task-list-field - = @issue.description + .gray-content-block.middle-block + %h2.issue-title + = gfm escape_once(@issue.title) + %div + - if @issue.description.present? + .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} + .wiki + = preserve do + = markdown(@issue.description) + %textarea.hidden.js-task-list-field + = @issue.description - %hr .issue-discussion = render 'projects/issues/discussion' diff --git a/app/views/projects/labels/_header_title.html.haml b/app/views/projects/labels/_header_title.html.haml new file mode 100644 index 00000000000..abe28da483b --- /dev/null +++ b/app/views/projects/labels/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Labels", namespace_project_labels_path(@project.namespace, @project)) diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 645402667fd..bc4ab0ca27c 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -1,4 +1,6 @@ - page_title "Edit", @label.name, "Labels" += render "header_title" + %h3 Edit label %span.light #{@label.name} diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index d44fe486212..97175f8232b 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,14 +1,16 @@ - page_title "Labels" -- if can? current_user, :admin_label, @project - = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do - New label -%h3.page-title - Labels -%hr += render "header_title" + +.gray-content-block.top-block + - if can? current_user, :admin_label, @project + = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do + New label + .oneline + Labels can be applied to issues and merge requests. .labels - if @labels.present? - %ul.bordered-list.manage-labels-list + %ul.content-list.manage-labels-list = render @labels = paginate @labels, theme: 'gitlab' - else diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index b3ef17025c3..342ad4f3f95 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,4 +1,6 @@ - page_title "New Label" += render "header_title" + %h3 New label .back-link = link_to namespace_project_labels_path(@project.namespace, @project) do diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index f855dfec321..38e66c3828b 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -7,18 +7,21 @@ = render 'shared/show_aside' +.gray-content-block.second-block + .row + .col-md-9 + .votes-holder.pull-right + #votes= render 'votes/votes_block', votable: @merge_request + = render "projects/merge_requests/show/participants" + .col-md-3 + %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'} + = cross_project_reference(@project, @merge_request) + .row %section.col-md-9 - .votes-holder.pull-right - #votes= render 'votes/votes_block', votable: @merge_request - = render "projects/merge_requests/show/participants" = render "projects/notes/notes_with_form" %aside.col-md-3 .issuable-affix - .clearfix - %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'} - = cross_project_reference(@project, @merge_request) - %hr .context = render 'shared/issuable/context', issuable: @merge_request diff --git a/app/views/projects/merge_requests/_header_title.html.haml b/app/views/projects/merge_requests/_header_title.html.haml new file mode 100644 index 00000000000..669a9b06bdf --- /dev/null +++ b/app/views/projects/merge_requests/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project)) diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 0bcd543fee7..25e4e8ba80d 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -14,11 +14,6 @@ %span %i.fa.fa-ban CLOSED - - else - %span.hidden-xs.hidden-sm - %span.label-branch< - %i.fa.fa-code-fork - %span= merge_request.target_branch - note_count = merge_request.mr_and_commit_notes.user.count - if merge_request.assignee @@ -48,4 +43,4 @@ = merge_request.task_status .pull-right.hidden-xs - %small updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')} + %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')} diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml index b8a0ca9a42f..d86707b3d97 100644 --- a/app/views/projects/merge_requests/_merge_requests.html.haml +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -1,9 +1,8 @@ -.panel.panel-default - %ul.well-list.mr-list - = render @merge_requests - - if @merge_requests.blank? - %li - .nothing-here-block No merge requests to show +%ul.content-list.mr-list + = render @merge_requests + - if @merge_requests.blank? + %li + .nothing-here-block No merge requests to show - if @merge_requests.present? .pull-right diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 7709330611a..452006162db 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -37,7 +37,7 @@ %h4 Compare failed %p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches. - else - .light-well + .light-well.append-bottom-10 .center %h4 There isn't anything to merge. diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 76f44211dac..6244d3ba0b4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -1,10 +1,11 @@ %h3.page-title New merge request %p.slead + - source_title, target_title = format_mr_branch_names(@merge_request) From - %strong.label-branch #{@merge_request.source_project_namespace}:#{@merge_request.source_branch} + %strong.label-branch #{source_title} %span into - %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch} + %strong.label-branch #{target_title} %span.pull-right = link_to 'Change branches', mr_change_branches_path(@merge_request) @@ -18,15 +19,13 @@ = f.hidden_field :target_branch .mr-compare.merge-request - %ul.nav.nav-tabs.merge-request-tabs + %ul.merge-request-tabs %li.commits-tab = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do - = icon('history') Commits %span.badge= @commits.size %li.diffs-tab.active = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do - = icon('list-alt') Changes %span.badge= @diffs.size diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index ec1838eb489..0b0f52c653c 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,14 +1,14 @@ - page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" += render "header_title" + - if params[:view] == 'parallel' - fluid_layout true .merge-request{'data-url' => merge_request_path(@merge_request)} .merge-request-details.issuable-details = render "projects/merge_requests/show/mr_title" - %hr = render "projects/merge_requests/show/mr_box" - %hr - .append-bottom-20.mr-source-target + .append-bottom-20.mr-source-target.prepend-top-default - if @merge_request.open? .pull-right - if @merge_request.source_branch_exists? @@ -39,20 +39,17 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits.present? - %ul.nav.nav-tabs.merge-request-tabs + %ul.merge-request-tabs %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do - = icon('comments') Discussion %span.badge= @merge_request.mr_and_commit_notes.user.count %li.commits-tab = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do - = icon('history') Commits %span.badge= @commits.size %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do - = icon('list-alt') Changes %span.badge= @merge_request.diffs.size diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 7e5cb07f249..303ca0a880b 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,4 +1,6 @@ - page_title "Edit", "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" += render "header_title" + %h3.page-title = "Edit merge request ##{@merge_request.iid}" %hr diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 72fbe2e27a7..086298e5af1 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,7 +1,9 @@ - page_title "Merge Requests" += render "header_title" + = render 'projects/last_push' -.append-bottom-10 - .pull-right +.project-issuable-filter + .controls = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - if can? current_user, :create_merge_request, @project diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml index 15bd4e2fafd..fc03ee73a3d 100644 --- a/app/views/projects/merge_requests/invalid.html.haml +++ b/app/views/projects/merge_requests/invalid.html.haml @@ -1,4 +1,6 @@ - page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" += render "header_title" + .merge-request = render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/mr_box" diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml index b038a640f67..9fdde80c6d9 100644 --- a/app/views/projects/merge_requests/new.html.haml +++ b/app/views/projects/merge_requests/new.html.haml @@ -1,4 +1,6 @@ - page_title "New Merge Request" += render "header_title" + - if @merge_request.can_be_created = render 'new_submit' - else diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index db1575f899a..f18cf96c17d 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -11,12 +11,12 @@ %pre.dark - if @merge_request.for_fork? :preserve - git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} - git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD + git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch} + git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD - else :preserve git fetch origin - git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch} + git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch} %p %strong Step 2. Review the changes locally @@ -27,18 +27,18 @@ %pre.dark - if @merge_request.for_fork? :preserve - git checkout #{@merge_request.target_branch} - git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch} + git checkout #{h @merge_request.target_branch} + git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} - else :preserve - git checkout #{@merge_request.target_branch} - git merge --no-ff #{@merge_request.source_branch} + git checkout #{h @merge_request.target_branch} + git merge --no-ff #{h @merge_request.source_branch} %p %strong Step 4. Push the result of the merge to GitLab %pre.dark :preserve - git push origin #{@merge_request.target_branch} + git push origin #{h @merge_request.target_branch} - unless @merge_request.can_be_merged_by?(current_user) %p Note that pushing to GitLab requires write access to this repository. diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index e3cd4346872..b4f62a75890 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -1,11 +1,12 @@ -%h2.issue-title - = gfm escape_once(@merge_request.title) +.gray-content-block.middle-block + %h2.issue-title + = gfm escape_once(@merge_request.title) -%div - - if @merge_request.description.present? - .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''} - .wiki - = preserve do - = markdown(@merge_request.description) - %textarea.hidden.js-task-list-field - = @merge_request.description + %div + - if @merge_request.description.present? + .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''} + .wiki + = preserve do + = markdown(@merge_request.description) + %textarea.hidden.js-task-list-field + = @merge_request.description diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 9a1eb36fc88..2bf9cd597a4 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,10 +1,11 @@ -%h4.page-title +.page-title .issue-box{ class: issue_box_class(@merge_request) } = @merge_request.state_human_name - Merge Request ##{@merge_request.iid} - %small.creator + %span.issue-id Merge Request ##{@merge_request.iid} + %span.creator + · + created by #{link_to_member(@project, @merge_request.author, size: 24)} · - created by #{link_to_member(@project, @merge_request.author)} = time_ago_with_tooltip(@merge_request.created_at) - if @merge_request.updated_at != @merge_request.created_at %span diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 4d4e2f68f61..68dda1424cf 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,29 +1,44 @@ - if @merge_request.has_ci? - .mr-widget-heading - - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status| - .ci_widget{class: "ci-#{status}", style: "display:none"} - - if status == :success - - status = "passed" - = icon("check-circle") - - else - = icon("circle") + - ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha) + - if ci_commit + - status = ci_commit.status + .mr-widget-heading + .ci_widget{class: "ci-#{status}"} + = ci_status_icon(ci_commit) %span CI build #{status} for #{@merge_request.last_commit_short_sha}. %span.ci-coverage - = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" + = link_to "View build details", ci_status_path(ci_commit) - .ci_widget - = icon("spinner spin") - Checking CI status for #{@merge_request.last_commit_short_sha}… + - else + - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX + - # Remove in later versions when services like Jenkins will set CI status via Commit status API + .mr-widget-heading + - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status| + .ci_widget{class: "ci-#{status}", style: "display:none"} + - if status == :success + - status = "passed" + = icon("check-circle") + - else + = icon("circle") + %span CI build #{status} + for #{@merge_request.last_commit_short_sha}. + %span.ci-coverage + - if ci_build_details_path(@merge_request) + = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" - .ci_widget.ci-not_found{style: "display:none"} - = icon("times-circle") - Could not find CI status for #{@merge_request.last_commit_short_sha}. + .ci_widget + = icon("spinner spin") + Checking CI status for #{@merge_request.last_commit_short_sha}… - .ci_widget.ci-error{style: "display:none"} - = icon("times-circle") - Could not connect to the CI server. Please check your settings and try again. + .ci_widget.ci-not_found{style: "display:none"} + = icon("times-circle") + Could not find CI status for #{@merge_request.last_commit_short_sha}. - :coffeescript - $ -> - merge_request_widget.getCiStatus() + .ci_widget.ci-error{style: "display:none"} + = icon("times-circle") + Could not connect to the CI server. Please check your settings and try again. + + :coffeescript + $ -> + merge_request_widget.getCiStatus() diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml index d22dfa085b8..f223f687def 100644 --- a/app/views/projects/merge_requests/widget/_merged.html.haml +++ b/app/views/projects/merge_requests/widget/_merged.html.haml @@ -20,7 +20,7 @@ The changes were merged into %span.label-branch= @merge_request.target_branch You can remove the source branch now. - = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do + = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do %i.fa.fa-times Remove Source Branch diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index b61e193fc42..613525437ab 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -9,7 +9,7 @@ = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do = check_box_tag :should_remove_source_branch Remove source branch - .accept-control + .accept-control.right = link_to "#", class: "modify-merge-commit-link js-toggle-button" do = icon('edit') Modify commit message diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index b93462e5bdf..74e9668052d 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -21,7 +21,7 @@ .form-group.milestone-description = f.label :description, "Description", class: "control-label" .col-sm-10 - = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' .hint .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. diff --git a/app/views/projects/milestones/_header_title.html.haml b/app/views/projects/milestones/_header_title.html.haml new file mode 100644 index 00000000000..5f4b6982a6d --- /dev/null +++ b/app/views/projects/milestones/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Milestones", namespace_project_milestones_path(@project.namespace, @project)) diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 2ce5358fa74..5e93d55b1fb 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -1,28 +1,34 @@ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) } - .pull-right - - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-sm edit-milestone-link btn-grouped" do - %i.fa.fa-pencil-square-o - Edit - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close" - = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do - %i.fa.fa-trash-o - Remove + .row + .col-sm-6 + %strong + = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) - %h4 - = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) - - if milestone.expired? and not milestone.closed? - %span.cred (Expired) - %small - = milestone.expires_at + .col-sm-6 + .pull-right.light #{milestone.percent_complete}% complete .row .col-sm-6 = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do = pluralize milestone.issues.count, 'Issue' - + · = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do = pluralize milestone.merge_requests.count, 'Merge Request' - - %span.light #{milestone.percent_complete}% complete .col-sm-6 = milestone_progress_bar(milestone) + + .row + .col-sm-6 + - if milestone.expired? and not milestone.closed? + %span.cred (Expired) + - if milestone.expires_at + %span + = milestone.expires_at + .col-sm-6 + - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? + = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do + %i.fa.fa-pencil-square-o + Edit + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close" + = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do + %i.fa.fa-trash-o + Remove diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml index c09815a212a..e9dc0b77462 100644 --- a/app/views/projects/milestones/edit.html.haml +++ b/app/views/projects/milestones/edit.html.haml @@ -1,2 +1,3 @@ - page_title "Edit", @milestone.title, "Milestones" += render "header_title" = render "form" diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 995eecd7830..a207385bd43 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,18 +1,22 @@ - page_title "Milestones" -.pull-right - - if can? current_user, :admin_milestone, @project - = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do - %i.fa.fa-plus - New Milestone += render "header_title" = render 'shared/milestones_filter' +.gray-content-block + .pull-right + - if can? current_user, :admin_milestone, @project + = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do + %i.fa.fa-plus + New Milestone + .oneline + Milestone allows you to group issues and set due date for it + .milestones - .panel.panel-default - %ul.well-list - = render @milestones + %ul.content-list + = render @milestones - - if @milestones.blank? - %li - .nothing-here-block No milestones to show + - if @milestones.blank? + %li + .nothing-here-block No milestones to show = paginate @milestones, theme: "gitlab" diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 47149dfea41..9ba9acb6f77 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,2 +1,3 @@ - page_title "New Milestone" += render "header_title" = render "form" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 7b1681df336..4eeb0621e52 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,4 +1,6 @@ - page_title @milestone.title, "Milestones" += render "header_title" + %h4.page-title .issue-box{ class: issue_box_class(@milestone) } - if @milestone.closed? diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 52b5b8b877e..16005161df6 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,4 +1,5 @@ - page_title "Network", @ref += header_title project_title(@project, "Network", namespace_project_network_path(@project.namespace, @project, current_ref)) = render "head" .project-network .controls diff --git a/app/views/projects/network/show.json.erb b/app/views/projects/network/show.json.erb index dc82adcb2c6..122e84b41b2 100644 --- a/app/views/projects/network/show.json.erb +++ b/app/views/projects/network/show.json.erb @@ -9,7 +9,7 @@ author: { name: c.author_name, email: c.author_email, - icon: avatar_icon(c.author_email, 20) + icon: image_path(avatar_icon(c.author_email, 20)) }, time: c.time, space: c.spaces.first, diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 636218368cc..bccea21e7a8 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -72,6 +72,11 @@ %i.fa.fa-google Google Code + - if fogbugz_import_enabled? + = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do + %i.fa.fa-bug + Fogbugz + - if git_import_enabled? = link_to "#", class: 'btn js-toggle-button import_git' do %i.fa.fa-git diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index 8f7d2e84c70..a0e26f9827e 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -1,7 +1,7 @@ .note-edit-form = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f| = note_target_fields(note) - = render layout: 'projects/md_preview', locals: { preview_class: 'note-text' } do + = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' = render 'projects/notes/hints' diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 3be8f44b282..d99445da59a 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -7,7 +7,7 @@ = f.hidden_field :noteable_id = f.hidden_field :noteable_type - = render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text' = render 'projects/notes/hints' .error-alert diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index de75d44fc41..1638ad6891a 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,11 +1,8 @@ %li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } } .timeline-entry-inner .timeline-icon - - if note.system - %span= icon('circle') - - else - = link_to user_path(note.author) do - = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: '' + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: '' .timeline-content .note-header - if note_editable?(note) @@ -17,14 +14,10 @@ = icon('trash-o') - unless note.system - - member = note.project.team.find_member(note.author.id) - - if member + - access = note.project.team.human_max_access(note.author.id) + - if access %span.note-role.label - = member.human_access - - - if note.system - = link_to user_path(note.author) do - = image_tag avatar_icon(note.author_email), class: 'avatar s16', alt: '' + = access = link_to_member(note.project, note.author, avatar: false) @@ -66,7 +59,9 @@ .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) - = render 'projects/notes/edit_form', note: note + - unless note.system? + -# System notes can't be edited + = render 'projects/notes/edit_form', note: note - if note.attachment.url .note-attachment diff --git a/app/views/projects/project_members/_header_title.html.haml b/app/views/projects/project_members/_header_title.html.haml new file mode 100644 index 00000000000..a31f0a37fa2 --- /dev/null +++ b/app/views/projects/project_members/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Members", namespace_project_project_members_path(@project.namespace, @project)) diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml index 6914543f6da..189906498cb 100644 --- a/app/views/projects/project_members/import.html.haml +++ b/app/views/projects/project_members/import.html.haml @@ -1,4 +1,6 @@ - page_title "Import members" += render "header_title" + %h3.page-title Import members from another project %p.light diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 162583e4b1d..9a0a824b811 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,30 +1,29 @@ - page_title "Members" -%h3.page-title - Users with access to this project - -%p.light += render "header_title" + +.gray-content-block.top-block + .clearfix.js-toggle-container + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' } + = button_tag 'Search', class: 'btn' + + - if can?(current_user, :admin_project_member, @project) + %span.pull-right + = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do + Add members + %i.fa.fa-chevron-down + = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do + Import members + + .js-toggle-content.hide.new-group-member-holder + = render "new_project_member" + +%p.prepend-top-default.light + Users with access to this project are listed below. Read more about project permissions %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" -%hr - -.clearfix.js-toggle-container - = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do - .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' } - = button_tag 'Search', class: 'btn' - - - if can?(current_user, :admin_project_member, @project) - %span.pull-right - = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do - Add members - %i.fa.fa-chevron-down - = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do - Import members - - .js-toggle-content.hide.new-group-member-holder - = render "new_project_member" - = render "team", members: @project_members - if @group diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml new file mode 100644 index 00000000000..e6b8a2e6fe7 --- /dev/null +++ b/app/views/projects/runners/_runner.html.haml @@ -0,0 +1,34 @@ +%li.runner{id: dom_id(runner)} + %h4 + = runner_status_icon(runner) + %span.monospace + - if @runners.include?(runner) + = link_to runner.short_sha, runner_path(runner) + %small + =link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do + %i.fa.fa-edit.btn + - else + = runner.short_sha + + .pull-right + - if @runners.include?(runner) + - if runner.belongs_to_one_project? + = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' + - else + - runner_project = @ci_project.runner_projects.find_by(runner_id: runner) + = link_to 'Disable for this project', [:ci, @ci_project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' + - elsif runner.specific? + = form_for [:ci, @ci_project, @ci_project.runner_projects.new] do |f| + = f.hidden_field :runner_id, value: runner.id + = f.submit 'Enable for this project', class: 'btn btn-sm' + .pull-right + %small.light + \##{runner.id} + - if runner.description.present? + %p.runner-description + = runner.description + - if runner.tag_list.present? + %p + - runner.tag_list.each do |tag| + %span.label.label-primary + = tag diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml new file mode 100644 index 00000000000..316ea747b14 --- /dev/null +++ b/app/views/projects/runners/_shared_runners.html.haml @@ -0,0 +1,23 @@ +%h3 Shared runners + +.bs-callout.bs-callout-warning + GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X. + %hr + - if @ci_project.shared_runners_enabled + = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-warning', method: :post do + Disable shared runners + - else + = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-success', method: :post do + Enable shared runners + for this project + +- if @shared_runners_count.zero? + This application has no shared runners yet. + Please use specific runners or ask administrator to create one +- else + %h4.underlined-title Available shared runners - #{@shared_runners_count} + %ul.bordered-list.available-shared-runners + = render partial: 'runner', collection: @shared_runners, as: :runner + - if @shared_runners_count > 10 + .light + and #{@shared_runners_count - 10} more... diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml new file mode 100644 index 00000000000..c13625c7e49 --- /dev/null +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -0,0 +1,29 @@ +%h3 Specific runners + +.bs-callout.help-callout + %h4 How to setup a new project specific runner + + %ol + %li + Install GitLab Runner software. + Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it + %li + Specify following URL during runner setup: + %code #{ci_root_url(only_path: false)} + %li + Use the following registration token during setup: + %code #{@ci_project.token} + %li + Start runner! + + +- if @runners.any? + %h4.underlined-title Runners activated for this project + %ul.bordered-list.activated-specific-runners + = render partial: 'runner', collection: @runners, as: :runner + +- if @specific_runners.any? + %h4.underlined-title Available specific runners + %ul.bordered-list.available-specific-runners + = render partial: 'runner', collection: @specific_runners, as: :runner + = paginate @specific_runners diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml new file mode 100644 index 00000000000..66851d38316 --- /dev/null +++ b/app/views/projects/runners/edit.html.haml @@ -0,0 +1,27 @@ +%h4 Runner ##{@runner.id} +%hr += form_for @runner, url: runner_path(@runner), html: { class: 'form-horizontal' } do |f| + .form-group + = label :active, "Active", class: 'control-label' + .col-sm-10 + .checkbox + = f.check_box :active + %span.light Paused runners don't accept new builds + .form-group + = label_tag :token, class: 'control-label' do + Token + .col-sm-10 + = f.text_field :token, class: 'form-control', readonly: true + .form-group + = label_tag :description, class: 'control-label' do + Description + .col-sm-10 + = f.text_field :description, class: 'form-control' + .form-group + = label_tag :tag_list, class: 'control-label' do + Tags + .col-sm-10 + = f.text_field :tag_list, class: 'form-control' + .help-block You can setup jobs to only use runners with specific tags + .form-actions + = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml new file mode 100644 index 00000000000..529fb9c296d --- /dev/null +++ b/app/views/projects/runners/index.html.haml @@ -0,0 +1,25 @@ +.light + %p + A 'runner' is a process which runs a build. + You can setup as many runners as you need. + %br + Runners can be placed on separate users, servers, and even on your local machine. + + %p Each runner can be in one of the following states: + %div + %ul + %li + %span.label.label-success active + \- runner is active and can process any new build + %li + %span.label.label-danger paused + \- runner is paused and will not receive any new build + +%hr + +%p.lead To start serving your builds you can either add specific runners to your project or use shared runners +.row + .col-sm-6 + = render 'specific_runners' + .col-sm-6 + = render 'shared_runners' diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml new file mode 100644 index 00000000000..ffec495f85a --- /dev/null +++ b/app/views/projects/runners/show.html.haml @@ -0,0 +1,64 @@ += content_for :title do + %h3.project-title + Runner ##{@runner.id} + .pull-right + - if @runner.shared? + %span.runner-state.runner-state-shared + Shared + - else + %span.runner-state.runner-state-specific + Specific + +%table.table + %thead + %tr + %th Property Name + %th Value + %tr + %td + Tags + %td + - @runner.tag_list.each do |tag| + %span.label.label-primary + = tag + %tr + %td + Name + %td + = @runner.name + %tr + %td + Version + %td + = @runner.version + %tr + %td + Revision + %td + = @runner.revision + %tr + %td + Platform + %td + = @runner.platform + %tr + %td + Architecture + %td + = @runner.architecture + %tr + %td + Description + %td + = @runner.description + %tr + %td + Last contact + %td + - if @runner.contacted_at + #{time_ago_in_words(@runner.contacted_at)} ago + - else + Never + + + diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 507f2c7beb0..efa119edd5a 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -11,10 +11,10 @@ = render "home_panel" -.project-stats +.project-stats.gray-content-block %ul.nav.nav-pills %li - = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do = pluralize(number_with_delimiter(@project.commit_count), 'commit') %li = link_to namespace_project_branches_path(@project.namespace, @project) do @@ -24,7 +24,7 @@ = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag') %li - = link_to namespace_project_path(@project.namespace, @project) do + = link_to project_files_path(@project) do = repository_size - if !prefer_readme? && @repository.readme @@ -63,21 +63,22 @@ = icon("exclamation-triangle fw") Archived project! Repository is read-only -%hr %section - if prefer_readme? - = render 'projects/readme' + .project-show-readme + = render 'projects/readme' - else - = render 'projects/activity' + .project-show-activity + = render 'projects/activity' - if current_user - access = user_max_access_in_project(current_user, @project) - if access - %hr - %p.light - You have #{access} access to this project. - - if @project.project_member_by_id(current_user) - = link_to leave_namespace_project_project_members_path(@project.namespace, @project), - data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do - Leave this project
\ No newline at end of file + .prepend-top-20.project-footer + .gray-content-block.footer-block.center + You have #{access} access to this project. + - if @project.project_member_by_id(current_user) + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), + data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do + Leave this project diff --git a/app/views/projects/snippets/_header_title.html.haml b/app/views/projects/snippets/_header_title.html.haml new file mode 100644 index 00000000000..04f0bbe9853 --- /dev/null +++ b/app/views/projects/snippets/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Snippets", namespace_project_snippets_path(@project.namespace, @project)) diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml index 945f0084dff..e69f2d99709 100644 --- a/app/views/projects/snippets/edit.html.haml +++ b/app/views/projects/snippets/edit.html.haml @@ -1,4 +1,6 @@ - page_title "Edit", @snippet.title, "Snippets" += render "header_title" + %h3.page-title Edit snippet %hr diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 45d4de6a385..3fed2c9949d 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,4 +1,6 @@ - page_title "Snippets" += render "header_title" + %h3.page-title Snippets - if can? current_user, :create_project_snippet, @project diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml index e38d95c45e7..67cd69fd215 100644 --- a/app/views/projects/snippets/new.html.haml +++ b/app/views/projects/snippets/new.html.haml @@ -1,4 +1,6 @@ - page_title "New Snippets" += render "header_title" + %h3.page-title New snippet %hr diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index 8cbb813c758..be7d4d486fa 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -1,4 +1,6 @@ - page_title @snippet.title, "Snippets" += render "header_title" + %h3.page-title = @snippet.title diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 28ad272322f..2ca295fc5f3 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -1,13 +1,14 @@ - commit = @repository.commit(tag.target) %li - %h4 + %div = link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do - %i.fa.fa-tag - = tag.name + %strong + %i.fa.fa-tag + = tag.name - if tag.message.present? = strip_gpg_signature(tag.message) - .pull-right + .controls - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs' - if can?(current_user, :admin_project, @project) @@ -15,8 +16,7 @@ %i.fa.fa-trash-o - if commit - %ul.list-unstyled - = render 'projects/commits/inline_commit', commit: commit, project: @project + = render 'projects/branches/commit', commit: commit, project: @project - else %p Cant find HEAD commit for this tag diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index d4652a47cba..85d76eae3b5 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -1,21 +1,19 @@ - page_title "Tags" += render "projects/commits/header_title" = render "projects/commits/head" -%h3.page-title - Git Tags +.gray-content-block - if can? current_user, :push_code, @project .pull-right = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do %i.fa.fa-add-sign New tag - -%p.light - Tags give the ability to mark specific points in history as being important -%hr + .oneline + Tags give the ability to mark specific points in history as being important .tags - unless @tags.empty? - %ul.bordered-list + %ul.content-list - @tags.each do |tag| = render 'tag', tag: @repository.find_tag(tag) diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 172fafdeeff..9f5c1be125c 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -1,4 +1,6 @@ - page_title "New Tag" += render "projects/commits/header_title" + - if @error .alert.alert-danger %button{ type: "button", class: "close", "data-dismiss" => "alert"} × diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index 5048154cb2f..367a87927d7 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -14,7 +14,7 @@ %small %i.fa.fa-plus -%div#tree-content-holder.tree-content-holder +%div#tree-content-holder.tree-content-holder.prepend-top-20 %table#tree-slider{class: "table_#{@hex_path} tree-table" } %thead %tr diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index c9e59428e78..dec4677f830 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,4 +1,5 @@ - page_title @path.presence || "Files", @ref +- header_title project_title(@project, "Files", project_files_path(@project)) = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml new file mode 100644 index 00000000000..48b3b5c9920 --- /dev/null +++ b/app/views/projects/triggers/_trigger.html.haml @@ -0,0 +1,14 @@ +%tr + %td + .clearfix + %span.monospace= trigger.token + + %td + - if trigger.last_trigger_request + #{time_ago_in_words(trigger.last_trigger_request.created_at)} ago + - else + Never + + %td + .pull-right + = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped" diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml new file mode 100644 index 00000000000..17dcb78e256 --- /dev/null +++ b/app/views/projects/triggers/index.html.haml @@ -0,0 +1,67 @@ +%h3.page-title + Triggers + +%p.light + Triggers can be used to force a rebuild of a specific branch or tag with an API call. + +%hr.clearfix + +-if @triggers.any? + %table.table + %thead + %th Token + %th Last used + %th + = render partial: 'trigger', collection: @triggers, as: :trigger +- else + %h4 No triggers + += form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create'), html: { class: 'form-horizontal' } do |f| + .clearfix + = f.submit "Add Trigger", class: 'btn btn-success pull-right' + +%hr.clearfix + +-if @triggers.any? + %h3 + Use CURL + + %p.light + Copy the token above and set your branch or tag name. This is the reference that will be rebuild. + + + %pre + :plain + curl -X POST \ + -F token=TOKEN \ + #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')} + %h3 + Use .gitlab-ci.yml + + %p.light + Copy the snippet to + %i .gitlab-ci.yml + of dependent project. + At the end of your build it will trigger this project to rebuilt. + + %pre + :plain + trigger: + type: deploy + script: + - "curl -X POST -F token=TOKEN #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}" + %h3 + Pass build variables + + %p.light + Add + %strong variables[VARIABLE]=VALUE + to API request. + The value of variable could then be used to distinguish triggered build from normal one. + + %pre + :plain + curl -X POST \ + -F token=TOKEN \ + -F "variables[RUN_NIGHTLY_BUILD]=true" \ + #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')} diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml new file mode 100644 index 00000000000..29416a94ff6 --- /dev/null +++ b/app/views/projects/variables/show.html.haml @@ -0,0 +1,39 @@ +%h3.page-title + Secret Variables + +%p.light + These variables will be set to environment by the runner and will be hidden in the build log. + %br + So you can use them for passwords, secret keys or whatever you want. + +%hr + + += nested_form_for @ci_project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f| + - if @project.errors.any? + #error_explanation + %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:" + .alert.alert-error + %ul + - @ci_project.errors.full_messages.each do |msg| + %li= msg + + = f.fields_for :variables do |variable_form| + .form-group + = variable_form.label :key, 'Key', class: 'control-label' + .col-sm-10 + = variable_form.text_field :key, class: 'form-control', placeholder: "PROJECT_VARIABLE" + + .form-group + = variable_form.label :value, 'Value', class: 'control-label' + .col-sm-10 + = variable_form.text_area :value, class: 'form-control', rows: 2, placeholder: "" + + = variable_form.link_to_remove "Remove this variable", class: 'btn btn-danger pull-right prepend-top-10' + %hr + %p + .clearfix + = f.link_to_add "Add a variable", :variables, class: 'btn btn-success pull-right' + + .form-actions + = f.submit 'Save changes', class: 'btn btn-save', return_to: request.original_url diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 904600499ae..05d754adbe5 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -21,7 +21,7 @@ .form-group.wiki-content = f.label :content, class: 'control-label' .col-sm-10 - = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' .col-sm-12.hint .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} diff --git a/app/views/projects/wikis/_header_title.html.haml b/app/views/projects/wikis/_header_title.html.haml new file mode 100644 index 00000000000..408adc36ca6 --- /dev/null +++ b/app/views/projects/wikis/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, 'Wiki', get_project_wiki_path(@project)) diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 788bb8cf1e2..14f25822259 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,8 +1,15 @@ %span.pull-right + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do + %i.fa.fa-plus + New Page + - if (@page && @page.persisted?) - = link_to history_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do + = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do Page History - if can?(current_user, :create_wiki, @project) - = link_to edit_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do + = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o Edit + += render 'projects/wikis/new' diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 804a1b52dbe..fffb4eb31ab 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,19 +1,10 @@ -%ul.nav.nav-tabs +%ul.center-top-menu = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = nav_link(path: 'wikis#pages') do - = link_to 'Pages', pages_namespace_project_wikis_path(@project.namespace, @project) + = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) = nav_link(path: 'wikis#git_access') do - = link_to git_access_namespace_project_wikis_path(@project.namespace, @project) do - %i.fa.fa-download + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do Git Access - - - if can?(current_user, :create_wiki, @project) - .pull-right - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - %i.fa.fa-plus - New Page - -= render 'projects/wikis/new' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 3f1dce1050c..0b709c3695b 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,4 +1,6 @@ - page_title "Edit", @page.title, "Wiki" += render "header_title" + = render 'nav' .pull-right = render 'main_links' diff --git a/app/views/projects/wikis/empty.html.haml b/app/views/projects/wikis/empty.html.haml index ead99412406..c7e490c3cd1 100644 --- a/app/views/projects/wikis/empty.html.haml +++ b/app/views/projects/wikis/empty.html.haml @@ -1,4 +1,6 @@ - page_title "Wiki" += render "header_title" + %h3.page-title Empty page %hr .error_message diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index 825f2a161c4..6417ef4a38b 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,15 +1,18 @@ - page_title "Git Access", "Wiki" += render "header_title" + = render 'nav' -.row - .col-sm-6 - %h3.page-title - Git access for - %strong= @project_wiki.path_with_namespace +.gray-content-block + .row + .col-sm-6 + %h3.page-title + Git access for + %strong= @project_wiki.path_with_namespace - .col-sm-6 - = render "shared/clone_panel", project: @project_wiki + .col-sm-6 + = render "shared/clone_panel", project: @project_wiki -.git-empty +.git-empty.prepend-top-default %fieldset %legend Install Gollum: %pre.dark @@ -20,7 +23,7 @@ %pre.dark :preserve git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} - cd #{@project_wiki.path} + cd #{h @project_wiki.path} %legend Start Gollum And Edit Locally: %pre.dark diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 673ec2d20e5..bfbef823b35 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,8 +1,11 @@ -- page_title "History", @page.title, "Wiki" +- page_title "History", @page.title.capitalize, "Wiki" += render "header_title" + = render 'nav' -%h3.page-title - %span.light History for - = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page) +.gray-content-block + %h3.page-title + %span.light History for + = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page) %table.table %thead diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 890ff1aed73..03e6a522b25 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,8 +1,11 @@ - page_title "All Pages", "Wiki" += render "header_title" + = render 'nav' -%h3.page-title - All Pages -%ul.bordered-list +.gray-content-block + %h3.page-title + All Pages +%ul.content-list - @wiki_pages.each do |wiki_page| %li %h4 diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 5c4dd7f91ae..55fbf5a8b6e 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,25 +1,28 @@ -- page_title @page.title, "Wiki" +- page_title @page.title.capitalize, "Wiki" += render "header_title" + = render 'nav' -%h3.page-title - = @page.title + +.gray-content-block = render 'main_links' + %h3.page-title + = @page.title.capitalize -.wiki-last-edit-by - Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} + .wiki-last-edit-by + Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} - if @page.historical? .warning_message This is an old version of this page. - You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", history_namespace_project_wiki_path(@project.namespace, @project, @page)}. + You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. -%hr -.wiki-holder +.wiki-holder.prepend-top-default .wiki = preserve do = render_wiki_content(@page) -%hr -.wiki-last-edit-by - Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} +.gray-content-block.footer-block + .wiki-last-edit-by + Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 07672359dba..b23b2f0d5eb 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -4,7 +4,7 @@ .input-group-btn %button{ | type: 'button', | - class: "btn btn-sm #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", | + class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", | :"data-clone" => project.ssh_url_to_repo, | :"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH", :"data-html" => "true", @@ -13,15 +13,15 @@ .input-group-btn %button{ | type: 'button', | - class: "btn btn-sm #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", | + class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", | :"data-clone" => project.http_url_to_repo, | :"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}", :"data-html" => "true", :"data-container" => "body"} = gitlab_config.protocol.upcase - = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true + = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true - if project.kind_of?(Project) .input-group-addon .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" } = visibility_level_icon(project.visibility_level) - = visibility_level_label(project.visibility_level).downcase + diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 334db60690d..8495774accc 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-pills.event_filter +.btn-group.btn-group-next.event-filter = event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml index 45ec49280d2..8d6e16f74c3 100644 --- a/app/views/shared/_field.html.haml +++ b/app/views/shared/_field.html.haml @@ -8,7 +8,10 @@ - help = field[:help] .form-group - = form.label name, title, class: "control-label" + - if type == "password" && value.present? + = form.label name, "Change #{title}", class: "control-label" + - else + = form.label name, title, class: "control-label" .col-sm-10 - if type == 'text' = form.text_field name, class: "form-control", placeholder: placeholder @@ -19,6 +22,6 @@ - elsif type == 'select' = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } - elsif type == 'password' - = form.password_field name, value: value, class: 'form-control' + = form.password_field name, autocomplete: "new-password", class: 'form-control' - if help %span.help-block= help diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index f685ae7726c..cbdecda4fff 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -1,14 +1,11 @@ -.milestones-filters.append-bottom-10 - %ul.nav.nav-tabs +.milestones-filters + %ul.center-top-menu %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} = link_to milestones_filter_path(state: 'opened') do - %i.fa.fa-exclamation-circle Open %li{class: ("active" if params[:state] == 'closed')} = link_to milestones_filter_path(state: 'closed') do - %i.fa.fa-check-circle Closed %li{class: ("active" if params[:state] == 'all')} = link_to milestones_filter_path(state: 'all') do - %i.fa.fa-compass All diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index 229ae359bc5..a54c5fa8c33 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -1,23 +1,21 @@ - group_member = local_assigns[:group_member] %li - if group_member - .pull-right.hidden-xs + .controls.hidden-xs - if can?(current_user, :admin_group, group) = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do %i.fa.fa-cogs - Settings = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do %i.fa.fa-sign-out - Leave - = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs" + = image_tag group_icon(group), class: "avatar s46 hidden-xs" = link_to group, class: 'group-name' do %strong= group.name - if group_member as - %strong #{group_member.human_access} + %span #{group_member.human_access} %div.light #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index bcaa48c7a12..8f16773077e 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,33 +1,28 @@ .issues-filters .issues-state-filters - %ul.nav.nav-tabs + %ul.center-top-menu %li{class: ("active" if params[:state] == 'opened')} = link_to page_filter_path(state: 'opened') do - = icon('exclamation-circle') #{state_filters_text_for(:opened, @project)} - if defined?(type) && type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} = link_to page_filter_path(state: 'merged') do - = icon('check-circle') #{state_filters_text_for(:merged, @project)} %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed') do - = icon('ban') #{state_filters_text_for(:closed, @project)} - else %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed') do - = icon('check-circle') #{state_filters_text_for(:closed, @project)} %li{class: ("active" if params[:state] == 'all')} = link_to page_filter_path(state: 'all') do - = icon('compass') #{state_filters_text_for(:all, @project)} - .issues-details-filters + .issues-details-filters.gray-content-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) .check-all-holder diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 09327d645f3..33ec726e93c 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -9,7 +9,7 @@ = f.label :title, class: 'control-label' do %strong= 'Title *' .col-sm-10 - = f.text_field :title, maxlength: 255, autofocus: true, + = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off', class: 'form-control pad js-gfm-input', required: true - if issuable.is_a?(MergeRequest) @@ -24,7 +24,7 @@ = f.label :description, 'Description', class: 'control-label' .col-sm-10 - = render layout: 'projects/md_preview', locals: { preview_class: "wiki", referenced_users: true } do + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' .col-sm-12.hint diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 021e3b689a1..16e1d8421de 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -1,14 +1,15 @@ - projects_limit = 20 unless local_assigns[:projects_limit] - avatar = true unless local_assigns[:avatar] == false - stars = true unless local_assigns[:stars] == false +- ci = false unless local_assigns[:ci] == true %ul.projects-list - projects.each_with_index do |project, i| - css_class = (i >= projects_limit) ? 'hide' : nil = render "shared/projects/project", project: project, - avatar: avatar, stars: stars, css_class: css_class + avatar: avatar, stars: stars, css_class: css_class, ci: ci - - if projects.count > projects_limit + - if projects.size > projects_limit %li.bottom.center .light #{projects_limit} of #{pluralize(projects.count, 'project')} displayed. diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 4bfdf4d55ff..e67e5a8a638 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -1,12 +1,14 @@ - avatar = true unless local_assigns[:avatar] == false - stars = true unless local_assigns[:stars] == false -- css_class = nil unless local_assigns[:css_class] +- ci = false unless local_assigns[:ci] == true +- css_class = '' unless local_assigns[:css_class] +- css_class += " no-description" unless project.description.present? %li.project-row{ class: css_class } - = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2'] do + = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2.2'] do = link_to project_path(project), class: dom_class(project) do - if avatar .dash-project-avatar - = project_icon(project, alt: '', class: 'avatar project-avatar s40') + = project_icon(project, alt: '', class: 'avatar project-avatar s46') %span.project-full-name %span.namespace-name - if project.namespace @@ -14,8 +16,16 @@ \/ %span.project-name.filter-title = project.name + + .project-controls + - if ci && !project.empty_repo? && project.commit + - if ci_commit = project.ci_commit(project.commit.sha) + = link_to ci_status_path(ci_commit), class: "c#{ci_status_color(ci_commit)}", + title: "Build status: #{ci_commit.status}", data: {toggle: 'tooltip', placement: 'left'} do + = ci_status_icon(ci_commit) + - if stars - %span.pull-right.light + %span %i.fa.fa-star = project.star_count - if project.description.present? diff --git a/app/views/snippets/_head.html.haml b/app/views/snippets/_head.html.haml deleted file mode 100644 index 0adf6b91f2c..00000000000 --- a/app/views/snippets/_head.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%ul.center-top-menu - = nav_link(page: user_snippets_path(current_user), html_options: {class: 'home'}) do - = link_to user_snippets_path(current_user), title: 'Your snippets', data: {placement: 'right'} do - Your Snippets - = nav_link(page: snippets_path) do - = link_to snippets_path, title: 'Explore snippets', data: {placement: 'right'} do - Explore Snippets diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml deleted file mode 100644 index 62a967a2e06..00000000000 --- a/app/views/snippets/current_user_index.html.haml +++ /dev/null @@ -1,35 +0,0 @@ -- page_title "Your Snippets" -= render 'head' - -.slead - Share code pastes with others out of git repository - - .pull-right - = link_to new_snippet_path, class: "btn btn-new btn-sm", title: "New Snippet" do - Add new snippet - -%ul.nav.nav-tabs - = nav_tab :scope, nil do - = link_to user_snippets_path(@user) do - All - %span.badge - = @user.snippets.count - = nav_tab :scope, 'are_private' do - = link_to user_snippets_path(@user, scope: 'are_private') do - Private - %span.badge - = @user.snippets.are_private.count - = nav_tab :scope, 'are_internal' do - = link_to user_snippets_path(@user, scope: 'are_internal') do - Internal - %span.badge - = @user.snippets.are_internal.count - = nav_tab :scope, 'are_public' do - = link_to user_snippets_path(@user, scope: 'are_public') do - Public - %span.badge - = @user.snippets.are_public.count - -.my-snippets - = render 'snippets' - diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 8608815e0a6..7e4918a6085 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -1,9 +1,13 @@ -- page_title "Public Snippets" -- if current_user - = render 'head' +- page_title "By #{@user.name}", "Snippets" -.slead - Public snippets created by you and other users are listed here +%ol.breadcrumb + %li + = link_to snippets_path do + Snippets + %li + = @user.name + .pull-right.hidden-xs + = link_to user_path(@user) do + #{@user.name} profile page = render 'snippets' - diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index aed00f9caeb..97374e073dc 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -20,10 +20,10 @@ .back-link - if @snippet.author == current_user - = link_to user_snippets_path(current_user) do + = link_to dashboard_snippets_path do ← your snippets - else - = link_to snippets_path do + = link_to explore_snippets_path do ← explore snippets .file-holder diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml deleted file mode 100644 index 7af5352da34..00000000000 --- a/app/views/snippets/user_index.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -- page_title "Snippets", @user.name - -%ol.breadcrumb - %li - = link_to snippets_path do - Snippets - %li - = @user.name - .pull-right.hidden-xs - = link_to user_path(@user) do - #{@user.name} profile page - -= render 'snippets' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index aa4e8722fb1..11beb3e3239 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -16,17 +16,16 @@ - if @user == current_user .pull-right.hidden-xs = link_to profile_path, class: 'btn btn-sm' do - %i.fa.fa-pencil-square-o - Edit Profile settings + = icon('user') + Profile settings - elsif current_user - .pull-right - %span.dropdown - %a.light.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"} + .report_abuse.pull-right + - if @user.abuse_report + %span#report_abuse_btn.light.btn.btn-sm.btn-close{title: 'Already reported for abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}} + = icon('exclamation-circle') + - else + %a.light.btn.btn-sm{href: new_abuse_report_path(user_id: @user.id), title: 'Report abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}} = icon('exclamation-circle') - %ul.dropdown-menu.dropdown-menu-right - %li - = link_to new_abuse_report_path(user_id: @user.id) do - Report abuse .username @#{@user.username} diff --git a/app/workers/ci/hip_chat_notifier_worker.rb b/app/workers/ci/hip_chat_notifier_worker.rb new file mode 100644 index 00000000000..ebb43570e2a --- /dev/null +++ b/app/workers/ci/hip_chat_notifier_worker.rb @@ -0,0 +1,19 @@ +module Ci + class HipChatNotifierWorker + include Sidekiq::Worker + + def perform(message, options={}) + room = options.delete('room') + token = options.delete('token') + server = options.delete('server') + name = options.delete('service_name') + client_opts = { + api_version: 'v2', + server_url: server + } + + client = HipChat::Client.new(token, client_opts) + client[room].send(name, message, options.symbolize_keys) + end + end +end diff --git a/app/workers/ci/slack_notifier_worker.rb b/app/workers/ci/slack_notifier_worker.rb new file mode 100644 index 00000000000..3bbb9b4bec7 --- /dev/null +++ b/app/workers/ci/slack_notifier_worker.rb @@ -0,0 +1,10 @@ +module Ci + class SlackNotifierWorker + include Sidekiq::Worker + + def perform(webhook_url, message, options={}) + notifier = Slack::Notifier.new(webhook_url) + notifier.ping(message, options) + end + end +end diff --git a/app/workers/ci/web_hook_worker.rb b/app/workers/ci/web_hook_worker.rb new file mode 100644 index 00000000000..0bb83845572 --- /dev/null +++ b/app/workers/ci/web_hook_worker.rb @@ -0,0 +1,9 @@ +module Ci + class WebHookWorker + include Sidekiq::Worker + + def perform(hook_id, data) + Ci::WebHook.find(hook_id).execute data + end + end +end diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 8cfb96ef376..5a921a73fe9 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -4,7 +4,7 @@ class EmailReceiverWorker sidekiq_options queue: :incoming_email def perform(raw) - return unless Gitlab::ReplyByEmail.enabled? + return unless Gitlab::IncomingEmail.enabled? begin Gitlab::Email::Receiver.new(raw).execute diff --git a/app/workers/fork_registration_worker.rb b/app/workers/fork_registration_worker.rb deleted file mode 100644 index fffa8b3a659..00000000000 --- a/app/workers/fork_registration_worker.rb +++ /dev/null @@ -1,12 +0,0 @@ -class ForkRegistrationWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(from_project_id, to_project_id, private_token) - from_project = Project.find(from_project_id) - to_project = Project.find(to_project_id) - - from_project.gitlab_ci_service.fork_registration(to_project, private_token) - end -end diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb new file mode 100644 index 00000000000..acd1c43f06b --- /dev/null +++ b/app/workers/repository_fork_worker.rb @@ -0,0 +1,34 @@ +class RepositoryForkWorker + include Sidekiq::Worker + include Gitlab::ShellAdapter + + sidekiq_options queue: :gitlab_shell + + def perform(project_id, source_path, target_path) + project = Project.find_by_id(project_id) + + unless project.present? + logger.error("Project #{project_id} no longer exists!") + return + end + + result = gitlab_shell.fork_repository(source_path, target_path) + + unless result + logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}") + project.import_fail + project.save + return + end + + if project.valid_repo? + ProjectCacheWorker.perform_async(project.id) + project.import_finish + else + project.import_fail + logger.error("Project #{id} had an invalid repository after fork") + end + + project.save + end +end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index f2ba2e15e7b..ea2808045eb 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -7,22 +7,31 @@ class RepositoryImportWorker def perform(project_id) project = Project.find(project_id) - import_result = gitlab_shell.send(:import_repository, + unless project.import_url == Project::UNKNOWN_IMPORT_URL + import_result = gitlab_shell.send(:import_repository, project.path_with_namespace, project.import_url) - return project.import_fail unless import_result + return project.import_fail unless import_result + else + unless project.create_repository + return project.import_fail + end + end - data_import_result = if project.import_type == 'github' - Gitlab::GithubImport::Importer.new(project).execute - elsif project.import_type == 'gitlab' - Gitlab::GitlabImport::Importer.new(project).execute - elsif project.import_type == 'bitbucket' - Gitlab::BitbucketImport::Importer.new(project).execute - elsif project.import_type == 'google_code' - Gitlab::GoogleCodeImport::Importer.new(project).execute - else - true - end + data_import_result = case project.import_type + when 'github' + Gitlab::GithubImport::Importer.new(project).execute + when 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + when 'bitbucket' + Gitlab::BitbucketImport::Importer.new(project).execute + when 'google_code' + Gitlab::GoogleCodeImport::Importer.new(project).execute + when 'fogbugz' + Gitlab::FogbugzImport::Importer.new(project).execute + else + true + end return project.import_fail unless data_import_result Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' |