Merge branch 'master' into fix/sm/31771-do-not-allow-jobs-to-be-erased-new
|
|
@ -14,7 +14,7 @@ linters:
|
|||
|
||||
# Whether or not to prefer `border: 0` over `border: none`.
|
||||
BorderZero:
|
||||
enabled: false
|
||||
enabled: true
|
||||
|
||||
# Reports when you define a rule set using a selector with chained classes
|
||||
# (a.k.a. adjoining classes).
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"iconCount":164,"spriteSize":72823,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","dashboard","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
|
||||
{"iconCount":173,"spriteSize":75815,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="492.509" height="453.68" viewBox="0 0 492.50943 453.67966"><g fill="none" fill-rule="evenodd"><path d="M491.589 259.398l-27.559-84.814L409.413 6.486c-2.81-8.648-15.045-8.648-17.856 0l-54.619 168.098H155.572L100.952 6.486c-2.81-8.648-15.046-8.648-17.856 0L28.478 174.584.921 259.398a18.775 18.775 0 0 0 6.82 20.992l238.513 173.29L484.77 280.39a18.777 18.777 0 0 0 6.82-20.992" fill="#fc6d26"/><path d="M246.255 453.68l90.684-279.096H155.57z" fill="#e24329"/><path d="M246.255 453.68L155.57 174.583H28.479z" fill="#fc6d26"/><path d="M28.479 174.584L.92 259.4a18.773 18.773 0 0 0 6.821 20.99l238.514 173.29z" fill="#fca326"/><path d="M28.479 174.584H155.57L100.952 6.487c-2.81-8.65-15.047-8.65-17.856 0z" fill="#e24329"/><path d="M246.255 453.68l90.684-279.096H464.03z" fill="#fc6d26"/><path d="M464.03 174.584l27.56 84.815a18.773 18.773 0 0 1-6.822 20.99L246.255 453.68z" fill="#fca326"/><path d="M464.03 174.584H336.94L391.557 6.487c2.811-8.65 15.047-8.65 17.856 0z" fill="#e24329"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="430" height="220" viewBox="0 0 430 220"><g fill="none" fill-rule="evenodd"><path fill="#EEE" fill-rule="nonzero" d="M189.8 182l2.4-12H114c-5.523 0-10-4.477-10-10V34c0-5.523 4.477-10 10-10h200c5.523 0 10 4.477 10 10v126c0 5.523-4.477 10-10 10h-78.2l2.4 12h22.52a9.651 9.651 0 0 1 9.28 7 5.491 5.491 0 0 1-5.28 7H164.159a5.787 5.787 0 0 1-5.659-7 8.855 8.855 0 0 1 8.659-7H189.8zM114 28a6 6 0 0 0-6 6v126a6 6 0 0 0 6 6h200a6 6 0 0 0 6-6V34a6 6 0 0 0-6-6H114zm5 6h190a5 5 0 0 1 5 5v116a5 5 0 0 1-5 5H119a5 5 0 0 1-5-5V39a5 5 0 0 1 5-5zm0 4a1 1 0 0 0-1 1v116a1 1 0 0 0 1 1h190a1 1 0 0 0 1-1V39a1 1 0 0 0-1-1H119zm112.72 132h-35.44l-2.4 12h40.24l-2.4-12zm-64.561 16c-2.29 0-4.268 1.6-4.748 3.838A1.787 1.787 0 0 0 164.16 192h100.56a1.491 1.491 0 0 0 1.435-1.901A5.651 5.651 0 0 0 260.72 186h-93.561z"/><path fill="#FEF0E8" d="M177.965 99H194a2 2 0 1 1 0 4h-16.322c-1.374 6.29-6.976 11-13.678 11-6.702 0-12.304-4.71-13.678-11h-3.365l-7.395 9.249a2 2 0 0 1-3.049.089L128.11 103h-5.844a2 2 0 1 1 0-4H129a2 2 0 0 1 1.487.662l7.423 8.248 6.523-8.159a2 2 0 0 1 1.562-.751h4.04c.513-7.265 6.57-13 13.965-13 7.396 0 13.452 5.735 13.965 13zM164 110c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z"/><path fill="#EFEDF8" d="M273.847 103c-.962 6.23-6.347 11-12.847 11-6.5 0-11.885-4.77-12.847-11H232a2 2 0 0 1 0-4h16.153c.962-6.23 6.347-11 12.847-11 6.5 0 11.885 4.77 12.847 11h3.998l8.404-9.338a2 2 0 0 1 3.048.09L296.692 99H305a2 2 0 0 1 0 4h-9.27a2 2 0 0 1-1.562-.751l-6.523-8.16-7.423 8.249a2 2 0 0 1-1.487.662h-4.888zM261 110a9 9 0 1 0 0-18 9 9 0 0 0 0 18z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M213 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15z"/><path fill="#FC6D26" d="M211.586 101.828L208.757 99a2 2 0 1 0-2.828 2.828l4.243 4.243c.39.39.902.586 1.414.586.512 0 1.023-.195 1.414-.586L220.071 99a2 2 0 1 0-2.828-2.828l-5.657 5.656z"/><path fill="#FDC4A8" d="M162.95 101.07l-1.768-1.767a1.5 1.5 0 0 0-2.121 2.121l2.828 2.829c.293.293.677.439 1.06.439.385 0 .769-.146 1.062-.44l4.242-4.242a1.5 1.5 0 1 0-2.121-2.121l-3.182 3.182z"/><path fill="#6B4FBB" d="M256.39 104.841A6 6 0 1 0 261 95v6l-4.61 3.841z"/><path fill="#FEF0E8" fill-rule="nonzero" d="M99 99h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-14.384-.078l-3.643-3.425a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-11.657-10.96l-3.642-3.425a2 2 0 1 0-2.74 2.914l3.642 3.425a2 2 0 0 0 2.74-2.914zm-11.656-10.96l-3.643-3.425a2 2 0 0 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-14.367-3.885l-3.593 3.477a2 2 0 0 0 2.782 2.875l3.593-3.477a2 2 0 0 0-2.782-2.875zM19.44 84.244l-3.593 3.477a2 2 0 1 0 2.781 2.874l3.593-3.477a2 2 0 0 0-2.781-2.874zM7.94 95.371l-3.593 3.477a2 2 0 1 0 2.782 2.874l3.593-3.477a2 2 0 1 0-2.782-2.874z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M423.611 99.56l-3.598 3.472a2 2 0 0 0 2.777 2.879l3.599-3.472a2 2 0 0 0-2.778-2.878zm-11.514 11.11l-3.598 3.472a2 2 0 0 0 2.777 2.878l3.598-3.471a2 2 0 0 0-2.777-2.879zm-11.514 11.11l-3.599 3.471a2 2 0 1 0 2.778 2.879l3.598-3.472a2 2 0 1 0-2.777-2.879zm-8.799 4.48l-3.642-3.426a2 2 0 0 0-2.74 2.915l3.642 3.425a2 2 0 0 0 2.74-2.915zm-11.656-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.426a2 2 0 1 0 2.74-2.915zm-11.657-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zM353.001 99h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 121.94154 121.84154" width="121.942" height="121.842"><style id="style200">.st0{fill:#ecb32d}.st1{fill:#63c1a0}.st2{fill:#e01a59}.st3{fill:#331433}.st4{fill:#d62027}.st5{fill:#89d3df}.st6{fill:#258b74}.st7{fill:#819c3c}</style><path class="st0" d="M79.03 7.511c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path202" fill="#ecb32d"/><path class="st1" d="M35.53 21.611c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path204" fill="#63c1a0"/><path class="st2" d="M114.43 79.011c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.5 28.2c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.5-28.1 86.5-28.1z" id="path206" fill="#e01a59"/><path class="st3" d="M39.23 103.511c5.6-1.8 12.9-4.2 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path208" fill="#331433"/><path class="st4" d="M82.83 89.311c7.8-2.5 15.1-4.9 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path210" fill="#d62027"/><path class="st5" d="M100.23 35.511c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.4 28.1c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.4-28 86.4-28z" id="path212" fill="#89d3df"/><path class="st6" d="M25.13 59.911c5.6-1.8 12.9-4.2 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path214" fill="#258b74"/><path class="st7" d="M68.63 45.811c7.8-2.5 15.1-4.9 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path216" fill="#819c3c"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
|
@ -1,7 +1,8 @@
|
|||
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
|
||||
|
||||
import _ from 'underscore';
|
||||
import { insertText, getSelectedFragment, nodeMatchesSelector } from './lib/utils/common_utils';
|
||||
import { placeholderImage } from './lazy_loader';
|
||||
import { insertText, getSelectedFragment, nodeMatchesSelector } from '../lib/utils/common_utils';
|
||||
import { placeholderImage } from '../lazy_loader';
|
||||
|
||||
const gfmRules = {
|
||||
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
|
||||
|
|
@ -284,7 +285,7 @@ const gfmRules = {
|
|||
},
|
||||
};
|
||||
|
||||
class CopyAsGFM {
|
||||
export class CopyAsGFM {
|
||||
constructor() {
|
||||
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
|
||||
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
|
||||
|
|
@ -469,7 +470,12 @@ class CopyAsGFM {
|
|||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.CopyAsGFM = CopyAsGFM;
|
||||
// Export CopyAsGFM as a global for rspec to access
|
||||
// see /spec/features/copy_as_gfm_spec.rb
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.CopyAsGFM = CopyAsGFM;
|
||||
}
|
||||
|
||||
new CopyAsGFM();
|
||||
export default function initCopyAsGFM() {
|
||||
return new CopyAsGFM();
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import './autosize';
|
||||
import './bind_in_out';
|
||||
import initCopyAsGFM from './copy_as_gfm';
|
||||
import './details_behavior';
|
||||
import installGlEmojiElement from './gl_emoji';
|
||||
import './quick_submit';
|
||||
|
|
@ -7,3 +8,4 @@ import './requires_input';
|
|||
import './toggler_behavior';
|
||||
|
||||
installGlEmojiElement();
|
||||
initCopyAsGFM();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
|||
// Issue boards is slightly different, we handle all the requests async
|
||||
// instead or reloading the page, we just re-fire the list ajax requests
|
||||
this.isHandledAsync = true;
|
||||
this.cantEdit = cantEdit;
|
||||
this.cantEdit = cantEdit.filter(i => typeof i === 'string');
|
||||
this.cantEditWithValue = cantEdit.filter(i => typeof i === 'object');
|
||||
}
|
||||
|
||||
updateObject(path) {
|
||||
|
|
@ -42,7 +43,9 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
|||
this.filteredSearchInput.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
canEdit(tokenName) {
|
||||
return this.cantEdit.indexOf(tokenName) === -1;
|
||||
canEdit(tokenName, tokenValue) {
|
||||
if (this.cantEdit.includes(tokenName)) return false;
|
||||
return this.cantEditWithValue.findIndex(token => token.name === tokenName &&
|
||||
token.value === tokenValue) === -1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,18 @@ gl.issueBoards.BoardsStore = {
|
|||
},
|
||||
state: {},
|
||||
detail: {
|
||||
issue: {}
|
||||
issue: {},
|
||||
},
|
||||
moving: {
|
||||
issue: {},
|
||||
list: {}
|
||||
list: {},
|
||||
},
|
||||
create () {
|
||||
this.state.lists = [];
|
||||
this.filter.path = getUrlParamsArray().join('&');
|
||||
this.detail = { issue: {} };
|
||||
this.detail = {
|
||||
issue: {},
|
||||
};
|
||||
},
|
||||
addList (listObj, defaultAvatar) {
|
||||
const list = new List(listObj, defaultAvatar);
|
||||
|
|
|
|||
|
|
@ -147,6 +147,16 @@ class DropdownUtils {
|
|||
return dataValue !== null;
|
||||
}
|
||||
|
||||
static getVisualTokenValues(visualToken) {
|
||||
const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim();
|
||||
let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim();
|
||||
if (tokenName === 'label' && tokenValue) {
|
||||
// remove leading symbol and wrapping quotes
|
||||
tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
|
||||
}
|
||||
return { tokenName, tokenValue };
|
||||
}
|
||||
|
||||
// Determines the full search query (visual tokens + input)
|
||||
static getSearchQuery(untilInput = false) {
|
||||
const container = FilteredSearchContainer.container;
|
||||
|
|
|
|||
|
|
@ -185,8 +185,8 @@ class FilteredSearchManager {
|
|||
if (e.keyCode === 8 || e.keyCode === 46) {
|
||||
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
const sanitizedTokenName = lastVisualToken && lastVisualToken.querySelector('.name').textContent.trim();
|
||||
const canEdit = sanitizedTokenName && this.canEdit && this.canEdit(sanitizedTokenName);
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
|
||||
const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
|
||||
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
|
|
@ -336,8 +336,8 @@ class FilteredSearchManager {
|
|||
let canClearToken = t.classList.contains('js-visual-token');
|
||||
|
||||
if (canClearToken) {
|
||||
const tokenKey = t.querySelector('.name').textContent.trim();
|
||||
canClearToken = this.canEdit && this.canEdit(tokenKey);
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(t);
|
||||
canClearToken = this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
}
|
||||
|
||||
if (canClearToken) {
|
||||
|
|
@ -469,7 +469,7 @@ class FilteredSearchManager {
|
|||
}
|
||||
|
||||
hasFilteredSearch = true;
|
||||
const canEdit = this.canEdit && this.canEdit(sanitizedKey);
|
||||
const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
sanitizedKey,
|
||||
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
|
||||
|
|
|
|||
|
|
@ -38,21 +38,14 @@ class FilteredSearchVisualTokens {
|
|||
}
|
||||
|
||||
static createVisualTokenElementHTML(canEdit = true) {
|
||||
let removeTokenMarkup = '';
|
||||
if (canEdit) {
|
||||
removeTokenMarkup = `
|
||||
<div class="remove-token" role="button">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="selectable" role="button">
|
||||
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
|
||||
<div class="name"></div>
|
||||
<div class="value-container">
|
||||
<div class="value"></div>
|
||||
${removeTokenMarkup}
|
||||
<div class="remove-token" role="button">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import DropdownUtils from './filtered_search/dropdown_utils';
|
|||
import CreateLabelDropdown from './create_label';
|
||||
|
||||
export default class LabelsSelect {
|
||||
constructor(els) {
|
||||
constructor(els, options = {}) {
|
||||
var _this, $els;
|
||||
_this = this;
|
||||
|
||||
|
|
@ -57,6 +57,7 @@ export default class LabelsSelect {
|
|||
labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
|
||||
labelNoneHTMLTemplate = '<span class="no-value">None</span>';
|
||||
}
|
||||
const handleClick = options.handleClick;
|
||||
|
||||
$sidebarLabelTooltip.tooltip();
|
||||
|
||||
|
|
@ -390,6 +391,10 @@ export default class LabelsSelect {
|
|||
.then(fadeOutLoader)
|
||||
.catch(fadeOutLoader);
|
||||
}
|
||||
else if (handleClick) {
|
||||
e.preventDefault();
|
||||
handleClick(label);
|
||||
}
|
||||
else {
|
||||
if ($dropdown.hasClass('js-multiselect')) {
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ import './commits';
|
|||
import './compare';
|
||||
import './compare_autocomplete';
|
||||
import './confirm_danger_modal';
|
||||
import './copy_as_gfm';
|
||||
import './copy_to_clipboard';
|
||||
import Flash, { removeFlashClickListener } from './flash';
|
||||
import './gl_dropdown';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import _ from 'underscore';
|
|||
|
||||
(function() {
|
||||
this.MilestoneSelect = (function() {
|
||||
function MilestoneSelect(currentProject, els) {
|
||||
function MilestoneSelect(currentProject, els, options = {}) {
|
||||
var _this, $els;
|
||||
if (currentProject != null) {
|
||||
_this = this;
|
||||
|
|
@ -136,19 +136,26 @@ import _ from 'underscore';
|
|||
},
|
||||
opened: function(e) {
|
||||
const $el = $(e.currentTarget);
|
||||
if ($dropdown.hasClass('js-issue-board-sidebar')) {
|
||||
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
|
||||
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
|
||||
}
|
||||
$('a.is-active', $el).removeClass('is-active');
|
||||
$(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
|
||||
},
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: function(options) {
|
||||
const { $el, e } = options;
|
||||
let selected = options.selectedObj;
|
||||
clicked: function(clickEvent) {
|
||||
const { $el, e } = clickEvent;
|
||||
let selected = clickEvent.selectedObj;
|
||||
|
||||
var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore;
|
||||
if (!selected) return;
|
||||
|
||||
if (options.handleClick) {
|
||||
e.preventDefault();
|
||||
options.handleClick(selected);
|
||||
return;
|
||||
}
|
||||
|
||||
page = $('body').attr('data-page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = (page === page && page === 'projects:merge_requests:index');
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@
|
|||
|
||||
renderAxesPaths() {
|
||||
this.timeSeries = createTimeSeries(
|
||||
this.graphData.queries[0],
|
||||
this.graphData.queries,
|
||||
this.graphWidth,
|
||||
this.graphHeight,
|
||||
this.graphHeightOffset,
|
||||
|
|
@ -153,8 +153,9 @@
|
|||
const axisYScale = d3.scale.linear()
|
||||
.range([this.graphHeight - this.graphHeightOffset, 0]);
|
||||
|
||||
axisXScale.domain(d3.extent(this.timeSeries[0].values, d => d.time));
|
||||
axisYScale.domain([0, d3.max(this.timeSeries[0].values.map(d => d.value))]);
|
||||
const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
|
||||
axisXScale.domain(d3.extent(allValues, d => d.time));
|
||||
axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
|
||||
|
||||
const xAxis = d3.svg.axis()
|
||||
.scale(axisXScale)
|
||||
|
|
@ -246,6 +247,7 @@
|
|||
:key="index"
|
||||
:generated-line-path="path.linePath"
|
||||
:generated-area-path="path.areaPath"
|
||||
:line-style="path.lineStyle"
|
||||
:line-color="path.lineColor"
|
||||
:area-color="path.areaColor"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@
|
|||
},
|
||||
|
||||
formatMetricUsage(series) {
|
||||
const value = series.values[this.currentDataIndex].value;
|
||||
const value = series.values[this.currentDataIndex] &&
|
||||
series.values[this.currentDataIndex].value;
|
||||
if (isNaN(value)) {
|
||||
return '-';
|
||||
}
|
||||
|
|
@ -92,6 +93,12 @@
|
|||
}
|
||||
return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`;
|
||||
},
|
||||
|
||||
strokeDashArray(type) {
|
||||
if (type === 'dashed') return '6, 3';
|
||||
if (type === 'dotted') return '3, 3';
|
||||
return null;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -162,13 +169,15 @@
|
|||
v-for="(series, index) in timeSeries"
|
||||
:key="index"
|
||||
:transform="translateLegendGroup(index)">
|
||||
<rect
|
||||
:fill="series.areaColor"
|
||||
:width="measurements.legends.width"
|
||||
:height="measurements.legends.height"
|
||||
x="20"
|
||||
:y="graphHeight - measurements.legendOffset">
|
||||
</rect>
|
||||
<line
|
||||
:stroke="series.lineColor"
|
||||
:stroke-width="measurements.legends.height"
|
||||
:stroke-dasharray="strokeDashArray(series.lineStyle)"
|
||||
:x1="measurements.legends.offsetX"
|
||||
:x2="measurements.legends.offsetX + measurements.legends.width"
|
||||
:y1="graphHeight - measurements.legends.offsetY"
|
||||
:y2="graphHeight - measurements.legends.offsetY">
|
||||
</line>
|
||||
<text
|
||||
v-if="timeSeries.length > 1"
|
||||
class="legend-metric-title"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
lineStyle: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
lineColor: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -18,6 +22,13 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
strokeDashArray() {
|
||||
if (this.lineStyle === 'dashed') return '3, 1';
|
||||
if (this.lineStyle === 'dotted') return '1, 1';
|
||||
return null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -34,6 +45,7 @@
|
|||
:stroke="lineColor"
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
:stroke-dasharray="strokeDashArray"
|
||||
transform="translate(-5, 20)">
|
||||
</path>
|
||||
</g>
|
||||
|
|
|
|||
|
|
@ -7,15 +7,16 @@ export default {
|
|||
left: 40,
|
||||
},
|
||||
legends: {
|
||||
width: 10,
|
||||
width: 15,
|
||||
height: 3,
|
||||
offsetX: 20,
|
||||
offsetY: 32,
|
||||
},
|
||||
backgroundLegend: {
|
||||
width: 30,
|
||||
height: 50,
|
||||
},
|
||||
axisLabelLineOffset: -20,
|
||||
legendOffset: 33,
|
||||
},
|
||||
large: { // This covers both md and lg screen sizes
|
||||
margin: {
|
||||
|
|
@ -27,13 +28,14 @@ export default {
|
|||
legends: {
|
||||
width: 15,
|
||||
height: 3,
|
||||
offsetX: 20,
|
||||
offsetY: 34,
|
||||
},
|
||||
backgroundLegend: {
|
||||
width: 30,
|
||||
height: 150,
|
||||
},
|
||||
axisLabelLineOffset: 20,
|
||||
legendOffset: 36,
|
||||
},
|
||||
xTicks: 8,
|
||||
yTicks: 3,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ const defaultColorPalette = {
|
|||
|
||||
const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
|
||||
|
||||
export default function createTimeSeries(queryData, graphWidth, graphHeight, graphHeightOffset) {
|
||||
const defaultStyleOrder = ['solid', 'dashed', 'dotted'];
|
||||
|
||||
function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) {
|
||||
let usedColors = [];
|
||||
|
||||
function pickColor(name) {
|
||||
|
|
@ -31,17 +33,7 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
|
|||
return defaultColorPalette[pick];
|
||||
}
|
||||
|
||||
const maxValues = queryData.result.map((timeSeries, index) => {
|
||||
const maxValue = d3.max(timeSeries.values.map(d => d.value));
|
||||
return {
|
||||
maxValue,
|
||||
index,
|
||||
};
|
||||
});
|
||||
|
||||
const maxValueFromSeries = _.max(maxValues, val => val.maxValue);
|
||||
|
||||
return queryData.result.map((timeSeries, timeSeriesNumber) => {
|
||||
return query.result.map((timeSeries, timeSeriesNumber) => {
|
||||
let metricTag = '';
|
||||
let lineColor = '';
|
||||
let areaColor = '';
|
||||
|
|
@ -52,9 +44,9 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
|
|||
const timeSeriesScaleY = d3.scale.linear()
|
||||
.range([graphHeight - graphHeightOffset, 0]);
|
||||
|
||||
timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time));
|
||||
timeSeriesScaleX.domain(xDom);
|
||||
timeSeriesScaleX.ticks(d3.time.minute, 60);
|
||||
timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]);
|
||||
timeSeriesScaleY.domain(yDom);
|
||||
|
||||
const defined = d => !isNaN(d.value) && d.value != null;
|
||||
|
||||
|
|
@ -72,10 +64,10 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
|
|||
.y1(d => timeSeriesScaleY(d.value));
|
||||
|
||||
const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
|
||||
const seriesCustomizationData = queryData.series != null &&
|
||||
_.findWhere(queryData.series[0].when,
|
||||
{ value: timeSeriesMetricLabel });
|
||||
if (seriesCustomizationData != null) {
|
||||
const seriesCustomizationData = query.series != null &&
|
||||
_.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
|
||||
|
||||
if (seriesCustomizationData) {
|
||||
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
|
||||
[lineColor, areaColor] = pickColor(seriesCustomizationData.color);
|
||||
} else {
|
||||
|
|
@ -83,14 +75,35 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
|
|||
[lineColor, areaColor] = pickColor();
|
||||
}
|
||||
|
||||
if (query.track) {
|
||||
metricTag += ` - ${query.track}`;
|
||||
}
|
||||
|
||||
return {
|
||||
linePath: lineFunction(timeSeries.values),
|
||||
areaPath: areaFunction(timeSeries.values),
|
||||
timeSeriesScaleX,
|
||||
values: timeSeries.values,
|
||||
lineStyle,
|
||||
lineColor,
|
||||
areaColor,
|
||||
metricTag,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) {
|
||||
const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat(
|
||||
query.result.reduce((allResults, result) => allResults.concat(result.values), []),
|
||||
), []);
|
||||
|
||||
const xDom = d3.extent(allValues, d => d.time);
|
||||
const yDom = [0, d3.max(allValues.map(d => d.value))];
|
||||
|
||||
return queries.reduce((series, query, index) => {
|
||||
const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length];
|
||||
return series.concat(
|
||||
queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle),
|
||||
);
|
||||
}, []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,13 +162,19 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
|
|||
items = [
|
||||
{
|
||||
header: "" + name
|
||||
}, {
|
||||
}
|
||||
];
|
||||
const issueItems = [
|
||||
{
|
||||
text: 'Issues assigned to me',
|
||||
url: issuesPath + "/?assignee_username=" + userName
|
||||
}, {
|
||||
text: "Issues I've created",
|
||||
url: issuesPath + "/?author_username=" + userName
|
||||
}, 'separator', {
|
||||
}
|
||||
];
|
||||
const mergeRequestItems = [
|
||||
{
|
||||
text: 'Merge requests assigned to me',
|
||||
url: mrPath + "/?assignee_username=" + userName
|
||||
}, {
|
||||
|
|
@ -176,6 +182,11 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
|
|||
url: mrPath + "/?author_username=" + userName
|
||||
}
|
||||
];
|
||||
if (options.issuesDisabled) {
|
||||
items = items.concat(mergeRequestItems);
|
||||
} else {
|
||||
items = items.concat(...issueItems, 'separator', ...mergeRequestItems);
|
||||
}
|
||||
if (!name) {
|
||||
items.splice(0, 1);
|
||||
}
|
||||
|
|
@ -408,6 +419,7 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
|
|||
gl.projectOptions[projectPath] = {
|
||||
name: $projectOptionsDataEl.data('name'),
|
||||
issuesPath: $projectOptionsDataEl.data('issues-path'),
|
||||
issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
|
||||
mrPath: $projectOptionsDataEl.data('mr-path')
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import _ from 'underscore';
|
||||
import 'mousetrap';
|
||||
import ShortcutsNavigation from './shortcuts_navigation';
|
||||
import { CopyAsGFM } from './behaviors/copy_as_gfm';
|
||||
|
||||
export default class ShortcutsIssuable extends ShortcutsNavigation {
|
||||
constructor(isMergeRequest) {
|
||||
|
|
@ -33,8 +34,8 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
|
|||
return false;
|
||||
}
|
||||
|
||||
const el = window.gl.CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
|
||||
const selected = window.gl.CopyAsGFM.nodeToGFM(el);
|
||||
const el = CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
|
||||
const selected = CopyAsGFM.nodeToGFM(el);
|
||||
|
||||
if (selected.trim() === '') {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import _ from 'underscore';
|
|||
// TODO: remove eventHub hack after code splitting refactor
|
||||
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
|
||||
|
||||
function UsersSelect(currentUser, els) {
|
||||
function UsersSelect(currentUser, els, options = {}) {
|
||||
var $els;
|
||||
this.users = this.users.bind(this);
|
||||
this.user = this.user.bind(this);
|
||||
|
|
@ -20,6 +20,8 @@ function UsersSelect(currentUser, els) {
|
|||
}
|
||||
}
|
||||
|
||||
const { handleClick } = options;
|
||||
|
||||
$els = $(els);
|
||||
|
||||
if (!els) {
|
||||
|
|
@ -442,6 +444,9 @@ function UsersSelect(currentUser, els) {
|
|||
}
|
||||
if ($el.closest('.add-issues-modal').length) {
|
||||
gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
|
||||
} else if (handleClick) {
|
||||
e.preventDefault();
|
||||
handleClick(user, isMarking);
|
||||
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
return Issuable.filterResults($dropdown.closest('form'));
|
||||
} else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import GLForm from '../../../gl_form';
|
||||
import markdownHeader from './header.vue';
|
||||
import markdownToolbar from './toolbar.vue';
|
||||
import icon from '../icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
components: {
|
||||
markdownHeader,
|
||||
markdownToolbar,
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
shouldShowReferencedUsers() {
|
||||
|
|
@ -45,8 +47,10 @@
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
toggleMarkdownPreview() {
|
||||
this.previewMarkdown = !this.previewMarkdown;
|
||||
showPreviewTab() {
|
||||
if (this.previewMarkdown) return;
|
||||
|
||||
this.previewMarkdown = true;
|
||||
|
||||
/*
|
||||
Can't use `$refs` as the component is technically in the parent component
|
||||
|
|
@ -54,20 +58,22 @@
|
|||
*/
|
||||
const text = this.$slots.textarea[0].elm.value;
|
||||
|
||||
if (!this.previewMarkdown) {
|
||||
this.markdownPreview = '';
|
||||
} else if (text) {
|
||||
if (text) {
|
||||
this.markdownPreviewLoading = true;
|
||||
this.$http.post(this.markdownPreviewPath, { text })
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
this.renderMarkdown(data);
|
||||
})
|
||||
.then(data => this.renderMarkdown(data))
|
||||
.catch(() => new Flash('Error loading markdown preview'));
|
||||
} else {
|
||||
this.renderMarkdown();
|
||||
}
|
||||
},
|
||||
|
||||
showWriteTab() {
|
||||
this.markdownPreview = '';
|
||||
this.previewMarkdown = false;
|
||||
},
|
||||
|
||||
renderMarkdown(data = {}) {
|
||||
this.markdownPreviewLoading = false;
|
||||
this.markdownPreview = data.body || 'Nothing to preview.';
|
||||
|
|
@ -104,7 +110,8 @@
|
|||
ref="gl-form">
|
||||
<markdown-header
|
||||
:preview-markdown="previewMarkdown"
|
||||
@toggle-markdown="toggleMarkdownPreview" />
|
||||
@preview-markdown="showPreviewTab"
|
||||
@write-markdown="showWriteTab" />
|
||||
<div
|
||||
class="md-write-holder"
|
||||
v-show="!previewMarkdown">
|
||||
|
|
@ -114,10 +121,10 @@
|
|||
class="zen-control zen-control-leave js-zen-leave"
|
||||
href="#"
|
||||
aria-label="Enter zen mode">
|
||||
<i
|
||||
class="fa fa-compress"
|
||||
aria-hidden="true">
|
||||
</i>
|
||||
<icon
|
||||
name="screen-normal"
|
||||
:size="32">
|
||||
</icon>
|
||||
</a>
|
||||
<markdown-toolbar
|
||||
:markdown-docs-path="markdownDocsPath"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import tooltip from '../../directives/tooltip';
|
||||
import toolbarButton from './toolbar_button.vue';
|
||||
import icon from '../icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -14,25 +15,34 @@
|
|||
},
|
||||
components: {
|
||||
toolbarButton,
|
||||
icon,
|
||||
},
|
||||
methods: {
|
||||
toggleMarkdownPreview(e, form) {
|
||||
if (form && !form.find('.js-vue-markdown-field').length) {
|
||||
return;
|
||||
} else if (e.target.blur) {
|
||||
e.target.blur();
|
||||
}
|
||||
isMarkdownForm(form) {
|
||||
return form && !form.find('.js-vue-markdown-field').length;
|
||||
},
|
||||
|
||||
this.$emit('toggle-markdown');
|
||||
previewMarkdownTab(event, form) {
|
||||
if (event.target.blur) event.target.blur();
|
||||
if (this.isMarkdownForm(form)) return;
|
||||
|
||||
this.$emit('preview-markdown');
|
||||
},
|
||||
|
||||
writeMarkdownTab(event, form) {
|
||||
if (event.target.blur) event.target.blur();
|
||||
if (this.isMarkdownForm(form)) return;
|
||||
|
||||
this.$emit('write-markdown');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$(document).on('markdown-preview:show.vue', this.toggleMarkdownPreview);
|
||||
$(document).on('markdown-preview:hide.vue', this.toggleMarkdownPreview);
|
||||
$(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
|
||||
$(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
|
||||
},
|
||||
beforeDestroy() {
|
||||
$(document).on('markdown-preview:show.vue', this.toggleMarkdownPreview);
|
||||
$(document).off('markdown-preview:hide.vue', this.toggleMarkdownPreview);
|
||||
$(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
|
||||
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -42,17 +52,19 @@
|
|||
<ul class="nav-links clearfix">
|
||||
<li :class="{ active: !previewMarkdown }">
|
||||
<a
|
||||
class="js-write-link"
|
||||
href="#md-write-holder"
|
||||
tabindex="-1"
|
||||
@click.prevent="toggleMarkdownPreview($event)">
|
||||
@click.prevent="writeMarkdownTab($event)">
|
||||
Write
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ active: previewMarkdown }">
|
||||
<a
|
||||
class="js-preview-link"
|
||||
href="#md-preview-holder"
|
||||
tabindex="-1"
|
||||
@click.prevent="toggleMarkdownPreview($event)">
|
||||
@click.prevent="previewMarkdownTab($event)">
|
||||
Preview
|
||||
</a>
|
||||
</li>
|
||||
|
|
@ -70,7 +82,7 @@
|
|||
tag="> "
|
||||
:prepend="true"
|
||||
button-title="Insert a quote"
|
||||
icon="quote-right" />
|
||||
icon="quote" />
|
||||
<toolbar-button
|
||||
tag="`"
|
||||
tag-block="```"
|
||||
|
|
@ -80,17 +92,17 @@
|
|||
tag="* "
|
||||
:prepend="true"
|
||||
button-title="Add a bullet list"
|
||||
icon="list-ul" />
|
||||
icon="list-bulleted" />
|
||||
<toolbar-button
|
||||
tag="1. "
|
||||
:prepend="true"
|
||||
button-title="Add a numbered list"
|
||||
icon="list-ol" />
|
||||
icon="list-numbered" />
|
||||
<toolbar-button
|
||||
tag="* [ ] "
|
||||
:prepend="true"
|
||||
button-title="Add a task list"
|
||||
icon="check-square-o" />
|
||||
icon="task-done" />
|
||||
</div>
|
||||
<div class="toolbar-group">
|
||||
<button
|
||||
|
|
@ -101,10 +113,9 @@
|
|||
tabindex="-1"
|
||||
title="Go full screen"
|
||||
type="button">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-arrows-alt fa-fw">
|
||||
</i>
|
||||
<icon
|
||||
name="screen-full">
|
||||
</icon>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import tooltip from '../../directives/tooltip';
|
||||
import icon from '../icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -26,14 +27,12 @@
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
return `fa-${this.icon}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -49,10 +48,8 @@
|
|||
:data-md-prepend="prepend"
|
||||
:title="buttonTitle"
|
||||
:aria-label="buttonTitle">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-fw"
|
||||
:class="iconClass">
|
||||
</i>
|
||||
<icon
|
||||
:name="icon">
|
||||
</icon>
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,17 +5,27 @@ export default {
|
|||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
kind: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'primary',
|
||||
},
|
||||
modalDialogClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
closeKind: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -30,6 +40,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
submitDisabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
@ -57,43 +72,58 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="modal popup-dialog"
|
||||
role="dialog"
|
||||
tabindex="-1">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button"
|
||||
class="close"
|
||||
@click="close"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">{{this.title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body" :text="text">
|
||||
<p>{{text}}</p>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="btnCancelKindClass"
|
||||
@click="close">
|
||||
{{ closeButtonLabel }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="btnKindClass"
|
||||
@click="emitSubmit(true)">
|
||||
{{ primaryButtonLabel }}
|
||||
</button>
|
||||
<div class="modal-open">
|
||||
<div
|
||||
class="modal popup-dialog"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
:class="modalDialogClass"
|
||||
class="modal-dialog"
|
||||
role="document"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<slot name="header">
|
||||
<h4 class="modal-title pull-left">
|
||||
{{this.title}}
|
||||
</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="close pull-right"
|
||||
@click="close"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body" :text="text">
|
||||
<p>{{this.text}}</p>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="!hideFooter">
|
||||
<button
|
||||
type="button"
|
||||
class="btn pull-left"
|
||||
:class="btnCancelKindClass"
|
||||
@click="close">
|
||||
{{ closeButtonLabel }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn pull-right"
|
||||
:disabled="submitDisabled"
|
||||
:class="btnKindClass"
|
||||
@click="emitSubmit(true)">
|
||||
{{ primaryButtonLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop fade in" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -42,8 +42,7 @@
|
|||
&.avatar-inline {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
margin-bottom: 2px;
|
||||
margin-left: 2px;
|
||||
flex-shrink: 0;
|
||||
-webkit-flex-shrink: 0;
|
||||
|
||||
|
|
@ -59,7 +58,7 @@
|
|||
|
||||
&.avatar-tile {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&:not([href]):hover {
|
||||
|
|
@ -96,7 +95,7 @@
|
|||
|
||||
.avatar {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
}
|
||||
|
||||
&.top-block {
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
|
||||
.container-fluid {
|
||||
background-color: inherit;
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
&.footer-block {
|
||||
margin-top: 0;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
margin-bottom: -$gl-padding;
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
|
||||
&.build-content {
|
||||
background-color: $white-light;
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,12 +287,12 @@
|
|||
cursor: pointer;
|
||||
color: $blue-300;
|
||||
z-index: 1;
|
||||
border: none;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border: none;
|
||||
border: 0;
|
||||
color: $blue-400;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@
|
|||
}
|
||||
|
||||
.btn-clipboard {
|
||||
border: none;
|
||||
border: 0;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
.cred { color: $common-red; }
|
||||
.cgreen { color: $common-green; }
|
||||
.cdark { color: $common-gray-dark; }
|
||||
.text-secondary {
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
.underlined-link { text-decoration: underline; }
|
||||
.hint { font-style: italic; color: $hint-color; }
|
||||
|
|
@ -28,7 +31,7 @@
|
|||
pre {
|
||||
&.clean {
|
||||
background: none;
|
||||
border: none;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
|
@ -142,7 +145,7 @@ li.note {
|
|||
img { max-width: 100%; }
|
||||
.note-title {
|
||||
li {
|
||||
border-bottom: none !important;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,7 +190,7 @@ li.note {
|
|||
|
||||
pre {
|
||||
background: $white-light;
|
||||
border: none;
|
||||
border: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
|
@ -386,7 +389,7 @@ img.emoji {
|
|||
}
|
||||
|
||||
.hide-bottom-border {
|
||||
border-bottom: none !important;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.gl-accessibility {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
.dropdown-menu-nav {
|
||||
@include set-visible;
|
||||
display: block;
|
||||
min-height: 40px;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@
|
|||
*/
|
||||
&.blame {
|
||||
table {
|
||||
border: none;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
|
@ -150,20 +150,20 @@
|
|||
border-bottom: 1px solid $blame-border;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
&.blame-commit {
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@
|
|||
.clear-search {
|
||||
width: 35px;
|
||||
background-color: $white-light;
|
||||
border: none;
|
||||
border: 0;
|
||||
outline: none;
|
||||
z-index: 1;
|
||||
|
||||
|
|
@ -418,7 +418,7 @@
|
|||
|
||||
.droplab-dropdown .dropdown-menu .filter-dropdown-item {
|
||||
.btn {
|
||||
border: none;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 8px 16px;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
z-index: 1000;
|
||||
margin-bottom: 0;
|
||||
min-height: $header-height;
|
||||
border: none;
|
||||
border: 0;
|
||||
border-bottom: 1px solid $border-color;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
|
@ -169,7 +169,7 @@
|
|||
|
||||
.navbar-collapse {
|
||||
flex: 0 0 auto;
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
padding: 0;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
|
|
@ -352,77 +352,7 @@
|
|||
|
||||
.header-user .dropdown-menu-nav,
|
||||
.header-new .dropdown-menu-nav {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 4px 8px 0;
|
||||
|
||||
form {
|
||||
height: 32px;
|
||||
border: 0;
|
||||
border-radius: $border-radius-default;
|
||||
transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
color: $white-light;
|
||||
background: none;
|
||||
transition: color ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
transition: color ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
font-size: 12px;
|
||||
margin: -4px 4px -4px -4px;
|
||||
line-height: 25px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 2px 0 0 2px;
|
||||
height: 32px;
|
||||
transition: border-color ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
&.search-active {
|
||||
form {
|
||||
background-color: rgba($indigo-200, .3);
|
||||
box-shadow: none;
|
||||
|
||||
.search-input {
|
||||
color: $gl-text-color;
|
||||
transition: color ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: $gl-text-color-tertiary;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
.search-icon,
|
||||
.clear-icon {
|
||||
color: $gl-text-color-tertiary;
|
||||
transition: color ease-in-out 0.15s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.location-badge {
|
||||
background-color: $nav-badge-bg;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
.clear-icon {
|
||||
color: $white-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
margin-top: $dropdown-vertical-offset;
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.file-content.code {
|
||||
border: none;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
pre {
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
font-family: $monospace_font;
|
||||
font-size: $code_font_size;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
|
||||
&.bottom {
|
||||
background: $gray-light;
|
||||
|
|
@ -92,7 +92,7 @@ ul.unstyled-list {
|
|||
}
|
||||
|
||||
ul.unstyled-list > li {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
// Generic content list
|
||||
|
|
@ -178,7 +178,7 @@ ul.content-list {
|
|||
|
||||
// When dragging a list item
|
||||
&.ui-sortable-helper {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&.list-placeholder {
|
||||
|
|
@ -295,7 +295,7 @@ ul.indent-list {
|
|||
}
|
||||
|
||||
> .group-list-tree > .group-row.has-children:first-child {
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -413,7 +413,7 @@ ul.indent-list {
|
|||
padding: 0;
|
||||
|
||||
&.has-children {
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
|
|
|
|||
|
|
@ -138,15 +138,23 @@
|
|||
|
||||
.toolbar-btn {
|
||||
float: left;
|
||||
padding: 0 5px;
|
||||
color: $gl-text-color-secondary;
|
||||
padding: 0 7px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-top: 3px;
|
||||
fill: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $gl-link-color;
|
||||
svg {
|
||||
fill: $gl-link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
margin: 0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
}
|
||||
|
||||
.modal-body {
|
||||
background-color: $modal-body-bg;
|
||||
position: relative;
|
||||
padding: #{3 * $grid-size} #{2 * $grid-size};
|
||||
|
||||
|
|
@ -42,3 +43,8 @@ body.modal-open {
|
|||
width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal.popup-dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
@media (min-width: $screen-md-min) {
|
||||
margin: 0;
|
||||
padding: $gl-padding 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid $white-normal;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
.nav-links {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
float: left;
|
||||
|
||||
&.wide {
|
||||
|
|
@ -335,69 +335,16 @@
|
|||
border-bottom: 1px solid $border-color;
|
||||
|
||||
.nav-links {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-with-layout-nav {
|
||||
.right-sidebar {
|
||||
top: ($header-height + 1) * 2;
|
||||
}
|
||||
|
||||
&.page-with-sub-nav {
|
||||
.right-sidebar {
|
||||
top: ($header-height + 1) * 3;
|
||||
|
||||
&.affix {
|
||||
top: $header-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.with-performance-bar .page-with-layout-nav {
|
||||
.right-sidebar {
|
||||
top: ($header-height + 1) * 2 + $performance-bar-height;
|
||||
}
|
||||
|
||||
&.page-with-sub-nav {
|
||||
.right-sidebar {
|
||||
top: ($header-height + 1) * 3 + $performance-bar-height;
|
||||
|
||||
&.affix {
|
||||
top: $header-height + $performance-bar-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.top-area {
|
||||
flex-flow: row wrap;
|
||||
|
||||
.nav-controls {
|
||||
$controls-margin: $btn-xs-side-margin - 2px;
|
||||
flex: 0 0 100%;
|
||||
|
||||
&.controls-flex {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 0 $gl-padding-top;
|
||||
}
|
||||
|
||||
.controls-item,
|
||||
.controls-item-full,
|
||||
.controls-item:last-child {
|
||||
flex: 1 1 35%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: $controls-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
.project-item-select-holder.btn-group {
|
||||
display: flex;
|
||||
max-width: 350px;
|
||||
overflow: hidden;
|
||||
float: right;
|
||||
|
||||
.new-project-item-link {
|
||||
white-space: nowrap;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
.select2-arrow {
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border: 0;
|
||||
padding-top: 12px;
|
||||
padding-right: 20px;
|
||||
font-size: 10px;
|
||||
|
|
@ -60,12 +60,17 @@
|
|||
border-radius: $border-radius-base;
|
||||
border: 1px solid $dropdown-border-color;
|
||||
min-width: 175px;
|
||||
color: $gl-grayish-blue;
|
||||
color: $gl-text-color;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label,
|
||||
.select2-more-results {
|
||||
padding: 10px 15px;
|
||||
.select2-drop-mask {
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above.select2-drop-active {
|
||||
border-top: 1px solid $dropdown-border-color;
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.select2-container-active {
|
||||
|
|
@ -158,18 +163,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-ajax-error,
|
||||
.select2-results .select2-selection-limit {
|
||||
background: $gray-light;
|
||||
display: list-item;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.select2-results {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
padding: #{$gl-padding / 2} 0;
|
||||
|
||||
.select2-no-results,
|
||||
.select2-searching,
|
||||
.select2-ajax-error,
|
||||
.select2-selection-limit {
|
||||
background: transparent;
|
||||
padding: #{$gl-padding / 2} $gl-padding;
|
||||
}
|
||||
|
||||
.select2-result-label,
|
||||
.select2-more-results {
|
||||
padding: #{$gl-padding / 2} $gl-padding;
|
||||
}
|
||||
|
||||
.select2-highlighted {
|
||||
background: transparent;
|
||||
color: $gl-text-color;
|
||||
|
||||
.select2-result-label {
|
||||
background: $dropdown-item-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-result {
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
|
|
@ -190,8 +212,6 @@
|
|||
}
|
||||
|
||||
.select2-highlighted {
|
||||
background: $gl-link-color !important;
|
||||
|
||||
.group-result {
|
||||
.group-path {
|
||||
color: $white-light;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
&.container-blank {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
}
|
||||
|
||||
.block:last-of-type {
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ table {
|
|||
th {
|
||||
background-color: $gray-light;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
|
||||
&.wide {
|
||||
width: 55%;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
|
||||
&.text-file .diff-file {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,5 +66,5 @@
|
|||
|
||||
.discussion .timeline-entry {
|
||||
margin: 0;
|
||||
border-right: none;
|
||||
border-right: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,3 +164,36 @@ $pre-border-color: $border-color;
|
|||
$table-bg-accent: $gray-light;
|
||||
|
||||
$zindex-popover: 900;
|
||||
|
||||
//== Modals
|
||||
//
|
||||
//##
|
||||
|
||||
//** Padding applied to the modal body
|
||||
$modal-inner-padding: $gl-padding;
|
||||
|
||||
//** Padding applied to the modal title
|
||||
$modal-title-padding: $gl-padding;
|
||||
//** Modal title line-height
|
||||
// $modal-title-line-height: $line-height-base
|
||||
|
||||
//** Background color of modal content area
|
||||
$modal-content-bg: $gray-light;
|
||||
$modal-body-bg: $white-light;
|
||||
//** Modal content border color
|
||||
// $modal-content-border-color: rgba(0,0,0,.2)
|
||||
//** Modal content border color **for IE8**
|
||||
// $modal-content-fallback-border-color: #999
|
||||
|
||||
//** Modal backdrop background color
|
||||
// $modal-backdrop-bg: #000
|
||||
//** Modal backdrop opacity
|
||||
// $modal-backdrop-opacity: .5
|
||||
//** Modal header border color
|
||||
// $modal-header-border-color: #e5e5e5
|
||||
//** Modal footer border color
|
||||
// $modal-footer-border-color: $modal-header-border-color
|
||||
|
||||
// $modal-lg: 900px
|
||||
// $modal-md: 600px
|
||||
// $modal-sm: 300px
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@
|
|||
|
||||
&.plain-readme {
|
||||
background: none;
|
||||
border: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
z-index: 1031;
|
||||
|
||||
textarea {
|
||||
border: none;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
color: $black;
|
||||
|
|
@ -57,7 +57,15 @@
|
|||
padding: 5px;
|
||||
font-size: 36px;
|
||||
|
||||
svg {
|
||||
fill: $gl-text-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $black;
|
||||
|
||||
svg {
|
||||
fill: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
|
||||
.bash {
|
||||
display: block;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
pre.commit-message {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
margin: 20px 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.commit-description {
|
||||
background: none;
|
||||
border: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
word-break: normal;
|
||||
|
|
@ -247,7 +247,7 @@
|
|||
word-break: normal;
|
||||
|
||||
pre {
|
||||
border: none;
|
||||
border: 0;
|
||||
background: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
.panel {
|
||||
.content-block {
|
||||
padding: 24px 0;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
|
|
@ -222,11 +222,11 @@
|
|||
}
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.stage-nav-item-cell {
|
||||
|
|
@ -290,7 +290,7 @@
|
|||
border-bottom: 1px solid $gray-darker;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
border-bottom: 1px solid $border-color;
|
||||
color: $gl-text-color;
|
||||
line-height: 34px;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
color: $gl-text-color;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
table {
|
||||
width: 100%;
|
||||
font-family: $monospace_font;
|
||||
border: none;
|
||||
border: 0;
|
||||
border-collapse: separate;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
.new_line {
|
||||
@include user-select(none);
|
||||
margin: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
padding: 0 5px;
|
||||
border-right: 1px solid;
|
||||
text-align: right;
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
display: block;
|
||||
margin: 0;
|
||||
padding: 0 1.5em;
|
||||
border: none;
|
||||
border: 0;
|
||||
position: relative;
|
||||
|
||||
&.parallel {
|
||||
|
|
@ -359,7 +359,7 @@
|
|||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
@ -388,7 +388,7 @@
|
|||
|
||||
.file-content .diff-file {
|
||||
margin: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.diff-wrap-lines .line_content {
|
||||
|
|
@ -400,7 +400,7 @@
|
|||
}
|
||||
|
||||
.files-changed {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.diff-stats-summary-toggler {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
border-top: 1px solid $border-color;
|
||||
border-right: 1px solid $border-color;
|
||||
border-left: 1px solid $border-color;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
border-radius: $border-radius-small $border-radius-small 0 0;
|
||||
background: $gray-normal;
|
||||
}
|
||||
|
||||
#editor {
|
||||
border: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
height: 500px;
|
||||
margin: 0;
|
||||
|
|
@ -171,7 +171,7 @@
|
|||
width: 100%;
|
||||
margin: 5px 0;
|
||||
padding: 0;
|
||||
border-left: none;
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@
|
|||
}
|
||||
|
||||
.no-btn {
|
||||
border: none;
|
||||
border: 0;
|
||||
background: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
|
|
@ -133,11 +133,11 @@
|
|||
}
|
||||
|
||||
.folder-row {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
|
||||
@media (min-width: $screen-sm-max) {
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,12 +256,6 @@
|
|||
padding: 0;
|
||||
padding-bottom: 100%;
|
||||
|
||||
.label-axis-text {
|
||||
fill: $black;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.text-metric-usage,
|
||||
.legend-metric-title {
|
||||
fill: $black;
|
||||
|
|
@ -276,19 +270,33 @@
|
|||
left: 0;
|
||||
top: 0;
|
||||
|
||||
.label-axis-text,
|
||||
.text-metric-usage {
|
||||
text {
|
||||
fill: $gl-text-color;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.text-metric-bold {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
}
|
||||
|
||||
.label-axis-text {
|
||||
fill: $black;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.legend-axis-text {
|
||||
fill: $black;
|
||||
}
|
||||
|
||||
.tick > text {
|
||||
font-size: 12px;
|
||||
.tick {
|
||||
> line {
|
||||
stroke: $gray-darker;
|
||||
}
|
||||
|
||||
> text {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-metric-title {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@
|
|||
}
|
||||
|
||||
pre {
|
||||
border: none;
|
||||
border: 0;
|
||||
background: $gray-light;
|
||||
border-radius: 0;
|
||||
color: $events-pre-color;
|
||||
|
|
@ -128,14 +128,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:last-child { border: none; }
|
||||
&:last-child { border: 0; }
|
||||
|
||||
.event_commits {
|
||||
li {
|
||||
&.commit {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
|
||||
.commit-row-title {
|
||||
font-size: $gl-font-size;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
.title {
|
||||
padding: 0;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
|
|
@ -131,12 +131,12 @@
|
|||
top: $header-height;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transition: width .3s;
|
||||
transition: width $right-sidebar-transition-duration;
|
||||
background: $gray-light;
|
||||
z-index: 200;
|
||||
overflow: hidden;
|
||||
|
||||
a,
|
||||
a:not(.btn-retry),
|
||||
.btn-link {
|
||||
color: inherit;
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
}
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
|
|
@ -338,7 +338,7 @@
|
|||
.block {
|
||||
width: $gutter_collapsed_width - 2px;
|
||||
padding: 15px 0 0;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
@ -399,7 +399,7 @@
|
|||
}
|
||||
|
||||
.btn-clipboard {
|
||||
border: none;
|
||||
border: 0;
|
||||
color: $issuable-sidebar-color;
|
||||
|
||||
&:hover {
|
||||
|
|
@ -613,6 +613,8 @@
|
|||
float: none;
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
height: auto;
|
||||
align-self: center;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
position: absolute;
|
||||
|
|
@ -626,6 +628,8 @@
|
|||
padding-left: 45px;
|
||||
padding-right: 45px;
|
||||
line-height: 35px;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: left;
|
||||
|
|
@ -637,11 +641,12 @@
|
|||
.issuable-actions {
|
||||
@include new-style-dropdown;
|
||||
|
||||
padding-top: 10px;
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -655,8 +660,9 @@
|
|||
|
||||
.issuable-meta {
|
||||
display: inline-block;
|
||||
line-height: 18px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.js-issuable-selector-wrap {
|
||||
|
|
|
|||
|
|
@ -134,11 +134,24 @@ ul.related-merge-requests > li {
|
|||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.issue-btn-group {
|
||||
width: 100%;
|
||||
.detail-page-header,
|
||||
.issuable-header {
|
||||
display: block;
|
||||
|
||||
.btn {
|
||||
.issuable-meta {
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-actions {
|
||||
margin-top: 10px;
|
||||
|
||||
.issue-btn-group {
|
||||
width: 100%;
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@
|
|||
border-left: 1px solid $border-color;
|
||||
|
||||
&:first-of-type {
|
||||
border-left: none;
|
||||
border-left: 0;
|
||||
border-top-left-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@
|
|||
border-bottom: 1px solid $border-color;
|
||||
|
||||
a {
|
||||
border: none;
|
||||
border: 0;
|
||||
border-bottom: 2px solid $link-underline-blue;
|
||||
margin-right: 0;
|
||||
color: $black;
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ $colors: (
|
|||
.editor {
|
||||
pre {
|
||||
height: 350px;
|
||||
border: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,18 +150,6 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
.mr-widget-body {
|
||||
@include clearfix;
|
||||
|
||||
&.media > *:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.approve-btn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.mr-widget-pipeline-graph {
|
||||
padding: 0 4px;
|
||||
|
||||
|
|
@ -169,9 +157,8 @@
|
|||
z-index: 300;
|
||||
}
|
||||
|
||||
.ci-action-icon-wrapper svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
.ci-action-icon-wrapper {
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,10 +182,6 @@
|
|||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
|
||||
&.media > *:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.label-truncated {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
|
@ -216,6 +199,18 @@
|
|||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mr-widget-body {
|
||||
@include clearfix;
|
||||
|
||||
&.media > *:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.approve-btn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
float: left;
|
||||
|
|
@ -239,10 +234,6 @@
|
|||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.approve-btn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: $gl-font-weight-normal;
|
||||
}
|
||||
|
|
@ -342,17 +333,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.mini-pipeline-graph-dropdown-menu .mini-pipeline-graph-dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ci-status-text,
|
||||
.ci-status-icon {
|
||||
top: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.mr-widget-help {
|
||||
padding: 10px 16px 10px 48px;
|
||||
font-style: italic;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
.discussion {
|
||||
.new-note {
|
||||
margin: 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,15 +106,35 @@
|
|||
background-color: $orange-100;
|
||||
border-radius: $border-radius-default $border-radius-default 0 0;
|
||||
border: 1px solid $border-gray-normal;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
padding: 3px 12px;
|
||||
margin: auto;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
margin-right: $issuable-warning-icon-margin;
|
||||
}
|
||||
|
||||
+ .md-area {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.disabled-comment {
|
||||
border: 0;
|
||||
border-radius: $label-border-radius;
|
||||
padding-top: $gl-vert-padding;
|
||||
padding-bottom: $gl-vert-padding;
|
||||
|
||||
.icon svg {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: $btn-xs-side-margin;
|
||||
width: $gl-font-size;
|
||||
height: $gl-font-size;
|
||||
fill: $orange-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-item-value {
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ ul.notes {
|
|||
|
||||
td {
|
||||
border: 1px solid $white-normal;
|
||||
border-left: none;
|
||||
border-left: 0;
|
||||
|
||||
&.notes_line {
|
||||
vertical-align: middle;
|
||||
|
|
@ -476,6 +476,10 @@ ul.notes {
|
|||
float: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.btn-group > .discussion-next-btn {
|
||||
margin-left: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-actions {
|
||||
|
|
@ -666,7 +670,7 @@ ul.notes {
|
|||
.timeline-entry-inner {
|
||||
padding-left: $gl-padding;
|
||||
padding-right: $gl-padding;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -679,7 +683,7 @@ ul.notes {
|
|||
padding: 90px 0;
|
||||
|
||||
&.discussion-locked {
|
||||
border: none;
|
||||
border: 0;
|
||||
background-color: $white-light;
|
||||
}
|
||||
|
||||
|
|
@ -759,7 +763,7 @@ ul.notes {
|
|||
top: 0;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
color: $gray-darkest;
|
||||
transition: color $general-hover-transition-duration $general-hover-transition-curve;
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@
|
|||
* Play button with icon in dropdowns
|
||||
*/
|
||||
.no-btn {
|
||||
border: none;
|
||||
border: 0;
|
||||
background: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
|
|
@ -288,7 +288,7 @@
|
|||
.pipeline-actions {
|
||||
@include new-style-dropdown;
|
||||
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
|
|
@ -318,7 +318,7 @@
|
|||
}
|
||||
|
||||
.build-log {
|
||||
border: none;
|
||||
border: 0;
|
||||
line-height: initial;
|
||||
}
|
||||
}
|
||||
|
|
@ -386,13 +386,13 @@
|
|||
// Remove right connecting horizontal line from first build in last stage
|
||||
&:first-child {
|
||||
&::after {
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
// Remove right curved connectors from all builds in last stage
|
||||
&:not(:first-child) {
|
||||
&::after {
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
// Remove opposite curve
|
||||
|
|
@ -409,7 +409,7 @@
|
|||
// Remove left curved connectors from all builds in first stage
|
||||
&:not(:first-child) {
|
||||
&::before {
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
// Remove opposite curve
|
||||
|
|
@ -518,7 +518,7 @@
|
|||
|
||||
.dropdown-menu-toggle {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
&:focus {
|
||||
|
|
@ -823,6 +823,11 @@ button.mini-pipeline-graph-dropdown-toggle {
|
|||
margin-left: 2px;
|
||||
display: inline-block;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
max-width: 60%;
|
||||
}
|
||||
|
|
@ -951,7 +956,7 @@ button.mini-pipeline-graph-dropdown-toggle {
|
|||
|
||||
.terminal-container {
|
||||
.content-block {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
#terminal {
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@
|
|||
|
||||
li {
|
||||
padding: 3px 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
|
||||
.project-feature-settings {
|
||||
background: $gray-lighter;
|
||||
border-top: none;
|
||||
border-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +128,7 @@
|
|||
|
||||
.project-feature-toggle {
|
||||
position: relative;
|
||||
border: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
display: block;
|
||||
width: 100px;
|
||||
|
|
@ -483,7 +483,7 @@ a.deploy-project-label {
|
|||
flex: 1;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border: 0;
|
||||
line-height: 34px;
|
||||
margin: 0;
|
||||
|
||||
|
|
@ -1012,7 +1012,7 @@ pre.light-well {
|
|||
margin: 0;
|
||||
border-radius: 0 0 1px 1px;
|
||||
padding: 20px 0;
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.table-bordered {
|
||||
|
|
@ -1165,7 +1165,7 @@ pre.light-well {
|
|||
table-layout: fixed;
|
||||
|
||||
&.table-responsive {
|
||||
border: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.variable-key {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
.monaco-editor.vs {
|
||||
.current-line {
|
||||
border: none;
|
||||
border: 0;
|
||||
background: $well-light-border;
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +139,7 @@
|
|||
|
||||
&.active {
|
||||
background: $white-light;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
&.tabs-divider {
|
||||
width: 100%;
|
||||
background-color: $white-light;
|
||||
border-right: none;
|
||||
border-right: 0;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
margin-bottom: $gl-padding;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ input[type="checkbox"]:hover {
|
|||
}
|
||||
|
||||
.search-input {
|
||||
border: none;
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
padding: 0 20px 0 0;
|
||||
margin-left: 5px;
|
||||
|
|
@ -78,10 +78,6 @@ input[type="checkbox"]:hover {
|
|||
}
|
||||
|
||||
.search-input-wrap {
|
||||
// Fallback if flexbox is not supported
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
.search-icon,
|
||||
.clear-icon {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@
|
|||
}
|
||||
|
||||
pre {
|
||||
border: none;
|
||||
border: 0;
|
||||
background: $gray-light;
|
||||
border-radius: 0;
|
||||
color: $todo-body-pre-color;
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@
|
|||
margin-top: 20px;
|
||||
padding: 0;
|
||||
border-top: 1px solid $white-dark;
|
||||
border-bottom: none;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.commit-stats li {
|
||||
|
|
|
|||
|
|
@ -57,12 +57,11 @@ module IssuableActions
|
|||
|
||||
def destroy
|
||||
issuable.destroy
|
||||
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
|
||||
TodoService.new.public_send(destroy_method, issuable, current_user) # rubocop:disable GitlabSecurity/PublicSend
|
||||
TodoService.new.destroy_issuable(issuable, current_user)
|
||||
|
||||
name = issuable.human_class_name
|
||||
flash[:notice] = "The #{name} was successfully deleted."
|
||||
index_path = polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
|
||||
index_path = polymorphic_path([parent, issuable.class])
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to index_path }
|
||||
|
|
@ -164,4 +163,8 @@ module IssuableActions
|
|||
def update_service
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def parent
|
||||
@project || @group
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ module NotesActions
|
|||
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Banzai::NoteRenderer.render([@note], @project, current_user)
|
||||
Notes::RenderService.new(current_user).execute([@note], @project)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
@ -52,7 +52,7 @@ module NotesActions
|
|||
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Banzai::NoteRenderer.render([@note], @project, current_user)
|
||||
Notes::RenderService.new(current_user).execute([@note], @project)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ module RendersNotes
|
|||
preload_noteable_for_regular_notes(notes)
|
||||
preload_max_access_for_authors(notes, @project)
|
||||
preload_first_time_contribution_for_authors(noteable, notes)
|
||||
Banzai::NoteRenderer.render(notes, @project, current_user)
|
||||
Notes::RenderService.new(current_user).execute(notes, @project)
|
||||
|
||||
notes
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,5 +57,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
|
|||
@events = EventCollection
|
||||
.new(projects, offset: params[:offset].to_i, filter: event_filter)
|
||||
.to_a
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ class DashboardController < Dashboard::ApplicationController
|
|||
@events = EventCollection
|
||||
.new(projects, offset: params[:offset].to_i, filter: @event_filter)
|
||||
.to_a
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events)
|
||||
end
|
||||
|
||||
def set_show_full_reference
|
||||
|
|
|
|||
|
|
@ -155,6 +155,8 @@ class GroupsController < Groups::ApplicationController
|
|||
@events = EventCollection
|
||||
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
|
||||
.to_a
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
|
||||
end
|
||||
|
||||
def user_actions
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@ class MetricsController < ActionController::Base
|
|||
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
before_action :validate_prometheus_metrics
|
||||
|
||||
def index
|
||||
render text: metrics_service.metrics_text, content_type: 'text/plain; version=0.0.4'
|
||||
response = if Gitlab::Metrics.prometheus_metrics_enabled?
|
||||
metrics_service.metrics_text
|
||||
else
|
||||
help_page = help_page_url('administration/monitoring/prometheus/gitlab_metrics',
|
||||
anchor: 'gitlab-prometheus-metrics'
|
||||
)
|
||||
"# Metrics are disabled, see: #{help_page}\n"
|
||||
end
|
||||
render text: response, content_type: 'text/plain; version=0.0.4'
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -14,8 +20,4 @@ class MetricsController < ActionController::Base
|
|||
def metrics_service
|
||||
@metrics_service ||= MetricsService.new
|
||||
end
|
||||
|
||||
def validate_prometheus_metrics
|
||||
render_404 unless Gitlab::Metrics.prometheus_metrics_enabled?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
class Projects::ClustersController < Projects::ApplicationController
|
||||
before_action :cluster, except: [:login, :index, :new, :create]
|
||||
before_action :cluster, except: [:login, :index, :new, :new_gcp, :create]
|
||||
before_action :authorize_read_cluster!
|
||||
before_action :authorize_create_cluster!, only: [:new, :create]
|
||||
before_action :authorize_google_api, only: [:new, :create]
|
||||
before_action :authorize_create_cluster!, only: [:new, :new_gcp, :create]
|
||||
before_action :authorize_google_api, only: [:new_gcp, :create]
|
||||
before_action :authorize_update_cluster!, only: [:update]
|
||||
before_action :authorize_admin_cluster!, only: [:destroy]
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
|
||||
def login
|
||||
begin
|
||||
state = generate_session_key_redirect(namespace_project_clusters_url.to_s)
|
||||
state = generate_session_key_redirect(providers_gcp_new_namespace_project_clusters_url.to_s)
|
||||
|
||||
@authorize_url = GoogleApi::CloudPlatform::Client.new(
|
||||
nil, callback_google_api_auth_url,
|
||||
|
|
@ -27,18 +27,23 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@cluster = project.build_cluster
|
||||
end
|
||||
|
||||
def new_gcp
|
||||
@cluster = Clusters::Cluster.new.tap do |cluster|
|
||||
cluster.build_provider_gcp
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@cluster = Ci::CreateClusterService
|
||||
@cluster = Clusters::CreateService
|
||||
.new(project, current_user, create_params)
|
||||
.execute(token_in_session)
|
||||
|
||||
if @cluster.persisted?
|
||||
redirect_to project_cluster_path(project, @cluster)
|
||||
else
|
||||
render :new
|
||||
render :new_gcp
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -58,7 +63,7 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
Ci::UpdateClusterService
|
||||
Clusters::UpdateService
|
||||
.new(project, current_user, update_params)
|
||||
.execute(cluster)
|
||||
|
||||
|
|
@ -88,19 +93,19 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
|
||||
def create_params
|
||||
params.require(:cluster).permit(
|
||||
:gcp_project_id,
|
||||
:gcp_cluster_zone,
|
||||
:gcp_cluster_name,
|
||||
:gcp_cluster_size,
|
||||
:gcp_machine_type,
|
||||
:project_namespace,
|
||||
:enabled)
|
||||
:enabled,
|
||||
:name,
|
||||
:provider_type,
|
||||
provider_gcp_attributes: [
|
||||
:gcp_project_id,
|
||||
:zone,
|
||||
:num_nodes,
|
||||
:machine_type
|
||||
])
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:cluster).permit(
|
||||
:project_namespace,
|
||||
:enabled)
|
||||
params.require(:cluster).permit(:enabled)
|
||||
end
|
||||
|
||||
def authorize_google_api
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
|
|||
before_action :check_merge_requests_available!
|
||||
before_action :merge_request
|
||||
before_action :authorize_read_merge_request!
|
||||
before_action :ensure_ref_fetched
|
||||
|
||||
private
|
||||
|
||||
|
|
@ -10,12 +9,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
|
|||
@issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
|
||||
end
|
||||
|
||||
# Make sure merge requests created before 8.0
|
||||
# have head file in refs/merge-requests/
|
||||
def ensure_ref_fetched
|
||||
@merge_request.ensure_ref_fetched if Gitlab::Database.read_write?
|
||||
end
|
||||
|
||||
def merge_request_params
|
||||
params.require(:merge_request).permit(merge_request_params_attributes)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
|
|||
include RendersCommits
|
||||
|
||||
skip_before_action :merge_request
|
||||
skip_before_action :ensure_ref_fetched
|
||||
before_action :authorize_create_merge_request!
|
||||
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
|
||||
before_action :build_merge_request, except: [:create]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
include IssuableCollections
|
||||
|
||||
skip_before_action :merge_request, only: [:index, :bulk_update]
|
||||
skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
|
||||
|
||||
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
|
||||
|
||||
|
|
@ -52,7 +51,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
|
||||
def show
|
||||
validates_merge_request
|
||||
ensure_ref_fetched
|
||||
close_merge_request_without_source_project
|
||||
check_if_can_be_merged
|
||||
|
||||
|
|
|
|||
|
|
@ -300,6 +300,8 @@ class ProjectsController < Projects::ApplicationController
|
|||
@events = EventCollection
|
||||
.new(projects, offset: params[:offset].to_i, filter: event_filter)
|
||||
.to_a
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
|
||||
end
|
||||
|
||||
def project_params
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ class UsersController < ApplicationController
|
|||
.references(:project)
|
||||
.with_associations
|
||||
.limit_recent(20, params[:offset])
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
|
||||
end
|
||||
|
||||
def load_projects
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
# Anonymous users will never return any `owned` groups. They will return all
|
||||
# public groups instead, even if `all_available` is set to false.
|
||||
class GroupsFinder < UnionFinder
|
||||
include CustomAttributesFilter
|
||||
|
||||
def initialize(current_user = nil, params = {})
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
|
|
@ -22,8 +24,12 @@ class GroupsFinder < UnionFinder
|
|||
|
||||
def execute
|
||||
items = all_groups.map do |item|
|
||||
by_parent(item)
|
||||
item = by_parent(item)
|
||||
item = by_custom_attributes(item)
|
||||
|
||||
item
|
||||
end
|
||||
|
||||
find_union(items, Group).with_route.order_id_desc
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
# non_archived: boolean
|
||||
#
|
||||
class ProjectsFinder < UnionFinder
|
||||
include CustomAttributesFilter
|
||||
|
||||
attr_accessor :params
|
||||
attr_reader :current_user, :project_ids_relation
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ class ProjectsFinder < UnionFinder
|
|||
collection = by_tags(collection)
|
||||
collection = by_search(collection)
|
||||
collection = by_archived(collection)
|
||||
collection = by_custom_attributes(collection)
|
||||
|
||||
sort(collection)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,17 +20,6 @@ module BoardsHelper
|
|||
project_issues_path(@project)
|
||||
end
|
||||
|
||||
def current_board_json
|
||||
board = @board || @boards.first
|
||||
|
||||
board.to_json(
|
||||
only: [:id, :name, :milestone_id],
|
||||
include: {
|
||||
milestone: { only: [:title] }
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def board_base_url
|
||||
project_boards_path(@project)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -172,16 +172,6 @@ module EventsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def event_note(text, options = {})
|
||||
text = first_line_in_markdown(text, 150, options)
|
||||
|
||||
sanitize(
|
||||
text,
|
||||
tags: %w(a img gl-emoji b pre code p span),
|
||||
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
|
||||
)
|
||||
end
|
||||
|
||||
def event_commit_title(message)
|
||||
message ||= ''
|
||||
(message.split("\n").first || "").truncate(70)
|
||||
|
|
|
|||
|
|
@ -69,10 +69,16 @@ module MarkupHelper
|
|||
# as Markdown. HTML tags in the parsed output are not counted toward the
|
||||
# +max_chars+ limit. If the length limit falls within a tag's contents, then
|
||||
# the tag contents are truncated without removing the closing tag.
|
||||
def first_line_in_markdown(text, max_chars = nil, options = {})
|
||||
md = markdown(text, options).strip
|
||||
def first_line_in_markdown(object, attribute, max_chars = nil, options = {})
|
||||
md = markdown_field(object, attribute, options)
|
||||
|
||||
truncate_visible(md, max_chars || md.length) if md.present?
|
||||
text = truncate_visible(md, max_chars || md.length) if md.present?
|
||||
|
||||
sanitize(
|
||||
text,
|
||||
tags: %w(a img gl-emoji b pre code p span),
|
||||
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
|
||||
)
|
||||
end
|
||||
|
||||
def markdown(text, context = {})
|
||||
|
|
@ -83,15 +89,17 @@ module MarkupHelper
|
|||
prepare_for_rendering(html, context)
|
||||
end
|
||||
|
||||
def markdown_field(object, field)
|
||||
def markdown_field(object, field, context = {})
|
||||
object = object.for_display if object.respond_to?(:for_display)
|
||||
redacted_field_html = object.try(:"redacted_#{field}_html")
|
||||
|
||||
return '' unless object.present?
|
||||
return redacted_field_html if redacted_field_html
|
||||
|
||||
html = Banzai.render_field(object, field)
|
||||
prepare_for_rendering(html, object.banzai_render_context(field))
|
||||
html = Banzai.render_field(object, field, context)
|
||||
context.reverse_merge!(object.banzai_render_context(field)) if object.respond_to?(:banzai_render_context)
|
||||
|
||||
prepare_for_rendering(html, context)
|
||||
end
|
||||
|
||||
def markup(file_name, text, context = {})
|
||||
|
|
@ -218,7 +226,7 @@ module MarkupHelper
|
|||
data: data,
|
||||
title: options[:title],
|
||||
aria: { label: options[:title] } do
|
||||
icon(options[:icon])
|
||||
sprite_icon(options[:icon])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
module Clusters
|
||||
class Cluster < ActiveRecord::Base
|
||||
include Presentable
|
||||
|
||||
self.table_name = 'clusters'
|
||||
|
||||
belongs_to :user
|
||||
|
||||
has_many :cluster_projects, class_name: 'Clusters::Project'
|
||||
has_many :projects, through: :cluster_projects, class_name: '::Project'
|
||||
|
||||
# we force autosave to happen when we save `Cluster` model
|
||||
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
|
||||
|
||||
# We have to ":destroy" it today to ensure that we clean also the Kubernetes Integration
|
||||
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
accepts_nested_attributes_for :provider_gcp, update_only: true
|
||||
accepts_nested_attributes_for :platform_kubernetes, update_only: true
|
||||
|
||||
validates :name, cluster_name: true
|
||||
validate :restrict_modification, on: :update
|
||||
|
||||
# TODO: Move back this into Clusters::Platforms::Kubernetes in 10.3
|
||||
# We need callback here because `enabled` belongs to Clusters::Cluster
|
||||
# Callbacks in Clusters::Platforms::Kubernetes will not be called after update
|
||||
after_save :update_kubernetes_integration!
|
||||
|
||||
delegate :status, to: :provider, allow_nil: true
|
||||
delegate :status_reason, to: :provider, allow_nil: true
|
||||
delegate :status_name, to: :provider, allow_nil: true
|
||||
delegate :on_creation?, to: :provider, allow_nil: true
|
||||
delegate :update_kubernetes_integration!, to: :platform, allow_nil: true
|
||||
|
||||
enum platform_type: {
|
||||
kubernetes: 1
|
||||
}
|
||||
|
||||
enum provider_type: {
|
||||
user: 0,
|
||||
gcp: 1
|
||||
}
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
scope :disabled, -> { where(enabled: false) }
|
||||
|
||||
def provider
|
||||
return provider_gcp if gcp?
|
||||
end
|
||||
|
||||
def platform
|
||||
return platform_kubernetes if kubernetes?
|
||||
end
|
||||
|
||||
def first_project
|
||||
return @first_project if defined?(@first_project)
|
||||
|
||||
@first_project = projects.first
|
||||
end
|
||||
alias_method :project, :first_project
|
||||
|
||||
private
|
||||
|
||||
def restrict_modification
|
||||
if provider&.on_creation?
|
||||
errors.add(:base, "cannot modify during creation")
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
module Clusters
|
||||
module Platforms
|
||||
class Kubernetes < ActiveRecord::Base
|
||||
self.table_name = 'cluster_platforms_kubernetes'
|
||||
|
||||
belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster'
|
||||
|
||||
attr_encrypted :password,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
attr_encrypted :token,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
before_validation :enforce_namespace_to_lower_case
|
||||
|
||||
validates :namespace,
|
||||
allow_blank: true,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
|
||||
validates :api_url, url: true, presence: true
|
||||
validates :token, presence: true
|
||||
|
||||
# TODO: Glue code till we migrate Kubernetes Integration into Platforms::Kubernetes
|
||||
after_destroy :destroy_kubernetes_integration!
|
||||
|
||||
alias_attribute :ca_pem, :ca_cert
|
||||
|
||||
delegate :project, to: :cluster, allow_nil: true
|
||||
delegate :enabled?, to: :cluster, allow_nil: true
|
||||
|
||||
class << self
|
||||
def namespace_for_project(project)
|
||||
"#{project.path}-#{project.id}"
|
||||
end
|
||||
end
|
||||
|
||||
def actual_namespace
|
||||
if namespace.present?
|
||||
namespace
|
||||
else
|
||||
default_namespace
|
||||
end
|
||||
end
|
||||
|
||||
def default_namespace
|
||||
self.class.namespace_for_project(project) if project
|
||||
end
|
||||
|
||||
def update_kubernetes_integration!
|
||||
raise 'Kubernetes service already configured' unless manages_kubernetes_service?
|
||||
|
||||
# This is neccesary, otheriwse enabled? returns true even though cluster updated with enabled: false
|
||||
cluster.reload
|
||||
|
||||
ensure_kubernetes_service&.update!(
|
||||
active: enabled?,
|
||||
api_url: api_url,
|
||||
namespace: namespace,
|
||||
token: token,
|
||||
ca_pem: ca_cert
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enforce_namespace_to_lower_case
|
||||
self.namespace = self.namespace&.downcase
|
||||
end
|
||||
|
||||
# TODO: glue code till we migrate Kubernetes Service into Platforms::Kubernetes class
|
||||
def manages_kubernetes_service?
|
||||
return true unless kubernetes_service&.active?
|
||||
|
||||
kubernetes_service.api_url == api_url
|
||||
end
|
||||
|
||||
def destroy_kubernetes_integration!
|
||||
return unless manages_kubernetes_service?
|
||||
|
||||
kubernetes_service&.destroy!
|
||||
end
|
||||
|
||||
def kubernetes_service
|
||||
@kubernetes_service ||= project&.kubernetes_service
|
||||
end
|
||||
|
||||
def ensure_kubernetes_service
|
||||
@kubernetes_service ||= kubernetes_service || project&.build_kubernetes_service
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
module Clusters
|
||||
class Project < ActiveRecord::Base
|
||||
self.table_name = 'cluster_projects'
|
||||
|
||||
belongs_to :cluster, class_name: 'Clusters::Cluster'
|
||||
belongs_to :project, class_name: '::Project'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
module Clusters
|
||||
module Providers
|
||||
class Gcp < ActiveRecord::Base
|
||||
self.table_name = 'cluster_providers_gcp'
|
||||
|
||||
belongs_to :cluster, inverse_of: :provider_gcp, class_name: 'Clusters::Cluster'
|
||||
|
||||
default_value_for :zone, 'us-central1-a'
|
||||
default_value_for :num_nodes, 3
|
||||
default_value_for :machine_type, 'n1-standard-4'
|
||||
|
||||
attr_encrypted :access_token,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
validates :gcp_project_id,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
validates :zone, presence: true
|
||||
|
||||
validates :num_nodes,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than: 0
|
||||
}
|
||||
|
||||
state_machine :status, initial: :scheduled do
|
||||
state :scheduled, value: 1
|
||||
state :creating, value: 2
|
||||
state :created, value: 3
|
||||
state :errored, value: 4
|
||||
|
||||
event :make_creating do
|
||||
transition any - [:creating] => :creating
|
||||
end
|
||||
|
||||
event :make_created do
|
||||
transition any - [:created] => :created
|
||||
end
|
||||
|
||||
event :make_errored do
|
||||
transition any - [:errored] => :errored
|
||||
end
|
||||
|
||||
before_transition any => [:errored, :created] do |provider|
|
||||
provider.access_token = nil
|
||||
provider.operation_id = nil
|
||||
end
|
||||
|
||||
before_transition any => [:creating] do |provider, transition|
|
||||
operation_id = transition.args.first
|
||||
raise ArgumentError.new('operation_id is required') unless operation_id.present?
|
||||
provider.operation_id = operation_id
|
||||
end
|
||||
|
||||
before_transition any => [:errored] do |provider, transition|
|
||||
status_reason = transition.args.first
|
||||
provider.status_reason = status_reason if status_reason
|
||||
end
|
||||
end
|
||||
|
||||
def on_creation?
|
||||
scheduled? || creating?
|
||||
end
|
||||
|
||||
def api_client
|
||||
return unless access_token
|
||||
|
||||
@api_client ||= GoogleApi::CloudPlatform::Client.new(access_token, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -21,8 +21,8 @@ module IgnorableColumn
|
|||
@ignored_columns ||= Set.new
|
||||
end
|
||||
|
||||
def ignore_column(name)
|
||||
ignored_columns << name.to_s
|
||||
def ignore_column(*names)
|
||||
ignored_columns.merge(names.map(&:to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
module Gcp
|
||||
class Cluster < ActiveRecord::Base
|
||||
extend Gitlab::Gcp::Model
|
||||
include Presentable
|
||||
|
||||
belongs_to :project, inverse_of: :cluster
|
||||
belongs_to :user
|
||||
belongs_to :service
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
scope :disabled, -> { where(enabled: false) }
|
||||
|
||||
default_value_for :gcp_cluster_zone, 'us-central1-a'
|
||||
default_value_for :gcp_cluster_size, 3
|
||||
default_value_for :gcp_machine_type, 'n1-standard-4'
|
||||
|
||||
attr_encrypted :password,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
attr_encrypted :kubernetes_token,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
attr_encrypted :gcp_token,
|
||||
mode: :per_attribute_iv,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
validates :gcp_project_id,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
validates :gcp_cluster_name,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
validates :gcp_cluster_zone, presence: true
|
||||
|
||||
validates :gcp_cluster_size,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than: 0
|
||||
}
|
||||
|
||||
validates :project_namespace,
|
||||
allow_blank: true,
|
||||
length: 1..63,
|
||||
format: {
|
||||
with: Gitlab::Regex.kubernetes_namespace_regex,
|
||||
message: Gitlab::Regex.kubernetes_namespace_regex_message
|
||||
}
|
||||
|
||||
# if we do not do status transition we prevent change
|
||||
validate :restrict_modification, on: :update, unless: :status_changed?
|
||||
|
||||
state_machine :status, initial: :scheduled do
|
||||
state :scheduled, value: 1
|
||||
state :creating, value: 2
|
||||
state :created, value: 3
|
||||
state :errored, value: 4
|
||||
|
||||
event :make_creating do
|
||||
transition any - [:creating] => :creating
|
||||
end
|
||||
|
||||
event :make_created do
|
||||
transition any - [:created] => :created
|
||||
end
|
||||
|
||||
event :make_errored do
|
||||
transition any - [:errored] => :errored
|
||||
end
|
||||
|
||||
before_transition any => [:errored, :created] do |cluster|
|
||||
cluster.gcp_token = nil
|
||||
cluster.gcp_operation_id = nil
|
||||
end
|
||||
|
||||
before_transition any => [:errored] do |cluster, transition|
|
||||
status_reason = transition.args.first
|
||||
cluster.status_reason = status_reason if status_reason
|
||||
end
|
||||
end
|
||||
|
||||
def project_namespace_placeholder
|
||||
"#{project.path}-#{project.id}"
|
||||
end
|
||||
|
||||
def on_creation?
|
||||
scheduled? || creating?
|
||||
end
|
||||
|
||||
def api_url
|
||||
'https://' + endpoint if endpoint
|
||||
end
|
||||
|
||||
def restrict_modification
|
||||
if on_creation?
|
||||
errors.add(:base, "cannot modify during creation")
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -26,6 +26,7 @@ class Group < Namespace
|
|||
has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :labels, class_name: 'GroupLabel'
|
||||
has_many :variables, class_name: 'Ci::GroupVariable'
|
||||
has_many :custom_attributes, class_name: 'GroupCustomAttribute'
|
||||
|
||||
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
|
||||
validate :visibility_level_allowed_by_projects
|
||||
|
|
|
|||