Commit b780efab authored by Kamil Trzciński's avatar Kamil Trzciński

Merge remote-tracking branch 'origin/master' into 38542-application-control-panel-in-settings-page

parents 2bc18355 ba4dc01e
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-65.0-node-8.x-yarn-1.2-postgresql-9.6"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-65.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner
retry: 1
......
......@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.8.4 (2018-06-06)
- No changes.
## 10.8.3 (2018-05-30)
### Fixed (4 changes)
......
......@@ -108,6 +108,7 @@ gem 'hamlit', '~> 2.6.1'
# Files attachments
gem 'carrierwave', '~> 1.2'
gem 'mini_magick'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
......@@ -408,13 +409,12 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
gem 'net-ssh', '~> 4.2.0'
gem 'net-ssh', '~> 5.0'
gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support
group :ed25519 do
gem 'rbnacl-libsodium'
gem 'rbnacl', '~> 4.0'
gem 'ed25519', '~> 1.2'
gem 'bcrypt_pbkdf', '~> 1.0'
end
......
......@@ -177,6 +177,7 @@ GEM
json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
rails (> 3.1)
ed25519 (1.2.4)
email_reply_trimmer (0.1.6)
email_spec (2.2.0)
htmlentities (~> 4.3.3)
......@@ -359,7 +360,7 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
grape-path-helpers (1.0.2)
grape-path-helpers (1.0.4)
activesupport (~> 4)
grape (~> 1.0)
rake (~> 12)
......@@ -498,6 +499,7 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.0)
mini_magick (4.8.0)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.7.0)
......@@ -510,7 +512,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.10)
net-ldap (0.16.0)
net-ssh (4.2.0)
net-ssh (5.0.1)
netrc (0.11.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
......@@ -696,10 +698,6 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
ffi
rbnacl-libsodium (1.0.11)
rbnacl (>= 3.0.1)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.0.0)
......@@ -1016,6 +1014,7 @@ DEPENDENCIES
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
factory_bot_rails (~> 4.8.2)
......@@ -1084,11 +1083,12 @@ DEPENDENCIES
loofah (~> 2.2)
mail_room (~> 0.9.1)
method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 4.2.0)
net-ssh (~> 5.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.9)
......@@ -1130,8 +1130,6 @@ DEPENDENCIES
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbnacl (~> 4.0)
rbnacl-libsodium
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
......
......@@ -179,6 +179,7 @@ GEM
json-jwt (~> 1.6)
dropzonejs-rails (0.7.4)
rails (> 3.1)
ed25519 (1.2.4)
email_reply_trimmer (0.1.10)
email_spec (2.2.0)
htmlentities (~> 4.3.3)
......@@ -505,7 +506,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.10)
net-ldap (0.16.1)
net-ssh (4.2.0)
net-ssh (5.0.1)
netrc (0.11.0)
nio4r (2.3.1)
nokogiri (1.8.2)
......@@ -696,10 +697,6 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.7)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
ffi
rbnacl-libsodium (1.0.16)
rbnacl (>= 3.0.1)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.4.0)
......@@ -1017,6 +1014,7 @@ DEPENDENCIES
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
factory_bot_rails (~> 4.8.2)
......@@ -1087,7 +1085,7 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 4.2.0)
net-ssh (~> 5.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.9)
......@@ -1130,8 +1128,6 @@ DEPENDENCIES
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbnacl (~> 4.0)
rbnacl-libsodium
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
......
import $ from 'jquery';
import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { __ } from './locale';
const tooltipTitles = {
group: __('Unsubscribe at group level'),
project: __('Unsubscribe at project level'),
};
export default class GroupLabelSubscription {
constructor(container) {
......@@ -35,6 +40,7 @@ export default class GroupLabelSubscription {
this.$unsubscribeButtons.attr('data-url', url);
axios.post(url)
.then(() => GroupLabelSubscription.setNewTooltip($btn))
.then(() => this.toggleSubscriptionButtons())
.catch(() => flash(__('There was an error when subscribing to this label.')));
}
......@@ -44,4 +50,14 @@ export default class GroupLabelSubscription {
this.$subscribeButtons.toggleClass('hidden');
this.$unsubscribeButtons.toggleClass('hidden');
}
static setNewTooltip($button) {
if (!$button.hasClass('js-subscribe-button')) return;
const type = $button.hasClass('js-group-level') ? 'group' : 'project';
const newTitle = tooltipTitles[type];
$('.js-unsubscribe-button', $button.closest('.label-actions-list'))
.tooltip('hide').attr('title', newTitle).tooltip('_fixTitle');
}
}
......@@ -13,6 +13,7 @@ export default class LabelManager {
this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.$badgeItemTemplate = $('#js-badge-item-template');
this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
filter: '.empty-message',
forceFallback: true,
......@@ -63,7 +64,11 @@ export default class LabelManager {
$target = this.otherLabels;
$from = this.prioritizedLabels;
}
$label.detach().appendTo($target);
const $detachedLabel = $label.detach();
this.toggleLabelPriorityBadge($detachedLabel, action);
$detachedLabel.appendTo($target);
if ($from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
......@@ -88,6 +93,14 @@ export default class LabelManager {
}
}
toggleLabelPriorityBadge($label, action) {
if (action === 'remove') {
$('.js-priority-badge', $label).remove();
} else {
$('.label-links', $label).append(this.$badgeItemTemplate.clone().html());
}
}
onPrioritySortUpdate() {
this.savePrioritySort()
.catch(() => flash(this.errorMessage));
......
......@@ -384,6 +384,49 @@ export const backOff = (fn, timeout = 60000) => {
});
};
export const createOverlayIcon = (iconPath, overlayPath) => {
const faviconImage = document.createElement('img');
return new Promise((resolve) => {
faviconImage.onload = () => {
const size = 32;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
context.clearRect(0, 0, size, size);
context.drawImage(
faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, size, size,
);
const overlayImage = document.createElement('img');
overlayImage.onload = () => {
context.drawImage(
overlayImage, 0, 0, overlayImage.width, overlayImage.height, 0, 0, size, size,
);
const faviconWithOverlayUrl = canvas.toDataURL();
resolve(faviconWithOverlayUrl);
};
overlayImage.src = overlayPath;
};
faviconImage.src = iconPath;
});
};
export const setFaviconOverlay = (overlayPath) => {
const faviconEl = document.getElementById('favicon');
if (!faviconEl) { return null; }
const iconPath = faviconEl.getAttribute('data-original-href');
return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl => faviconEl.setAttribute('href', faviconWithOverlayUrl));
};
export const setFavicon = (faviconPath) => {
const faviconEl = document.getElementById('favicon');
if (faviconEl && faviconPath) {
......@@ -393,8 +436,9 @@ export const setFavicon = (faviconPath) => {
export const resetFavicon = () => {
const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
if (faviconEl) {
const originalFavicon = faviconEl.getAttribute('data-original-href');
faviconEl.setAttribute('href', originalFavicon);
}
};
......@@ -403,10 +447,9 @@ export const setCiStatusFavicon = pageUrl =>
axios.get(pageUrl)
.then(({ data }) => {
if (data && data.favicon) {
setFavicon(data.favicon);
} else {
resetFavicon();
return setFaviconOverlay(data.favicon);
}
return resetFavicon();
})
.catch(resetFavicon);
......
......@@ -269,6 +269,17 @@ export const totalDaysInMonth = date => {
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
};
/**
* Returns number of days in a quarter from provided
* months array.
*
* @param {Array} quarter
*/
export const totalDaysInQuarter = quarter => quarter.reduce(
(acc, month) => acc + totalDaysInMonth(month),
0,
);
/**
* Returns list of Dates referring to Sundays of the month
* based on provided date
......@@ -309,42 +320,27 @@ export const getSundays = date => {
};
/**
* Returns list of Dates representing a timeframe of Months from month of provided date (inclusive)
* up to provided length
*
* For eg;
* If current month is January 2018 and `length` provided is `6`
* Then this method will return list of Date objects as follows;
*
* [ October 2017, November 2017, December 2017, January 2018, February 2018, March 2018 ]
*
* If current month is March 2018 and `length` provided is `3`
* Then this method will return list of Date objects as follows;
*
* [ February 2018, March 2018, April 2018 ]
* Returns list of Dates representing a timeframe of months from startDate and length
*
* @param {Date} startDate
* @param {Number} length
* @param {Date} date
*/
export const getTimeframeWindow = (length, date) => {
if (!length) {
export const getTimeframeWindowFrom = (startDate, length) => {
if (!(startDate instanceof Date) || !length) {
return [];
}
const currentDate = date instanceof Date ? date : new Date();
const currentMonthIndex = Math.floor(length / 2);
const timeframe = [];
// Move date object backward to the first month of timeframe
currentDate.setDate(1);
currentDate.setMonth(currentDate.getMonth() - currentMonthIndex);
// Iterate and update date for the size of length
// Iterate and set date for the size of length
// and push date reference to timeframe list
for (let i = 0; i < length; i += 1) {
timeframe.push(new Date(currentDate.getTime()));
currentDate.setMonth(currentDate.getMonth() + 1);
}
const timeframe = new Array(length)
.fill()
.map(
(val, i) => new Date(
startDate.getFullYear(),
startDate.getMonth() + i,
1,
),
);
// Change date of last timeframe item to last date of the month
timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
......@@ -352,6 +348,29 @@ export const getTimeframeWindow = (length, date) => {
return timeframe;
};
/**
* Returns count of day within current quarter from provided date
* and array of months for the quarter
*
* Eg;
* If date is 15 Feb 2018
* and quarter is [Jan, Feb, Mar]
*
* Then 15th Feb is 46th day of the quarter
* Where 31 (days in Jan) + 15 (date of Feb).
*
* @param {Date} date
* @param {Array} quarter
*/
export const dayInQuarter = (date, quarter) => quarter.reduce((acc, month) => {
if (date.getMonth() > month.getMonth()) {
return acc + totalDaysInMonth(month);
} else if (date.getMonth() === month.getMonth()) {
return acc + date.getDate();
}
return acc + 0;
}, 0);
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
......
......@@ -85,9 +85,9 @@ export function redirectTo(url) {
}
export function webIDEUrl(route = undefined) {
let returnUrl = `${gon.relative_url_root}/-/ide/`;
let returnUrl = `${gon.relative_url_root || ''}/-/ide/`;
if (route) {
returnUrl += `project${route}`;
returnUrl += `project${route.replace(new RegExp(`^${gon.relative_url_root || ''}`), '')}`;
}
return returnUrl;
}
......@@ -3,6 +3,17 @@ import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
const tooltipTitles = {
group: {
subscribed: __('Unsubscribe at group level'),
unsubscribed: __('Subscribe at group level'),
},
project: {
subscribed: __('Unsubscribe at project level'),
unsubscribed: __('Subscribe at project level'),
},
};
export default class ProjectLabelSubscription {
constructor(container) {
this.$container = $(container);
......@@ -15,12 +26,10 @@ export default class ProjectLabelSubscription {
event.preventDefault();
const $btn = $(event.currentTarget);
const $span = $btn.find('span');
const url = $btn.attr('data-url');
const oldStatus = $btn.attr('data-status');
$btn.addClass('disabled');
$span.toggleClass('hidden');
axios.post(url).then(() => {
let newStatus;
......@@ -32,21 +41,28 @@ export default class ProjectLabelSubscription {
[newStatus, newAction] = ['unsubscribed', 'Subscribe'];
}
$span.toggleClass('hidden');
$btn.removeClass('disabled');
this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction);
this.$buttons.map((button) => {
this.$buttons.map((i, button) => {
const $button = $(button);
const originalTitle = $button.attr('data-original-title');
if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('_fixTitle');
if (originalTitle) {
ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction);
}
return button;
});
}).catch(() => flash(__('There was an error subscribing to this label.')));
}
static setNewTitle($button, originalTitle, newStatus) {
const type = /group/.test(originalTitle) ? 'group' : 'project';
const newTitle = tooltipTitles[type][newStatus];
$button.attr('title', newTitle).tooltip('_fixTitle');
}
}
......@@ -89,14 +89,13 @@ export default {
<div>
<div
class="js-gcp-machine-type-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
:value="selectedMachineType"
/>
<dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }"
:class="{ 'border-danger': hasErrors }"
:is-disabled="isDisabled"
:is-loading="isLoading"
:toggle-text="toggleText"
......@@ -132,8 +131,11 @@ export default {
</div>
</div>
<span
class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }"
class="form-text"
:class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-if="hasErrors"
>
{{ errorMessage }}
......
......@@ -147,7 +147,6 @@ export default {
<div>
<div
class="js-gcp-project-id-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
......@@ -155,7 +154,7 @@ export default {
/>
<dropdown-button
:class="{
'gl-field-error-outline': hasErrors,
'border-danger': hasErrors,
'read-only': hasOneProject
}"
:is-disabled="isDisabled"
......@@ -193,8 +192,11 @@ export default {
</div>
</div>
<span
class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }"
class="form-text"
:class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-html="helpText"
></span>
</div>
......
......@@ -63,14 +63,13 @@ export default {
<div>
<div
class="js-gcp-zone-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
:value="selectedZone"
/>
<dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }"
:class="{ 'border-danger': hasErrors }"
:is-disabled="isDisabled"
:is-loading="isLoading"
:toggle-text="toggleText"
......@@ -106,8 +105,11 @@ export default {
</div>
</div>
<span
class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }"
class="form-text"
:class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-if="hasErrors"
>
{{ errorMessage }}
......
......@@ -36,7 +36,7 @@ import {
notify,
SourceBranchRemovalStatus,
} from './dependencies';
import { setFavicon } from '../lib/utils/common_utils';
import { setFaviconOverlay } from '../lib/utils/common_utils';
export default {
el: '#js-vue-mr-widget',
......@@ -159,8 +159,9 @@ export default {
},
setFaviconHelper() {
if (this.mr.ciStatusFaviconPath) {
setFavicon(this.mr.ciStatusFaviconPath);
return setFaviconOverlay(this.mr.ciStatusFaviconPath);
}
return Promise.resolve();
},
fetchDeployments() {
return this.service.fetchDeployments()
......
......@@ -513,7 +513,7 @@ const fileNameIcons = {
'credits.md': 'credits',
'credits.md.rendered': 'credits',
'.flowconfig': 'flow',
'favicon.ico': 'favicon',
'favicon.png': 'favicon',
'karma.conf.js': 'karma',
'karma.conf.ts': 'karma',
'karma.conf.coffee': 'karma',
......
......@@ -251,3 +251,13 @@ table {
pre code {
white-space: pre-wrap;
}
.alert-danger {
background-color: $red-500;
border-color: $red-500;
color: $white-light;
h4 {
color: $white-light;
}
}
......@@ -299,6 +299,7 @@
height: 14px;
width: 14px;
vertical-align: middle;
margin-bottom: 4px;
}
.dropdown-toggle-text {
......
......@@ -3,26 +3,26 @@
*/
@mixin gitlab-theme(
$color-100,
$color-200,
$color-500,
$color-700,
$color-800,
$color-900,
$location-badge-color,
$search-and-nav-links,
$active-tab-border,
$border-and-box-shadow,
$sidebar-text,
$nav-svg-color,
$color-alternate
) {
// Header
.navbar-gitlab {
background-color: $color-900;
background-color: $nav-svg-color;
.navbar-collapse {
color: $color-200;
color: $search-and-nav-links;
}
.container-fluid {
.navbar-toggler {
border-left: 1px solid lighten($color-700, 10%);
border-left: 1px solid lighten($border-and-box-shadow, 10%);
}
}
......@@ -31,40 +31,40 @@
> li {
> a:hover,
> a:focus {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
&.active > a,
&.dropdown.show > a {
color: $color-900;
color: $nav-svg-color;
background-color: $color-alternate;
}
&.line-separator {
border-left: 1px solid rgba($color-200, 0.2);
border-left: 1px solid rgba($search-and-nav-links, 0.2);
}
}
}
.navbar-sub-nav {
color: $color-200;
color: $search-and-nav-links;
}
.nav {
> li {
color: $color-200;
color: $search-and-nav-links;
> a {
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $color-200;
border-color: $search-and-nav-links;
}
}
&:hover,
&:focus {
@include media-breakpoint-up(sm) {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
svg {
......@@ -75,12 +75,12 @@
&.active > a,
&.dropdown.show > a {
color: $color-900;
color: $nav-svg-color;
background-color: $color-alternate;
&:hover {
svg {
fill: $color-900;
fill: $nav-svg-color;
}
}
}
......@@ -88,7 +88,7 @@
.impersonated-user,
.impersonated-user:hover {
svg {
fill: $color-900;
fill: $nav-svg-color;
}
}
}
......@@ -99,34 +99,34 @@
> a {
&:hover,
&:focus {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
}
}
.search {
form {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
&:hover {
background-color: rgba($color-200, 0.3);
background-color: rgba($search-and-nav-links, 0.3);
}
}
.location-badge {
color: $color-100;
background-color: rgba($color-200, 0.1);
border-right: 1px solid $color-800;
color: $location-badge-color;
background-color: rgba($search-and-nav-links, 0.1);
border-right: 1px solid $sidebar-text;
}
.search-input::placeholder {
color: rgba($color-200, 0.8);
color: rgba($search-and-nav-links, 0.8);
}
.search-input-wrap {
.search-icon,
.clear-icon {
fill: rgba($color-200, 0.8);
fill: rgba($search-and-nav-links, 0.8);
}
}
......@@ -141,38 +141,34 @@
.search-input-wrap {
.search-icon {
fill: rgba($color-200, 0.8);
fill: rgba($search-and-nav-links, 0.8);
}
}
}
}
.btn-sign-in {
background-color: $color-100;
color: $color-900;
}
// Sidebar
.nav-sidebar li.active {
box-shadow: inset 4px 0 0 $color-700;
box-shadow: inset 4px 0 0 $border-and-box-shadow;
> a {
color: $color-800;
color: $sidebar-text;
}
svg {
fill: $color-800;
fill: $sidebar-text;
}
}
.sidebar-top-level-items > li.active .badge.badge-pill {
color: $color-800;
color: $sidebar-text;
}
.nav-links li {
&.active a,
a.active {
border-bottom: 2px solid $color-500;
border-bottom: 2px solid $active-tab-border;
.badge.badge-pill {
font-weight: $gl-font-weight-bold;
......@@ -181,27 +177,27 @@
}
.branch-header-title {
color: $color-700;
color: $border-and-box-shadow;
}
.ide-file-list .file.file-active {
color: $color-700;
color: $border-and-box-shadow;
}
.ide-sidebar-link {
&.active {
color: $color-700;
box-shadow: inset 3px 0 $color-700;
color: $border-and-box-shadow;
box-shadow: inset 3px 0 $border-and-box-shadow;
&.is-right {
box-shadow: inset -3px 0 $color-700;
box-shadow: inset -3px 0 $border-and-box-shadow;
}
}
}
}
body {
&.ui_indigo {
&.ui-indigo {
@include gitlab-theme(
$indigo-100,
$indigo-200,
......@@ -213,19 +209,19 @@ body {
);
}
&.ui_dark {
&.ui-light-indigo {
@include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$indigo-100,
$indigo-200,
$indigo-500,
$indigo-500,
$indigo-700,
$indigo-700,
$white-light
);
}
&.ui_blue {
&.ui-blue {
@include gitlab-theme(
$theme-blue-100,
$theme-blue-200,
......@@ -237,7 +233,19 @@ body {
);
}
&.ui_green {
&.ui-light-blue {
@include gitlab-theme(
$theme-light-blue-100,
$theme-light-blue-200,
$theme-light-blue-500,
$theme-light-blue-500,
$theme-light-blue-700,
$theme-light-blue-700,
$white-light
);
}
&.ui-green {
@include gitlab-theme(
$theme-green-100,
$theme-green-200,
......@@ -249,7 +257,55 @@ body {
);
}
&.ui_light {
&.ui-light-green {
@include gitlab-theme(
$theme-green-100,
$theme-green-200,
$theme-green-500,
$theme-green-500,
$theme-light-green-700,
$theme-light-green-700,
$white-light
);
}
&.ui-red {
@include gitlab-theme(
$theme-red-100,
$theme-red-200,
$theme-red-500,
$theme-red-700,
$theme-red-800,
$theme-red-900,
$white-light
);
}
&.ui-light-red {
@include gitlab-theme(
$theme-light-red-100,
$theme-light-red-200,
$theme-light-red-500,
$theme-light-red-500,
$theme-light-red-700,
$theme-light-red-700,
$white-light
);
}
&.ui-dark {
@include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$white-light
);
}
&.ui-light {
@include gitlab-theme(
$theme-gray-900,
$theme-gray-700,
......
......@@ -139,6 +139,8 @@
}
.nav {
flex-wrap: nowrap;
> li:not(.d-none) a {
@include media-breakpoint-down(xs) {
margin-left: 0;
......@@ -158,11 +160,12 @@
}
.navbar-toggler {
position: relative;
right: -10px;
border-radius: 0;
min-width: 45px;
padding: 0;
margin-right: -7px;
margin: $gl-padding-8 -7px $gl-padding-8 0;
font-size: 14px;
text-align: center;
color: currentColor;
......@@ -186,6 +189,7 @@
display: -webkit-flex;
display: flex;
padding-right: 10px;
flex-direction: row;
}
li {
......@@ -290,6 +294,10 @@
margin: 8px;
}
}
.dropdown-menu {
position: absolute;
}
}
.navbar-sub-nav {
......@@ -437,6 +445,8 @@
}
.btn-sign-in {
background-color: $indigo-100;
color: $indigo-900;
margin-top: 3px;
font-weight: $gl-font-weight-bold;
......
.table-holder {
margin: 0;
overflow: auto;
}
table {
......@@ -38,6 +39,11 @@ table {
&.wide {
width: 55%;
}
&.table-th-transparent {
background: none;
color: $gl-text-color-secondary;
}
}
td {
......@@ -45,9 +51,86 @@ table {
}
}
}
&.responsive-table {
@include media-breakpoint-down(sm) {
thead {
display: none;
}
td {
display: block;
color: $gl-text-color-secondary;
}
tbody td.responsive-table-cell {
padding: $gl-padding 0;
width: 100%;
display: flex;
text-align: right;
align-items: center;
justify-content: space-between;
&[data-column]::before {
content: attr(data-column);
display: block;
text-align: left;
padding-right: $gl-padding;
color: $gl-text-color-secondary;
}
&:not([data-column]) {
flex-direction: row-reverse;
}
}
tr.responsive-table-border-start,
tr.responsive-table-border-end {
display: block;
border: solid $gl-text-color-quaternary;
padding-left: 0;
padding-right: 0;
> td {
border-color: $gl-text-color-quaternary;
&,
&:last-child {
padding-left: $gl-padding;
padding-right: $gl-padding;
}
}
}
tr.responsive-table-border-start {
border-width: 1px 1px 0;
border-radius: $border-radius-default $border-radius-default 0 0;
padding-top: 0;
padding-bottom: 0;
> td:first-child {
border-top: 0; // always have the <table> top border
}
> td:last-child {
border-bottom: 1px solid $gl-text-color-quaternary;
}
}
tr.responsive-table-border-end {
border-width: 0 1px 1px;
border-radius: 0 0 $border-radius-default $border-radius-default;
margin-bottom: 2 * $gl-padding;
> :last-child {
border-bottom: 0;
}
}
}
}
}
.responsive-table {
.responsive-table:not(table) {
@include media-breakpoint-down(sm) {
th {
width: 100%;
......
......@@ -117,6 +117,15 @@ $theme-blue-800: #25496e;
$theme-blue-900: #1a3652;
$theme-blue-950: #0f2235;
$theme-light-blue-50: #f2f7fc;
$theme-light-blue-100: #ebf1f7;
$theme-light-blue-200: #c9dcf2;
$theme-light-blue-300: #83abd4;
$theme-light-blue-400: #4d86bf;
$theme-light-blue-500: #367cc2;
$theme-light-blue-600: #3771ab;
$theme-light-blue-700: #2261a1;
$theme-green-50: #f2faf6;
$theme-green-100: #e4f3ea;
$theme-green-200: #c0dfcd;
......@@ -129,6 +138,29 @@ $theme-green-800: #145d33;
$theme-green-900: #0d4524;
$theme-green-950: #072d16;
$theme-light-green-700: #156b39;
$theme-red-50: #fcf4f2;
$theme-red-100: #fae9e6;
$theme-red-200: #ebcac5;
$theme-red-300: #d99b91;
$theme-red-400: #b0655a;
$theme-red-500: #ad4a3b;
$theme-red-600: #9e4133;
$theme-red-700: #912f20;
$theme-red-800: #78291d;
$theme-red-900: #691a16;
$theme-red-950: #36140f;
$theme-light-red-50: #fff6f5;
$theme-light-red-100: #fae2de;
$theme-light-red-200: #f7d5d0;
$theme-light-red-300: #d9796a;
$theme-light-red-400: #cf604e;
$theme-light-red-500: #c24b38;
$theme-light-red-600: #b03927;
$theme-light-red-700: #a62e21;
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
$almost-black: #242424;
......@@ -776,3 +808,5 @@ $modal-body-height: 134px;
Prometheus
*/
$prometheus-table-row-highlight-color: $theme-gray-100;
$priority-label-empty-state-width: 114px;
......@@ -282,9 +282,6 @@
box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none;
// as a fallback, hide overflow content so that dragging and dropping still works
overflow: hidden;
&:not(:last-child) {
margin-bottom: 5px;
}
......
......@@ -12,26 +12,22 @@
@keyframes blinking-dots {
0% {
background-color: rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
24px 0 0 0 rgba($white-light, 0.2);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2), 24px 0 0 0 rgba($white-light, 0.2);
}
25% {
background-color: rgba($white-light, 0.4);
box-shadow: 12px 0 0 0 rgba($white-light, 2),
24px 0 0 0 rgba($white-light, 0.2);
box-shadow: 12px 0 0 0 rgba($white-light, 2), 24px 0 0 0 rgba($white-light, 0.2);
}
75% {
background-color: rgba($white-light, 0.4);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
24px 0 0 0 rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2), 24px 0 0 0 rgba($white-light, 1);
}
100% {
background-color: rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
24px 0 0 0 rgba($white-light, 0.2);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2), 24px 0 0 0 rgba($white-light, 0.2);
}
}
......@@ -71,6 +67,10 @@
.bash {
display: block;
}
&.build-trace-rounded {
border-radius: $border-radius-base;
}
}
.top-bar {
......
......@@ -57,69 +57,8 @@
border-bottom-left-radius: $border-radius-base;
}
.label-row {
.label-name {
display: inline-block;
margin-bottom: 10px;
@include media-breakpoint-up(sm) {
width: 200px;
margin-left: $gl-padding * 2;
margin-bottom: 0;
}
.badge {
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
}
.label-type {
display: block;
margin-bottom: 10px;
margin-left: 50px;
@include media-breakpoint-up(sm) {
display: inline-block;
width: 100px;
margin-left: 10px;
margin-bottom: 0;
vertical-align: top;
}
}
.label-description {
display: block;
margin-bottom: 10px;
.description-text {
margin-bottom: $gl-padding;
}
a {
color: $blue-600;
}
@include media-breakpoint-up(sm) {
display: inline-block;
max-width: 50%;
margin-left: 10px;
margin-bottom: 0;
vertical-align: top;
}
}
.badge {
padding: 4px $grid-size;
font-size: $label-font-size;
position: relative;
top: ($grid-size / 2);
}
}
.color-label {
padding: 0 $grid-size;
padding: $gl-padding-4 $grid-size;
line-height: 16px;
border-radius: $label-border-radius;
color: $white-light;
......@@ -133,26 +72,29 @@
}
.manage-labels-list {
@media(min-width: map-get($grid-breakpoints, md)) {
&.content-list li {
padding: $gl-padding 0;
}
}
> li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light;
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
&:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
margin-bottom: 5px;
display: flex;
justify-content: space-between;
padding: $gl-padding;
border-radius: $border-radius-default;
&.sortable-ghost {
opacity: 0.3;
}
.prioritized-labels & {
box-shadow: 0 1px 2px $issue-boards-card-shadow;
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
&:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
}
}
.btn-action {
......@@ -170,36 +112,11 @@
}
}
}
.dropdown {
@include media-breakpoint-up(sm) {
float: right;
}
}
@include media-breakpoint-down(xs) {
.dropdown-menu {
min-width: 100%;
}
}
}
.draggable-handler {
display: inline-block;
vertical-align: top;
margin: 5px 0;
opacity: 0;
transition: opacity .3s;
color: $gray-darkest;
}
.prioritized-labels {
margin-bottom: 30px;
h5 {
font-size: $gl-font-size;
}
.add-priority {
display: none;
color: $gray-light;
......@@ -214,31 +131,11 @@
}
.other-labels {
h5 {
font-size: $gl-font-size;
}
.remove-priority {
display: none;
}
}
.toggle-priority {
display: inline-block;
vertical-align: top;
button {
border-color: transparent;
padding: 5px 8px;
vertical-align: top;
font-size: 14px;
&:hover {
border-color: transparent;
}
}
}
.filtered-labels {
font-size: 0;
padding: 12px 16px;
......@@ -292,10 +189,8 @@
}
.label-subscribe-button {
@media(min-width: map-get($grid-breakpoints, md)) {
min-width: 105px;
margin-left: $gl-padding;
}
width: 105px;
font-weight: 200;
.label-subscribe-button-icon {
&[disabled] {
......@@ -332,3 +227,95 @@
font-size: $label-font-size;
}
}
.labels-container {
background-color: $gray-light;
border-radius: $border-radius-default;
padding: $gl-padding $gl-padding-8;
}
.label-actions-list {
list-style: none;
flex-shrink: 0;
padding: 0;
}
.label-badge {
color: $theme-gray-900;
font-weight: $gl-font-weight-normal;
padding: $gl-padding-4 $gl-padding-8;
border-radius: $border-radius-default;
font-size: $label-font-size;
}
.label-badge-blue {
background-color: $theme-blue-100;
}
.label-badge-gray {
background-color: $theme-gray-100;
}
.label-links {
list-style: none;
padding: 0;
white-space: nowrap;
}
.label-link-item {
padding: 0;
}
.label-list-item {
.content-list &::before,
.content-list &::after {
content: none;
}
.label-name {
width: 150px;
flex-shrink: 0;
.label {
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
}
.label-description {
flex-grow: 1;
a {
color: $blue-600;
}
}
.label {
padding: 4px $grid-size;
font-size: $label-font-size;
position: relative;
top: $gl-padding-4;
}
.label-action {
color: $theme-gray-800;
cursor: pointer;
svg {
fill: $theme-gray-800;
}
&:hover {
color: $blue-600;
svg {
fill: $blue-600;
}
}
}
}
.priority-labels-empty-state .svg-content img {
max-width: $priority-label-empty-state-width;
}
......@@ -321,18 +321,17 @@
}
.build-failures {
th {
border-top: 0;
}
.build-state {
padding: 20px 2px;
.build-name {
float: right;
font-weight: $gl-font-weight-normal;
}
.ci-status-icon-failed svg {
vertical-align: middle;
}
.stage {
color: $gl-text-color-secondary;
font-weight: $gl-font-weight-normal;
......@@ -344,6 +343,81 @@
border: 0;
line-height: initial;
}
.build-trace-row td {
border-top: 0;
border-bottom-width: 1px;
border-bottom-style: solid;
padding-top: 0;
}
.build-trace {
width: 100%;
text-align: left;
margin-top: $gl-padding;
}
.build-name {
width: 196px;
a {
font-weight: $gl-font-weight-bold;
color: $gl-text-color;
text-decoration: none;
&:focus,
&:hover {
text-decoration: underline;
}
}
}
.build-actions {
width: 70px;
text-align: right;
}
.build-stage {
width: 140px;
}
.ci-status-icon-failed {
padding: 10px 0 10px 12px;
width: 12px + 24px; // padding-left + svg width
}
.build-icon svg {
width: 24px;
height: 24px;
vertical-align: middle;
}
.build-state,
.build-trace-row {
> td:last-child {
padding-right: 0;
}
}
@include media-breakpoint-down(sm) {
td:empty {
display: none;
}
.ci-table {
margin-top: 2 * $gl-padding;
}
.build-trace-container {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
}
.build-trace {
margin-bottom: 0;
margin-top: 0;
}
}
}
.pipeline-tab-content {
......@@ -929,7 +1003,7 @@ button.mini-pipeline-graph-dropdown-toggle {
&.dropdown-menu {
transform: translate(-80%, 0);
@media(min-width: map-get($grid-breakpoints, md)) {
@media (min-width: map-get($grid-breakpoints, md)) {
transform: translate(-50%, 0);
right: auto;
left: 50%;
......
@mixin application-theme-preview($color-1, $color-2, $color-3, $color-4) {
.one {
background-color: $color-1;
border-top-left-radius: $border-radius-default;
}
.two {
background-color: $color-2;
border-top-right-radius: $border-radius-default;
}
.three {
background-color: $color-3;
border-bottom-left-radius: $border-radius-default;
}
.four {
background-color: $color-4;
border-bottom-right-radius: $border-radius-default;
}
}
.multi-file-editor-options {
label {
margin-right: 20px;
......@@ -38,44 +16,61 @@
.application-theme {
label {
margin-right: 20px;
margin: 0 $gl-padding $gl-padding 0;
text-align: center;
}
.preview {
font-size: 0;
margin-bottom: 10px;
height: 48px;
border-radius: 4px;
min-width: 135px;
margin-bottom: $gl-padding-8;
&.ui-indigo {
background-color: $indigo-900;
}
&.ui-light-indigo {
background-color: $indigo-700;
}
&.indigo {
@include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500);
&.ui-blue {
background-color: $theme-blue-900;
}
&.dark {
@include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600);
&.ui-light-blue {
background-color: $theme-light-blue-700;
}
&.light {
@include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100);
&.ui-green {
background-color: $theme-green-900;
}
&.blue {
@include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500);
&.ui-light-green {
background-color: $theme-light-green-700;
}
&.green {
@include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500);
&.ui-red {
background-color: $theme-red-900;
}
&.ui-light-red {
background-color: $theme-light-red-700;
}
&.ui-dark {
background-color: $theme-gray-900;
}
&.ui-light {
background-color: $theme-gray-200;
}
}
.preview-row {
display: block;
}
.quadrant {
display: inline-block;
height: 50px;
width: 80px;
}
}
.syntax-theme {
......
......@@ -15,6 +15,7 @@
color: $perf-bar-text;
select {
color: $perf-bar-text;
width: 200px;
}
......
......@@ -41,6 +41,13 @@ class Admin::AppearancesController < Admin::ApplicationController
redirect_to admin_appearances_path, notice: 'Header logo was succesfully removed.'
end
def favicon
@appearance.remove_favicon!
@appearance.save
redirect_to admin_appearances_path, notice: 'Favicon was succesfully removed.'
end
private
# Use callbacks to share common setup or constraints between actions.
......@@ -61,6 +68,8 @@ class Admin::AppearancesController < Admin::ApplicationController
logo_cache
header_logo
header_logo_cache
favicon
favicon_cache
new_project_guidelines
updated_by
]
......
......@@ -91,6 +91,10 @@ class ApplicationController < ActionController::Base
payload[:user_id] = logged_user.try(:id)
payload[:username] = logged_user.try(:username)
end
if response.status == 422 && response.body.present? && response.content_type == 'application/json'.freeze
payload[:response] = response.body
end
end
# Controllers such as GitHttpController may use alternative methods
......
......@@ -7,6 +7,19 @@ module IssuableActions
before_action :authorize_admin_issuable!, only: :bulk_update
end
def permitted_keys
[
:issuable_ids,
:assignee_id,
:milestone_id,
:state_event,
:subscription_event,
label_ids: [],
add_label_ids: [],
remove_label_ids: []
]
end
def show
respond_to do |format|
format.html
......@@ -140,24 +153,15 @@ module IssuableActions
end
def bulk_update_params
permitted_keys = [
:issuable_ids,
:assignee_id,
:milestone_id,
:state_event,
:subscription_event,
label_ids: [],
add_label_ids: [],
remove_label_ids: []
]
permitted_keys_array = permitted_keys.dup
if resource_name == 'issue'
permitted_keys << { assignee_ids: [] }
permitted_keys_array << { assignee_ids: [] }
else
permitted_keys.unshift(:assignee_id)
permitted_keys_array.unshift(:assignee_id)
end
params.require(:update).permit(permitted_keys)
params.require(:update).permit(permitted_keys_array)
end
def resource_name
......
......@@ -2,7 +2,7 @@ module UploadsActions
include Gitlab::Utils::StrongMemoize
include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo).freeze
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
def create
link_to_file = UploadService.new(model, params[:file], uploader_class).execute
......@@ -31,6 +31,11 @@ module UploadsActions
disposition = uploader.image_or_video? ? 'inline' : 'attachment'
uploaders = [uploader, *uploader.versions.values]
uploader = uploaders.find { |version| version.filename == params[:filename] }
return render_404 unless uploader
send_upload(uploader, attachment: uploader.filename, disposition: disposition)
end
......
......@@ -2,6 +2,7 @@ class Groups::LabelsController < Groups::ApplicationController
include ToggleSubscriptionAction
before_action :label, only: [:edit, :update, :destroy]
before_action :available_labels, only: [:index]
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy]
before_action :save_previous_label_path, only: [:edit]
......@@ -12,17 +13,8 @@ class Groups::LabelsController < Groups::ApplicationController
format.html do
@labels = @group.labels.page(params[:page])
end
format.json do
available_labels = LabelsFinder.new(
current_user,
group_id: @group.id,
only_group_labels: params[:only_group_labels],
include_ancestor_groups: params[:include_ancestor_groups],
include_descendant_groups: params[:include_descendant_groups]
).execute
render json: LabelSerializer.new.represent_appearance(available_labels)
render json: LabelSerializer.new.represent_appearance(@available_labels)
end
end
end
......@@ -113,4 +105,15 @@ class Groups::LabelsController < Groups::ApplicationController
def save_previous_label_path
session[:previous_labels_path] = URI(request.referer || '').path
end
def available_labels
@available_labels ||=
LabelsFinder.new(
current_user,
group_id: @group.id,
only_group_labels: params[:only_group_labels],
include_ancestor_groups: params[:include_ancestor_groups],
include_descendant_groups: params[:include_descendant_groups]
).execute
end
end
......@@ -42,6 +42,6 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll
owner: current_user
}
Applications::CreateService.new(current_user, oauth_application_params).execute
Applications::CreateService.new(current_user, oauth_application_params).execute(request)
end
end
module FaviconHelper
def favicon_extension_whitelist
FaviconUploader::EXTENSION_WHITELIST
.map { |extension| "'.#{extension}'"}
.to_sentence
end
end
......@@ -211,6 +211,14 @@ module LabelsHelper
end
end
def label_status_tooltip(label, status)
type = label.is_a?(ProjectLabel) ? 'project' : 'group'
level = status.unsubscribed? ? type : status.sub('-level', '')
action = status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'
"#{action} at #{level} level"
end
# Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once
end
......@@ -39,10 +39,7 @@ module PageLayoutHelper
end
def favicon
return 'favicon-yellow.ico' if Gitlab::Utils.to_boolean(ENV['CANARY'])
return 'favicon-blue.ico' if Rails.env.development?
'favicon.ico'
Gitlab::Favicon.main
end
def page_image
......
......@@ -14,6 +14,7 @@ class Appearance < ActiveRecord::Base
mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader
mount_uploader :favicon, FaviconUploader
# Overrides CacheableAttributes.current_without_cache
def self.current_without_cache
......
......@@ -141,13 +141,14 @@ class Group < Namespace
)
end
def add_user(user, access_level, current_user: nil, expires_at: nil)
def add_user(user, access_level, current_user: nil, expires_at: nil, ldap: false)
GroupMember.add_user(
self,
user,
access_level,
current_user: current_user,
expires_at: expires_at
expires_at: expires_at,
ldap: ldap
)
end
......@@ -195,6 +196,10 @@ class Group < Namespace
owners.include?(user) && owners.size == 1
end
def ldap_synced?
false
end
def post_create_hook
Gitlab::AppLogger.info("Group \"#{name}\" was created")
......
......@@ -137,6 +137,10 @@ class Label < ActiveRecord::Base
priority.try(:priority)
end
def priority?
priorities.present?
end
def template?
template
end
......
......@@ -64,7 +64,7 @@ class NotificationRecipient
return false unless @target
return false unless @target.respond_to?(:subscriptions)
subscription = @target.subscriptions.find_by_user_id(@user.id)
subscription = @target.subscriptions.find { |subscription| subscription.user_id == @user.id }
subscription && !subscription.subscribed
end
......
......@@ -675,6 +675,12 @@ class Project < ActiveRecord::Base
end
end
def human_import_status_name
ensure_import_state
import_state.human_status_name
end
def import_schedule
ensure_import_state(force: true)
......
......@@ -11,6 +11,8 @@ class ProjectAutoDevops < ActiveRecord::Base
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
after_save :create_gitlab_deploy_token, if: :needs_to_create_deploy_token?
def instance_domain
Gitlab::CurrentSettings.auto_devops_domain
end
......@@ -32,4 +34,23 @@ class ProjectAutoDevops < ActiveRecord::Base
end
end
end
private
def create_gitlab_deploy_token
project.deploy_tokens.create!(
name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME,
read_registry: true
)
end
def needs_to_create_deploy_token?
auto_devops_enabled? &&
!project.public? &&
!project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present?
end
def auto_devops_enabled?
Gitlab::CurrentSettings.auto_devops_enabled? || enabled?
end
end
......@@ -265,7 +265,7 @@ class JiraService < IssueTrackerService
title: title,
status: status,
icon: {
title: 'GitLab', url16x16: asset_url('favicon.ico', host: gitlab_config.url)
title: 'GitLab', url16x16: asset_url(Gitlab::Favicon.main, host: gitlab_config.url)
}
}
}
......
......@@ -1038,7 +1038,10 @@ class User < ActiveRecord::Base
def notification_settings_for(source)
if notification_settings.loaded?
notification_settings.find { |notification| notification.source == source }
notification_settings.find do |notification|
notification.source_type == source.class.base_class.name &&
notification.source_id == source.id
end
else
notification_settings.find_or_initialize_by(source: source)
end
......
......@@ -7,16 +7,7 @@ class StatusEntity < Grape::Entity
expose :details_path
expose :favicon do |status|
dir =
if Gitlab::Utils.to_boolean(ENV['CANARY'])
File.join('ci_favicons', 'canary')
elsif Rails.env.development?
File.join('ci_favicons', 'dev')
else
'ci_favicons'
end
ActionController::Base.helpers.image_path(File.join(dir, "#{status.favicon}.ico"))
Gitlab::Favicon.status_overlay(status.favicon)
end
expose :action, if: -> (status, _) { status.has_action? } do
......
......@@ -5,7 +5,7 @@ module Applications
@params = params.except(:ip_address)
end
def execute(request = nil)
def execute(request)
Doorkeeper::Application.create(@params)
end
end
......
class FaviconUploader < AttachmentUploader
EXTENSION_WHITELIST = %w[png ico].freeze
include CarrierWave::MiniMagick
version :favicon_main do
process resize_to_fill: [32, 32]
process convert: 'png'
def full_filename(filename)
filename_for_different_format(super(filename), 'png')
end
end
def extension_whitelist
EXTENSION_WHITELIST
end
private
def filename_for_different_format(filename, format)
filename.chomp(File.extname(filename)) + ".#{format}"
end
end
# Extra methods for uploader
module UploaderHelper
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff].freeze
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
# We recommend using the .mp4 format over .mov. Videos in .mov format can
# still be used but you really need to make sure they are served with the
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
......
......@@ -11,13 +11,32 @@
= image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo"
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
%hr
= f.hidden_field :header_logo_cache
= f.file_field :header_logo, class: ""
.hint
Maximum file size is 1MB. Pages are optimized for a 28px tall header logo
%fieldset.app_logo
%legend
Favicon:
.form-group.row
= f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label'
.col-sm-10
- if @appearance.favicon?
= image_tag @appearance.favicon.favicon_main.url, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
%hr
= f.hidden_field :favicon_cache
= f.file_field :favicon, class: ''
.hint
Maximum file size is 1MB. Allowed image formats are #{favicon_extension_whitelist}.
%br
The resulting favicons will be cropped to be square and scaled down to a size of 32x32 px.
%fieldset.sign-in
%legend
Sign in/Sign up pages:
......@@ -38,7 +57,7 @@
= image_tag @appearance.logo_url, class: 'appearance-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-logo"
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
%hr
= f.hidden_field :logo_cache
= f.file_field :logo, class: ""
......
......@@ -2,6 +2,9 @@
= form_errors(@group)
= render 'shared/group_form', f: f
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
= render_if_exists 'admin/namespace_plan', f: f
.form-group.row.group-description-holder
= f.label :avatar, "Group avatar", class: 'col-form-label col-sm-2'
.col-sm-10
......@@ -15,6 +18,8 @@
= render 'groups/group_admin_settings', f: f
= render_if_exists 'namespaces/shared_runners_minutes_settings', group: @group, form: f
- if @group.new_record?
.form-group.row
.offset-sm-2.col-sm-10
......@@ -28,3 +33,5 @@
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
= render_if_exists 'ldap_group_links/ldap_syncrhonizations', group: @group
- group = local_assigns.fetch(:group)
- css_class = 'no-description' if group.description.blank?
%li.group-row{ class: css_class }
......@@ -8,6 +9,8 @@
%span.badge.badge-pill
= storage_counter(group.storage_size)
= render_if_exists 'admin/namespace_plan_badge', namespace: group
%span
= icon('bookmark')
= number_with_delimiter(group.projects.count)
......
......@@ -40,6 +40,8 @@
%strong
= @group.created_at.to_s(:medium)
= render_if_exists 'admin/namespace_plan_info', namespace: @group
%li
%span.light Storage:
%strong= storage_counter(@group.storage_size)
......@@ -58,6 +60,10 @@
= group_lfs_status(@group)
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
= render_if_exists 'namespaces/shared_runner_status', namespace: @group
= render_if_exists 'ldap_group_links/ldap_group_links_show', group: @group
.card
.card-header
%h3.card-title
......@@ -104,7 +110,7 @@
= form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
= users_select_tag(:user_ids, multiple: true, email_user: true, skip_ldap: @group.ldap_synced?, scope: :all)
.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
......
......@@ -38,7 +38,7 @@
%li.divider
- if user.can_be_removed?
%li
%button.delete-user-button.btn.btn-danger{ data: { toggle: 'modal',
%button.delete-user-button.btn.text-danger{ data: { toggle: 'modal',
target: '#delete-user-modal',
delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user),
......@@ -47,7 +47,7 @@
= s_('AdminUsers|Delete user')
%li
%button.delete-user-button.btn.btn-danger{ data: { toggle: 'modal',
%button.delete-user-button.btn.text-danger{ data: { toggle: 'modal',
target: '#delete-user-modal',
delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user),
......
- page_title 'Labels'
- @no_container = true
- page_title "Labels"
- can_admin_label = can?(current_user, :admin_label, @group)
- hide_class = ''
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
- issuables = ['issues', 'merge requests']
.top-area.adjust
.nav-text
= _("Labels can be applied to %{features}. Group labels are available for any project within the group.") % { features: issuables.to_sentence }
- if can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_group_label_path(@group), class: "btn btn-new"
- if @labels.exists?
#promote-label-modal
%div{ class: container_class }
.top-area.adjust
.nav-text
= _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence }
.nav-controls
- if can?(current_user, :admin_label, @group)
= link_to "New label", new_group_label_path(@group), class: "btn btn-new"
.labels-container.prepend-top-5
.other-labels
- if can_admin_label
%h5{ class: ('hide' if hide) } Labels
%ul.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', subject: @group, collection: @labels, as: :label, locals: { use_label_priority: false }
= paginate @labels, theme: 'gitlab'
- else
= render 'shared/empty_states/labels'
.labels
.other-labels
- if @labels.present?
%ul.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', subject: @group, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- else
.nothing-here-block
= _("No labels created yet.")
%template#js-badge-item-template
%li.label-link-item.js-priority-badge.inline.prepend-left-10
.label-badge.label-badge-blue= _('Prioritized label')
......@@ -25,7 +25,7 @@
%title= page_title(site_name)
%meta{ name: "description", content: page_description }
= favicon_link_tag favicon, id: 'favicon'
= favicon_link_tag favicon, id: 'favicon', data: { original_href: favicon }, type: 'image/png'
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
......
!!! 5
%html.devise-layout-html{ class: system_message_class }
= render "layouts/head"
%body.ui_indigo.login-page.application.navless{ data: { page: body_data_page } }
%body.ui-indigo.login-page.application.navless{ data: { page: body_data_page } }
.page-wrap
= render "layouts/header/empty"
.login-page-broadcast
......
!!! 5
%html{ lang: "en", class: system_message_class }
= render "layouts/head"
%body.ui_indigo.login-page.application.navless
%body.ui-indigo.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
......
......@@ -9,13 +9,7 @@
.col-lg-8.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
.preview{ class: theme.name.downcase }
.preview-row
.quadrant.one
.quadrant.two
.preview-row
.quadrant.three
.quadrant.four
.preview{ class: theme.css_class }
= f.radio_button :theme_id, theme.id, checked: Gitlab::Themes.for_user(@user).id == theme.id
= theme.name
......
......@@ -7,8 +7,8 @@
- link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke }
.card.form-group
%label.text-danger
.sub-section.form-group
%h4.text-danger
= s_('ClusterIntegration|Remove Kubernetes cluster integration')
%p
= s_("ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster.")
......
......@@ -15,7 +15,7 @@
= field.fields_for :provider_gcp, @cluster.provider_gcp do |provider_gcp_field|
.form-group
= provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID')
= provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project')
.js-gcp-project-id-dropdown-entry-point{ data: { docsUrl: 'https://console.cloud.google.com/home/dashboard' } }
= provider_gcp_field.hidden_field :gcp_project_id
.dropdown
......
= form_for @cluster, url: user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
= form_errors(@cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-light'
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-light'
= field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL')
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL'), class: 'label-light'
= platform_kubernetes_field.text_field :api_url, class: 'form-control', placeholder: s_('ClusterIntegration|API URL')
.form-group
= platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate')
= platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate'), class: 'label-light'
= platform_kubernetes_field.text_area :ca_cert, class: 'form-control', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)')
.form-group
= platform_kubernetes_field.label :token, s_('ClusterIntegration|Token')
= platform_kubernetes_field.label :token, s_('ClusterIntegration|Token'), class: 'label-light'
= platform_kubernetes_field.text_field :token, class: 'form-control', placeholder: s_('ClusterIntegration|Service token'), autocomplete: 'off'
.form-group
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-light'
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
.form-group
......
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-light'
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL')
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL'), class: 'label-light'
= platform_kubernetes_field.text_field :api_url, class: 'form-control', placeholder: s_('ClusterIntegration|API URL')
.form-group
= platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate')
= platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate'), class: 'label-light'
= platform_kubernetes_field.text_area :ca_cert, class: 'form-control', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)')
.form-group
= platform_kubernetes_field.label :token, s_('ClusterIntegration|Token')
= platform_kubernetes_field.label :token, s_('ClusterIntegration|Token'), class: 'label-light'
.input-group
= platform_kubernetes_field.text_field :token, class: 'form-control js-cluster-token', type: 'password', placeholder: s_('ClusterIntegration|Token'), autocomplete: 'off'
%span.input-group-append.clipboard-addon
......@@ -23,7 +23,7 @@
= s_('ClusterIntegration|Show')
.form-group
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-light'
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
.form-group
......
- @no_container = true
- page_title "Labels"
- hide_class = ''
- can_admin_label = can?(current_user, :admin_label, @project)
- hide_class = ''
- if can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_project_label_path(@project), class: "btn btn-new"
- if @labels.exists? || @prioritized_labels.exists?
#promote-label-modal
%div{ class: container_class }
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests.
= _('Labels can be applied to issues and merge requests.')
- if can_admin_label
Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
= _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
- if can_admin_label
.nav-controls
= link_to new_project_label_path(@project), class: "btn btn-new" do
New label
.labels
.labels-container.prepend-top-5
- if can_admin_label
-# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5.prepend-top-10 Prioritized Labels
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
#js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
%h5.prepend-top-10= _('Prioritized Labels')
.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
#js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
= render 'shared/empty_states/priority_labels'
- if @prioritized_labels.present?
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
= render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label, locals: { force_priority: true }
- if @labels.present?
.other-labels
- if can_admin_label
%h5{ class: ('hide' if hide) } Other Labels
%ul.content-list.manage-labels-list.js-other-labels
%h5{ class: ('hide' if hide) }= _('Other Labels')
.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- else
= render 'shared/empty_states/labels'
%template#js-badge-item-template
%li.label-link-item.js-priority-badge.inline.prepend-left-10
.label-badge.label-badge-blue= _('Prioritized label')
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment