diff options
Diffstat (limited to 'app')
32 files changed, 436 insertions, 306 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 44606989395..d716218d9a4 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -20,15 +20,15 @@ import groupsSelect from './groups_select'; import NamespaceSelect from './namespace_select'; /* global NewCommitForm */ /* global NewBranchForm */ -/* global Project */ -/* global ProjectAvatar */ +import Project from './project'; +import projectAvatar from './project_avatar'; /* global MergeRequest */ /* global Compare */ /* global CompareAutocomplete */ /* global ProjectFindFile */ /* global ProjectNew */ /* global ProjectShow */ -/* global ProjectImport */ +import projectImport from './project_import'; import Labels from './labels'; import LabelManager from './label_manager'; /* global Sidebar */ @@ -378,7 +378,7 @@ import Diff from './diff'; initSettingsPanels(); break; case 'projects:imports:show': - new ProjectImport(); + projectImport(); break; case 'projects:pipelines:new': new NewBranchForm($('.js-new-pipeline-form')); @@ -604,7 +604,7 @@ import Diff from './diff'; break; case 'projects': new Project(); - new ProjectAvatar(); + projectAvatar(); switch (path[1]) { case 'compare': new CompareAutocomplete(); diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index 917a45eb06b..a02c79b787e 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -52,3 +52,31 @@ export function bytesToKiB(number) { export function bytesToMiB(number) { return number / (BYTES_IN_KIB * BYTES_IN_KIB); } + +/** + * Utility function that calculates GiB of the given bytes. + * @param {Number} number + * @returns {Number} + */ +export function bytesToGiB(number) { + return number / (BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB); +} + +/** + * Port of rails number_to_human_size + * Formats the bytes in number into a more understandable + * representation (e.g., giving it 1500 yields 1.5 KB). + * + * @param {Number} size + * @returns {String} + */ +export function numberToHumanSize(size) { + if (size < BYTES_IN_KIB) { + return `${size} bytes`; + } else if (size < BYTES_IN_KIB * BYTES_IN_KIB) { + return `${bytesToKiB(size).toFixed(2)} KiB`; + } else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) { + return `${bytesToMiB(size).toFixed(2)} MiB`; + } + return `${bytesToGiB(size).toFixed(2)} GiB`; +} diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 1aa63216baf..17236c91490 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -100,6 +100,10 @@ export function visitUrl(url, external = false) { } } +export function redirectTo(url) { + return window.location.assign(url); +} + window.gl = window.gl || {}; window.gl.utils = { ...(window.gl.utils || {}), diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 127fddcf8d3..0035dd23011 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -69,8 +69,6 @@ import './notifications_dropdown'; import './notifications_form'; import './pager'; import './preview_markdown'; -import './project'; -import './project_avatar'; import './project_find_file'; import './project_import'; import './project_label_subscription'; diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js index 6264750a4fb..52315e969d1 100644 --- a/app/assets/javascripts/members.js +++ b/app/assets/javascripts/members.js @@ -5,7 +5,6 @@ export default class Members { } addListeners() { - $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); $('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this)); $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this)); gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change'); @@ -33,17 +32,6 @@ export default class Members { }); }); } - // eslint-disable-next-line class-methods-use-this - removeRow(e) { - const $target = $(e.target); - - if ($target.hasClass('btn-remove')) { - $target.closest('.member') - .fadeOut(function fadeOutMemberRow() { - $(this).remove(); - }); - } - } formSubmit(e, $el = null) { const $this = e ? $(e.currentTarget) : $el; diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index fe6602259e2..ddb78aaeea1 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -1,139 +1,131 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ +/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ /* global ProjectSelect */ import Cookies from 'js-cookie'; -(function() { - this.Project = (function() { - function Project() { - const $cloneOptions = $('ul.clone-options-dropdown'); - const $projectCloneField = $('#project_clone'); - const $cloneBtnText = $('a.clone-dropdown-btn span'); +export default class Project { + constructor() { + const $cloneOptions = $('ul.clone-options-dropdown'); + const $projectCloneField = $('#project_clone'); + const $cloneBtnText = $('a.clone-dropdown-btn span'); - const selectedCloneOption = $cloneBtnText.text().trim(); - if (selectedCloneOption.length > 0) { - $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active'); - } - - $('a', $cloneOptions).on('click', (e) => { - const $this = $(e.currentTarget); - const url = $this.attr('href'); - - e.preventDefault(); - - $('.is-active', $cloneOptions).not($this).removeClass('is-active'); - $this.toggleClass('is-active'); - $projectCloneField.val(url); - $cloneBtnText.text($this.text()); - - return $('.clone').text(url); - }); - // Ref switcher - this.initRefSwitcher(); - $('.project-refs-select').on('change', function() { - return $(this).parents('form').submit(); - }); - $('.hide-no-ssh-message').on('click', function(e) { - Cookies.set('hide_no_ssh_message', 'false'); - $(this).parents('.no-ssh-key-message').remove(); - return e.preventDefault(); - }); - $('.hide-no-password-message').on('click', function(e) { - Cookies.set('hide_no_password_message', 'false'); - $(this).parents('.no-password-message').remove(); - return e.preventDefault(); - }); - this.projectSelectDropdown(); + const selectedCloneOption = $cloneBtnText.text().trim(); + if (selectedCloneOption.length > 0) { + $(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active'); } - Project.prototype.projectSelectDropdown = function() { - new ProjectSelect(); - $('.project-item-select').on('click', (function(_this) { - return function(e) { - return _this.changeProject($(e.currentTarget).val()); - }; - })(this)); - }; - - Project.prototype.changeProject = function(url) { - return window.location = url; - }; - - Project.prototype.initRefSwitcher = function() { - var refListItem = document.createElement('li'); - var refLink = document.createElement('a'); - - refLink.href = '#'; - - return $('.js-project-refs-dropdown').each(function() { - var $dropdown, selected; - $dropdown = $(this); - selected = $dropdown.data('selected'); - return $dropdown.glDropdown({ - data: function(term, callback) { - return $.ajax({ - url: $dropdown.data('refs-url'), - data: { - ref: $dropdown.data('ref'), - search: term - }, - dataType: "json" - }).done(function(refs) { - return callback(refs); - }); - }, - selectable: true, - filterable: true, - filterRemote: true, - filterByText: true, - inputFieldName: $dropdown.data('input-field-name'), - fieldName: $dropdown.data('field-name'), - renderRow: function(ref) { - var li = refListItem.cloneNode(false); - - if (ref.header != null) { - li.className = 'dropdown-header'; - li.textContent = ref.header; - } else { - var link = refLink.cloneNode(false); - - if (ref === selected) { - link.className = 'is-active'; - } - - link.textContent = ref; - link.dataset.ref = ref; - - li.appendChild(link); + $('a', $cloneOptions).on('click', (e) => { + const $this = $(e.currentTarget); + const url = $this.attr('href'); + + e.preventDefault(); + + $('.is-active', $cloneOptions).not($this).removeClass('is-active'); + $this.toggleClass('is-active'); + $projectCloneField.val(url); + $cloneBtnText.text($this.text()); + + return $('.clone').text(url); + }); + // Ref switcher + Project.initRefSwitcher(); + $('.project-refs-select').on('change', function() { + return $(this).parents('form').submit(); + }); + $('.hide-no-ssh-message').on('click', function(e) { + Cookies.set('hide_no_ssh_message', 'false'); + $(this).parents('.no-ssh-key-message').remove(); + return e.preventDefault(); + }); + $('.hide-no-password-message').on('click', function(e) { + Cookies.set('hide_no_password_message', 'false'); + $(this).parents('.no-password-message').remove(); + return e.preventDefault(); + }); + Project.projectSelectDropdown(); + } + + static projectSelectDropdown () { + new ProjectSelect(); + $('.project-item-select').on('click', e => Project.changeProject($(e.currentTarget).val())); + } + + static changeProject(url) { + return window.location = url; + } + + static initRefSwitcher() { + var refListItem = document.createElement('li'); + var refLink = document.createElement('a'); + + refLink.href = '#'; + + return $('.js-project-refs-dropdown').each(function() { + var $dropdown, selected; + $dropdown = $(this); + selected = $dropdown.data('selected'); + return $dropdown.glDropdown({ + data: function(term, callback) { + return $.ajax({ + url: $dropdown.data('refs-url'), + data: { + ref: $dropdown.data('ref'), + search: term, + }, + dataType: 'json', + }).done(function(refs) { + return callback(refs); + }); + }, + selectable: true, + filterable: true, + filterRemote: true, + filterByText: true, + inputFieldName: $dropdown.data('input-field-name'), + fieldName: $dropdown.data('field-name'), + renderRow: function(ref) { + var li = refListItem.cloneNode(false); + + if (ref.header != null) { + li.className = 'dropdown-header'; + li.textContent = ref.header; + } else { + var link = refLink.cloneNode(false); + + if (ref === selected) { + link.className = 'is-active'; } - return li; - }, - id: function(obj, $el) { - return $el.attr('data-ref'); - }, - toggleLabel: function(obj, $el) { - return $el.text().trim(); - }, - clicked: function(options) { - const { e } = options; - e.preventDefault(); - if ($('input[name="ref"]').length) { - var $form = $dropdown.closest('form'); - - var $visit = $dropdown.data('visit'); - var shouldVisit = $visit ? true : $visit; - var action = $form.attr('action'); - var divider = action.indexOf('?') === -1 ? '?' : '&'; - if (shouldVisit) { - gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`); - } + link.textContent = ref; + link.dataset.ref = ref; + + li.appendChild(link); + } + + return li; + }, + id: function(obj, $el) { + return $el.attr('data-ref'); + }, + toggleLabel: function(obj, $el) { + return $el.text().trim(); + }, + clicked: function(options) { + const { e } = options; + e.preventDefault(); + if ($('input[name="ref"]').length) { + var $form = $dropdown.closest('form'); + + var $visit = $dropdown.data('visit'); + var shouldVisit = $visit ? true : $visit; + var action = $form.attr('action'); + var divider = action.indexOf('?') === -1 ? '?' : '&'; + if (shouldVisit) { + gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`); } } - }); + }, }); - }; - - return Project; - })(); -}).call(window); + }); + } +} diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js index aabdfbf65e2..56627aa155c 100644 --- a/app/assets/javascripts/project_avatar.js +++ b/app/assets/javascripts/project_avatar.js @@ -1,20 +1,13 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */ -(function() { - this.ProjectAvatar = (function() { - function ProjectAvatar() { - $('.js-choose-project-avatar-button').bind('click', function() { - var form; - form = $(this).closest('form'); - return form.find('.js-project-avatar-input').click(); - }); - $('.js-project-avatar-input').bind('change', function() { - var filename, form; - form = $(this).closest('form'); - filename = $(this).val().replace(/^.*[\\\/]/, ''); - return form.find('.js-avatar-filename').text(filename); - }); - } +export default function projectAvatar() { + $('.js-choose-project-avatar-button').bind('click', function onClickAvatar() { + const form = $(this).closest('form'); + return form.find('.js-project-avatar-input').click(); + }); - return ProjectAvatar; - })(); -}).call(window); + $('.js-project-avatar-input').bind('change', function onClickAvatarInput() { + const form = $(this).closest('form'); + // eslint-disable-next-line no-useless-escape + const filename = $(this).val().replace(/^.*[\\\/]/, ''); + return form.find('.js-avatar-filename').text(filename); + }); +} diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js index 08334bf1ec5..d2d26d6f67e 100644 --- a/app/assets/javascripts/project_import.js +++ b/app/assets/javascripts/project_import.js @@ -1,13 +1,8 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */ +import { visitUrl } from './lib/utils/url_utility'; -(function() { - this.ProjectImport = (function() { - function ProjectImport() { - setTimeout(function() { - return gl.utils.visitUrl(location.href); - }, 5000); - } +export default function projectImport() { + setTimeout(() => { + visitUrl(location.href); + }, 5000); +} - return ProjectImport; - })(); -}).call(window); diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index e917279947e..14d43e135fe 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -8,6 +8,7 @@ import tooltip from '../../vue_shared/directives/tooltip'; import timeagoMixin from '../../vue_shared/mixins/timeago'; import { errorMessages, errorMessagesTypes } from '../constants'; + import { numberToHumanSize } from '../../lib/utils/number_utils'; export default { props: { @@ -41,6 +42,10 @@ return item.layers ? n__('%d layer', '%d layers', item.layers) : ''; }, + formatSize(size) { + return numberToHumanSize(size); + }, + handleDeleteRegistry(registry) { this.deleteRegistry(registry) .then(() => this.fetchList({ repo: this.repo })) @@ -97,7 +102,7 @@ </span> </td> <td> - {{item.size}} + {{formatSize(item.size)}} <template v-if="item.size && item.layers"> · </template> diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c334f39f416..66212be1b8f 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -34,6 +34,7 @@ @import "framework/modal"; @import "framework/pagination"; @import "framework/panels"; +@import "framework/popup"; @import "framework/secondary-navigation-elements"; @import "framework/selects"; @import "framework/sidebar"; diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index 6bb096fc5bd..10f9e9b70b0 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -7,29 +7,67 @@ width: 100%; height: 100%; padding-bottom: 25px; - border: 1px solid $border-color; border-radius: $border-radius-default; } } -.blank-state { - padding-top: 20px; - padding-bottom: 20px; +.blank-state-row { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + height: 100%; +} + +.blank-state-welcome { text-align: center; + padding: 20px 0 40px; + + .blank-state-welcome-title { + font-size: 24px; + } + + .blank-state-text { + margin-bottom: 0; + } +} - &.blank-state-welcome { - .blank-state-welcome-title { - font-size: 24px; +.blank-state-link { + display: block; + color: $gl-text-color; + flex: 0 0 100%; + margin-bottom: 15px; + + @media (min-width: $screen-sm-min) { + flex: 0 0 49%; + + &:nth-child(odd) { + margin-right: 5px; } - .blank-state-text { - margin-bottom: 0; + &:nth-child(even) { + margin-left: 5px; } } - .blank-state-icon { - padding-bottom: 20px; + &:hover { + background-color: $gray-light; + text-decoration: none; + color: $gl-text-color; + } +} + +.blank-state { + padding: 20px; + border: 1px solid $border-color; + border-radius: $border-radius-default; + + @media (min-width: $screen-sm-min) { + display: flex; + align-items: center; + padding: 50px 30px; + } + .blank-state-icon { svg { display: block; margin: auto; @@ -38,13 +76,17 @@ .blank-state-title { margin-top: 0; - margin-bottom: 10px; font-size: 18px; } - .blank-state-text { - max-width: $container-text-max-width; - margin: 0 auto $gl-padding; - font-size: 14px; + .blank-state-body { + @media (max-width: $screen-xs-max) { + text-align: center; + margin-top: 20px; + } + + @media (min-width: $screen-sm-min) { + padding-left: 20px; + } } } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 16d5edde61e..33012133b66 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -180,3 +180,31 @@ display: none; } } + +@mixin triangle($color, $border-color, $size, $border-size) { + &::before, + &::after { + bottom: 100%; + left: 50%; + border: solid transparent; + content: ''; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + } + + &::before { + border-color: transparent; + border-bottom-color: $border-color; + border-width: ($size + $border-size); + margin-left: -($size + $border-size); + } + + &::after { + border-color: transparent; + border-bottom-color: $color; + border-width: $size; + margin-left: -$size; + } +} diff --git a/app/assets/stylesheets/framework/popup.scss b/app/assets/stylesheets/framework/popup.scss new file mode 100644 index 00000000000..5c76205095f --- /dev/null +++ b/app/assets/stylesheets/framework/popup.scss @@ -0,0 +1,15 @@ +.popup { + @include triangle( + $gray-lighter, + $gray-darker, + $popup-triangle-size, + $popup-triangle-border-size + ); + + padding: $gl-padding; + background-color: $gray-lighter; + border: 1px solid $gray-darker; + border-radius: $border-radius-default; + box-shadow: 0 5px 8px $popup-box-shadow-color; + position: relative; +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 8ab48e4844f..2dafd1ce47c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -719,3 +719,10 @@ Image Commenting cursor */ $image-comment-cursor-left-offset: 12; $image-comment-cursor-top-offset: 30; + +/* +Popup +*/ +$popup-triangle-size: 15px; +$popup-triangle-border-size: 1px; +$popup-box-shadow-color: rgba(90, 90, 90, 0.05); diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 6cca9f95618..4311f9d4db9 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -92,15 +92,7 @@ module LfsRequest end def storage_project - @storage_project ||= begin - result = project - - # TODO: Make this go to the fork_network root immeadiatly - # dependant on the discussion in: https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 - result = result.fork_source while result.forked? - - result - end + @storage_project ||= project.lfs_storage_project end def objects diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index 1b985ea9763..1c4c09c772f 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -4,7 +4,8 @@ class Projects::JobsController < Projects::ApplicationController before_action :authorize_read_build!, only: [:index, :show, :status, :raw, :trace] before_action :authorize_update_build!, - except: [:index, :show, :status, :raw, :trace, :cancel_all] + except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase] + before_action :authorize_erase_build!, only: [:erase] layout 'project' @@ -131,6 +132,10 @@ class Projects::JobsController < Projects::ApplicationController return access_denied! unless can?(current_user, :update_build, build) end + def authorize_erase_build! + return access_denied! unless can?(current_user, :erase_build, build) + end + def build @build ||= project.builds.find(params[:id]) .present(current_user: current_user) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 24c07f3dc70..b46ec5e5350 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -36,6 +36,7 @@ class IssuableFinder iids label_name milestone_title + my_reaction_emoji non_archived project_id scope diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6ca46ae89c1..1b2b0d17910 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -192,6 +192,10 @@ module Ci project.build_timeout end + def triggered_by?(current_user) + user == current_user + end + # A slugified version of the build ref, suitable for inclusion in URLs and # domain names. Rules: # diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index fcbe3d2b67b..19814864e50 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -66,8 +66,8 @@ module Ci state_machine :status, initial: :created do event :enqueue do - transition created: :pending - transition [:success, :failed, :canceled, :skipped] => :running + transition [:created, :skipped] => :pending + transition [:success, :failed, :canceled] => :running end event :run do diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index b7cf96abe83..fc586fa216e 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -6,16 +6,8 @@ class LfsObject < ActiveRecord::Base mount_uploader :file, LfsObjectUploader - def storage_project(project) - if project && project.forked? - storage_project(project.forked_from_project) - else - project - end - end - def project_allowed_access?(project) - projects.exists?(storage_project(project).id) + projects.exists?(project.lfs_storage_project.id) end def self.destroy_unreferenced diff --git a/app/models/project.rb b/app/models/project.rb index bae16b6b2af..853f6bc504a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1047,6 +1047,18 @@ class Project < ActiveRecord::Base forked_from_project || fork_network&.root_project end + def lfs_storage_project + @lfs_storage_project ||= begin + result = self + + # TODO: Make this go to the fork_network root immeadiatly + # dependant on the discussion in: https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 + result = result.fork_source while result&.forked? + + result || self + end + end + def personal? !group end diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index 984e5482288..1ab391a5a9d 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -10,6 +10,15 @@ module Ci end end - rule { protected_ref }.prevent :update_build + condition(:owner_of_job) do + can?(:developer_access) && @subject.triggered_by?(@user) + end + + rule { protected_ref }.policy do + prevent :update_build + prevent :erase_build + end + + rule { can?(:master_access) | owner_of_job }.enable :erase_build end end diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 8c89eea607f..69d46f5ec14 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -6,7 +6,7 @@ class BuildDetailsEntity < JobEntity expose :pipeline, using: PipelineEntity expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity - expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build| + expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build| erase_project_job_path(project, build) end diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 4965dffab9d..4f60be698e9 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -64,7 +64,7 @@ %th Projects %th Jobs %th Tags - %th Last contact + %th= link_to 'Last contact', admin_runners_path(params.slice(:search).merge(sort: 'contacted_asc')) %th - @runners.each do |runner| diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml index 57544559824..573a4b93d67 100644 --- a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml +++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml @@ -1,33 +1,41 @@ -.blank-state - .blank-state-icon - = custom_icon("add_new_user", size: 50) - .blank-state-body - %h3.blank-state-title - Add user - %p.blank-state-text - Add your team members and others to GitLab. - = link_to new_admin_user_path, class: "btn btn-new" do - New user +.blank-state-row + = link_to new_project_path, class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("add_new_project", size: 50) + .blank-state-body + %h3.blank-state-title + Create a project + %p.blank-state-text + Projects are where you store your code, access issues, wiki and other features of GitLab. -.blank-state - .blank-state-icon - = custom_icon("configure_server", size: 50) - .blank-state-body - %h3.blank-state-title - Configure GitLab - %p.blank-state-text - Make adjustments to how your GitLab instance is set up. - = link_to admin_root_path, class: "btn btn-new" do - Configure + - if current_user.can_create_group? + = link_to admin_root_path, class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("add_new_group", size: 50) + .blank-state-body + %h3.blank-state-title + Create a group + %p.blank-state-text + Groups are a great way to organize projects and people. -- if current_user.can_create_group? - .blank-state - .blank-state-icon - = custom_icon("add_new_group", size: 50) - .blank-state-body - %h3.blank-state-title - Create a group - %p.blank-state-text - Groups are a great way to organize projects and people. - = link_to new_group_path, class: "btn btn-new" do - New group + = link_to new_admin_user_path, class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("add_new_user", size: 50) + .blank-state-body + %h3.blank-state-title + Add people + %p.blank-state-text + Add your team members and others to GitLab. + + = link_to admin_root_path, class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("configure_server", size: 50) + .blank-state-body + %h3.blank-state-title + Configure GitLab + %p.blank-state-text + Make adjustments to how your GitLab instance is set up. diff --git a/app/views/dashboard/projects/_blank_state_welcome.html.haml b/app/views/dashboard/projects/_blank_state_welcome.html.haml index a93a3415ee1..8d5bddbb288 100644 --- a/app/views/dashboard/projects/_blank_state_welcome.html.haml +++ b/app/views/dashboard/projects/_blank_state_welcome.html.haml @@ -1,48 +1,58 @@ - public_project_count = ProjectsFinder.new(current_user: current_user).execute.count -- if current_user.can_create_group? - .blank-state - .blank-state-icon - = custom_icon("add_new_group", size: 50) - .blank-state-body - %h3.blank-state-title - Create a group for several dependent projects. - %p.blank-state-text - Groups are the best way to manage projects and members. - = link_to new_group_path, class: "btn btn-new" do - New group +.blank-state-row + - if current_user.can_create_project? + = link_to new_project_path, class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("add_new_project", size: 50) + .blank-state-body + %h3.blank-state-title + Create a project + %p.blank-state-text + Projects are where you store your code, access issues, wiki and other features of GitLab. + - else + .blank-state + .blank-state-icon + = custom_icon("add_new_project", size: 50) + .blank-state-body + %h3.blank-state-title + Create a project + %p.blank-state-text + If you are added to a project, it will be displayed here. -.blank-state - .blank-state-icon - = custom_icon("add_new_project", size: 50) - .blank-state-body - %h3.blank-state-title - Create a project - %p.blank-state-text - - if current_user.can_create_project? - You don't have access to any projects right now. - You can create up to - %strong= number_with_delimiter(current_user.projects_limit) - = succeed "." do - = "project".pluralize(current_user.projects_limit) - - else - If you are added to a project, it will be displayed here. - - if current_user.can_create_project? - = link_to new_project_path, class: "btn btn-new" do - New project + - if current_user.can_create_group? + = link_to new_group_path, class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("add_new_group", size: 50) + .blank-state-body + %h3.blank-state-title + Create a group + %p.blank-state-text + Groups are the best way to manage projects and members. -- if public_project_count > 0 - .blank-state - .blank-state-icon - = custom_icon("globe", size: 50) - .blank-state-body - %h3.blank-state-title - Explore public projects - %p.blank-state-text - There are - = number_with_delimiter(public_project_count) - public projects on this server. - Public projects are an easy way to allow - everyone to have read-only access. - = link_to trending_explore_projects_path, class: "btn btn-new" do - Browse projects + - if public_project_count > 0 + = link_to trending_explore_projects_path, class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("globe", size: 50) + .blank-state-body + %h3.blank-state-title + Explore public projects + %p.blank-state-text + There are + = number_with_delimiter(public_project_count) + public projects on this server. + Public projects are an easy way to allow + everyone to have read-only access. + + = link_to "https://docs.gitlab.com/", class: "blank-state-link" do + .blank-state + .blank-state-icon + = custom_icon("lightbulb", size: 50) + .blank-state-body + %h3.blank-state-title + Learn more about GitLab + %p.blank-state-text + Take a look at the documentation to discover all of GitLab's capabilities. diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index ad3fac6d164..18a82feb189 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,12 +1,13 @@ -.row.blank-state-parent-container +.blank-state-parent-container .section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" } .container.section-body - .blank-state.blank-state-welcome - %h2.blank-state-welcome-title - Welcome to GitLab - %p.blank-state-text - Code, test, and deploy together - - if current_user.admin? - = render "blank_state_admin_welcome" - - else - = render "blank_state_welcome" + .row + .blank-state-welcome + %h2.blank-state-welcome-title + Welcome to GitLab + %p.blank-state-text + Code, test, and deploy together + - if current_user.admin? + = render "blank_state_admin_welcome" + - else + = render "blank_state_welcome" diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index ce0e3872240..2abd2c9e652 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -71,7 +71,7 @@ class: 'js-raw-link-controller has-tooltip controllers-buttons' do = icon('file-text-o') - - if can?(current_user, :update_build, @project) && @build.erasable? + - if @build.erasable? && can?(current_user, :erase_build, @build) = link_to erase_project_job_path(@project, @build), method: :post, data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' }, diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml index ea91e8af70e..f53b81cada6 100644 --- a/app/views/projects/protected_tags/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml @@ -2,7 +2,7 @@ .create_access_levels-container = dropdown_tag('Select', options: { toggle_class: 'js-allowed-to-create wide', - dropdown_class: 'dropdown-menu-selectable', + dropdown_class: 'dropdown-menu-selectable capitalize-header', data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }}) = render 'projects/protected_tags/shared/create_protected_tag' diff --git a/app/views/shared/icons/_add_new_project.svg b/app/views/shared/icons/_add_new_project.svg index 3c1e15453df..cf8762944ca 100644 --- a/app/views/shared/icons/_add_new_project.svg +++ b/app/views/shared/icons/_add_new_project.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M30 24a4 4 0 0 0-4 4v22a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V28a4 4 0 0 0-4-4H30zm0-4h18a8 8 0 0 1 8 8v22a8 8 0 0 1-8 8H30a8 8 0 0 1-8-8V28a8 8 0 0 1 8-8z"/><path fill="#FC6D26" d="M33 30h8a2 2 0 1 1 0 4h-8a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4z"/></g></svg>
\ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M30 24c-2.21 0-4 1.79-4 4v22c0 2.21 1.79 4 4 4h18c2.21 0 4-1.79 4-4V28c0-2.21-1.79-4-4-4H30zm0-4h18c4.418 0 8 3.582 8 8v22c0 4.418-3.582 8-8 8H30c-4.418 0-8-3.582-8-8V28c0-4.418 3.582-8 8-8z"/><path fill="#6B4FBB" d="M33 30h8c1.105 0 2 .895 2 2s-.895 2-2 2h-8c-1.105 0-2-.895-2-2s.895-2 2-2zm0 7h12c1.105 0 2 .895 2 2s-.895 2-2 2H33c-1.105 0-2-.895-2-2s.895-2 2-2zm0 7h12c1.105 0 2 .895 2 2s-.895 2-2 2H33c-1.105 0-2-.895-2-2s.895-2 2-2z"/></g></svg> diff --git a/app/views/shared/icons/_lightbulb.svg b/app/views/shared/icons/_lightbulb.svg new file mode 100644 index 00000000000..2fcc4c65f99 --- /dev/null +++ b/app/views/shared/icons/_lightbulb.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#6B4FBB" d="M33 52h12c1.105 0 2 .895 2 2s-.895 2-2 2H33c-1.105 0-2-.895-2-2s.895-2 2-2zm1 5h10c1.105 0 2 .895 2 2s-.895 2-2 2H34c-1.105 0-2-.895-2-2s.895-2 2-2z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M45.542 46.932l.346-2.36c.198-1.348.737-2.623 1.566-3.705 3.025-3.946 4.485-7.29 4.547-9.96C52.153 24.41 46.843 20 39 20c-7.777 0-13 4.374-13 11 0 2.4 1.462 5.73 4.573 9.846.815 1.08 1.343 2.345 1.536 3.683l.353 2.456 13.08-.054zm-17.038.624L28.15 45.1c-.097-.67-.36-1.303-.768-1.842C23.794 38.51 22 34.424 22 31c0-9.39 7.61-15 17-15s17.218 5.614 17 15c-.085 3.64-1.875 7.74-5.37 12.3-.416.54-.685 1.18-.784 1.853l-.346 2.36c-.288 1.958-1.963 3.41-3.942 3.42l-13.08.053c-1.994.008-3.69-1.455-3.974-3.43z"/><path fill="#6B4FBB" d="M41 38.732c-.598-.345-1-.992-1-1.732 0-1.105.895-2 2-2s2 .895 2 2c0 .74-.402 1.387-1 1.732V42c0 .552-.448 1-1 1s-1-.448-1-1v-3.268zm-6 0c-.598-.345-1-.992-1-1.732 0-1.105.895-2 2-2s2 .895 2 2c0 .74-.402 1.387-1 1.732V42c0 .552-.448 1-1 1s-1-.448-1-1v-3.268z"/></g></svg> diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 951b4dd7b36..2c27dd638a7 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -104,7 +104,6 @@ class: 'btn btn-remove prepend-left-10' - else = link_to member, - remote: true, method: :delete, data: { confirm: remove_member_message(member) }, class: 'btn btn-remove prepend-left-10', |