Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-11 15:08:10 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-11 15:08:10 +0300
commitb86f474bf51e20d2db4cf0895d0a8e0894e31c08 (patch)
tree061d2a4c749924f5a35fe6199dd1d8982c4b0b27 /app
parent6b8040dc25fdc5fe614c3796a147517dd50bc7d8 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue22
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue2
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue2
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js2
-rw-r--r--app/assets/javascripts/notes.js4
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue2
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue2
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue2
-rw-r--r--app/assets/javascripts/notes/stores/actions.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue138
-rw-r--r--app/controllers/admin/sessions_controller.rb29
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/concerns/enforces_admin_authentication.rb1
-rw-r--r--app/controllers/concerns/initializes_current_user_mode.rb13
-rw-r--r--app/controllers/concerns/sessionless_authentication.rb4
-rw-r--r--app/controllers/oauth/applications_controller.rb1
-rw-r--r--app/controllers/oauth/authorizations_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb25
-rw-r--r--app/graphql/types/project_type.rb2
-rw-r--r--app/helpers/container_expiration_policies_helper.rb21
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/models/container_expiration_policy.rb41
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/project.rb7
-rw-r--r--app/models/user.rb4
-rw-r--r--app/views/admin/sessions/_signin_box.html.haml11
-rw-r--r--app/views/admin/sessions/new.html.haml15
39 files changed, 241 insertions, 157 deletions
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 7a6ad3dc771..dd300b8a307 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -4,6 +4,7 @@ import 'core-js/es/array/find';
import 'core-js/es/array/find-index';
import 'core-js/es/array/from';
import 'core-js/es/array/includes';
+import 'core-js/es/number/is-integer';
import 'core-js/es/object/assign';
import 'core-js/es/object/values';
import 'core-js/es/object/entries';
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue
index 67ca419bf81..2f7fcfcb755 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue
@@ -1,4 +1,5 @@
<script>
+import $ from 'jquery';
import { GlIcon } from '@gitlab/ui';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
@@ -106,6 +107,7 @@ export default {
data() {
return {
searchQuery: '',
+ focusOnSearch: false,
};
},
computed: {
@@ -141,6 +143,18 @@ export default {
return itemsProp(this.selectedItems, this.valueProperty).join(', ');
},
},
+ mounted() {
+ $(this.$refs.dropdown)
+ .on('shown.bs.dropdown', () => {
+ this.focusOnSearch = true;
+ })
+ .on('hidden.bs.dropdown', () => {
+ this.focusOnSearch = false;
+ });
+ },
+ beforeDestroy() {
+ $(this.$refs.dropdown).off();
+ },
methods: {
getItemsOrEmptyList() {
return this.items || [];
@@ -170,7 +184,7 @@ export default {
<template>
<div>
- <div class="js-gcp-machine-type-dropdown dropdown">
+ <div ref="dropdown" class="dropdown">
<dropdown-hidden-input :name="fieldName" :value="selectedItemsValues" />
<dropdown-button
:class="{ 'border-danger': hasErrors }"
@@ -179,7 +193,11 @@ export default {
:toggle-text="toggleText"
/>
<div class="dropdown-menu dropdown-select">
- <dropdown-search-input v-model="searchQuery" :placeholder-text="searchFieldPlaceholder" />
+ <dropdown-search-input
+ v-model="searchQuery"
+ :focused="focusOnSearch"
+ :placeholder-text="searchFieldPlaceholder"
+ />
<div class="dropdown-content">
<ul>
<li v-if="!results.length">
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 06477477aad..1df57f1aa14 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -1,8 +1,8 @@
<script>
-import { __, sprintf } from '~/locale';
import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import { GlLink, GlButton } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import Icon from '~/vue_shared/components/icon.vue';
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index 922f64d93fe..5edb8ff555b 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -1,6 +1,6 @@
<script>
-import { __ } from '~/locale';
import { GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
const HIDDEN_VALUE = '••••••';
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index ed01d0ee553..0d442f14aea 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -1,9 +1,9 @@
<script>
-import { s__, __ } from '~/locale';
import _ from 'underscore';
import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
+import { s__, __ } from '~/locale';
import { roundOffFloat } from '~/lib/utils/common_utils';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue';
diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue
index 0388a6190d9..c3beae18726 100644
--- a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue
+++ b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue
@@ -1,7 +1,7 @@
<script>
import _ from 'underscore';
-import { s__, sprintf } from '~/locale';
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
import { dateFormats } from '~/monitoring/constants';
const inputGroupText = {
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index dae1fbad547..86f5559af8f 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -1,7 +1,7 @@
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
-import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
+import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import GraphGroup from './graph_group.vue';
import { sidebarAnimationDuration } from '../constants';
import { getTimeDiff } from '../utils';
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index ab8c9712ce4..728910dd633 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -1,6 +1,6 @@
<script>
-import { __ } from '~/locale';
import { GlEmptyState } from '@gitlab/ui';
+import { __ } from '~/locale';
export default {
components: {
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 080fe6f2b4b..d0dce4f5116 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -1,7 +1,6 @@
<script>
import { mapState } from 'vuex';
import _ from 'underscore';
-import { __ } from '~/locale';
import {
GlDropdown,
GlDropdownItem,
@@ -9,6 +8,7 @@ import {
GlModalDirective,
GlTooltipDirective,
} from '@gitlab/ui';
+import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorAnomalyChart from './charts/anomaly.vue';
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index a14145d480b..d296f5b7a66 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
+import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
-import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
import store from './stores';
Vue.use(GlToast);
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index defa278c089..fcd5b391b38 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -16,10 +16,10 @@ import Cookies from 'js-cookie';
import Autosize from 'autosize';
import 'jquery.caret'; // required by at.js
import 'at.js';
-import AjaxCache from '~/lib/utils/ajax_cache';
import Vue from 'vue';
-import syntaxHighlight from '~/syntax_highlight';
import { GlSkeletonLoading } from '@gitlab/ui';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import syntaxHighlight from '~/syntax_highlight';
import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index df537ba1ed2..fe22737c7fc 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -1,10 +1,10 @@
<script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { mapState, mapActions } from 'vuex';
+import { GlSkeletonLoading } from '@gitlab/ui';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
-import { GlSkeletonLoading } from '@gitlab/ui';
import { getDiffMode } from '~/diffs/store/utils';
import { diffViewerModes } from '~/ide/constants';
diff --git a/app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue b/app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue
index 07a5bda6bcb..f87ca097b40 100644
--- a/app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue
+++ b/app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue
@@ -1,6 +1,6 @@
<script>
-import icon from '~/vue_shared/components/icon.vue';
import { GlTooltipDirective } from '@gitlab/ui';
+import icon from '~/vue_shared/components/icon.vue';
export default {
name: 'JumpToNextDiscussionButton',
diff --git a/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
index f03e6fd73d7..1d1529bfa5b 100644
--- a/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
+++ b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
@@ -1,6 +1,6 @@
<script>
-import Icon from '~/vue_shared/components/icon.vue';
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'ResolveWithIssueButton',
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 89d434a60ba..dc514f00801 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,8 +1,8 @@
<script>
import { mapGetters } from 'vuex';
-import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import resolvedStatusMixin from 'ee_else_ce/batch_comments/mixins/resolved_status';
+import Icon from '~/vue_shared/components/icon.vue';
import ReplyButton from './note_actions/reply_button.vue';
export default {
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 222badf70d1..b024884bea0 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -1,7 +1,7 @@
<script>
-import { mergeUrlParams } from '~/lib/utils/url_utility';
import { mapGetters, mapActions } from 'vuex';
import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
+import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 62d401d4911..0151a3f10a5 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,10 +1,10 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
+import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import { s__, __ } from '~/locale';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import icon from '~/vue_shared/components/icon.vue';
-import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index fa8fc7d02e4..b3dae69d0bc 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -2,9 +2,9 @@
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore';
+import draftMixin from 'ee_else_ce/notes/mixins/draft';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-import draftMixin from 'ee_else_ce/notes/mixins/draft';
import { __, s__, sprintf } from '../../locale';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 7df99610132..be2adb07526 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -1,6 +1,5 @@
<script>
import { mapGetters, mapActions } from 'vuex';
-import { __ } from '~/locale';
import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import * as constants from '../constants';
@@ -14,6 +13,7 @@ import placeholderNote from '../../vue_shared/components/notes/placeholder_note.
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
+import { __ } from '~/locale';
import initUserPopovers from '../../user_popovers';
export default {
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 82c291379ec..97b0269509a 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import $ from 'jquery';
-import axios from '~/lib/utils/axios_utils';
import Visibility from 'visibilityjs';
+import axios from '~/lib/utils/axios_utils';
import TaskList from '../../task_list';
import Flash from '../../flash';
import Poll from '../../lib/utils/poll';
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue
index c01c7cc4ccc..610bce9a705 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue
@@ -8,6 +8,11 @@ export default {
required: true,
default: __('Search'),
},
+ focused: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return { searchQuery: this.value };
@@ -16,6 +21,11 @@ export default {
searchQuery(query) {
this.$emit('input', query);
},
+ focused(val) {
+ if (val) {
+ this.$refs.searchInput.focus();
+ }
+ },
},
};
</script>
@@ -23,6 +33,7 @@ export default {
<template>
<div class="dropdown-input">
<input
+ ref="searchInput"
v-model="searchQuery"
:placeholder="placeholderText"
class="dropdown-input-field"
diff --git a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
index e89638130f5..29a4a90a59f 100644
--- a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
@@ -1,15 +1,18 @@
<script>
+import { GlPagination } from '@gitlab/ui';
import {
- PAGINATION_UI_BUTTON_LIMIT,
- UI_LIMIT,
- SPREAD,
PREV,
NEXT,
- FIRST,
- LAST,
+ LABEL_FIRST_PAGE,
+ LABEL_PREV_PAGE,
+ LABEL_NEXT_PAGE,
+ LABEL_LAST_PAGE,
} from '~/vue_shared/components/pagination/constants';
export default {
+ components: {
+ GlPagination,
+ },
props: {
/**
This function will take the information given by the pagination component
@@ -46,113 +49,34 @@ export default {
},
},
computed: {
- prev() {
- return this.pageInfo.previousPage;
- },
- next() {
- return this.pageInfo.nextPage;
- },
- getItems() {
- const { totalPages, nextPage, previousPage, page } = this.pageInfo;
- const items = [];
-
- if (page > 1) {
- items.push({ title: FIRST, first: true });
- }
-
- if (previousPage) {
- items.push({ title: PREV, prev: true });
- } else {
- items.push({ title: PREV, disabled: true, prev: true });
- }
-
- if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
-
- if (totalPages) {
- const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
- const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, totalPages);
-
- for (let i = start; i <= end; i += 1) {
- const isActive = i === page;
- items.push({ title: i, active: isActive, page: true });
- }
-
- if (totalPages - page > PAGINATION_UI_BUTTON_LIMIT) {
- items.push({ title: SPREAD, separator: true, page: true });
- }
- }
-
- if (nextPage) {
- items.push({ title: NEXT, next: true });
- } else {
- items.push({ title: NEXT, disabled: true, next: true });
- }
-
- if (totalPages && totalPages - page >= 1) {
- items.push({ title: LAST, last: true });
- }
-
- return items;
- },
showPagination() {
return this.pageInfo.nextPage || this.pageInfo.previousPage;
},
},
- methods: {
- changePage(text, isDisabled) {
- if (isDisabled) return;
-
- const { totalPages, nextPage, previousPage } = this.pageInfo;
-
- switch (text) {
- case SPREAD:
- break;
- case LAST:
- this.change(totalPages);
- break;
- case NEXT:
- this.change(nextPage);
- break;
- case PREV:
- this.change(previousPage);
- break;
- case FIRST:
- this.change(1);
- break;
- default:
- this.change(Number(text));
- break;
- }
- },
- hideOnSmallScreen(item) {
- return !item.first && !item.last && !item.next && !item.prev && !item.active;
- },
- },
+ prevText: PREV,
+ nextText: NEXT,
+ labelFirstPage: LABEL_FIRST_PAGE,
+ labelPrevPage: LABEL_PREV_PAGE,
+ labelNextPage: LABEL_NEXT_PAGE,
+ labelLastPage: LABEL_LAST_PAGE,
};
</script>
<template>
- <div v-if="showPagination" class="gl-pagination prepend-top-default">
- <ul class="pagination justify-content-center">
- <li
- v-for="(item, index) in getItems"
- :key="index"
- :class="{
- page: item.page,
- 'js-previous-button': item.prev,
- 'js-next-button': item.next,
- 'js-last-button': item.last,
- 'js-first-button': item.first,
- 'd-none d-md-block': hideOnSmallScreen(item),
- separator: item.separator,
- active: item.active,
- disabled: item.disabled || item.separator,
- }"
- class="page-item"
- >
- <button type="button" class="page-link" @click="changePage(item.title, item.disabled)">
- {{ item.title }}
- </button>
- </li>
- </ul>
- </div>
+ <gl-pagination
+ v-if="showPagination"
+ class="justify-content-center prepend-top-default"
+ v-bind="$attrs"
+ :value="pageInfo.page"
+ :per-page="pageInfo.perPage"
+ :total-items="pageInfo.total"
+ :prev-page="pageInfo.previousPage"
+ :prev-text="$options.prevText"
+ :next-page="pageInfo.nextPage"
+ :next-text="$options.nextText"
+ :label-first-page="$options.labelFirstPage"
+ :label-prev-page="$options.labelPrevPage"
+ :label-next-page="$options.labelNextPage"
+ :label-last-page="$options.labelLastPage"
+ @input="change"
+ />
</template>
diff --git a/app/controllers/admin/sessions_controller.rb b/app/controllers/admin/sessions_controller.rb
index 1f946e41995..f9587655a8d 100644
--- a/app/controllers/admin/sessions_controller.rb
+++ b/app/controllers/admin/sessions_controller.rb
@@ -6,17 +6,23 @@ class Admin::SessionsController < ApplicationController
before_action :user_is_admin!
def new
- # Renders a form in which the admin can enter their password
+ if current_user_mode.admin_mode?
+ redirect_to redirect_path, notice: _('Admin mode already enabled')
+ else
+ current_user_mode.request_admin_mode! unless current_user_mode.admin_mode_requested?
+ store_location_for(:redirect, redirect_path)
+ end
end
def create
if current_user_mode.enable_admin_mode!(password: params[:password])
- redirect_location = stored_location_for(:redirect) || admin_root_path
- redirect_to safe_redirect_path(redirect_location)
+ redirect_to redirect_path, notice: _('Admin mode enabled')
else
- flash.now[:alert] = _('Invalid Login or password')
+ flash.now[:alert] = _('Invalid login or password')
render :new
end
+ rescue Gitlab::Auth::CurrentUserMode::NotRequestedError
+ redirect_to new_admin_session_path, alert: _('Re-authentication period expired or never requested. Please try again')
end
def destroy
@@ -30,4 +36,19 @@ class Admin::SessionsController < ApplicationController
def user_is_admin!
render_404 unless current_user&.admin?
end
+
+ def redirect_path
+ redirect_to_path = safe_redirect_path(stored_location_for(:redirect)) || safe_redirect_path_for_url(request.referer)
+
+ if redirect_to_path &&
+ excluded_redirect_paths.none? { |excluded| redirect_to_path.include?(excluded) }
+ redirect_to_path
+ else
+ admin_root_path
+ end
+ end
+
+ def excluded_redirect_paths
+ [new_admin_session_path, admin_session_path]
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index ee2b3741ac9..33ae778769a 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -16,6 +16,7 @@ class ApplicationController < ActionController::Base
include ConfirmEmailWarning
include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern
+ include InitializesCurrentUserMode
before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms?
@@ -41,7 +42,6 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception, prepend: true
helper_method :can?
- helper_method :current_user_mode
helper_method :import_sources_enabled?, :github_import_enabled?,
:gitea_import_enabled?, :github_import_configured?,
:gitlab_import_enabled?, :gitlab_import_configured?,
@@ -546,10 +546,6 @@ class ApplicationController < ActionController::Base
end
end
- def current_user_mode
- @current_user_mode ||= Gitlab::Auth::CurrentUserMode.new(current_user)
- end
-
# A user requires a role and have the setup_for_company attribute set when they are part of the experimental signup
# flow (executed by the Growth team). Users are redirected to the welcome page when their role is required and the
# experiment is enabled for the current user.
diff --git a/app/controllers/concerns/enforces_admin_authentication.rb b/app/controllers/concerns/enforces_admin_authentication.rb
index e731211f423..527759de0bb 100644
--- a/app/controllers/concerns/enforces_admin_authentication.rb
+++ b/app/controllers/concerns/enforces_admin_authentication.rb
@@ -18,6 +18,7 @@ module EnforcesAdminAuthentication
return unless Feature.enabled?(:user_mode_in_session)
unless current_user_mode.admin_mode?
+ current_user_mode.request_admin_mode!
store_location_for(:redirect, request.fullpath) if storable_location?
redirect_to(new_admin_session_path, notice: _('Re-authentication required'))
end
diff --git a/app/controllers/concerns/initializes_current_user_mode.rb b/app/controllers/concerns/initializes_current_user_mode.rb
new file mode 100644
index 00000000000..df7cea5c754
--- /dev/null
+++ b/app/controllers/concerns/initializes_current_user_mode.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module InitializesCurrentUserMode
+ extend ActiveSupport::Concern
+
+ included do
+ helper_method :current_user_mode
+ end
+
+ def current_user_mode
+ @current_user_mode ||= Gitlab::Auth::CurrentUserMode.new(current_user)
+ end
+end
diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb
index f644923443b..d5c26fca957 100644
--- a/app/controllers/concerns/sessionless_authentication.rb
+++ b/app/controllers/concerns/sessionless_authentication.rb
@@ -33,6 +33,8 @@ module SessionlessAuthentication
end
def enable_admin_mode!
- current_user_mode.enable_admin_mode!(skip_password_validation: true) if Feature.enabled?(:user_mode_in_session)
+ return unless Feature.enabled?(:user_mode_in_session)
+
+ current_user_mode.enable_sessionless_admin_mode!
end
end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 8dd51ce1d64..bbf0bdd3662 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -6,6 +6,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include PageLayoutHelper
include OauthApplications
include Gitlab::Experimentation::ControllerConcern
+ include InitializesCurrentUserMode
before_action :verify_user_oauth_applications_enabled, except: :index
before_action :authenticate_user!
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index e65726dffbf..2a4e659c5b9 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -2,6 +2,8 @@
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
include Gitlab::Experimentation::ControllerConcern
+ include InitializesCurrentUserMode
+
layout 'profile'
# Overridden from Doorkeeper::AuthorizationsController to
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index eca58748cc5..92f36c031f1 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -4,6 +4,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
include AuthHelper
+ include InitializesCurrentUserMode
protect_from_forgery except: [:kerberos, :saml, :cas3, :failure], with: :exception, prepend: true
@@ -94,8 +95,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
return render_403 unless link_provider_allowed?(oauth['provider'])
log_audit_event(current_user, with: oauth['provider'])
- identity_linker ||= auth_module::IdentityLinker.new(current_user, oauth, session)
+ if Feature.enabled?(:user_mode_in_session)
+ return admin_mode_flow if current_user_mode.admin_mode_requested?
+ end
+
+ identity_linker ||= auth_module::IdentityLinker.new(current_user, oauth, session)
link_identity(identity_linker)
if identity_linker.changed?
@@ -239,6 +244,24 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
store_location_for(:user, uri.to_s)
end
end
+
+ def admin_mode_flow
+ if omniauth_identity_matches_current_user?
+ current_user_mode.enable_admin_mode!(skip_password_validation: true)
+
+ redirect_to stored_location_for(:redirect) || admin_root_path, notice: _('Admin mode enabled')
+ else
+ fail_admin_mode_invalid_credentials
+ end
+ end
+
+ def omniauth_identity_matches_current_user?
+ current_user.matches_identity?(oauth['provider'], oauth['uid'])
+ end
+
+ def fail_admin_mode_invalid_credentials
+ redirect_to new_admin_session_path, alert: _('Invalid login or password')
+ end
end
OmniauthCallbacksController.prepend_if_ee('EE::OmniauthCallbacksController')
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index a11676770a9..bd80ad7ff74 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -159,3 +159,5 @@ module Types
resolver: Resolvers::Projects::SnippetsResolver
end
end
+
+Types::ProjectType.prepend_if_ee('::EE::Types::ProjectType')
diff --git a/app/helpers/container_expiration_policies_helper.rb b/app/helpers/container_expiration_policies_helper.rb
new file mode 100644
index 00000000000..17791e7b0ff
--- /dev/null
+++ b/app/helpers/container_expiration_policies_helper.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module ContainerExpirationPoliciesHelper
+ def cadence_options
+ ContainerExpirationPolicy.cadence_options.map do |key, val|
+ { key: key.to_s, label: val }
+ end
+ end
+
+ def keep_n_options
+ ContainerExpirationPolicy.keep_n_options.map do |key, val|
+ { key: key, label: val }
+ end
+ end
+
+ def older_than_options
+ ContainerExpirationPolicy.older_than_options.map do |key, val|
+ { key: key.to_s, label: val }
+ end
+ end
+end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 2ce45cec878..6013475acb1 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -87,7 +87,7 @@ module NavHelper
end
if Feature.enabled?(:user_mode_in_session)
- if current_user&.admin? && current_user_mode&.admin_mode?
+ if current_user_mode.admin_mode?
links << :admin_mode
end
end
diff --git a/app/models/container_expiration_policy.rb b/app/models/container_expiration_policy.rb
new file mode 100644
index 00000000000..f60a0179c83
--- /dev/null
+++ b/app/models/container_expiration_policy.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+class ContainerExpirationPolicy < ApplicationRecord
+ belongs_to :project, inverse_of: :container_expiration_policy
+
+ validates :project, presence: true
+ validates :enabled, inclusion: { in: [true, false] }
+ validates :cadence, presence: true, inclusion: { in: ->(_) { self.cadence_options.stringify_keys } }
+ validates :older_than, inclusion: { in: ->(_) { self.older_than_options.stringify_keys } }, allow_nil: true
+ validates :keep_n, inclusion: { in: ->(_) { self.keep_n_options.keys } }, allow_nil: true
+
+ def self.keep_n_options
+ {
+ 1 => _('%{tags} tag per image name') % { tags: 1 },
+ 5 => _('%{tags} tags per image name') % { tags: 5 },
+ 10 => _('%{tags} tags per image name') % { tags: 10 },
+ 25 => _('%{tags} tags per image name') % { tags: 25 },
+ 50 => _('%{tags} tags per image name') % { tags: 50 },
+ 100 => _('%{tags} tags per image name') % { tags: 100 }
+ }
+ end
+
+ def self.cadence_options
+ {
+ '1d': _('Every day'),
+ '7d': _('Every week'),
+ '14d': _('Every two weeks'),
+ '1month': _('Every month'),
+ '3month': _('Every three months')
+ }
+ end
+
+ def self.older_than_options
+ {
+ '7d': _('%{days} days until tags are automatically removed') % { days: 7 },
+ '14d': _('%{days} days until tags are automatically removed') % { days: 14 },
+ '30d': _('%{days} days until tags are automatically removed') % { days: 30 },
+ '90d': _('%{days} days until tags are automatically removed') % { days: 90 }
+ }
+ end
+end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 5663ebf8ba1..d5a7c172fec 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -123,8 +123,10 @@ class Namespace < ApplicationRecord
def find_by_pages_host(host)
gitlab_host = "." + Settings.pages.host.downcase
- name = host.downcase.delete_suffix(gitlab_host)
+ host = host.downcase
+ return unless host.ends_with?(gitlab_host)
+ name = host.delete_suffix(gitlab_host)
Namespace.find_by_full_path(name)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 1cf69bf8403..84289e1d5ac 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -97,8 +97,11 @@ class Project < ApplicationRecord
unless: :ci_cd_settings,
if: proc { ProjectCiCdSetting.available? }
+ after_create :create_container_expiration_policy,
+ unless: :container_expiration_policy
+
after_create :create_pages_metadatum,
- unless: :pages_metadatum
+ unless: :pages_metadatum
after_create :set_timestamps_for_create
after_update :update_forks_visibility_level
@@ -248,6 +251,7 @@ class Project < ApplicationRecord
# which is not managed by the DB. Hence we're still using dependent: :destroy
# here.
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_one :container_expiration_policy, inverse_of: :project
has_many :commit_statuses
# The relation :all_pipelines is intended to be used when we want to get the
@@ -305,6 +309,7 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops, update_only: true
accepts_nested_attributes_for :ci_cd_settings, update_only: true
+ accepts_nested_attributes_for :container_expiration_policy, update_only: true
accepts_nested_attributes_for :remote_mirrors,
allow_destroy: true,
diff --git a/app/models/user.rb b/app/models/user.rb
index fd02db86582..6a29de20d86 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -996,6 +996,10 @@ class User < ApplicationRecord
@ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end
+ def matches_identity?(provider, extern_uid)
+ identities.where(provider: provider, extern_uid: extern_uid).exists?
+ end
+
def project_deploy_keys
DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
end
diff --git a/app/views/admin/sessions/_signin_box.html.haml b/app/views/admin/sessions/_signin_box.html.haml
deleted file mode 100644
index 1d19915d3c5..00000000000
--- a/app/views/admin/sessions/_signin_box.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- if any_form_based_providers_enabled?
-
- - if password_authentication_enabled_for_web?
- .login-box.tab-pane{ id: 'login-pane', role: 'tabpanel' }
- .login-body
- = render 'admin/sessions/new_base'
-
-- elsif password_authentication_enabled_for_web?
- .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
- .login-body
- = render 'admin/sessions/new_base'
diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml
index 73028e78ea5..a1d440f2cfd 100644
--- a/app/views/admin/sessions/new.html.haml
+++ b/app/views/admin/sessions/new.html.haml
@@ -7,9 +7,16 @@
#signin-container
= render 'admin/sessions/tabs_normal'
.tab-content
- - if password_authentication_enabled_for_web?
- = render 'admin/sessions/signin_box'
- - else
- -# Show a message if none of the mechanisms above are enabled
+ - if !current_user.require_password_creation_for_web?
+ .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
+ .login-body
+ = render 'admin/sessions/new_base'
+
+ - if omniauth_enabled? && button_based_providers_enabled?
+ .clearfix
+ = render 'devise/shared/omniauth_box'
+
+ -# Show a message if none of the mechanisms above are enabled
+ - if current_user.require_password_creation_for_web? && !omniauth_enabled?
.prepend-top-default.center
= _('No authentication methods configured.')