mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-09 04:36:11 +10:00
Merge master into issue_12758
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.9.0 (unreleased)
|
||||
- Fix error when CI job variables key specified but not defined
|
||||
- Fix pipeline status when there are no builds in pipeline
|
||||
- Fix Error 500 when using closes_issues API with an external issue tracker
|
||||
- Add more information into RSS feed for issues (Alexander Matyushentsev)
|
||||
- Bulk assign/unassign labels to issues.
|
||||
@@ -12,6 +14,7 @@ v 8.9.0 (unreleased)
|
||||
- Fix an issue where note polling stopped working if a window was in the
|
||||
background during a refresh.
|
||||
- Make EmailsOnPushWorker use Sidekiq mailers queue
|
||||
- Redesign all Devise emails. !4297
|
||||
- Fix wiki page events' webhook to point to the wiki repository
|
||||
- Don't show tags for revert and cherry-pick operations
|
||||
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
|
||||
@@ -22,42 +25,56 @@ v 8.9.0 (unreleased)
|
||||
- Added descriptions to notification settings dropdown
|
||||
- Improve note validation to prevent errors when creating invalid note via API
|
||||
- Reduce number of fog gem dependencies
|
||||
- Implement a fair usage of shared runners
|
||||
- Remove project notification settings associated with deleted projects
|
||||
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects
|
||||
- Add a metric for the number of new Redis connections created by a transaction
|
||||
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
|
||||
- Redesign navigation for project pages
|
||||
- Fix images in sign-up confirmation email
|
||||
- Added shortcut 'y' for copying a files content hash URL #14470
|
||||
- Fix groups API to list only user's accessible projects
|
||||
- Fix horizontal scrollbar for long commit message.
|
||||
- Add Environments and Deployments
|
||||
- Redesign account and email confirmation emails
|
||||
- Don't fail builds for projects that are deleted
|
||||
- Support Docker Registry manifest v1
|
||||
- `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix
|
||||
- Bump nokogiri to 1.6.8
|
||||
- Use gitlab-shell v3.0.0
|
||||
- Fixed alignment of download dropdown in merge requests
|
||||
- Upgrade to jQuery 2
|
||||
- Adds selected branch name to the dropdown toggle
|
||||
- Use Knapsack to evenly distribute tests across multiple nodes
|
||||
- Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
|
||||
- Don't allow MRs to be merged when commits were added since the last review / page load
|
||||
- Add DB index on users.state
|
||||
- Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
|
||||
- Changed the Slack build message to use the singular duration if necessary (Aran Koning)
|
||||
- Fix race condition on merge when build succeeds
|
||||
- Links from a wiki page to other wiki pages should be rewritten as expected
|
||||
- Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
|
||||
- Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393
|
||||
- Fix issues filter when ordering by milestone
|
||||
- Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3
|
||||
- Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid)
|
||||
- TeamCity Service: Fix URL handling when base URL contains a path
|
||||
- Todos will display target state if issuable target is 'Closed' or 'Merged'
|
||||
- Validate only and except regexp
|
||||
- Fix bug when sorting issues by milestone due date and filtering by two or more labels
|
||||
- Add support for using Yubikeys (U2F) for two-factor authentication
|
||||
- Link to blank group icon doesn't throw a 404 anymore
|
||||
- Remove 'main language' feature
|
||||
- Toggle whitespace button now available for compare branches diffs #17881
|
||||
- Pipelines can be canceled only when there are running builds
|
||||
- Allow authentication using personal access tokens
|
||||
- Use downcased path to container repository as this is expected path by Docker
|
||||
- Custom notification settings
|
||||
- Projects pending deletion will render a 404 page
|
||||
- Measure queue duration between gitlab-workhorse and Rails
|
||||
- Added Gfm autocomplete for labels
|
||||
- Make Omniauth providers specs to not modify global configuration
|
||||
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
|
||||
- Make authentication service for Container Registry to be compatible with < Docker 1.11
|
||||
- Add Application Setting to configure Container Registry token expire delay (default 5min)
|
||||
- Cache assigned issue and merge request counts in sidebar nav
|
||||
@@ -74,9 +91,11 @@ v 8.9.0 (unreleased)
|
||||
- Replace Colorize with Rainbow for coloring console output in Rake tasks.
|
||||
- Add workhorse controller and API helpers
|
||||
- An indicator is now displayed at the top of the comment field for confidential issues.
|
||||
- Show categorised search queries in the search autocomplete
|
||||
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
|
||||
- Improve issuables APIs performance when accessing notes !4471
|
||||
- External links now open in a new tab
|
||||
- Prevent default actions of disabled buttons and links
|
||||
- Markdown editor now correctly resets the input value on edit cancellation !4175
|
||||
- Toggling a task list item in a issue/mr description does not creates a Todo for mentions
|
||||
- Improved UX of date pickers on issue & milestone forms
|
||||
@@ -94,21 +113,31 @@ v 8.9.0 (unreleased)
|
||||
- New custom icons for navigation
|
||||
- Horizontally scrolling navigation on project, group, and profile settings pages
|
||||
- Hide global side navigation by default
|
||||
- Fix project Star/Unstar project button tooltip
|
||||
- Remove tanuki logo from side navigation; center on top nav
|
||||
- Include user relationships when retrieving award_emoji
|
||||
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
|
||||
- Set inverse_of for Project/Service association to reduce the number of queries
|
||||
- Update tanuki logo highlight/loading colors
|
||||
- Use Git cached counters for branches and tags on project page
|
||||
|
||||
v 8.8.5 (unreleased)
|
||||
- Ensure branch cleanup regardless of whether the GitHub import process succeeds
|
||||
- Fix todos page throwing errors when you have a project pending deletion
|
||||
- Reduce number of SQL queries when rendering user references
|
||||
- Import GitHub repositories respecting the API rate limit
|
||||
- Fix importer for GitHub comments on diff
|
||||
- Disable Webhooks before proceeding with the GitHub import
|
||||
- Fix incremental trace upload API when using multi-byte UTF-8 chars in trace
|
||||
v 8.8.5
|
||||
- Import GitHub repositories respecting the API rate limit !4166
|
||||
- Fix todos page throwing errors when you have a project pending deletion !4300
|
||||
- Disable Webhooks before proceeding with the GitHub import !4470
|
||||
- Fix importer for GitHub comments on diff !4488
|
||||
- Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498
|
||||
- Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541
|
||||
- Prevent unauthorized access for projects build traces
|
||||
- Forbid scripting for wiki files
|
||||
- Only show notes through JSON on confidential issues that the user has access to
|
||||
- Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions
|
||||
- Banzai::Filter::ExternalLinkFilter use XPath instead CSS expressions
|
||||
|
||||
v 8.8.4
|
||||
- Fix LDAP-based login for users with 2FA enabled. !4493
|
||||
- Added descriptions to notification settings dropdown
|
||||
- Due date can be removed from milestones
|
||||
|
||||
v 8.8.3
|
||||
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
|
||||
@@ -224,6 +253,9 @@ v 8.8.0
|
||||
|
||||
v 8.7.7
|
||||
- Fix import by `Any Git URL` broken if the URL contains a space
|
||||
- Prevent unauthorized access to other projects build traces
|
||||
- Forbid scripting for wiki files
|
||||
- Only show notes through JSON on confidential issues that the user has access to
|
||||
|
||||
v 8.7.6
|
||||
- Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
|
||||
@@ -386,6 +418,11 @@ v 8.7.0
|
||||
- Add RAW build trace output and button on build page
|
||||
- Add incremental build trace update into CI API
|
||||
|
||||
v 8.6.9
|
||||
- Prevent unauthorized access to other projects build traces
|
||||
- Forbid scripting for wiki files
|
||||
- Only show notes through JSON on confidential issues that the user has access to
|
||||
|
||||
v 8.6.8
|
||||
- Prevent privilege escalation via "impersonate" feature
|
||||
- Prevent privilege escalation via notes API
|
||||
@@ -540,6 +577,10 @@ v 8.6.0
|
||||
- Trigger a todo for mentions on commits page
|
||||
- Let project owners and admins soft delete issues and merge requests
|
||||
|
||||
v 8.5.13
|
||||
- Prevent unauthorized access to other projects build traces
|
||||
- Forbid scripting for wiki files
|
||||
|
||||
v 8.5.12
|
||||
- Prevent privilege escalation via "impersonate" feature
|
||||
- Prevent privilege escalation via notes API
|
||||
@@ -701,6 +742,10 @@ v 8.5.0
|
||||
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
|
||||
- Add Todos
|
||||
|
||||
v 8.4.11
|
||||
- Prevent unauthorized access to other projects build traces
|
||||
- Forbid scripting for wiki files
|
||||
|
||||
v 8.4.10
|
||||
- Prevent privilege escalation via "impersonate" feature
|
||||
- Prevent privilege escalation via notes API
|
||||
@@ -837,6 +882,10 @@ v 8.4.0
|
||||
- Add IP check against DNSBLs at account sign-up
|
||||
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
|
||||
|
||||
v 8.3.10
|
||||
- Prevent unauthorized access to other projects build traces
|
||||
- Forbid scripting for wiki files
|
||||
|
||||
v 8.3.9
|
||||
- Prevent privilege escalation via "impersonate" feature
|
||||
- Prevent privilege escalation via notes API
|
||||
@@ -955,6 +1004,10 @@ v 8.3.0
|
||||
- Expose Git's version in the admin area
|
||||
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
|
||||
|
||||
v 8.2.6
|
||||
- Prevent unauthorized access to other projects build traces
|
||||
- Forbid scripting for wiki files
|
||||
|
||||
v 8.2.5
|
||||
- Prevent privilege escalation via "impersonate" feature
|
||||
- Prevent privilege escalation via notes API
|
||||
|
||||
@@ -52,7 +52,7 @@ gem "browser", '~> 2.0.3'
|
||||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem "gitlab_git", '~> 10.0'
|
||||
gem "gitlab_git", '~> 10.2'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
@@ -227,7 +227,6 @@ gem 'gon', '~> 6.0.1'
|
||||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||
gem 'jquery-rails', '~> 4.1.0'
|
||||
gem 'jquery-ui-rails', '~> 5.0.0'
|
||||
gem 'raphael-rails', '~> 2.1.2'
|
||||
gem 'request_store', '~> 1.3.0'
|
||||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
|
||||
+2
-4
@@ -277,7 +277,7 @@ GEM
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_emoji (0.3.1)
|
||||
gemojione (~> 2.2, >= 2.2.1)
|
||||
gitlab_git (10.1.3)
|
||||
gitlab_git (10.2.0)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
@@ -556,7 +556,6 @@ GEM
|
||||
rainbow (2.1.0)
|
||||
raindrops (0.15.0)
|
||||
rake (10.5.0)
|
||||
raphael-rails (2.1.2)
|
||||
rb-fsevent (0.9.6)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
@@ -875,7 +874,7 @@ DEPENDENCIES
|
||||
github-markup (~> 1.3.1)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_emoji (~> 0.3.0)
|
||||
gitlab_git (~> 10.0)
|
||||
gitlab_git (~> 10.2)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.1.0)
|
||||
@@ -938,7 +937,6 @@ DEPENDENCIES
|
||||
rails (= 4.2.6)
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rainbow (~> 2.1.0)
|
||||
raphael-rails (~> 2.1.2)
|
||||
rblineprof
|
||||
rdoc (~> 3.6)
|
||||
recaptcha (~> 3.0)
|
||||
|
||||
@@ -42,10 +42,10 @@ class @LabelManager
|
||||
$from = @prioritizedLabels
|
||||
|
||||
if $from.find('li').length is 1
|
||||
$from.find('.empty-message').show()
|
||||
$from.find('.empty-message').removeClass('hidden')
|
||||
|
||||
if not $target.find('li').length
|
||||
$target.find('.empty-message').hide()
|
||||
$target.find('.empty-message').addClass('hidden')
|
||||
|
||||
$label.detach().appendTo($target)
|
||||
|
||||
@@ -54,6 +54,9 @@ class @LabelManager
|
||||
|
||||
if action is 'remove'
|
||||
xhr = $.ajax url: url, type: 'DELETE'
|
||||
|
||||
# Restore empty message
|
||||
$from.find('.empty-message').removeClass('hidden') unless $from.find('li').length
|
||||
else
|
||||
xhr = @savePrioritySort($label, action)
|
||||
|
||||
|
||||
@@ -32,10 +32,6 @@
|
||||
#= require bootstrap/tooltip
|
||||
#= require bootstrap/popover
|
||||
#= require select2
|
||||
#= require raphael
|
||||
#= require g.raphael
|
||||
#= require g.bar
|
||||
#= require branch-graph
|
||||
#= require ace/ace
|
||||
#= require ace/ext-searchbox
|
||||
#= require underscore
|
||||
@@ -125,9 +121,10 @@ window.onload = ->
|
||||
setTimeout shiftWindow, 100
|
||||
|
||||
$ ->
|
||||
gl.utils.preventDisabledButtons()
|
||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
||||
|
||||
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
|
||||
$(".nav-sidebar").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
|
||||
|
||||
# Click a .js-select-on-focus field, select the contents
|
||||
$(".js-select-on-focus").on "focusin", ->
|
||||
@@ -257,3 +254,31 @@ $ ->
|
||||
gl.awardsHandler = new AwardsHandler()
|
||||
checkInitialSidebarSize()
|
||||
new Aside()
|
||||
|
||||
# Sidenav pinning
|
||||
if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
|
||||
$.cookie('pin_nav', 'false')
|
||||
$('.page-with-sidebar')
|
||||
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
|
||||
.removeClass('page-sidebar-pinned')
|
||||
$('.navbar-fixed-top').removeClass('header-pinned-nav')
|
||||
|
||||
$(document)
|
||||
.off 'click', '.js-nav-pin'
|
||||
.on 'click', '.js-nav-pin', (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$(this).toggleClass 'is-active'
|
||||
|
||||
if $.cookie('pin_nav') is 'true'
|
||||
$.cookie 'pin_nav', 'false'
|
||||
$('.page-with-sidebar')
|
||||
.removeClass('page-sidebar-pinned')
|
||||
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
|
||||
$('.navbar-fixed-top')
|
||||
.removeClass('header-pinned-nav')
|
||||
.toggleClass('header-collapsed header-expanded')
|
||||
else
|
||||
$.cookie 'pin_nav', 'true'
|
||||
$('.page-with-sidebar').addClass('page-sidebar-pinned')
|
||||
$('.navbar-fixed-top').addClass('header-pinned-nav')
|
||||
|
||||
@@ -40,7 +40,7 @@ class @AwardsHandler
|
||||
$menu = $ '.emoji-menu'
|
||||
|
||||
if $addBtn.hasClass 'js-note-emoji'
|
||||
$addBtn.parents('.note').find('.js-awards-block').addClass 'current'
|
||||
$addBtn.closest('.note').find('.js-awards-block').addClass 'current'
|
||||
else
|
||||
$addBtn.closest('.js-awards-block').addClass 'current'
|
||||
|
||||
|
||||
@@ -1,58 +1,5 @@
|
||||
class @BlobGitignoreSelector
|
||||
constructor: (opts) ->
|
||||
{
|
||||
@dropdown
|
||||
@editor
|
||||
@$wrapper = @dropdown.closest('.gitignore-selector')
|
||||
@$filenameInput = $('#file_name')
|
||||
@data = @dropdown.data('filenames')
|
||||
} = opts
|
||||
#= require blob/template_selector
|
||||
|
||||
@dropdown.glDropdown(
|
||||
data: @data,
|
||||
filterable: true,
|
||||
selectable: true,
|
||||
search:
|
||||
fields: ['name']
|
||||
clicked: @onClick
|
||||
text: (gitignore) ->
|
||||
gitignore.name
|
||||
)
|
||||
|
||||
@toggleGitignoreSelector()
|
||||
@bindEvents()
|
||||
|
||||
bindEvents: ->
|
||||
@$filenameInput
|
||||
.on 'keyup blur', (e) =>
|
||||
@toggleGitignoreSelector()
|
||||
|
||||
toggleGitignoreSelector: ->
|
||||
filename = @$filenameInput.val() or $('.editor-file-name').text().trim()
|
||||
@$wrapper.toggleClass 'hidden', filename isnt '.gitignore'
|
||||
|
||||
onClick: (item, el, e) =>
|
||||
e.preventDefault()
|
||||
@requestIgnoreFile(item.name)
|
||||
|
||||
requestIgnoreFile: (name) ->
|
||||
Api.gitignoreText name, @requestIgnoreFileSuccess.bind(@)
|
||||
|
||||
requestIgnoreFileSuccess: (gitignore) ->
|
||||
@editor.setValue(gitignore.content, 1)
|
||||
@editor.focus()
|
||||
|
||||
class @BlobGitignoreSelectors
|
||||
constructor: (opts) ->
|
||||
{
|
||||
@$dropdowns = $('.js-gitignore-selector')
|
||||
@editor
|
||||
} = opts
|
||||
|
||||
@$dropdowns.each (i, dropdown) =>
|
||||
$dropdown = $(dropdown)
|
||||
|
||||
new BlobGitignoreSelector(
|
||||
dropdown: $dropdown,
|
||||
editor: @editor
|
||||
)
|
||||
class @BlobGitignoreSelector extends TemplateSelector
|
||||
requestFile: (query) ->
|
||||
Api.gitignoreText query.name, @requestFileSuccess.bind(@)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
class @BlobGitignoreSelectors
|
||||
constructor: (opts) ->
|
||||
{
|
||||
@$dropdowns = $('.js-gitignore-selector')
|
||||
@editor
|
||||
} = opts
|
||||
|
||||
@$dropdowns.each (i, dropdown) =>
|
||||
$dropdown = $(dropdown)
|
||||
|
||||
new BlobGitignoreSelector(
|
||||
pattern: /(.gitignore)/,
|
||||
data: $dropdown.data('data'),
|
||||
wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
|
||||
dropdown: $dropdown,
|
||||
editor: @editor
|
||||
)
|
||||
@@ -1,30 +1,9 @@
|
||||
class @BlobLicenseSelector
|
||||
licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
|
||||
#= require blob/template_selector
|
||||
|
||||
constructor: (editor) ->
|
||||
@$licenseSelector = $('.js-license-selector')
|
||||
$fileNameInput = $('#file_name')
|
||||
class @BlobLicenseSelector extends TemplateSelector
|
||||
requestFile: (query) ->
|
||||
data =
|
||||
project: @dropdown.data('project')
|
||||
fullname: @dropdown.data('fullname')
|
||||
|
||||
initialFileNameValue = if $fileNameInput.length
|
||||
$fileNameInput.val()
|
||||
else if $('.editor-file-name').length
|
||||
$('.editor-file-name').text().trim()
|
||||
|
||||
@toggleLicenseSelector(initialFileNameValue)
|
||||
|
||||
if $fileNameInput
|
||||
$fileNameInput.on 'keyup blur', (e) =>
|
||||
@toggleLicenseSelector($(e.target).val())
|
||||
|
||||
$('select.license-select').on 'change', (e) ->
|
||||
data =
|
||||
project: $(this).data('project')
|
||||
fullname: $(this).data('fullname')
|
||||
Api.licenseText $(this).val(), data, (license) ->
|
||||
editor.setValue(license.content, -1)
|
||||
|
||||
toggleLicenseSelector: (fileName) =>
|
||||
if @licenseRegex.test(fileName)
|
||||
@$licenseSelector.show()
|
||||
else
|
||||
@$licenseSelector.hide()
|
||||
Api.licenseText query.id, data, @requestFileSuccess.bind(@)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
class @BlobLicenseSelectors
|
||||
constructor: (opts) ->
|
||||
{
|
||||
@$dropdowns = $('.js-license-selector')
|
||||
@editor
|
||||
} = opts
|
||||
|
||||
@$dropdowns.each (i, dropdown) =>
|
||||
$dropdown = $(dropdown)
|
||||
|
||||
new BlobLicenseSelector(
|
||||
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
|
||||
data: $dropdown.data('data'),
|
||||
wrapper: $dropdown.closest('.js-license-selector-wrap'),
|
||||
dropdown: $dropdown,
|
||||
editor: @editor
|
||||
)
|
||||
@@ -12,8 +12,9 @@ class @EditBlob
|
||||
$("#file-content").val(@editor.getValue())
|
||||
|
||||
@initModePanesAndLinks()
|
||||
new BlobLicenseSelector(@editor)
|
||||
new BlobGitignoreSelectors(editor: @editor)
|
||||
|
||||
new BlobLicenseSelectors { @editor }
|
||||
new BlobGitignoreSelectors { @editor }
|
||||
|
||||
initModePanesAndLinks: ->
|
||||
@$editModePanes = $(".js-edit-mode-pane")
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
class @TemplateSelector
|
||||
constructor: (opts = {}) ->
|
||||
{
|
||||
@dropdown,
|
||||
@data,
|
||||
@pattern,
|
||||
@wrapper,
|
||||
@editor,
|
||||
@fileEndpoint,
|
||||
@$input = $('#file_name')
|
||||
} = opts
|
||||
|
||||
@buildDropdown()
|
||||
@bindEvents()
|
||||
@onFilenameUpdate()
|
||||
|
||||
buildDropdown: ->
|
||||
@dropdown.glDropdown(
|
||||
data: @data,
|
||||
filterable: true,
|
||||
selectable: true,
|
||||
search:
|
||||
fields: ['name']
|
||||
clicked: @onClick
|
||||
text: (item) ->
|
||||
item.name
|
||||
)
|
||||
|
||||
bindEvents: ->
|
||||
@$input.on('keyup blur', (e) =>
|
||||
@onFilenameUpdate()
|
||||
)
|
||||
|
||||
onFilenameUpdate: ->
|
||||
return unless @$input.length
|
||||
|
||||
filenameMatches = @pattern.test(@$input.val().trim())
|
||||
|
||||
if not filenameMatches
|
||||
@wrapper.addClass('hidden')
|
||||
return
|
||||
|
||||
@wrapper.removeClass('hidden')
|
||||
|
||||
onClick: (item, el, e) =>
|
||||
e.preventDefault()
|
||||
@requestFile(item)
|
||||
|
||||
requestFile: (item) ->
|
||||
# To be implemented on the extending class
|
||||
# e.g.
|
||||
# Api.gitignoreText item.name, @requestFileSuccess.bind(@)
|
||||
|
||||
requestFileSuccess: (file) ->
|
||||
@editor.setValue(file.content, 1)
|
||||
@editor.focus()
|
||||
@@ -29,6 +29,7 @@ class Dispatcher
|
||||
new Todos()
|
||||
when 'projects:milestones:new', 'projects:milestones:edit'
|
||||
new ZenMode()
|
||||
new DueDateSelect()
|
||||
new GLForm($('.milestone-form'))
|
||||
when 'groups:milestones:new'
|
||||
new ZenMode()
|
||||
@@ -53,9 +54,13 @@ class Dispatcher
|
||||
new Diff()
|
||||
shortcut_handler = new ShortcutsIssuable(true)
|
||||
new ZenMode()
|
||||
new MergedButtons()
|
||||
when 'projects:merge_requests:commits', 'projects:merge_requests:builds'
|
||||
new MergedButtons()
|
||||
when "projects:merge_requests:diffs"
|
||||
new Diff()
|
||||
new ZenMode()
|
||||
new MergedButtons()
|
||||
when 'projects:merge_requests:index'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
Issuable.init()
|
||||
@@ -68,9 +73,7 @@ class Dispatcher
|
||||
new Diff()
|
||||
new ZenMode()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:commits:show'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:activity'
|
||||
when 'projects:commits:show', 'projects:activity'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:show'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
@@ -97,6 +100,7 @@ class Dispatcher
|
||||
when 'projects:blob:show', 'projects:blame:show'
|
||||
new LineHighlighter()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
new ShortcutsBlob true
|
||||
when 'projects:labels:new', 'projects:labels:edit'
|
||||
new Labels()
|
||||
when 'projects:labels:index'
|
||||
@@ -132,14 +136,13 @@ class Dispatcher
|
||||
new Project()
|
||||
new ProjectAvatar()
|
||||
switch path[1]
|
||||
when 'compare'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'edit'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
new ProjectNew()
|
||||
when 'new'
|
||||
new ProjectNew()
|
||||
when 'show'
|
||||
new ProjectNew()
|
||||
new ProjectShow()
|
||||
new NotificationsDropdown()
|
||||
when 'wikis'
|
||||
@@ -150,9 +153,9 @@ class Dispatcher
|
||||
when 'snippets'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
new ZenMode() if path[2] == 'show'
|
||||
when 'labels', 'graphs'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
|
||||
when 'labels', 'graphs', 'compare', 'pipelines', 'forks', \
|
||||
'milestones', 'project_members', 'deploy_keys', 'builds', \
|
||||
'hooks', 'services', 'protected_branches'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
|
||||
# If we haven't installed a custom shortcut handler, install the default one
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
class @DueDateSelect
|
||||
constructor: ->
|
||||
# Milestone edit/new form
|
||||
$datePicker = $('.datepicker')
|
||||
|
||||
if $datePicker.length
|
||||
$dueDate = $('#milestone_due_date')
|
||||
$datePicker.datepicker
|
||||
dateFormat: 'yy-mm-dd'
|
||||
onSelect: (dateText, inst) ->
|
||||
$dueDate.val(dateText)
|
||||
.datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()))
|
||||
|
||||
$('.js-clear-due-date').on 'click', (e) ->
|
||||
e.preventDefault()
|
||||
$.datepicker._clearDate($datePicker)
|
||||
|
||||
# Issuable sidebar
|
||||
$loading = $('.js-issuable-update .due_date')
|
||||
.find('.block-loading')
|
||||
.hide()
|
||||
@@ -32,7 +48,7 @@ class @DueDateSelect
|
||||
date = new Date value.replace(new RegExp('-', 'g'), ',')
|
||||
mediumDate = $.datepicker.formatDate 'M d, yy', date
|
||||
else
|
||||
mediumDate = 'None'
|
||||
mediumDate = 'No due date'
|
||||
|
||||
data = {}
|
||||
data[abilityName] = {}
|
||||
@@ -50,7 +66,8 @@ class @DueDateSelect
|
||||
$selectbox.hide()
|
||||
$value.css('display', '')
|
||||
|
||||
$valueContent.html(mediumDate)
|
||||
cssClass = if Date.parse(mediumDate) then 'bold' else 'no-value'
|
||||
$valueContent.html("<span class='#{cssClass}'>#{mediumDate}</span>")
|
||||
$sidebarValue.html(mediumDate)
|
||||
|
||||
if value isnt ''
|
||||
|
||||
@@ -15,6 +15,9 @@ GitLab.GfmAutoComplete =
|
||||
Members:
|
||||
template: '<li>${username} <small>${title}</small></li>'
|
||||
|
||||
Labels:
|
||||
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
|
||||
|
||||
# Issues and MergeRequests
|
||||
Issues:
|
||||
template: '<li><small>${id}</small> ${title}</li>'
|
||||
@@ -176,6 +179,25 @@ GitLab.GfmAutoComplete =
|
||||
title: sanitize(m.title)
|
||||
search: "#{m.iid} #{m.title}"
|
||||
|
||||
@input.atwho
|
||||
at: '~'
|
||||
alias: 'labels'
|
||||
searchKey: 'search'
|
||||
displayTpl: @Labels.template
|
||||
insertTpl: '${atwho-at}${title}'
|
||||
callbacks:
|
||||
beforeSave: (merges) ->
|
||||
sanitizeLabelTitle = (title)->
|
||||
if /\w+\s+\w+/g.test(title)
|
||||
"\"#{sanitize(title)}\""
|
||||
else
|
||||
sanitize(title)
|
||||
|
||||
$.map merges, (m) ->
|
||||
title: sanitizeLabelTitle(m.title)
|
||||
color: m.color
|
||||
search: "#{m.title}"
|
||||
|
||||
destroyAtWho: ->
|
||||
@input.atwho('destroy')
|
||||
|
||||
@@ -195,6 +217,8 @@ GitLab.GfmAutoComplete =
|
||||
@input.atwho 'load', 'mergerequests', data.mergerequests
|
||||
# load emojis
|
||||
@input.atwho 'load', ':', data.emojis
|
||||
# load labels
|
||||
@input.atwho 'load', '~', data.labels
|
||||
|
||||
# This trigger at.js again
|
||||
# otherwise we would be stuck with loading until the user types
|
||||
|
||||
@@ -39,7 +39,7 @@ class @LabelsSelect
|
||||
</a>
|
||||
<% }); %>'
|
||||
)
|
||||
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
|
||||
labelNoneHTMLTemplate = '<span class="no-value">None</span>'
|
||||
|
||||
if newLabelField.length
|
||||
|
||||
@@ -145,7 +145,7 @@ class @LabelsSelect
|
||||
template = labelHTMLTemplate(data)
|
||||
labelCount = data.labels.length
|
||||
else
|
||||
template = labelNoneHTMLTemplate()
|
||||
template = labelNoneHTMLTemplate
|
||||
$value
|
||||
.removeAttr('style')
|
||||
.html(template)
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
((w) ->
|
||||
|
||||
w.gl or= {}
|
||||
w.gl.utils or= {}
|
||||
|
||||
w.gl.utils.isInGroupsPage = ->
|
||||
|
||||
return $('body').data('page').split(':')[0] is 'groups'
|
||||
|
||||
|
||||
w.gl.utils.isInProjectPage = ->
|
||||
|
||||
return $('body').data('page').split(':')[0] is 'projects'
|
||||
|
||||
|
||||
w.gl.utils.getProjectSlug = ->
|
||||
|
||||
return if @isInProjectPage() then $('body').data 'project' else null
|
||||
|
||||
|
||||
w.gl.utils.getGroupSlug = ->
|
||||
|
||||
return if @isInGroupsPage() then $('body').data 'group' else null
|
||||
|
||||
|
||||
|
||||
gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) ->
|
||||
|
||||
$tooltipEl
|
||||
.tooltip 'destroy'
|
||||
.attr 'title', newTitle
|
||||
.tooltip 'fixTitle'
|
||||
|
||||
|
||||
gl.utils.preventDisabledButtons = ->
|
||||
|
||||
$('.btn').click (e) ->
|
||||
if $(this).hasClass 'disabled'
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
return false
|
||||
|
||||
|
||||
jQuery.timefor = (time, suffix, expiredLabel) ->
|
||||
|
||||
return '' unless time
|
||||
|
||||
@@ -9,7 +9,7 @@ class @MergeRequest
|
||||
# Options:
|
||||
# action - String, current controller action
|
||||
#
|
||||
constructor: (@opts) ->
|
||||
constructor: (@opts = {}) ->
|
||||
this.$el = $('.merge-request')
|
||||
|
||||
this.$('.show-all-commits').on 'click', =>
|
||||
|
||||
@@ -88,7 +88,7 @@ class @MergeRequestTabs
|
||||
|
||||
scrollToElement: (container) ->
|
||||
if window.location.hash
|
||||
navBarHeight = $('.navbar-gitlab').outerHeight()
|
||||
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
|
||||
|
||||
$el = $("#{container} #{window.location.hash}:not(.match)")
|
||||
$.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
class @MergedButtons
|
||||
constructor: ->
|
||||
@$removeBranchWidget = $('.remove_source_branch_widget')
|
||||
@$removeBranchProgress = $('.remove_source_branch_in_progress')
|
||||
@$removeBranchFailed = $('.remove_source_branch_widget.failed')
|
||||
|
||||
@cleanEventListeners()
|
||||
@initEventListeners()
|
||||
|
||||
cleanEventListeners: ->
|
||||
$(document).off 'click', '.remove_source_branch'
|
||||
$(document).off 'ajax:success', '.remove_source_branch'
|
||||
$(document).off 'ajax:error', '.remove_source_branch'
|
||||
|
||||
initEventListeners: ->
|
||||
$(document).on 'click', '.remove_source_branch', @removeSourceBranch
|
||||
$(document).on 'ajax:success', '.remove_source_branch', @removeBranchSuccess
|
||||
$(document).on 'ajax:error', '.remove_source_branch', @removeBranchError
|
||||
|
||||
removeSourceBranch: =>
|
||||
@$removeBranchWidget.hide()
|
||||
@$removeBranchProgress.show()
|
||||
|
||||
removeBranchSuccess: ->
|
||||
location.reload()
|
||||
|
||||
removeBranchError: ->
|
||||
@$removeBranchWidget.hide()
|
||||
@$removeBranchProgress.hide()
|
||||
@$removeBranchFailed.show()
|
||||
@@ -24,14 +24,10 @@ class @MilestoneSelect
|
||||
|
||||
if issueUpdateURL
|
||||
milestoneLinkTemplate = _.template(
|
||||
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>">
|
||||
<span class="has-tooltip" data-container="body" title="<%= remaining %>">
|
||||
<%= _.escape(title) %>
|
||||
</span>
|
||||
</a>'
|
||||
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>" class="bold has-tooltip" data-container="body" title="<%= remaining %>"><%= _.escape(title) %></a>'
|
||||
)
|
||||
|
||||
milestoneLinkNoneTemplate = '<div class="light">None</div>'
|
||||
milestoneLinkNoneTemplate = '<span class="no-value">None</span>'
|
||||
|
||||
collapsedSidebarLabelTemplate = _.template(
|
||||
'<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
|
||||
@@ -116,7 +112,7 @@ class @MilestoneSelect
|
||||
.val()
|
||||
data = {}
|
||||
data[abilityName] = {}
|
||||
data[abilityName].milestone_id = selected
|
||||
data[abilityName].milestone_id = if selected? then selected else null
|
||||
$loading
|
||||
.fadeIn()
|
||||
$dropdown.trigger('loading.gl.dropdown')
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# This is a manifest file that'll be compiled into including all the files listed below.
|
||||
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
||||
# be included in the compiled file accessible from http://example.com/assets/application.js
|
||||
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
# the compiled file.
|
||||
#
|
||||
#= require raphael
|
||||
#= require g.raphael
|
||||
#= require g.bar
|
||||
#= require_tree .
|
||||
|
||||
$ ->
|
||||
network_graph = new Network({
|
||||
url: $(".network-graph").attr('data-url'),
|
||||
commit_url: $(".network-graph").attr('data-commit-url'),
|
||||
ref: $(".network-graph").attr('data-ref'),
|
||||
commit_id: $(".network-graph").attr('data-commit-id')
|
||||
})
|
||||
|
||||
new ShortcutsNetwork(network_graph.branch_graph)
|
||||
@@ -67,8 +67,12 @@ class @SearchAutocomplete
|
||||
getData: (term, callback) ->
|
||||
_this = @
|
||||
|
||||
# Do not trigger request if input is empty
|
||||
return if @searchInput.val() is ''
|
||||
unless term
|
||||
if contents = @getCategoryContents()
|
||||
@searchInput.data('glDropdown').filter.options.callback contents
|
||||
@enableAutocomplete()
|
||||
|
||||
return
|
||||
|
||||
# Prevent multiple ajax calls
|
||||
return if @loadingSuggestions
|
||||
@@ -122,6 +126,37 @@ class @SearchAutocomplete
|
||||
).always ->
|
||||
_this.loadingSuggestions = false
|
||||
|
||||
|
||||
getCategoryContents: ->
|
||||
|
||||
userId = gon.current_user_id
|
||||
{ utils, projectOptions, groupOptions, dashboardOptions } = gl
|
||||
|
||||
if utils.isInGroupsPage() and groupOptions
|
||||
options = groupOptions[utils.getGroupSlug()]
|
||||
|
||||
else if utils.isInProjectPage() and projectOptions
|
||||
options = projectOptions[utils.getProjectSlug()]
|
||||
|
||||
else if dashboardOptions
|
||||
options = dashboardOptions
|
||||
|
||||
{ issuesPath, mrPath, name } = options
|
||||
|
||||
items = [
|
||||
{ header: "#{name}" }
|
||||
{ text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" }
|
||||
{ text: "Issues I've created", url: "#{issuesPath}/?author_id=#{userId}" }
|
||||
'separator'
|
||||
{ text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" }
|
||||
{ text: "Merge requests I've created", url: "#{mrPath}/?author_id=#{userId}" }
|
||||
]
|
||||
|
||||
items.splice 0, 1 unless name
|
||||
|
||||
return items
|
||||
|
||||
|
||||
serializeState: ->
|
||||
{
|
||||
# Search Criteria
|
||||
@@ -209,6 +244,12 @@ class @SearchAutocomplete
|
||||
@isFocused = true
|
||||
@wrap.addClass('search-active')
|
||||
|
||||
@getData() if @getValue() is ''
|
||||
|
||||
|
||||
getValue: -> return @searchInput.val()
|
||||
|
||||
|
||||
onClearInputClick: (e) =>
|
||||
e.preventDefault()
|
||||
@searchInput.val('').focus()
|
||||
@@ -229,6 +270,10 @@ class @SearchAutocomplete
|
||||
@locationBadgeEl.text(badgeText).show()
|
||||
@wrap.addClass('has-location-badge')
|
||||
|
||||
|
||||
hasLocationBadge: -> return @wrap.is '.has-location-badge'
|
||||
|
||||
|
||||
restoreOriginalState: ->
|
||||
inputs = Object.keys @originalState
|
||||
|
||||
@@ -257,13 +302,14 @@ class @SearchAutocomplete
|
||||
|
||||
@getElement("##{input}").val('')
|
||||
|
||||
|
||||
removeLocationBadge: ->
|
||||
|
||||
@locationBadgeEl.hide()
|
||||
|
||||
# Reset state
|
||||
@resetSearchState()
|
||||
|
||||
@wrap.removeClass('has-location-badge')
|
||||
@disableAutocomplete()
|
||||
|
||||
|
||||
disableAutocomplete: ->
|
||||
@searchInput.addClass('disabled')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class @Shortcuts
|
||||
constructor: ->
|
||||
constructor: (skipResetBindings) ->
|
||||
@enabledHelp = []
|
||||
Mousetrap.reset()
|
||||
Mousetrap.reset() if not skipResetBindings
|
||||
Mousetrap.bind('?', @onToggleHelp)
|
||||
Mousetrap.bind('s', Shortcuts.focusSearch)
|
||||
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#= require shortcuts
|
||||
|
||||
class @ShortcutsBlob extends Shortcuts
|
||||
constructor: (skipResetBindings) ->
|
||||
super skipResetBindings
|
||||
Mousetrap.bind('y', ShortcutsBlob.copyToClipboard)
|
||||
|
||||
@copyToClipboard: ->
|
||||
clipboardButton = $('.btn-clipboard')
|
||||
clipboardButton.click() if clipboardButton
|
||||
@@ -3,13 +3,33 @@ expanded = 'page-sidebar-expanded'
|
||||
|
||||
toggleSidebar = ->
|
||||
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
|
||||
$('header').toggleClass("header-collapsed header-expanded")
|
||||
$('.navbar-fixed-top').toggleClass("header-collapsed header-expanded")
|
||||
|
||||
if $.cookie('pin_nav') is 'true'
|
||||
$('.navbar-fixed-top').toggleClass('header-pinned-nav')
|
||||
$('.page-with-sidebar').toggleClass('page-sidebar-pinned')
|
||||
|
||||
setTimeout ( ->
|
||||
niceScrollBars = $('.nicescroll').niceScroll();
|
||||
niceScrollBars = $('.nav-sidebar').niceScroll();
|
||||
niceScrollBars.updateScrollBar();
|
||||
), 300
|
||||
|
||||
$(document)
|
||||
.off 'click', 'body'
|
||||
.on 'click', 'body', (e) ->
|
||||
unless $.cookie('pin_nav') is 'true'
|
||||
$target = $(e.target)
|
||||
$nav = $target.closest('.sidebar-wrapper')
|
||||
pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded')
|
||||
$toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle')
|
||||
|
||||
if $nav.length is 0 and pageExpanded and $toggle.length is 0
|
||||
$('.page-with-sidebar')
|
||||
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
|
||||
|
||||
$('.navbar-fixed-top')
|
||||
.toggleClass('header-collapsed header-expanded')
|
||||
|
||||
$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ class @Star
|
||||
$this.parent().find('.star-count').text data.star_count
|
||||
if isStarred
|
||||
$starSpan.removeClass('starred').text 'Star'
|
||||
gl.utils.updateTooltipTitle $this, 'Star project'
|
||||
$starIcon.removeClass('fa-star').addClass 'fa-star-o'
|
||||
else
|
||||
$starSpan.addClass('starred').text 'Unstar'
|
||||
gl.utils.updateTooltipTitle $this, 'Unstar project'
|
||||
$starIcon.removeClass('fa-star-o').addClass 'fa-star'
|
||||
return
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class @UsersSelect
|
||||
assignTo = (selected) ->
|
||||
data = {}
|
||||
data[abilityName] = {}
|
||||
data[abilityName].assignee_id = selected
|
||||
data[abilityName].assignee_id = if selected? then selected else null
|
||||
$loading
|
||||
.fadeIn()
|
||||
$dropdown.trigger('loading.gl.dropdown')
|
||||
@@ -72,7 +72,7 @@ class @UsersSelect
|
||||
|
||||
assigneeTemplate = _.template(
|
||||
'<% if (username) { %>
|
||||
<a class="author_link " href="/u/<%= username %>">
|
||||
<a class="author_link bold" href="/u/<%= username %>">
|
||||
<% if( avatar ) { %>
|
||||
<img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
|
||||
<% } %>
|
||||
@@ -82,7 +82,7 @@ class @UsersSelect
|
||||
</span>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<span class="assign-yourself">
|
||||
<span class="no-value assign-yourself">
|
||||
No assignee -
|
||||
<a href="#" class="js-assign-yourself">
|
||||
assign yourself
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
background-color: $white-light;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&.top-block .container-fluid {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.cover-block {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
*/
|
||||
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
|
||||
.page-with-sidebar {
|
||||
|
||||
.collapse-nav a {
|
||||
.toggle-nav-collapse,
|
||||
.pin-nav-btn {
|
||||
color: $color-light;
|
||||
background: $color;
|
||||
|
||||
|
||||
@@ -2,8 +2,19 @@
|
||||
* Application Header
|
||||
*
|
||||
*/
|
||||
@mixin tanuki-logo-colors($path-color) {
|
||||
fill: $path-color;
|
||||
transition: all 0.8s;
|
||||
|
||||
&:hover,
|
||||
&.highlight {
|
||||
fill: lighten($path-color, 25%);
|
||||
transition: all 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
transition-duration: .3s;
|
||||
transition: padding $sidebar-transition-duration;
|
||||
|
||||
&.navbar-empty {
|
||||
height: $header-height;
|
||||
@@ -79,14 +90,9 @@ header {
|
||||
|
||||
&.header-collapsed {
|
||||
padding: 0 16px;
|
||||
|
||||
.side-nav-toggle {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav-toggle {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
margin: 6px 0;
|
||||
@@ -108,9 +114,7 @@ header {
|
||||
.header-content {
|
||||
position: relative;
|
||||
height: $header-height;
|
||||
padding-right: 40px;
|
||||
padding-left: 30px;
|
||||
transition-duration: .3s;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-right: 0;
|
||||
@@ -198,25 +202,24 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
.header-collapsed {
|
||||
margin-left: 0;
|
||||
#tanuki-logo {
|
||||
|
||||
.header-content {
|
||||
|
||||
@media (min-width: $screen-sm-max) {
|
||||
padding-left: 30px;
|
||||
transition-duration: .3s;
|
||||
}
|
||||
#tanuki-left-ear,
|
||||
#tanuki-right-ear,
|
||||
#tanuki-nose {
|
||||
@include tanuki-logo-colors($tanuki-red);
|
||||
}
|
||||
}
|
||||
|
||||
.tanuki-shape {
|
||||
transition: all 0.8s;
|
||||
|
||||
&:hover, &.highlight {
|
||||
fill: rgb(255, 255, 255);
|
||||
transition: all 0.1s;
|
||||
#tanuki-left-eye,
|
||||
#tanuki-right-eye {
|
||||
@include tanuki-logo-colors($tanuki-orange);
|
||||
}
|
||||
|
||||
#tanuki-left-cheek,
|
||||
#tanuki-right-cheek {
|
||||
@include tanuki-logo-colors($tanuki-yellow);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
|
||||
@@ -159,7 +159,7 @@ ul.content-list {
|
||||
background-color: $gray-light;
|
||||
border: dotted 1px $gray-dark;
|
||||
margin: 1px 0;
|
||||
min-height: 30px;
|
||||
min-height: 52px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +242,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.adjust {
|
||||
.nav-text, .nav-controls {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-nav {
|
||||
@@ -251,7 +257,7 @@
|
||||
z-index: 11;
|
||||
background: $background-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
transition-duration: .3s;
|
||||
transition: padding $sidebar-transition-duration;
|
||||
text-align: center;
|
||||
|
||||
.container-fluid {
|
||||
@@ -347,6 +353,12 @@
|
||||
.badge {
|
||||
color: $gl-icon-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a, i {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
.page-with-sidebar {
|
||||
padding-top: $header-height;
|
||||
transition-duration: .3s;
|
||||
transition: padding $sidebar-transition-duration;
|
||||
|
||||
.sidebar-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
transition-duration: .3s;
|
||||
overflow: hidden;
|
||||
transition: width $sidebar-transition-duration;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
z-index: 1000;
|
||||
background: $background-color;
|
||||
|
||||
.nicescroll-rails-hr {
|
||||
// TODO: Figure out why nicescroll doesn't hide horizontal bar
|
||||
display: none!important;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
width: 100%;
|
||||
transition: padding $sidebar-transition-duration;
|
||||
|
||||
.container-fluid {
|
||||
background: #fff;
|
||||
@@ -34,50 +39,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
.sidebar-user {
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: $sidebar_width;
|
||||
overflow: hidden;
|
||||
font-size: 16px;
|
||||
line-height: 36px;
|
||||
transition: width $sidebar-transition-duration, padding $sidebar-transition-duration;
|
||||
|
||||
.sidebar-user {
|
||||
padding: 15px 22px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: $sidebar_width;
|
||||
overflow: hidden;
|
||||
transition-duration: .3s;
|
||||
|
||||
.username {
|
||||
margin-left: 10px;
|
||||
width: $sidebar_width - 2 * 10px;
|
||||
font-size: 16px;
|
||||
line-height: 34px;
|
||||
}
|
||||
@media (min-width: $sidebar-breakpoint) {
|
||||
bottom: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tanuki-shape {
|
||||
transition: all 0.8s;
|
||||
|
||||
&:hover, &.highlight {
|
||||
fill: rgb(255, 255, 255);
|
||||
transition: all 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.nav-sidebar {
|
||||
margin-top: 22 + $header-height;
|
||||
margin-bottom: 116px;
|
||||
transition-duration: .3s;
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
bottom: 65px;
|
||||
width: $sidebar_width;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
@media (min-width: $sidebar-breakpoint) {
|
||||
bottom: 115px;
|
||||
}
|
||||
|
||||
&.navbar-collapse {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
li {
|
||||
width: $sidebar_width;
|
||||
|
||||
&.separate-item {
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
@@ -90,20 +84,18 @@
|
||||
}
|
||||
|
||||
a {
|
||||
width: $sidebar_width;
|
||||
padding: 7px 15px 7px 23px;
|
||||
padding: 7px 15px 7px 12px;
|
||||
font-size: $gl-font-size;
|
||||
line-height: 24px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
outline: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active, &:focus {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -115,10 +107,6 @@
|
||||
svg {
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
&.back-link i {
|
||||
transition-duration: .3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,37 +117,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-subnav {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
.toggle-nav-collapse {
|
||||
width: $sidebar_width;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
min-height: 50px;
|
||||
padding: 5px 0;
|
||||
font-size: 18px;
|
||||
background: transparent;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
transition-duration: .3s;
|
||||
outline: none;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.nav-header-btn {
|
||||
padding: 10px 5px;
|
||||
color: inherit;
|
||||
transition-duration: .3s;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $white-light;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
&.hidden-nav {
|
||||
width: 0;
|
||||
.pin-nav-btn {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 50px;
|
||||
width: $sidebar_width;
|
||||
line-height: 30px;
|
||||
|
||||
@media (min-width: $sidebar-breakpoint) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fa {
|
||||
transition: transform .15s;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
.fa {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,62 +169,34 @@
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: 0;
|
||||
|
||||
.nav-sidebar {
|
||||
width: 0;
|
||||
|
||||
li {
|
||||
width: auto;
|
||||
|
||||
a {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
width: 0;
|
||||
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
width: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
.username {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-sidebar-expanded {
|
||||
|
||||
@media (max-width: $screen-sm-max) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: $sidebar_width;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sidebar {
|
||||
width: $sidebar_width;
|
||||
.page-sidebar-pinned {
|
||||
.content-wrapper,
|
||||
.layout-nav {
|
||||
@media (min-width: $sidebar-breakpoint) {
|
||||
padding-left: $sidebar_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header.header-pinned-nav {
|
||||
@media (min-width: $sidebar-breakpoint) {
|
||||
padding-left: ($sidebar_width + $gl-padding);
|
||||
|
||||
.side-nav-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-sidebar li a {
|
||||
width: $sidebar_width;
|
||||
|
||||
&.back-link {
|
||||
i {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.header-content {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ $sidebar_width: 220px;
|
||||
$gutter_collapsed_width: 62px;
|
||||
$gutter_width: 290px;
|
||||
$gutter_inner_width: 258px;
|
||||
$sidebar-transition-duration: .15s;
|
||||
$sidebar-breakpoint: 1440px;
|
||||
|
||||
/*
|
||||
* UI elements
|
||||
@@ -154,6 +156,11 @@ $warning-message-border: #f0e2bb;
|
||||
/* header */
|
||||
$light-grey-header: #faf9f9;
|
||||
|
||||
/* tanuki logo colors */
|
||||
$tanuki-red: #e24329;
|
||||
$tanuki-orange: #fc6d26;
|
||||
$tanuki-yellow: #fca326;
|
||||
|
||||
/*
|
||||
* State colors:
|
||||
*/
|
||||
@@ -261,5 +268,10 @@ $calendar-hover-bg: #ecf3fe;
|
||||
$calendar-border-color: rgba(#000, .1);
|
||||
$calendar-unselectable-bg: #faf9f9;
|
||||
|
||||
/*
|
||||
* Personal Access Tokens
|
||||
*/
|
||||
$personal-access-tokens-disabled-label-color: #bbb;
|
||||
|
||||
$ci-output-bg: #1d1f21;
|
||||
$ci-text-color: #c5c8c6;
|
||||
|
||||
@@ -38,6 +38,10 @@ table {
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
width: 600px;
|
||||
|
||||
& > td {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&#body {
|
||||
|
||||
@@ -7,84 +7,111 @@
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
.lists-separator {
|
||||
margin: 10px 0;
|
||||
border-color: #ddd;
|
||||
}
|
||||
.commit-header {
|
||||
padding: 5px 10px;
|
||||
background-color: $background-color;
|
||||
border-top: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 14px;
|
||||
|
||||
.commits-row {
|
||||
ul {
|
||||
margin: 0;
|
||||
|
||||
li.commit {
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.commits-row-date {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 5px;
|
||||
&:first-child {
|
||||
border-top-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
li.commit {
|
||||
list-style: none;
|
||||
.commit-row-title {
|
||||
line-height: 1;
|
||||
margin-bottom: 7px;
|
||||
|
||||
.commit-row-title {
|
||||
font-size: $list-font-size;
|
||||
line-height: 20px;
|
||||
margin-bottom: 2px;
|
||||
.notes_count {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
margin-top: -1px;
|
||||
.str-truncated {
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.commit-row-message {
|
||||
color: $gl-dark-link-color;
|
||||
}
|
||||
|
||||
.text-expander {
|
||||
display: inline-block;
|
||||
background: $gray-light;
|
||||
color: $gl-placeholder-color;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
border: 1px solid $border-gray-dark;
|
||||
border-radius: $border-radius-default;
|
||||
margin-left: 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($gray-light, 10%);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notes_count {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
.commit-actions {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: right;
|
||||
margin-left: $gl-padding;
|
||||
margin-top: 2px;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.btn-transparent {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
&:not(:first-child) {
|
||||
margin-left: $gl-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.commit_short_id {
|
||||
min-width: 65px;
|
||||
color: $gl-dark-link-color;
|
||||
font-family: $monospace_font;
|
||||
}
|
||||
.commit-short-id {
|
||||
font-family: $monospace_font;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.str-truncated {
|
||||
max-width: 70%;
|
||||
}
|
||||
.commit {
|
||||
padding: 10px 0;
|
||||
|
||||
.commit-row-message {
|
||||
color: $gl-dark-link-color;
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-left: 46px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.text-expander {
|
||||
background: #eee;
|
||||
color: #555;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
&:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
||||
a,
|
||||
button {
|
||||
color: $gl-dark-link-color;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-left: -46px;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
display: inline-block;
|
||||
max-width: 70%;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
max-width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.commit-row-description {
|
||||
font-size: 14px;
|
||||
border-left: 1px solid #eee;
|
||||
padding: 10px 15px;
|
||||
margin: 5px 0 10px 5px;
|
||||
margin: 10px 0;
|
||||
background: #f9f9f9;
|
||||
display: none;
|
||||
|
||||
@@ -93,6 +120,7 @@ li.commit {
|
||||
background: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -102,7 +130,7 @@ li.commit {
|
||||
|
||||
.commit-row-info {
|
||||
color: $gl-gray;
|
||||
line-height: 24px;
|
||||
line-height: 1;
|
||||
|
||||
a {
|
||||
color: $gl-gray;
|
||||
@@ -111,10 +139,6 @@ li.commit {
|
||||
.avatar {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.committed_ago {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline-commit {
|
||||
|
||||
@@ -66,8 +66,7 @@
|
||||
font-family: $regular_font;
|
||||
}
|
||||
|
||||
.gitignore-selector {
|
||||
|
||||
.gitignore-selector, .license-selector {
|
||||
.dropdown {
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,6 @@
|
||||
|
||||
.assign-yourself {
|
||||
margin-top: 10px;
|
||||
font-weight: normal;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -158,6 +157,10 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.no-value {
|
||||
color: $gl-placeholder-color;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-icon {
|
||||
display: none;
|
||||
}
|
||||
@@ -248,11 +251,16 @@
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.issuable-header-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-header-btn {
|
||||
background: $gray-normal;
|
||||
border: 1px solid $border-gray-normal;
|
||||
|
||||
&:hover {
|
||||
background: $gray-dark;
|
||||
border: 1px solid $border-gray-dark;
|
||||
@@ -322,7 +330,7 @@
|
||||
margin-left: 5px;
|
||||
|
||||
a {
|
||||
color: #8c8c8c;
|
||||
color: $gl-placeholder-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.draggable-handler {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transition: opacity .3s;
|
||||
color: $gray-darkest;
|
||||
}
|
||||
|
||||
.prioritized-labels {
|
||||
margin-bottom: 30px;
|
||||
|
||||
@@ -122,6 +129,13 @@
|
||||
display: none;
|
||||
color: $gray-light;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
.draggable-handler {
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.other-labels {
|
||||
|
||||
@@ -244,6 +244,10 @@
|
||||
|
||||
.panel-footer {
|
||||
padding: 5px 10px;
|
||||
|
||||
.btn {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.commit {
|
||||
@@ -252,9 +256,7 @@
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 5px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.commit-row-info {
|
||||
@@ -313,3 +315,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.merged-buttons {
|
||||
.btn {
|
||||
float: left;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,12 @@ ul.notes {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-min) {
|
||||
.inline {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note-emoji-button {
|
||||
@@ -258,7 +264,11 @@ ul.notes {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
|
||||
.note-action-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -192,6 +192,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.personal-access-tokens-never-expires-label {
|
||||
color: $personal-access-tokens-disabled-label-color;
|
||||
}
|
||||
|
||||
.datepicker.personal-access-tokens-expires-at .ui-state-disabled span {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.created-personal-access-token-container {
|
||||
#created-personal-access-token {
|
||||
width: 90%;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.cover-block {
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.no-ssh-key-message, .project-limit-message {
|
||||
background-color: #f28d35;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.new_project,
|
||||
.edit-project {
|
||||
fieldset.features {
|
||||
@@ -18,13 +20,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.project-name-holder {
|
||||
.help-inline {
|
||||
vertical-align: top;
|
||||
padding: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-home-panel {
|
||||
background: $white-light;
|
||||
text-align: left;
|
||||
@@ -33,7 +28,7 @@
|
||||
.container-fluid {
|
||||
position: relative;
|
||||
|
||||
@media (min-width: $screen-md-max) {
|
||||
@media (min-width: $screen-lg-min) {
|
||||
.row {
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
@@ -224,7 +219,7 @@
|
||||
right: 16px;
|
||||
bottom: 0;
|
||||
|
||||
@media (max-width: $screen-lg-min) {
|
||||
@media (max-width: $screen-md-max) {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
@@ -233,7 +228,7 @@
|
||||
right: 0;
|
||||
bottom: 61px;
|
||||
|
||||
@media (max-width: $screen-lg-min) {
|
||||
@media (max-width: $screen-md-max) {
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
margin-right: 10px;
|
||||
@@ -371,6 +366,7 @@ a.deploy-project-label {
|
||||
|
||||
.project-import .btn {
|
||||
float: left;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
margin: 0;
|
||||
|
||||
.commit {
|
||||
padding: 0;
|
||||
padding: 0 0 0 55px;
|
||||
|
||||
.commit-row-title {
|
||||
.commit-row-message {
|
||||
@@ -129,4 +129,6 @@
|
||||
.tree-controls {
|
||||
float: right;
|
||||
margin-top: 11px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
|
||||
include PageLayoutHelper
|
||||
include WorkhorseHelper
|
||||
|
||||
before_action :authenticate_user_from_token!
|
||||
before_action :authenticate_user_from_private_token!
|
||||
before_action :authenticate_user!
|
||||
before_action :validate_user_service_ticket!
|
||||
before_action :reject_blocked!
|
||||
@@ -64,17 +64,10 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
# From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
|
||||
# https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
|
||||
def authenticate_user_from_token!
|
||||
user_token = if params[:authenticity_token].presence
|
||||
params[:authenticity_token].presence
|
||||
elsif params[:private_token].presence
|
||||
params[:private_token].presence
|
||||
elsif request.headers['PRIVATE-TOKEN'].present?
|
||||
request.headers['PRIVATE-TOKEN']
|
||||
end
|
||||
user = user_token && User.find_by_authentication_token(user_token.to_s)
|
||||
# This filter handles both private tokens and personal access tokens
|
||||
def authenticate_user_from_private_token!
|
||||
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
|
||||
user = User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
|
||||
|
||||
if user
|
||||
# Notice we are passing store false, so the user is not
|
||||
|
||||
@@ -4,14 +4,12 @@ class NotificationSettingsController < ApplicationController
|
||||
def create
|
||||
project = Project.find(params[:project][:id])
|
||||
|
||||
if can?(current_user, :read_project, project)
|
||||
@notification_setting = current_user.notification_settings_for(project)
|
||||
@saved = @notification_setting.update_attributes(notification_setting_params)
|
||||
return render_404 unless can?(current_user, :read_project, project)
|
||||
|
||||
render_response
|
||||
else
|
||||
render_404
|
||||
end
|
||||
@notification_setting = current_user.notification_settings_for(project)
|
||||
@saved = @notification_setting.update_attributes(notification_setting_params)
|
||||
|
||||
render_response
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -25,7 +23,7 @@ class NotificationSettingsController < ApplicationController
|
||||
|
||||
def render_response
|
||||
render json: {
|
||||
html: view_to_html_string("shared/notifications/buttons/_button", notification_setting: @notification_setting),
|
||||
html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting),
|
||||
saved: @saved
|
||||
}
|
||||
end
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
|
||||
before_action :load_personal_access_tokens, only: :index
|
||||
|
||||
def index
|
||||
@personal_access_token = current_user.personal_access_tokens.build
|
||||
end
|
||||
|
||||
def create
|
||||
@personal_access_token = current_user.personal_access_tokens.generate(personal_access_token_params)
|
||||
|
||||
if @personal_access_token.save
|
||||
flash[:personal_access_token] = @personal_access_token.token
|
||||
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
|
||||
else
|
||||
load_personal_access_tokens
|
||||
render :index
|
||||
end
|
||||
end
|
||||
|
||||
def revoke
|
||||
@personal_access_token = current_user.personal_access_tokens.find(params[:id])
|
||||
|
||||
if @personal_access_token.revoke!
|
||||
flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!"
|
||||
else
|
||||
flash[:alert] = "Could not revoke personal access token #{@personal_access_token.name}."
|
||||
end
|
||||
|
||||
redirect_to profile_personal_access_tokens_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def personal_access_token_params
|
||||
params.require(:personal_access_token).permit(:name, :expires_at)
|
||||
end
|
||||
|
||||
def load_personal_access_tokens
|
||||
@active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at)
|
||||
@inactive_personal_access_tokens = current_user.personal_access_tokens.inactive
|
||||
end
|
||||
end
|
||||
@@ -204,10 +204,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
|
||||
@merge_request.update(merge_error: nil)
|
||||
|
||||
if params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active?
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
|
||||
.execute(@merge_request)
|
||||
@status = :merge_when_build_succeeds
|
||||
if params[:merge_when_build_succeeds].present?
|
||||
if @merge_request.pipeline && @merge_request.pipeline.active?
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
|
||||
.execute(@merge_request)
|
||||
@status = :merge_when_build_succeeds
|
||||
elsif @merge_request.pipeline.success?
|
||||
# This can be triggered when a user clicks the auto merge button while
|
||||
# the tests finish at about the same time
|
||||
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
|
||||
@status = :success
|
||||
else
|
||||
@status = :failed
|
||||
end
|
||||
else
|
||||
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
|
||||
@status = :success
|
||||
|
||||
@@ -143,6 +143,7 @@ class ProjectsController < Projects::ApplicationController
|
||||
issues: autocomplete.issues,
|
||||
milestones: autocomplete.milestones,
|
||||
mergerequests: autocomplete.merge_requests,
|
||||
labels: autocomplete.labels,
|
||||
members: participants
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class SessionsController < Devise::SessionsController
|
||||
# Handle an "initial setup" state, where there's only one user, it's an admin,
|
||||
# and they require a password change.
|
||||
def check_initial_setup
|
||||
return unless User.count == 1
|
||||
return unless User.limit(2).count == 1 # Count as much 2 to know if we have exactly one
|
||||
|
||||
user = User.admins.last
|
||||
|
||||
|
||||
@@ -180,8 +180,8 @@ module BlobHelper
|
||||
licenses = Licensee::License.all
|
||||
|
||||
@licenses_for_select = {
|
||||
Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
|
||||
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
|
||||
Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } },
|
||||
Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } }
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -17,7 +17,15 @@ module ButtonHelper
|
||||
def clipboard_button(data = {})
|
||||
content_tag :button,
|
||||
icon('clipboard'),
|
||||
class: 'btn btn-clipboard',
|
||||
class: "btn",
|
||||
data: data,
|
||||
type: :button
|
||||
end
|
||||
|
||||
def clipboard_button_with_class(data = {}, css_class: 'btn-clipboard')
|
||||
content_tag :button,
|
||||
icon('clipboard'),
|
||||
class: "btn #{css_class}",
|
||||
data: data,
|
||||
type: :button
|
||||
end
|
||||
|
||||
@@ -38,10 +38,10 @@ module CiStatusHelper
|
||||
icon(icon_name + ' fw')
|
||||
end
|
||||
|
||||
def render_commit_status(commit, tooltip_placement: 'auto left')
|
||||
def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
|
||||
project = commit.project
|
||||
path = builds_namespace_project_commit_path(project.namespace, project, commit)
|
||||
render_status_with_link('commit', commit.status, path, tooltip_placement)
|
||||
render_status_with_link('commit', commit.status, path, tooltip_placement, cssclass: cssclass)
|
||||
end
|
||||
|
||||
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
|
||||
@@ -57,10 +57,10 @@ module CiStatusHelper
|
||||
|
||||
private
|
||||
|
||||
def render_status_with_link(type, status, path, tooltip_placement)
|
||||
def render_status_with_link(type, status, path, tooltip_placement, cssclass: '')
|
||||
link_to ci_icon_for_status(status),
|
||||
path,
|
||||
class: "ci-status-link ci-status-icon-#{status.dasherize}",
|
||||
class: "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}",
|
||||
title: "#{type.titleize}: #{ci_label_for_status(status)}",
|
||||
data: { toggle: 'tooltip', placement: tooltip_placement }
|
||||
end
|
||||
|
||||
@@ -16,6 +16,16 @@ module CommitsHelper
|
||||
commit_person_link(commit, options.merge(source: :committer))
|
||||
end
|
||||
|
||||
def commit_author_avatar(commit, options = {})
|
||||
options = options.merge(source: :author)
|
||||
user = commit.send(options[:source])
|
||||
|
||||
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
|
||||
person_email = user.try(:email) || source_email
|
||||
|
||||
image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]} hidden-xs", width: options[:size], alt: "")
|
||||
end
|
||||
|
||||
def image_diff_class(diff)
|
||||
if diff.deleted_file
|
||||
"deleted"
|
||||
@@ -102,24 +112,24 @@ module CommitsHelper
|
||||
if current_controller?(:projects, :commits)
|
||||
if @repo.blob_at(commit.id, @path)
|
||||
return link_to(
|
||||
"Browse File »",
|
||||
"Browse File",
|
||||
namespace_project_blob_path(project.namespace, project,
|
||||
tree_join(commit.id, @path)),
|
||||
class: "pull-right"
|
||||
class: "btn btn-default"
|
||||
)
|
||||
elsif @path.present?
|
||||
return link_to(
|
||||
"Browse Directory »",
|
||||
"Browse Directory",
|
||||
namespace_project_tree_path(project.namespace, project,
|
||||
tree_join(commit.id, @path)),
|
||||
class: "pull-right"
|
||||
class: "btn btn-default"
|
||||
)
|
||||
end
|
||||
end
|
||||
link_to(
|
||||
"Browse Files",
|
||||
namespace_project_tree_path(project.namespace, project, commit),
|
||||
class: "pull-right"
|
||||
class: "btn btn-default"
|
||||
)
|
||||
end
|
||||
|
||||
@@ -129,7 +139,7 @@ module CommitsHelper
|
||||
tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip
|
||||
|
||||
if can_collaborate_with_project?
|
||||
btn_class = "btn btn-grouped btn-close btn-#{btn_class}" unless btn_class.nil?
|
||||
btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil?
|
||||
link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
|
||||
elsif can?(current_user, :fork_project, @project)
|
||||
continue_params = {
|
||||
@@ -141,7 +151,7 @@ module CommitsHelper
|
||||
namespace_key: current_user.namespace.id,
|
||||
continue: continue_params)
|
||||
|
||||
btn_class = "btn btn-grouped btn-close" unless btn_class.nil?
|
||||
btn_class = "btn btn-grouped btn-warning" unless btn_class.nil?
|
||||
|
||||
link_to 'Revert', fork_path, class: btn_class, method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: (tooltip if has_tooltip)
|
||||
end
|
||||
@@ -153,7 +163,7 @@ module CommitsHelper
|
||||
tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
|
||||
|
||||
if can_collaborate_with_project?
|
||||
btn_class = "btn btn-default btn-grouped btn-#{btn_class}" unless btn_class.nil?
|
||||
btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil?
|
||||
link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
|
||||
elsif can?(current_user, :fork_project, @project)
|
||||
continue_params = {
|
||||
@@ -187,12 +197,10 @@ module CommitsHelper
|
||||
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
|
||||
|
||||
person_name = user.try(:name) || source_name
|
||||
person_email = user.try(:email) || source_email
|
||||
|
||||
text =
|
||||
if options[:avatar]
|
||||
avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
|
||||
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>}
|
||||
%Q{<span class="commit-#{options[:source]}-name">#{person_name}</span>}
|
||||
else
|
||||
person_name
|
||||
end
|
||||
|
||||
@@ -135,6 +135,11 @@ module DiffHelper
|
||||
toggle_whitespace_link(url, options)
|
||||
end
|
||||
|
||||
def diff_compare_whitespace_link(project, from, to, options)
|
||||
url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace)
|
||||
toggle_whitespace_link(url, options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hide_whitespace?
|
||||
|
||||
@@ -6,12 +6,6 @@ module MembersHelper
|
||||
"#{action}_#{member.type.underscore}".to_sym
|
||||
end
|
||||
|
||||
def can_see_member_roles?(source:, user: nil)
|
||||
return false unless user
|
||||
|
||||
user.is_admin? || source.members.exists?(user_id: user.id)
|
||||
end
|
||||
|
||||
def remove_member_message(member, user: nil)
|
||||
user = current_user if defined?(current_user)
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ module NavHelper
|
||||
end
|
||||
|
||||
def page_sidebar_class
|
||||
if nav_menu_collapsed?
|
||||
"page-sidebar-collapsed"
|
||||
if pinned_nav?
|
||||
"page-sidebar-expanded page-sidebar-pinned"
|
||||
else
|
||||
"page-sidebar-expanded"
|
||||
"page-sidebar-collapsed"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,7 +36,15 @@ module NavHelper
|
||||
end
|
||||
|
||||
def nav_header_class
|
||||
class_name = " with-horizontal-nav" if defined?(nav) && nav
|
||||
class_name = ''
|
||||
class_name << " with-horizontal-nav" if defined?(nav) && nav
|
||||
|
||||
if pinned_nav?
|
||||
class_name << " header-expanded header-pinned-nav"
|
||||
else
|
||||
class_name << " header-collapsed"
|
||||
end
|
||||
|
||||
class_name
|
||||
end
|
||||
|
||||
@@ -47,4 +55,8 @@ module NavHelper
|
||||
def nav_control_class
|
||||
"nav-control" if current_user
|
||||
end
|
||||
|
||||
def pinned_nav?
|
||||
cookies[:pin_nav] == 'true'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ module ProjectsHelper
|
||||
author_html = author_html.html_safe
|
||||
|
||||
if opts[:name]
|
||||
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
|
||||
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
|
||||
else
|
||||
title = opts[:title].sub(":name", sanitize(author.name))
|
||||
link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe
|
||||
|
||||
@@ -341,6 +341,7 @@ module Ci
|
||||
def erase_artifacts!
|
||||
remove_artifacts_file!
|
||||
remove_artifacts_metadata!
|
||||
save
|
||||
end
|
||||
|
||||
def erase(opts = {})
|
||||
|
||||
+23
-15
@@ -94,10 +94,13 @@ module Ci
|
||||
end
|
||||
|
||||
def create_builds(user, trigger_request = nil)
|
||||
##
|
||||
# We persist pipeline only if there are builds available
|
||||
#
|
||||
return unless config_processor
|
||||
config_processor.stages.any? do |stage|
|
||||
CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
|
||||
end
|
||||
|
||||
build_builds_for_stages(config_processor.stages, user,
|
||||
'success', trigger_request) && save
|
||||
end
|
||||
|
||||
def create_next_builds(build)
|
||||
@@ -115,10 +118,10 @@ module Ci
|
||||
prior_builds = latest_builds.where.not(stage: next_stages)
|
||||
prior_status = prior_builds.status
|
||||
|
||||
# create builds for next stages based
|
||||
next_stages.any? do |stage|
|
||||
CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present?
|
||||
end
|
||||
# build builds for next stage that has builds available
|
||||
# and save pipeline if we have builds
|
||||
build_builds_for_stages(next_stages, build.user, prior_status,
|
||||
build.trigger_request) && save
|
||||
end
|
||||
|
||||
def retried
|
||||
@@ -139,10 +142,10 @@ module Ci
|
||||
@config_processor ||= begin
|
||||
Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
|
||||
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
|
||||
save_yaml_error(e.message)
|
||||
self.yaml_errors = e.message
|
||||
nil
|
||||
rescue
|
||||
save_yaml_error("Undefined error")
|
||||
self.yaml_errors = 'Undefined error'
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -169,6 +172,17 @@ module Ci
|
||||
|
||||
private
|
||||
|
||||
def build_builds_for_stages(stages, user, status, trigger_request)
|
||||
##
|
||||
# Note that `Array#any?` implements a short circuit evaluation, so we
|
||||
# build builds only for the first stage that has builds available.
|
||||
#
|
||||
stages.any? do |stage|
|
||||
CreateBuildsService.new(self)
|
||||
.execute(stage, user, status, trigger_request).present?
|
||||
end
|
||||
end
|
||||
|
||||
def update_state
|
||||
statuses.reload
|
||||
self.status = if yaml_errors.blank?
|
||||
@@ -181,11 +195,5 @@ module Ci
|
||||
self.duration = statuses.latest.duration
|
||||
save
|
||||
end
|
||||
|
||||
def save_yaml_error(error)
|
||||
return if self.yaml_errors?
|
||||
self.yaml_errors = error
|
||||
update_state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+6
-4
@@ -9,6 +9,12 @@ class Group < Namespace
|
||||
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
|
||||
alias_method :members, :group_members
|
||||
has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members
|
||||
|
||||
has_many :owners,
|
||||
-> { where(members: { access_level: Gitlab::Access::OWNER }) },
|
||||
through: :group_members,
|
||||
source: :user
|
||||
|
||||
has_many :project_group_links, dependent: :destroy
|
||||
has_many :shared_projects, through: :project_group_links, source: :project
|
||||
has_many :notification_settings, dependent: :destroy, as: :source
|
||||
@@ -88,10 +94,6 @@ class Group < Namespace
|
||||
end
|
||||
end
|
||||
|
||||
def owners
|
||||
@owners ||= group_members.owners.includes(:user).map(&:user)
|
||||
end
|
||||
|
||||
def add_users(user_ids, access_level, current_user = nil)
|
||||
user_ids.each do |user_id|
|
||||
Member.add_user(self.group_members, user_id, access_level, current_user)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class JiraIssue < ExternalIssue
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
class PersonalAccessToken < ActiveRecord::Base
|
||||
include TokenAuthenticatable
|
||||
add_authentication_token_field :token
|
||||
|
||||
belongs_to :user
|
||||
|
||||
scope :active, -> { where(revoked: false).where("expires_at >= NOW() OR expires_at IS NULL") }
|
||||
scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
|
||||
|
||||
def self.generate(params)
|
||||
personal_access_token = self.new(params)
|
||||
personal_access_token.ensure_token
|
||||
personal_access_token
|
||||
end
|
||||
|
||||
def revoke!
|
||||
self.revoked = true
|
||||
self.save
|
||||
end
|
||||
end
|
||||
+18
-2
@@ -81,7 +81,7 @@ class Project < ActiveRecord::Base
|
||||
has_one :jira_service, dependent: :destroy
|
||||
has_one :redmine_service, dependent: :destroy
|
||||
has_one :custom_issue_tracker_service, dependent: :destroy
|
||||
has_one :gitlab_issue_tracker_service, dependent: :destroy
|
||||
has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
|
||||
has_one :external_wiki_service, dependent: :destroy
|
||||
|
||||
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
|
||||
@@ -262,7 +262,23 @@ class Project < ActiveRecord::Base
|
||||
#
|
||||
# Returns a Project, or nil if no project could be found.
|
||||
def find_with_namespace(path)
|
||||
where_paths_in([path]).reorder(nil).take
|
||||
namespace_path, project_path = path.split('/', 2)
|
||||
|
||||
return unless namespace_path && project_path
|
||||
|
||||
namespace_path = connection.quote(namespace_path)
|
||||
project_path = connection.quote(project_path)
|
||||
|
||||
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
|
||||
# any literal matches come first, for this we have to use "BINARY".
|
||||
# Without this there's still no guarantee in what order MySQL will return
|
||||
# rows.
|
||||
binary = Gitlab::Database.mysql? ? 'BINARY' : ''
|
||||
|
||||
order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \
|
||||
"AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)"
|
||||
|
||||
where_paths_in([path]).reorder(order_sql).take
|
||||
end
|
||||
|
||||
# Builds a relation to find multiple projects by their full paths.
|
||||
|
||||
@@ -243,7 +243,7 @@ class Repository
|
||||
end
|
||||
|
||||
def cache_keys
|
||||
%i(size branch_names tag_names commit_count
|
||||
%i(size branch_names tag_names branch_count tag_count commit_count
|
||||
readme version contribution_guide changelog
|
||||
license_blob license_key gitignore)
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@ class Service < ActiveRecord::Base
|
||||
after_commit :reset_updated_properties
|
||||
after_commit :cache_project_has_external_issue_tracker
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :project, inverse_of: :services
|
||||
has_one :service_hook
|
||||
|
||||
validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
|
||||
|
||||
@@ -51,6 +51,7 @@ class User < ActiveRecord::Base
|
||||
# Profile
|
||||
has_many :keys, dependent: :destroy
|
||||
has_many :emails, dependent: :destroy
|
||||
has_many :personal_access_tokens, dependent: :destroy
|
||||
has_many :identities, dependent: :destroy, autosave: true
|
||||
has_many :u2f_registrations, dependent: :destroy
|
||||
|
||||
@@ -267,6 +268,11 @@ class User < ActiveRecord::Base
|
||||
find_by!('lower(username) = ?', username.downcase)
|
||||
end
|
||||
|
||||
def find_by_personal_access_token(token_string)
|
||||
personal_access_token = PersonalAccessToken.active.find_by_token(token_string) if token_string
|
||||
personal_access_token.user if personal_access_token
|
||||
end
|
||||
|
||||
def by_username_or_id(name_or_id)
|
||||
find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
|
||||
end
|
||||
|
||||
@@ -2,10 +2,11 @@ module Ci
|
||||
class CreateBuildsService
|
||||
def initialize(pipeline)
|
||||
@pipeline = pipeline
|
||||
@config = pipeline.config_processor
|
||||
end
|
||||
|
||||
def execute(stage, user, status, trigger_request = nil)
|
||||
builds_attrs = config_processor.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request)
|
||||
builds_attrs = @config.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request)
|
||||
|
||||
# check when to create next build
|
||||
builds_attrs = builds_attrs.select do |build_attrs|
|
||||
@@ -19,34 +20,37 @@ module Ci
|
||||
end
|
||||
end
|
||||
|
||||
builds_attrs.map do |build_attrs|
|
||||
# don't create the same build twice
|
||||
unless @pipeline.builds.find_by(ref: @pipeline.ref, tag: @pipeline.tag,
|
||||
trigger_request: trigger_request, name: build_attrs[:name])
|
||||
build_attrs.slice!(:name,
|
||||
:commands,
|
||||
:tag_list,
|
||||
:options,
|
||||
:allow_failure,
|
||||
:stage,
|
||||
:stage_idx,
|
||||
:environment)
|
||||
|
||||
build_attrs.merge!(ref: @pipeline.ref,
|
||||
tag: @pipeline.tag,
|
||||
trigger_request: trigger_request,
|
||||
user: user,
|
||||
project: @pipeline.project)
|
||||
|
||||
@pipeline.builds.create!(build_attrs)
|
||||
end
|
||||
# don't create the same build twice
|
||||
builds_attrs.reject! do |build_attrs|
|
||||
@pipeline.builds.find_by(ref: @pipeline.ref,
|
||||
tag: @pipeline.tag,
|
||||
trigger_request: trigger_request,
|
||||
name: build_attrs[:name])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
builds_attrs.map do |build_attrs|
|
||||
build_attrs.slice!(:name,
|
||||
:commands,
|
||||
:tag_list,
|
||||
:options,
|
||||
:allow_failure,
|
||||
:stage,
|
||||
:stage_idx,
|
||||
:environment)
|
||||
|
||||
def config_processor
|
||||
@config_processor ||= @pipeline.config_processor
|
||||
build_attrs.merge!(pipeline: @pipeline,
|
||||
ref: @pipeline.ref,
|
||||
tag: @pipeline.tag,
|
||||
trigger_request: trigger_request,
|
||||
user: user,
|
||||
project: @pipeline.project)
|
||||
|
||||
##
|
||||
# We do not persist new builds here.
|
||||
# Those will be persisted when @pipeline is saved.
|
||||
#
|
||||
@pipeline.builds.new(build_attrs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,9 @@ module Ci
|
||||
return pipeline
|
||||
end
|
||||
|
||||
unless commit
|
||||
if commit
|
||||
pipeline.sha = commit.id
|
||||
else
|
||||
pipeline.errors.add(:base, 'Commit not found')
|
||||
return pipeline
|
||||
end
|
||||
@@ -18,22 +20,18 @@ module Ci
|
||||
return pipeline
|
||||
end
|
||||
|
||||
begin
|
||||
Ci::Pipeline.transaction do
|
||||
pipeline.sha = commit.id
|
||||
|
||||
unless pipeline.config_processor
|
||||
pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
pipeline.save!
|
||||
pipeline.create_builds(current_user)
|
||||
end
|
||||
rescue
|
||||
pipeline.errors.add(:base, 'The pipeline could not be created. Please try again.')
|
||||
unless pipeline.config_processor
|
||||
pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
|
||||
return pipeline
|
||||
end
|
||||
|
||||
pipeline.save!
|
||||
|
||||
unless pipeline.create_builds(current_user)
|
||||
pipeline.errors.add(:base, 'No builds for this pipeline.')
|
||||
end
|
||||
|
||||
pipeline.save
|
||||
pipeline
|
||||
end
|
||||
|
||||
|
||||
@@ -7,14 +7,18 @@ module Ci
|
||||
|
||||
builds =
|
||||
if current_runner.shared?
|
||||
# don't run projects which have not enables shared runners
|
||||
builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true })
|
||||
else
|
||||
# do run projects which are only assigned to this runner
|
||||
builds.where(project: current_runner.projects.where(builds_enabled: true))
|
||||
end
|
||||
builds.
|
||||
# don't run projects which have not enabled shared runners
|
||||
joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }).
|
||||
|
||||
builds = builds.order('created_at ASC')
|
||||
# this returns builds that are ordered by number of running builds
|
||||
# we prefer projects that don't use shared runners at all
|
||||
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
|
||||
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
|
||||
else
|
||||
# do run projects which are only assigned to this runner (FIFO)
|
||||
builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC')
|
||||
end
|
||||
|
||||
build = builds.find do |build|
|
||||
build.can_be_served?(current_runner)
|
||||
@@ -35,5 +39,12 @@ module Ci
|
||||
rescue StateMachines::InvalidTransition
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def running_builds_for_shared_runners
|
||||
Ci::Build.running.where(runner: Ci::Runner.shared).
|
||||
group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
class CreateCommitBuildsService
|
||||
def execute(project, user, params)
|
||||
return false unless project.builds_enabled?
|
||||
return unless project.builds_enabled?
|
||||
|
||||
before_sha = params[:checkout_sha] || params[:before]
|
||||
sha = params[:checkout_sha] || params[:after]
|
||||
origin_ref = params[:ref]
|
||||
|
||||
unless origin_ref && sha.present?
|
||||
return false
|
||||
end
|
||||
|
||||
ref = Gitlab::Git.ref_name(origin_ref)
|
||||
tag = Gitlab::Git.tag_ref?(origin_ref)
|
||||
|
||||
@@ -18,23 +14,50 @@ class CreateCommitBuildsService
|
||||
return false
|
||||
end
|
||||
|
||||
pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
|
||||
@pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
|
||||
|
||||
# Skip creating pipeline when no gitlab-ci.yml is found
|
||||
unless pipeline.ci_yaml_file
|
||||
##
|
||||
# Skip creating pipeline if no gitlab-ci.yml is found
|
||||
#
|
||||
unless @pipeline.ci_yaml_file
|
||||
return false
|
||||
end
|
||||
|
||||
# Create a new pipeline
|
||||
pipeline.save!
|
||||
|
||||
##
|
||||
# Skip creating builds for commits that have [ci skip]
|
||||
unless pipeline.skip_ci?
|
||||
# Create builds for commit
|
||||
pipeline.create_builds(user)
|
||||
# but save pipeline object
|
||||
#
|
||||
if @pipeline.skip_ci?
|
||||
return save_pipeline!
|
||||
end
|
||||
|
||||
pipeline.touch
|
||||
pipeline
|
||||
##
|
||||
# Skip creating builds when CI config is invalid
|
||||
# but save pipeline object
|
||||
#
|
||||
unless @pipeline.config_processor
|
||||
return save_pipeline!
|
||||
end
|
||||
|
||||
##
|
||||
# Skip creating pipeline object if there are no builds for it.
|
||||
#
|
||||
unless @pipeline.create_builds(user)
|
||||
@pipeline.errors.add(:base, 'No builds created')
|
||||
return false
|
||||
end
|
||||
|
||||
save_pipeline!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Create a new pipeline and touch object to calculate status
|
||||
#
|
||||
def save_pipeline!
|
||||
@pipeline.save!
|
||||
@pipeline.touch
|
||||
@pipeline
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class GitHooksService
|
||||
|
||||
def execute(user, repo_path, oldrev, newrev, ref)
|
||||
@repo_path = repo_path
|
||||
@user = Gitlab::ShellEnv.gl_id(user)
|
||||
@user = Gitlab::GlId.gl_id(user)
|
||||
@oldrev = oldrev
|
||||
@newrev = newrev
|
||||
@ref = ref
|
||||
|
||||
@@ -11,5 +11,9 @@ module Projects
|
||||
def merge_requests
|
||||
@project.merge_requests.opened.select([:iid, :title])
|
||||
end
|
||||
|
||||
def labels
|
||||
@project.labels.select([:title, :color])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.nav-links.sub-nav
|
||||
%ul{ class: (container_class) }
|
||||
= nav_link(controller: :background_jobs) do
|
||||
= link_to admin_background_jobs_path, title: 'Background Jobs' do
|
||||
%span
|
||||
Background Jobs
|
||||
= nav_link(controller: :logs) do
|
||||
= link_to admin_logs_path, title: 'Logs' do
|
||||
%span
|
||||
Logs
|
||||
= nav_link(controller: :health_check) do
|
||||
= link_to admin_health_check_path, title: 'Health Check' do
|
||||
%span
|
||||
Health Check
|
||||
@@ -1,46 +1,50 @@
|
||||
- @no_container = true
|
||||
- page_title "Background Jobs"
|
||||
%h3.page-title Background Jobs
|
||||
%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
|
||||
= render 'admin/background_jobs/head'
|
||||
|
||||
%hr
|
||||
%div{ class: (container_class) }
|
||||
%h3.page-title Background Jobs
|
||||
%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
|
||||
|
||||
.panel.panel-default
|
||||
.panel-heading Sidekiq running processes
|
||||
.panel-body
|
||||
- if @sidekiq_processes.empty?
|
||||
%h4.cred
|
||||
%i.fa.fa-exclamation-triangle
|
||||
There are no running sidekiq processes. Please restart GitLab
|
||||
- else
|
||||
.table-holder
|
||||
%table.table
|
||||
%thead
|
||||
%th USER
|
||||
%th PID
|
||||
%th CPU
|
||||
%th MEM
|
||||
%th STATE
|
||||
%th START
|
||||
%th COMMAND
|
||||
%tbody
|
||||
- @sidekiq_processes.each do |process|
|
||||
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
|
||||
- data = process.strip.split(' ')
|
||||
%tr
|
||||
%td= gitlab_config.user
|
||||
- 5.times do
|
||||
%td= data.shift
|
||||
%td= data.join(' ')
|
||||
%hr
|
||||
|
||||
.clearfix
|
||||
%p
|
||||
%i.fa.fa-exclamation-circle
|
||||
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
|
||||
%p
|
||||
%i.fa.fa-exclamation-circle
|
||||
If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
|
||||
.panel.panel-default
|
||||
.panel-heading Sidekiq running processes
|
||||
.panel-body
|
||||
- if @sidekiq_processes.empty?
|
||||
%h4.cred
|
||||
%i.fa.fa-exclamation-triangle
|
||||
There are no running sidekiq processes. Please restart GitLab
|
||||
- else
|
||||
.table-holder
|
||||
%table.table
|
||||
%thead
|
||||
%th USER
|
||||
%th PID
|
||||
%th CPU
|
||||
%th MEM
|
||||
%th STATE
|
||||
%th START
|
||||
%th COMMAND
|
||||
%tbody
|
||||
- @sidekiq_processes.each do |process|
|
||||
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
|
||||
- data = process.strip.split(' ')
|
||||
%tr
|
||||
%td= gitlab_config.user
|
||||
- 5.times do
|
||||
%td= data.shift
|
||||
%td= data.join(' ')
|
||||
|
||||
.clearfix
|
||||
%p
|
||||
%i.fa.fa-exclamation-circle
|
||||
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
|
||||
%p
|
||||
%i.fa.fa-exclamation-circle
|
||||
If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
|
||||
|
||||
|
||||
|
||||
.panel.panel-default
|
||||
%iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"}
|
||||
.panel.panel-default
|
||||
%iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"}
|
||||
|
||||
@@ -1,49 +1,54 @@
|
||||
.top-area
|
||||
%ul.nav-links
|
||||
%li{class: ('active' if @scope.nil?)}
|
||||
= link_to admin_builds_path do
|
||||
All
|
||||
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
|
||||
- @no_container = true
|
||||
= render "admin/dashboard/head"
|
||||
|
||||
%li{class: ('active' if @scope == 'running')}
|
||||
= link_to admin_builds_path(scope: :running) do
|
||||
Running
|
||||
%span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id))
|
||||
%div{ class: (container_class) }
|
||||
|
||||
%li{class: ('active' if @scope == 'finished')}
|
||||
= link_to admin_builds_path(scope: :finished) do
|
||||
Finished
|
||||
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
|
||||
.top-area
|
||||
%ul.nav-links
|
||||
%li{class: ('active' if @scope.nil?)}
|
||||
= link_to admin_builds_path do
|
||||
All
|
||||
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
|
||||
|
||||
.nav-controls
|
||||
- if @all_builds.running_or_pending.any?
|
||||
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
|
||||
%li{class: ('active' if @scope == 'running')}
|
||||
= link_to admin_builds_path(scope: :running) do
|
||||
Running
|
||||
%span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id))
|
||||
|
||||
.row-content-block.second-block
|
||||
#{(@scope || 'all').capitalize} builds
|
||||
%li{class: ('active' if @scope == 'finished')}
|
||||
= link_to admin_builds_path(scope: :finished) do
|
||||
Finished
|
||||
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
|
||||
|
||||
%ul.content-list
|
||||
- if @builds.blank?
|
||||
%li
|
||||
.nothing-here-block No builds to show
|
||||
- else
|
||||
.table-holder
|
||||
%table.table.builds
|
||||
%thead
|
||||
%tr
|
||||
%th Status
|
||||
%th Build ID
|
||||
%th Project
|
||||
%th Commit
|
||||
%th Ref
|
||||
%th Runner
|
||||
%th Name
|
||||
%th Tags
|
||||
%th Duration
|
||||
%th Finished at
|
||||
%th
|
||||
.nav-controls
|
||||
- if @all_builds.running_or_pending.any?
|
||||
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
|
||||
|
||||
- @builds.each do |build|
|
||||
= render "admin/builds/build", build: build
|
||||
.row-content-block.second-block
|
||||
#{(@scope || 'all').capitalize} builds
|
||||
|
||||
= paginate @builds, theme: 'gitlab'
|
||||
%ul.content-list
|
||||
- if @builds.blank?
|
||||
%li
|
||||
.nothing-here-block No builds to show
|
||||
- else
|
||||
.table-holder
|
||||
%table.table.builds
|
||||
%thead
|
||||
%tr
|
||||
%th Status
|
||||
%th Build ID
|
||||
%th Project
|
||||
%th Commit
|
||||
%th Ref
|
||||
%th Runner
|
||||
%th Name
|
||||
%th Tags
|
||||
%th Duration
|
||||
%th Finished at
|
||||
%th
|
||||
|
||||
- @builds.each do |build|
|
||||
= render "admin/builds/build", build: build
|
||||
|
||||
= paginate @builds, theme: 'gitlab'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
.nav-links.sub-nav
|
||||
%ul{ class: (container_class) }
|
||||
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
|
||||
= link_to admin_root_path, title: 'Overview' do
|
||||
%span
|
||||
Overview
|
||||
= nav_link(controller: [:admin, :projects]) do
|
||||
= link_to admin_namespaces_projects_path, title: 'Projects' do
|
||||
%span
|
||||
Projects
|
||||
= nav_link(controller: :users) do
|
||||
= link_to admin_users_path, title: 'Users' do
|
||||
%span
|
||||
Users
|
||||
= nav_link(controller: :groups) do
|
||||
= link_to admin_groups_path, title: 'Groups' do
|
||||
%span
|
||||
Groups
|
||||
= nav_link path: 'builds#index' do
|
||||
= link_to admin_builds_path, title: 'Builds' do
|
||||
%span
|
||||
Builds
|
||||
@@ -1,155 +1,159 @@
|
||||
.admin-dashboard.prepend-top-default
|
||||
.row
|
||||
.col-md-4
|
||||
%h4 Statistics
|
||||
%hr
|
||||
%p
|
||||
Forks
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(ForkedProjectLink.count)
|
||||
%p
|
||||
Issues
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Issue.count)
|
||||
%p
|
||||
Merge Requests
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(MergeRequest.count)
|
||||
%p
|
||||
Notes
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Note.count)
|
||||
%p
|
||||
Snippets
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Snippet.count)
|
||||
%p
|
||||
SSH Keys
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Key.count)
|
||||
%p
|
||||
Milestones
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Milestone.count)
|
||||
%p
|
||||
Active Users
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(User.active.count)
|
||||
.col-md-4
|
||||
%h4
|
||||
Features
|
||||
%hr
|
||||
%p
|
||||
Sign up
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon signup_enabled?
|
||||
%p
|
||||
LDAP
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon Gitlab.config.ldap.enabled
|
||||
%p
|
||||
Gravatar
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon gravatar_enabled?
|
||||
%p
|
||||
OmniAuth
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon Gitlab.config.omniauth.enabled
|
||||
%p
|
||||
Reply by email
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon Gitlab::IncomingEmail.enabled?
|
||||
.col-md-4
|
||||
%h4
|
||||
Components
|
||||
- if current_application_settings.version_check_enabled
|
||||
.pull-right
|
||||
= version_status_badge
|
||||
- @no_container = true
|
||||
= render "admin/dashboard/head"
|
||||
|
||||
%hr
|
||||
%p
|
||||
GitLab
|
||||
%span.pull-right
|
||||
= Gitlab::VERSION
|
||||
%p
|
||||
GitLab Shell
|
||||
%span.pull-right
|
||||
= Gitlab::Shell.new.version
|
||||
%p
|
||||
GitLab API
|
||||
%span.pull-right
|
||||
= API::API::version
|
||||
%p
|
||||
Git
|
||||
%span.pull-right
|
||||
= Gitlab::Git.version
|
||||
%p
|
||||
Ruby
|
||||
%span.pull-right
|
||||
#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
|
||||
|
||||
%p
|
||||
Rails
|
||||
%span.pull-right
|
||||
#{Rails::VERSION::STRING}
|
||||
|
||||
%p
|
||||
= Gitlab::Database.adapter_name
|
||||
%span.pull-right
|
||||
= Gitlab::Database.version
|
||||
%hr
|
||||
.row
|
||||
.col-sm-4
|
||||
.light-well
|
||||
%h4 Projects
|
||||
.data
|
||||
= link_to admin_namespaces_projects_path do
|
||||
%h1= number_with_delimiter(Project.count)
|
||||
%hr
|
||||
= link_to('New Project', new_project_path, class: "btn btn-new")
|
||||
.col-sm-4
|
||||
.light-well
|
||||
%h4 Users
|
||||
.data
|
||||
= link_to admin_users_path do
|
||||
%h1= number_with_delimiter(User.count)
|
||||
%hr
|
||||
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
|
||||
.col-sm-4
|
||||
.light-well
|
||||
%h4 Groups
|
||||
.data
|
||||
= link_to admin_groups_path do
|
||||
%h1= number_with_delimiter(Group.count)
|
||||
%hr
|
||||
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
|
||||
|
||||
.row.prepend-top-10
|
||||
.col-md-4
|
||||
%h4 Latest projects
|
||||
%hr
|
||||
- @projects.each do |project|
|
||||
%div{ class: (container_class) }
|
||||
.admin-dashboard.prepend-top-default
|
||||
.row
|
||||
.col-md-4
|
||||
%h4 Statistics
|
||||
%hr
|
||||
%p
|
||||
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated'
|
||||
Forks
|
||||
%span.light.pull-right
|
||||
#{time_ago_with_tooltip(project.created_at)}
|
||||
|
||||
.col-md-4
|
||||
%h4 Latest users
|
||||
%hr
|
||||
- @users.each do |user|
|
||||
= number_with_delimiter(ForkedProjectLink.count)
|
||||
%p
|
||||
= link_to [:admin, user], class: 'str-truncated' do
|
||||
= user.name
|
||||
Issues
|
||||
%span.light.pull-right
|
||||
#{time_ago_with_tooltip(user.created_at)}
|
||||
|
||||
.col-md-4
|
||||
%h4 Latest groups
|
||||
%hr
|
||||
- @groups.each do |group|
|
||||
= number_with_delimiter(Issue.count)
|
||||
%p
|
||||
= link_to [:admin, group], class: 'str-truncated' do
|
||||
= group.name
|
||||
Merge Requests
|
||||
%span.light.pull-right
|
||||
#{time_ago_with_tooltip(group.created_at)}
|
||||
= number_with_delimiter(MergeRequest.count)
|
||||
%p
|
||||
Notes
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Note.count)
|
||||
%p
|
||||
Snippets
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Snippet.count)
|
||||
%p
|
||||
SSH Keys
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Key.count)
|
||||
%p
|
||||
Milestones
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(Milestone.count)
|
||||
%p
|
||||
Active Users
|
||||
%span.light.pull-right
|
||||
= number_with_delimiter(User.active.count)
|
||||
.col-md-4
|
||||
%h4
|
||||
Features
|
||||
%hr
|
||||
%p
|
||||
Sign up
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon signup_enabled?
|
||||
%p
|
||||
LDAP
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon Gitlab.config.ldap.enabled
|
||||
%p
|
||||
Gravatar
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon gravatar_enabled?
|
||||
%p
|
||||
OmniAuth
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon Gitlab.config.omniauth.enabled
|
||||
%p
|
||||
Reply by email
|
||||
%span.light.pull-right
|
||||
= boolean_to_icon Gitlab::IncomingEmail.enabled?
|
||||
.col-md-4
|
||||
%h4
|
||||
Components
|
||||
- if current_application_settings.version_check_enabled
|
||||
.pull-right
|
||||
= version_status_badge
|
||||
|
||||
%hr
|
||||
%p
|
||||
GitLab
|
||||
%span.pull-right
|
||||
= Gitlab::VERSION
|
||||
%p
|
||||
GitLab Shell
|
||||
%span.pull-right
|
||||
= Gitlab::Shell.new.version
|
||||
%p
|
||||
GitLab API
|
||||
%span.pull-right
|
||||
= API::API::version
|
||||
%p
|
||||
Git
|
||||
%span.pull-right
|
||||
= Gitlab::Git.version
|
||||
%p
|
||||
Ruby
|
||||
%span.pull-right
|
||||
#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
|
||||
|
||||
%p
|
||||
Rails
|
||||
%span.pull-right
|
||||
#{Rails::VERSION::STRING}
|
||||
|
||||
%p
|
||||
= Gitlab::Database.adapter_name
|
||||
%span.pull-right
|
||||
= Gitlab::Database.version
|
||||
%hr
|
||||
.row
|
||||
.col-sm-4
|
||||
.light-well
|
||||
%h4 Projects
|
||||
.data
|
||||
= link_to admin_namespaces_projects_path do
|
||||
%h1= number_with_delimiter(Project.count)
|
||||
%hr
|
||||
= link_to('New Project', new_project_path, class: "btn btn-new")
|
||||
.col-sm-4
|
||||
.light-well
|
||||
%h4 Users
|
||||
.data
|
||||
= link_to admin_users_path do
|
||||
%h1= number_with_delimiter(User.count)
|
||||
%hr
|
||||
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
|
||||
.col-sm-4
|
||||
.light-well
|
||||
%h4 Groups
|
||||
.data
|
||||
= link_to admin_groups_path do
|
||||
%h1= number_with_delimiter(Group.count)
|
||||
%hr
|
||||
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
|
||||
|
||||
.row.prepend-top-10
|
||||
.col-md-4
|
||||
%h4 Latest projects
|
||||
%hr
|
||||
- @projects.each do |project|
|
||||
%p
|
||||
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated'
|
||||
%span.light.pull-right
|
||||
#{time_ago_with_tooltip(project.created_at)}
|
||||
|
||||
.col-md-4
|
||||
%h4 Latest users
|
||||
%hr
|
||||
- @users.each do |user|
|
||||
%p
|
||||
= link_to [:admin, user], class: 'str-truncated' do
|
||||
= user.name
|
||||
%span.light.pull-right
|
||||
#{time_ago_with_tooltip(user.created_at)}
|
||||
|
||||
.col-md-4
|
||||
%h4 Latest groups
|
||||
%hr
|
||||
- @groups.each do |group|
|
||||
%p
|
||||
= link_to [:admin, group], class: 'str-truncated' do
|
||||
= group.name
|
||||
%span.light.pull-right
|
||||
#{time_ago_with_tooltip(group.created_at)}
|
||||
|
||||
@@ -1,41 +1,45 @@
|
||||
- @no_container = true
|
||||
- page_title "Groups"
|
||||
%h3.page-title
|
||||
Groups (#{number_with_delimiter(@groups.total_count)})
|
||||
= render "admin/dashboard/head"
|
||||
|
||||
%p.light
|
||||
Group allows you to keep projects organized.
|
||||
Use groups for uniting related projects.
|
||||
%div{ class: (container_class) }
|
||||
%h3.page-title
|
||||
Groups (#{number_with_delimiter(@groups.total_count)})
|
||||
|
||||
.top-area
|
||||
.nav-search
|
||||
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
|
||||
= hidden_field_tag :sort, @sort
|
||||
= text_field_tag :name, params[:name], class: "form-control"
|
||||
= button_tag "Search", class: "btn submit btn-primary"
|
||||
%p.light
|
||||
Group allows you to keep projects organized.
|
||||
Use groups for uniting related projects.
|
||||
|
||||
.nav-controls
|
||||
.dropdown.inline
|
||||
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_recently_created
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to admin_groups_path(sort: sort_value_recently_created) do
|
||||
.top-area
|
||||
.nav-search
|
||||
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
|
||||
= hidden_field_tag :sort, @sort
|
||||
= text_field_tag :name, params[:name], class: "form-control"
|
||||
= button_tag "Search", class: "btn submit btn-primary"
|
||||
|
||||
.nav-controls
|
||||
.dropdown.inline
|
||||
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_recently_created
|
||||
= link_to admin_groups_path(sort: sort_value_oldest_created) do
|
||||
= sort_title_oldest_created
|
||||
= link_to admin_groups_path(sort: sort_value_recently_updated) do
|
||||
= sort_title_recently_updated
|
||||
= link_to admin_groups_path(sort: sort_value_oldest_updated) do
|
||||
= sort_title_oldest_updated
|
||||
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to admin_groups_path(sort: sort_value_recently_created) do
|
||||
= sort_title_recently_created
|
||||
= link_to admin_groups_path(sort: sort_value_oldest_created) do
|
||||
= sort_title_oldest_created
|
||||
= link_to admin_groups_path(sort: sort_value_recently_updated) do
|
||||
= sort_title_recently_updated
|
||||
= link_to admin_groups_path(sort: sort_value_oldest_updated) do
|
||||
= sort_title_oldest_updated
|
||||
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
|
||||
|
||||
%ul.content-list
|
||||
- @groups.each do |group|
|
||||
= render 'group', group: group
|
||||
%ul.content-list
|
||||
- @groups.each do |group|
|
||||
= render 'group', group: group
|
||||
|
||||
= paginate @groups, theme: "gitlab"
|
||||
= paginate @groups, theme: "gitlab"
|
||||
|
||||
@@ -1,49 +1,52 @@
|
||||
- @no_container = true
|
||||
- page_title "Health Check"
|
||||
= render 'admin/background_jobs/head'
|
||||
|
||||
%h3.page-title
|
||||
Health Check
|
||||
.bs-callout.clearfix
|
||||
.pull-left
|
||||
%p
|
||||
Access token is
|
||||
%code#health-check-token= current_application_settings.health_check_access_token
|
||||
= button_to reset_health_check_token_admin_application_settings_path,
|
||||
method: :put, class: 'btn btn-default',
|
||||
data: { confirm: 'Are you sure you want to reset the health check token?' } do
|
||||
= icon('refresh')
|
||||
Reset health check access token
|
||||
%p.light
|
||||
Health information can be retrieved as plain text, JSON, or XML using:
|
||||
%ul
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :json)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml)
|
||||
%div{ class: (container_class) }
|
||||
%h3.page-title
|
||||
Health Check
|
||||
.bs-callout.clearfix
|
||||
.pull-left
|
||||
%p
|
||||
Access token is
|
||||
%code#health-check-token= current_application_settings.health_check_access_token
|
||||
= button_to reset_health_check_token_admin_application_settings_path,
|
||||
method: :put, class: 'btn btn-default',
|
||||
data: { confirm: 'Are you sure you want to reset the health check token?' } do
|
||||
= icon('refresh')
|
||||
Reset health check access token
|
||||
%p.light
|
||||
Health information can be retrieved as plain text, JSON, or XML using:
|
||||
%ul
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :json)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml)
|
||||
|
||||
%p.light
|
||||
You can also ask for the status of specific services:
|
||||
%ul
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations)
|
||||
%p.light
|
||||
You can also ask for the status of specific services:
|
||||
%ul
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database)
|
||||
%li
|
||||
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations)
|
||||
|
||||
%hr
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Current Status:
|
||||
- if @errors.blank?
|
||||
= icon('circle', class: 'cgreen')
|
||||
Healthy
|
||||
- else
|
||||
= icon('warning', class: 'cred')
|
||||
Unhealthy
|
||||
.panel-body
|
||||
- if @errors.blank?
|
||||
No Health Problems Detected
|
||||
- else
|
||||
= @errors
|
||||
%hr
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Current Status:
|
||||
- if @errors.blank?
|
||||
= icon('circle', class: 'cgreen')
|
||||
Healthy
|
||||
- else
|
||||
= icon('warning', class: 'cred')
|
||||
Unhealthy
|
||||
.panel-body
|
||||
- if @errors.blank?
|
||||
No Health Problems Detected
|
||||
- else
|
||||
= @errors
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
- @no_container = true
|
||||
- page_title "Logs"
|
||||
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
|
||||
Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
|
||||
Gitlab::RepositoryCheckLogger]
|
||||
%ul.nav-links.log-tabs
|
||||
- loggers.each do |klass|
|
||||
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
|
||||
= link_to klass::file_name, "##{klass::file_name_noext}",
|
||||
'data-toggle' => 'tab'
|
||||
.row-content-block
|
||||
To prevent performance issues admin logs output the last 2000 lines
|
||||
.tab-content
|
||||
- loggers.each do |klass|
|
||||
.tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
|
||||
id: klass::file_name_noext }
|
||||
.file-holder#README
|
||||
.file-title
|
||||
%i.fa.fa-file
|
||||
= klass::file_name
|
||||
.pull-right
|
||||
= link_to '#', class: 'log-bottom' do
|
||||
%i.fa.fa-arrow-down
|
||||
Scroll down
|
||||
.file-content.logs
|
||||
%ol
|
||||
- klass.read_latest.each do |line|
|
||||
%li
|
||||
%p= line
|
||||
= render 'admin/background_jobs/head'
|
||||
|
||||
%div{ class: (container_class) }
|
||||
%ul.nav-links.log-tabs
|
||||
- loggers.each do |klass|
|
||||
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
|
||||
= link_to klass::file_name, "##{klass::file_name_noext}",
|
||||
'data-toggle' => 'tab'
|
||||
.row-content-block
|
||||
To prevent performance issues admin logs output the last 2000 lines
|
||||
.tab-content
|
||||
- loggers.each do |klass|
|
||||
.tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
|
||||
id: klass::file_name_noext }
|
||||
.file-holder#README
|
||||
.file-title
|
||||
%i.fa.fa-file
|
||||
= klass::file_name
|
||||
.pull-right
|
||||
= link_to '#', class: 'log-bottom' do
|
||||
%i.fa.fa-arrow-down
|
||||
Scroll down
|
||||
.file-content.logs
|
||||
%ol
|
||||
- klass.read_latest.each do |line|
|
||||
%li
|
||||
%p= line
|
||||
|
||||
@@ -1,94 +1,97 @@
|
||||
- @no_container = true
|
||||
- page_title "Projects"
|
||||
= render 'shared/show_aside'
|
||||
= render "admin/dashboard/head"
|
||||
|
||||
.row.prepend-top-default
|
||||
%aside.col-md-3
|
||||
.panel.admin-filter
|
||||
= form_tag admin_namespaces_projects_path, method: :get, class: '' do
|
||||
.form-group
|
||||
= label_tag :name, 'Name:'
|
||||
= text_field_tag :name, params[:name], class: "form-control"
|
||||
%div{ class: (container_class) }
|
||||
.row.prepend-top-default
|
||||
%aside.col-md-3
|
||||
.panel.admin-filter
|
||||
= form_tag admin_namespaces_projects_path, method: :get, class: '' do
|
||||
.form-group
|
||||
= label_tag :name, 'Name:'
|
||||
= text_field_tag :name, params[:name], class: "form-control"
|
||||
|
||||
.form-group
|
||||
= label_tag :namespace_id, "Namespace"
|
||||
= namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large'
|
||||
.form-group
|
||||
= label_tag :namespace_id, "Namespace"
|
||||
= namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large'
|
||||
|
||||
.form-group
|
||||
%strong Activity
|
||||
.checkbox
|
||||
= label_tag :with_push do
|
||||
= check_box_tag :with_push, 1, params[:with_push]
|
||||
%span Projects with push events
|
||||
.checkbox
|
||||
= label_tag :abandoned do
|
||||
= check_box_tag :abandoned, 1, params[:abandoned]
|
||||
%span No activity over 6 month
|
||||
.checkbox
|
||||
= label_tag :with_archived do
|
||||
= check_box_tag :with_archived, 1, params[:with_archived]
|
||||
%span Show archived projects
|
||||
.form-group
|
||||
%strong Activity
|
||||
.checkbox
|
||||
= label_tag :with_push do
|
||||
= check_box_tag :with_push, 1, params[:with_push]
|
||||
%span Projects with push events
|
||||
.checkbox
|
||||
= label_tag :abandoned do
|
||||
= check_box_tag :abandoned, 1, params[:abandoned]
|
||||
%span No activity over 6 month
|
||||
.checkbox
|
||||
= label_tag :with_archived do
|
||||
= check_box_tag :with_archived, 1, params[:with_archived]
|
||||
%span Show archived projects
|
||||
|
||||
%fieldset
|
||||
%strong Visibility level:
|
||||
.visibility-levels
|
||||
- Project.visibility_levels.each do |label, level|
|
||||
.checkbox
|
||||
%label
|
||||
= check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s)
|
||||
%span.descr
|
||||
= visibility_level_icon(level)
|
||||
= label
|
||||
%fieldset
|
||||
%strong Problems
|
||||
.checkbox
|
||||
= label_tag :last_repository_check_failed do
|
||||
= check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed]
|
||||
%span Last repository check failed
|
||||
%fieldset
|
||||
%strong Visibility level:
|
||||
.visibility-levels
|
||||
- Project.visibility_levels.each do |label, level|
|
||||
.checkbox
|
||||
%label
|
||||
= check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s)
|
||||
%span.descr
|
||||
= visibility_level_icon(level)
|
||||
= label
|
||||
%fieldset
|
||||
%strong Problems
|
||||
.checkbox
|
||||
= label_tag :last_repository_check_failed do
|
||||
= check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed]
|
||||
%span Last repository check failed
|
||||
|
||||
= hidden_field_tag :sort, params[:sort]
|
||||
= button_tag "Search", class: "btn submit btn-primary"
|
||||
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
|
||||
= hidden_field_tag :sort, params[:sort]
|
||||
= button_tag "Search", class: "btn submit btn-primary"
|
||||
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
|
||||
|
||||
%section.col-md-9
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Projects (#{@projects.total_count})
|
||||
.controls
|
||||
.dropdown.inline
|
||||
%button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_recently_created
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do
|
||||
%section.col-md-9
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Projects (#{@projects.total_count})
|
||||
.controls
|
||||
.dropdown.inline
|
||||
%button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_recently_created
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do
|
||||
= sort_title_oldest_created
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do
|
||||
= sort_title_recently_updated
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do
|
||||
= sort_title_oldest_updated
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do
|
||||
= sort_title_largest_repo
|
||||
= link_to 'New Project', new_project_path, class: "btn btn-sm btn-success"
|
||||
%ul.well-list
|
||||
- @projects.each do |project|
|
||||
%li
|
||||
.list-item-name
|
||||
%span{ class: visibility_level_color(project.visibility_level) }
|
||||
= visibility_level_icon(project.visibility_level)
|
||||
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
|
||||
.pull-right
|
||||
- if project.archived
|
||||
%span.label.label-warning archived
|
||||
%span.label.label-gray
|
||||
= repository_size(project)
|
||||
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
|
||||
= link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove"
|
||||
- if @projects.blank?
|
||||
.nothing-here-block 0 projects matches
|
||||
= paginate @projects, theme: "gitlab"
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do
|
||||
= sort_title_recently_created
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do
|
||||
= sort_title_oldest_created
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do
|
||||
= sort_title_recently_updated
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do
|
||||
= sort_title_oldest_updated
|
||||
= link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do
|
||||
= sort_title_largest_repo
|
||||
= link_to 'New Project', new_project_path, class: "btn btn-sm btn-success"
|
||||
%ul.well-list
|
||||
- @projects.each do |project|
|
||||
%li
|
||||
.list-item-name
|
||||
%span{ class: visibility_level_color(project.visibility_level) }
|
||||
= visibility_level_icon(project.visibility_level)
|
||||
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
|
||||
.pull-right
|
||||
- if project.archived
|
||||
%span.label.label-warning archived
|
||||
%span.label.label-gray
|
||||
= repository_size(project)
|
||||
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
|
||||
= link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove"
|
||||
- if @projects.blank?
|
||||
.nothing-here-block 0 projects matches
|
||||
= paginate @projects, theme: "gitlab"
|
||||
|
||||
@@ -1,107 +1,110 @@
|
||||
- @no_container = true
|
||||
- page_title "Users"
|
||||
= render 'shared/show_aside'
|
||||
= render "admin/dashboard/head"
|
||||
|
||||
.admin-filter
|
||||
%ul.nav-links
|
||||
%li{class: "#{'active' unless params[:filter]}"}
|
||||
= link_to admin_users_path do
|
||||
Active
|
||||
%small.badge= number_with_delimiter(User.active.count)
|
||||
%li{class: "#{'active' if params[:filter] == "admins"}"}
|
||||
= link_to admin_users_path(filter: "admins") do
|
||||
Admins
|
||||
%small.badge= number_with_delimiter(User.admins.count)
|
||||
%li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"}
|
||||
= link_to admin_users_path(filter: 'two_factor_enabled') do
|
||||
2FA Enabled
|
||||
%small.badge= number_with_delimiter(User.with_two_factor.count)
|
||||
%li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"}
|
||||
= link_to admin_users_path(filter: 'two_factor_disabled') do
|
||||
2FA Disabled
|
||||
%small.badge= number_with_delimiter(User.without_two_factor.count)
|
||||
%li.filter-external{class: "#{'active' if params[:filter] == 'external'}"}
|
||||
= link_to admin_users_path(filter: 'external') do
|
||||
External
|
||||
%small.badge= number_with_delimiter(User.external.count)
|
||||
%li{class: "#{'active' if params[:filter] == "blocked"}"}
|
||||
= link_to admin_users_path(filter: "blocked") do
|
||||
Blocked
|
||||
%small.badge= number_with_delimiter(User.blocked.count)
|
||||
%li{class: "#{'active' if params[:filter] == "wop"}"}
|
||||
= link_to admin_users_path(filter: "wop") do
|
||||
Without projects
|
||||
%small.badge= number_with_delimiter(User.without_projects.count)
|
||||
%div{ class: (container_class) }
|
||||
.admin-filter
|
||||
%ul.nav-links
|
||||
%li{class: "#{'active' unless params[:filter]}"}
|
||||
= link_to admin_users_path do
|
||||
Active
|
||||
%small.badge= number_with_delimiter(User.active.count)
|
||||
%li{class: "#{'active' if params[:filter] == "admins"}"}
|
||||
= link_to admin_users_path(filter: "admins") do
|
||||
Admins
|
||||
%small.badge= number_with_delimiter(User.admins.count)
|
||||
%li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"}
|
||||
= link_to admin_users_path(filter: 'two_factor_enabled') do
|
||||
2FA Enabled
|
||||
%small.badge= number_with_delimiter(User.with_two_factor.count)
|
||||
%li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"}
|
||||
= link_to admin_users_path(filter: 'two_factor_disabled') do
|
||||
2FA Disabled
|
||||
%small.badge= number_with_delimiter(User.without_two_factor.count)
|
||||
%li.filter-external{class: "#{'active' if params[:filter] == 'external'}"}
|
||||
= link_to admin_users_path(filter: 'external') do
|
||||
External
|
||||
%small.badge= number_with_delimiter(User.external.count)
|
||||
%li{class: "#{'active' if params[:filter] == "blocked"}"}
|
||||
= link_to admin_users_path(filter: "blocked") do
|
||||
Blocked
|
||||
%small.badge= number_with_delimiter(User.blocked.count)
|
||||
%li{class: "#{'active' if params[:filter] == "wop"}"}
|
||||
= link_to admin_users_path(filter: "wop") do
|
||||
Without projects
|
||||
%small.badge= number_with_delimiter(User.without_projects.count)
|
||||
|
||||
.row-content-block.second-block
|
||||
.pull-right
|
||||
.dropdown.inline
|
||||
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_name
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do
|
||||
.row-content-block.second-block
|
||||
.pull-right
|
||||
.dropdown.inline
|
||||
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
|
||||
%span.light
|
||||
- if @sort.present?
|
||||
= sort_options_hash[@sort]
|
||||
- else
|
||||
= sort_title_name
|
||||
= link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do
|
||||
= sort_title_recently_signin
|
||||
= link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do
|
||||
= sort_title_oldest_signin
|
||||
= link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do
|
||||
= sort_title_recently_created
|
||||
= link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do
|
||||
= sort_title_oldest_created
|
||||
= link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do
|
||||
= sort_title_recently_updated
|
||||
= link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
|
||||
= sort_title_oldest_updated
|
||||
%b.caret
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do
|
||||
= sort_title_name
|
||||
= link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do
|
||||
= sort_title_recently_signin
|
||||
= link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do
|
||||
= sort_title_oldest_signin
|
||||
= link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do
|
||||
= sort_title_recently_created
|
||||
= link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do
|
||||
= sort_title_oldest_created
|
||||
= link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do
|
||||
= sort_title_recently_updated
|
||||
= link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
|
||||
= sort_title_oldest_updated
|
||||
|
||||
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
|
||||
= form_tag admin_users_path, method: :get, class: 'form-inline' do
|
||||
.form-group
|
||||
= search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false
|
||||
= hidden_field_tag "filter", params[:filter]
|
||||
= button_tag class: 'btn btn-primary' do
|
||||
%i.fa.fa-search
|
||||
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
|
||||
= form_tag admin_users_path, method: :get, class: 'form-inline' do
|
||||
.form-group
|
||||
= search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false
|
||||
= hidden_field_tag "filter", params[:filter]
|
||||
= button_tag class: 'btn btn-primary' do
|
||||
%i.fa.fa-search
|
||||
|
||||
|
||||
.panel.panel-default
|
||||
%ul.well-list
|
||||
- @users.each do |user|
|
||||
%li
|
||||
.list-item-name
|
||||
- if user.blocked?
|
||||
= icon("lock", class: "cred")
|
||||
- else
|
||||
= icon("user", class: "cgreen")
|
||||
= link_to user.name, [:admin, user]
|
||||
- if user.admin?
|
||||
%strong.cred (Admin)
|
||||
- if user.external?
|
||||
%strong.cred (External)
|
||||
- if user == current_user
|
||||
%span.cred It's you!
|
||||
.pull-right
|
||||
%span.light
|
||||
%i.fa.fa-envelope
|
||||
= mail_to user.email, user.email, class: 'light'
|
||||
|
||||
.panel.panel-default
|
||||
%ul.well-list
|
||||
- @users.each do |user|
|
||||
%li
|
||||
.list-item-name
|
||||
- if user.blocked?
|
||||
= icon("lock", class: "cred")
|
||||
- else
|
||||
= icon("user", class: "cgreen")
|
||||
= link_to user.name, [:admin, user]
|
||||
- if user.admin?
|
||||
%strong.cred (Admin)
|
||||
- if user.external?
|
||||
%strong.cred (External)
|
||||
- if user == current_user
|
||||
%span.cred It's you!
|
||||
.pull-right
|
||||
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn-grouped btn btn-xs'
|
||||
- unless user == current_user
|
||||
- if user.ldap_blocked?
|
||||
= link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn-grouped btn btn-xs btn-success disabled' do
|
||||
%i.fa.fa-lock
|
||||
Unblock
|
||||
- elsif user.blocked?
|
||||
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success'
|
||||
- else
|
||||
= link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: 'btn-grouped btn btn-xs btn-warning'
|
||||
- if user.access_locked?
|
||||
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped 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 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-grouped btn btn-xs btn-remove'
|
||||
= paginate @users, theme: "gitlab"
|
||||
%span.light
|
||||
%i.fa.fa-envelope
|
||||
= mail_to user.email, user.email, class: 'light'
|
||||
|
||||
.pull-right
|
||||
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn-grouped btn btn-xs'
|
||||
- unless user == current_user
|
||||
- if user.ldap_blocked?
|
||||
= link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn-grouped btn btn-xs btn-success disabled' do
|
||||
%i.fa.fa-lock
|
||||
Unblock
|
||||
- elsif user.blocked?
|
||||
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success'
|
||||
- else
|
||||
= link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: 'btn-grouped btn btn-xs btn-warning'
|
||||
- if user.access_locked?
|
||||
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped 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 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-grouped btn btn-xs btn-remove'
|
||||
= paginate @users, theme: "gitlab"
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.center
|
||||
#content
|
||||
%h2 Hello, #{@resource.name}!
|
||||
%p
|
||||
The password for your GitLab account on
|
||||
#{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}
|
||||
has successfully been changed.
|
||||
%p
|
||||
If you did not initiate this change, please contact your administrator
|
||||
immediately.
|
||||
@@ -0,0 +1,7 @@
|
||||
Hello, <%= @resource.name %>!
|
||||
|
||||
The password for your GitLab account on <%= Gitlab.config.gitlab.url %>
|
||||
has successfully been changed.
|
||||
|
||||
If you did not initiate this change, please contact your administrator
|
||||
immediately.
|
||||
@@ -1,8 +0,0 @@
|
||||
<p>Hello <%= @resource.email %>!</p>
|
||||
|
||||
<p>Someone has requested a link to change your password, and you can do this through the link below.</p>
|
||||
|
||||
<p><%= link_to 'Change your password', edit_password_url(@resource, reset_password_token: @token) %></p>
|
||||
|
||||
<p>If you didn't request this, please ignore this email.</p>
|
||||
<p>Your password won't change until you access the link above and create a new one.</p>
|
||||
@@ -0,0 +1,12 @@
|
||||
.center
|
||||
#content
|
||||
%h2 Hello, #{@resource.name}!
|
||||
%p
|
||||
Someone, hopefully you, has requested to reset the password for your
|
||||
GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}.
|
||||
%p
|
||||
If you did not perform this request, you can safely ignore this email.
|
||||
%p
|
||||
Otherwise, click the link below to complete the process.
|
||||
#cta
|
||||
= link_to('Reset password', edit_password_url(@resource, reset_password_token: @token))
|
||||
@@ -0,0 +1,10 @@
|
||||
Hello, <%= @resource.name %>!
|
||||
|
||||
Someone, hopefully you, has requested to reset the password for your GitLab
|
||||
account on <%= Gitlab.config.gitlab.url %>
|
||||
|
||||
If you did not perform this request, you can safely ignore this email.
|
||||
|
||||
Otherwise, click the link below to complete the process:
|
||||
|
||||
<%= edit_password_url(@resource, reset_password_token: @token) %>
|
||||
@@ -1,10 +1,9 @@
|
||||
%p
|
||||
Hello #{@resource.name}!
|
||||
|
||||
%p
|
||||
Your GitLab account has been locked due to an excessive amount of unsuccessful
|
||||
sign in attempts. Your account will automatically unlock in
|
||||
= time_ago_in_words(Devise.unlock_in.from_now)
|
||||
or you may click the link below to unlock now.
|
||||
|
||||
%p= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token)
|
||||
.center
|
||||
#content
|
||||
%h2 Hello, #{@resource.name}!
|
||||
%p
|
||||
Your GitLab account has been locked due to an excessive amount of unsuccessful
|
||||
sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)}
|
||||
or you may click the link below to unlock now.
|
||||
#cta
|
||||
= link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Hello, <%= @resource.name %>!
|
||||
|
||||
Your GitLab account has been locked due to an excessive amount of unsuccessful
|
||||
sign in attempts. Your account will automatically unlock in <%= time_ago_in_words(Devise.unlock_in.from_now) %>
|
||||
or you may click the link below to unlock now.
|
||||
|
||||
<%= unlock_url(@resource, unlock_token: @token) %>
|
||||
@@ -1,2 +1,2 @@
|
||||
:plain
|
||||
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member))}');
|
||||
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
= link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
|
||||
= link_to '#', class: 'nav-header-btn text-center toggle-nav-collapse', title: "Open/Close" do
|
||||
%span.sr-only Toggle navigation
|
||||
= icon('bars')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.page-with-sidebar.page-sidebar-collapsed{ class: "#{page_gutter_class}" }
|
||||
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
|
||||
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
|
||||
|
||||
= render partial: 'layouts/collapse_button'
|
||||
- if defined?(sidebar) && sidebar
|
||||
= render "layouts/nav/#{sidebar}"
|
||||
- elsif current_user
|
||||
@@ -8,13 +8,14 @@
|
||||
- else
|
||||
= render 'layouts/nav/explore'
|
||||
|
||||
.collapse-nav
|
||||
= render partial: 'layouts/collapse_button'
|
||||
- if current_user
|
||||
= link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
|
||||
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
|
||||
.username
|
||||
= current_user.username
|
||||
= link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do
|
||||
%span.sr-only Toggle navigation pinning
|
||||
= icon('thumb-tack')
|
||||
- if defined?(nav) && nav
|
||||
.layout-nav
|
||||
.container-fluid
|
||||
|
||||
@@ -36,6 +36,31 @@
|
||||
- else
|
||||
= hidden_field_tag :search_code, true
|
||||
|
||||
:javascript
|
||||
gl.projectOptions = gl.projectOptions || {};
|
||||
gl.projectOptions["#{j(@project.path)}"] = {
|
||||
issuesPath: "#{namespace_project_issues_path(@project.namespace, @project)}",
|
||||
mrPath: "#{namespace_project_merge_requests_path(@project.namespace, @project)}",
|
||||
name: "#{j(@project.name)}"
|
||||
};
|
||||
|
||||
- if @group and @group.path
|
||||
:javascript
|
||||
gl.groupOptions = gl.groupOptions || {};
|
||||
gl.groupOptions["#{j(@group.path)}"] = {
|
||||
name: "#{j(@group.name)}",
|
||||
issuesPath: "#{issues_group_path(j(@group.path))}",
|
||||
mrPath: "#{merge_requests_group_path(j(@group.path))}"
|
||||
};
|
||||
|
||||
|
||||
:javascript
|
||||
gl.dashboardOptions = {
|
||||
issuesPath: "#{issues_dashboard_url}",
|
||||
mrPath: "#{merge_requests_dashboard_url}"
|
||||
};
|
||||
|
||||
|
||||
- if @snippet || @snippets
|
||||
= hidden_field_tag :snippets, true
|
||||
= hidden_field_tag :repository_ref, @ref
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- page_title "Admin Area"
|
||||
- header_title "Admin Area", admin_root_path
|
||||
- sidebar "admin"
|
||||
- nav "admin"
|
||||
|
||||
= render template: "layouts/application"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
!!! 5
|
||||
%html{ lang: "en"}
|
||||
= render "layouts/head"
|
||||
%body{class: "#{user_application_theme}", 'data-page' => body_data_page}
|
||||
%body{class: "#{user_application_theme}", data: {page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}"}}
|
||||
= Gon::Base.render_data
|
||||
|
||||
-# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%header.navbar.navbar-fixed-top.navbar-gitlab.header-collapsed{ class: nav_header_class }
|
||||
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
|
||||
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
|
||||
.header-content
|
||||
%button.side-nav-toggle{type: 'button'}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user