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