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:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/deploy_keys/components/action_btn.vue23
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue280
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue110
-rw-r--r--app/assets/javascripts/deploy_keys/components/keys_panel.vue10
-rw-r--r--app/assets/javascripts/deploy_keys/graphql/resolvers.js35
-rw-r--r--app/assets/javascripts/deploy_keys/index.js32
-rw-r--r--app/assets/javascripts/deploy_keys/service/index.js19
-rw-r--r--app/assets/javascripts/deploy_keys/store/index.js9
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue111
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue10
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql13
-rw-r--r--app/assets/javascripts/work_items/components/widget_wrapper.vue21
-rw-r--r--app/controllers/projects/settings/packages_and_registries_controller.rb1
-rw-r--r--app/views/shared/deploy_keys/_index.html.haml7
14 files changed, 429 insertions, 252 deletions
diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue
index 7bc1eb5d652..4a2da487e9b 100644
--- a/app/assets/javascripts/deploy_keys/components/action_btn.vue
+++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue
@@ -1,6 +1,5 @@
<script>
import { GlButton } from '@gitlab/ui';
-import eventHub from '../eventhub';
export default {
components: {
@@ -11,10 +10,6 @@ export default {
type: Object,
required: true,
},
- type: {
- type: String,
- required: true,
- },
category: {
type: String,
required: false,
@@ -30,6 +25,10 @@ export default {
required: false,
default: '',
},
+ mutation: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
@@ -39,10 +38,15 @@ export default {
methods: {
doAction() {
this.isLoading = true;
-
- eventHub.$emit(`${this.type}.key`, this.deployKey, () => {
- this.isLoading = false;
- });
+ this.$apollo
+ .mutate({
+ mutation: this.mutation,
+ variables: { id: this.deployKey.id },
+ })
+ .catch((error) => this.$emit('error', error))
+ .finally(() => {
+ this.isLoading = false;
+ });
},
},
};
@@ -50,6 +54,7 @@ export default {
<template>
<gl-button
+ v-bind="$attrs"
:category="category"
:variant="variant"
:icon="icon"
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index ec17bbea48f..7168a209b52 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -1,11 +1,18 @@
<script>
-import { GlButton, GlIcon, GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlIcon, GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { createAlert } from '~/alert';
-import { s__ } from '~/locale';
+import { s__, __, sprintf } from '~/locale';
+import { captureException } from '~/sentry/sentry_browser_wrapper';
+import pageInfoQuery from '~/graphql_shared/client/page_info.query.graphql';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
-import eventHub from '../eventhub';
-import DeployKeysService from '../service';
-import DeployKeysStore from '../store';
+import deployKeysQuery from '../graphql/queries/deploy_keys.query.graphql';
+import currentPageQuery from '../graphql/queries/current_page.query.graphql';
+import currentScopeQuery from '../graphql/queries/current_scope.query.graphql';
+import confirmRemoveKeyQuery from '../graphql/queries/confirm_remove_key.query.graphql';
+import updateCurrentScopeMutation from '../graphql/mutations/update_current_scope.mutation.graphql';
+import updateCurrentPageMutation from '../graphql/mutations/update_current_page.mutation.graphql';
+import confirmDisableMutation from '../graphql/mutations/confirm_action.mutation.graphql';
+import disableKeyMutation from '../graphql/mutations/disable_key.mutation.graphql';
import ConfirmModal from './confirm_modal.vue';
import KeysPanel from './keys_panel.vue';
@@ -17,120 +24,147 @@ export default {
GlButton,
GlIcon,
GlLoadingIcon,
+ GlPagination,
},
props: {
- endpoint: {
+ projectId: {
type: String,
required: true,
},
- projectId: {
+ projectPath: {
type: String,
required: true,
},
},
+ apollo: {
+ deployKeys: {
+ query: deployKeysQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ scope: this.currentScope,
+ page: this.currentPage,
+ };
+ },
+ update(data) {
+ return data?.project?.deployKeys || [];
+ },
+ error(error) {
+ createAlert({
+ message: s__('DeployKeys|Error getting deploy keys'),
+ captureError: true,
+ error,
+ });
+ },
+ },
+ pageInfo: {
+ query: pageInfoQuery,
+ variables() {
+ return { input: { page: this.currentPage, scope: this.currentScope } };
+ },
+ update({ pageInfo }) {
+ return pageInfo || {};
+ },
+ },
+ currentPage: {
+ query: currentPageQuery,
+ },
+ currentScope: {
+ query: currentScopeQuery,
+ },
+ deployKeyToRemove: {
+ query: confirmRemoveKeyQuery,
+ },
+ },
data() {
return {
- currentTab: 'enabled_keys',
- isLoading: false,
- store: new DeployKeysStore(),
- removeKey: () => {},
- cancel: () => {},
- confirmModalVisible: false,
+ deployKeys: [],
+ pageInfo: {},
+ deployKeyToRemove: null,
};
},
scopes: {
- enabled_keys: s__('DeployKeys|Enabled deploy keys'),
- available_project_keys: s__('DeployKeys|Privately accessible deploy keys'),
- public_keys: s__('DeployKeys|Publicly accessible deploy keys'),
+ enabledKeys: s__('DeployKeys|Enabled deploy keys'),
+ availableProjectKeys: s__('DeployKeys|Privately accessible deploy keys'),
+ availablePublicKeys: s__('DeployKeys|Publicly accessible deploy keys'),
},
i18n: {
loading: s__('DeployKeys|Loading deploy keys'),
addButton: s__('DeployKeys|Add new key'),
+ prevPage: __('Go to previous page'),
+ nextPage: __('Go to next page'),
+ next: __('Next'),
+ prev: __('Prev'),
+ goto: (page) => sprintf(__('Go to page %{page}'), { page }),
},
computed: {
tabs() {
- return Object.keys(this.$options.scopes).map((scope) => {
- const count = Array.isArray(this.keys[scope]) ? this.keys[scope].length : null;
-
+ return Object.entries(this.$options.scopes).map(([scope, name]) => {
return {
- name: this.$options.scopes[scope],
+ name,
scope,
- isActive: scope === this.currentTab,
- count,
+ isActive: scope === this.currentScope,
};
});
},
- hasKeys() {
- return Object.keys(this.keys).length;
- },
- keys() {
- return this.store.keys;
+ confirmModalVisible() {
+ return Boolean(this.deployKeyToRemove);
},
},
- created() {
- this.service = new DeployKeysService(this.endpoint);
-
- eventHub.$on('enable.key', this.enableKey);
- eventHub.$on('remove.key', this.confirmRemoveKey);
- eventHub.$on('disable.key', this.confirmRemoveKey);
- },
- mounted() {
- this.fetchKeys();
- },
- beforeDestroy() {
- eventHub.$off('enable.key', this.enableKey);
- eventHub.$off('remove.key', this.confirmRemoveKey);
- eventHub.$off('disable.key', this.confirmRemoveKey);
- },
methods: {
- onChangeTab(tab) {
- this.currentTab = tab;
+ onChangeTab(scope) {
+ return this.$apollo
+ .mutate({
+ mutation: updateCurrentScopeMutation,
+ variables: { scope },
+ })
+ .then(() => {
+ this.$apollo.queries.deployKeys.refetch();
+ })
+ .catch((error) => {
+ captureException(error, {
+ tags: {
+ deployKeyScope: scope,
+ },
+ });
+ });
},
- fetchKeys() {
- this.isLoading = true;
-
- return this.service
- .getKeys()
- .then((data) => {
- this.isLoading = false;
- this.store.keys = data;
+ moveNext() {
+ return this.movePage('next');
+ },
+ movePrevious() {
+ return this.movePage('previous');
+ },
+ movePage(direction) {
+ return this.moveToPage(this.pageInfo[`${direction}Page`]);
+ },
+ moveToPage(page) {
+ return this.$apollo.mutate({ mutation: updateCurrentPageMutation, variables: { page } });
+ },
+ removeKey() {
+ this.$apollo
+ .mutate({
+ mutation: disableKeyMutation,
+ variables: { id: this.deployKeyToRemove.id },
+ })
+ .then(() => {
+ if (!this.deployKeys.length) {
+ return this.movePage('previous');
+ }
+ return null;
})
+ .then(() => this.$apollo.queries.deployKeys.refetch())
.catch(() => {
- this.isLoading = false;
- this.store.keys = {};
- return createAlert({
- message: s__('DeployKeys|Error getting deploy keys'),
+ createAlert({
+ message: s__('DeployKeys|Error removing deploy key'),
});
});
},
- enableKey(deployKey) {
- this.service
- .enableKey(deployKey.id)
- .then(this.fetchKeys)
- .catch(() =>
- createAlert({
- message: s__('DeployKeys|Error enabling deploy key'),
- }),
- );
- },
- confirmRemoveKey(deployKey, callback) {
- const hideModal = () => {
- this.confirmModalVisible = false;
- callback?.();
- };
- this.removeKey = () => {
- this.service
- .disableKey(deployKey.id)
- .then(this.fetchKeys)
- .then(hideModal)
- .catch(() =>
- createAlert({
- message: s__('DeployKeys|Error removing deploy key'),
- }),
- );
- };
- this.cancel = hideModal;
- this.confirmModalVisible = true;
+ cancel() {
+ this.$apollo.mutate({
+ mutation: confirmDisableMutation,
+ variables: { id: null },
+ });
},
},
};
@@ -139,47 +173,59 @@ export default {
<template>
<div class="deploy-keys">
<confirm-modal :visible="confirmModalVisible" @remove="removeKey" @cancel="cancel" />
+ <div class="gl-new-card-header gl-align-items-center gl-py-0 gl-pl-0">
+ <div class="top-area scrolling-tabs-container inner-page-scroll-tabs gl-border-b-0">
+ <div class="fade-left">
+ <gl-icon name="chevron-lg-left" :size="12" />
+ </div>
+ <div class="fade-right">
+ <gl-icon name="chevron-lg-right" :size="12" />
+ </div>
+
+ <navigation-tabs
+ :tabs="tabs"
+ scope="deployKeys"
+ class="gl-rounded-lg"
+ @onChangeTab="onChangeTab"
+ />
+ </div>
+
+ <div class="gl-new-card-actions">
+ <gl-button
+ size="small"
+ class="js-toggle-button js-toggle-content"
+ data-testid="add-new-deploy-key-button"
+ >
+ {{ $options.i18n.addButton }}
+ </gl-button>
+ </div>
+ </div>
<gl-loading-icon
- v-if="isLoading && !hasKeys"
+ v-if="$apollo.queries.deployKeys.loading"
:label="$options.i18n.loading"
- size="sm"
+ size="md"
class="gl-m-5"
/>
- <template v-else-if="hasKeys">
- <div class="gl-new-card-header gl-align-items-center gl-pt-0 gl-pb-0 gl-pl-0">
- <div class="top-area scrolling-tabs-container inner-page-scroll-tabs gl-border-b-0">
- <div class="fade-left">
- <gl-icon name="chevron-lg-left" :size="12" />
- </div>
- <div class="fade-right">
- <gl-icon name="chevron-lg-right" :size="12" />
- </div>
-
- <navigation-tabs
- :tabs="tabs"
- scope="deployKeys"
- class="gl-rounded-lg"
- @onChangeTab="onChangeTab"
- />
- </div>
-
- <div class="gl-new-card-actions">
- <gl-button
- size="small"
- class="js-toggle-button js-toggle-content"
- data-testid="add-new-deploy-key-button"
- >
- {{ $options.i18n.addButton }}
- </gl-button>
- </div>
- </div>
+ <template v-else>
<keys-panel
:project-id="projectId"
- :keys="keys[currentTab]"
- :store="store"
- :endpoint="endpoint"
+ :keys="deployKeys"
data-testid="project-deploy-keys-container"
/>
+ <gl-pagination
+ align="center"
+ :total-items="pageInfo.total"
+ :per-page="pageInfo.perPage"
+ :value="currentPage"
+ :next="$options.i18n.next"
+ :prev="$options.i18n.prev"
+ :label-previous-page="$options.i18n.prevPage"
+ :label-next-page="$options.i18n.nextPage"
+ :label-page="$options.i18n.goto"
+ @next="moveNext()"
+ @previous="movePrevious()"
+ @input="moveToPage"
+ />
</template>
</div>
</template>
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index 16c745d8cff..d4b140f1adb 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -2,8 +2,12 @@
<script>
import { GlBadge, GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { head, tail } from 'lodash';
+import { createAlert } from '~/alert';
import { s__, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
+import currentScopeQuery from '../graphql/queries/current_scope.query.graphql';
+import enableKeyMutation from '../graphql/mutations/enable_key.mutation.graphql';
+import confirmDisableMutation from '../graphql/mutations/confirm_action.mutation.graphql';
import ActionBtn from './action_btn.vue';
@@ -23,48 +27,25 @@ export default {
type: Object,
required: true,
},
- store: {
- type: Object,
- required: true,
- },
- endpoint: {
- type: String,
- required: true,
- },
projectId: {
type: String,
required: false,
default: null,
},
},
+ apollo: {
+ currentScope: {
+ query: currentScopeQuery,
+ },
+ },
data() {
return {
projectsExpanded: false,
};
},
computed: {
- editDeployKeyPath() {
- return `${this.endpoint}/${this.deployKey.id}/edit`;
- },
projects() {
- const projects = [...this.deployKey.deploy_keys_projects];
-
- if (this.projectId !== null) {
- const indexOfCurrentProject = projects.findIndex(
- (project) =>
- project &&
- project.project &&
- project.project.id &&
- project.project.id.toString() === this.projectId,
- );
-
- if (indexOfCurrentProject > -1) {
- const currentProject = projects.splice(indexOfCurrentProject, 1);
- currentProject[0].project.full_name = s__('DeployKeys|Current project');
- return currentProject.concat(projects);
- }
- }
- return projects;
+ return this.deployKey.deployKeysProjects;
},
firstProject() {
return head(this.projects);
@@ -81,13 +62,11 @@ export default {
return sprintf(s__('DeployKeys|+%{count} others'), { count: this.restProjects.length });
},
isEnabled() {
- return this.store.isEnabled(this.deployKey.id);
+ return this.currentScope === 'enabledKeys';
},
isRemovable() {
return (
- this.store.isEnabled(this.deployKey.id) &&
- this.deployKey.destroyed_when_orphaned &&
- this.deployKey.almost_orphaned
+ this.isEnabled && this.deployKey.destroyedWhenOrphaned && this.deployKey.almostOrphaned
);
},
isExpandable() {
@@ -99,14 +78,37 @@ export default {
},
methods: {
projectTooltipTitle(project) {
- return project.can_push
+ return project.canPush
? s__('DeployKeys|Grant write permissions to this key')
: s__('DeployKeys|Read access only');
},
toggleExpanded() {
this.projectsExpanded = !this.projectsExpanded;
},
+ isCurrentProject({ project } = {}) {
+ if (this.projectId !== null) {
+ return Boolean(project?.id?.toString() === this.projectId);
+ }
+
+ return false;
+ },
+ projectName(project) {
+ if (this.isCurrentProject(project)) {
+ return s__('DeployKeys|Current project');
+ }
+
+ return project?.project?.fullName;
+ },
+ onEnableError(error) {
+ createAlert({
+ message: s__('DeployKeys|Error enabling deploy key'),
+ captureError: true,
+ error,
+ });
+ },
},
+ enableKeyMutation,
+ confirmDisableMutation,
};
</script>
@@ -128,7 +130,7 @@ export default {
<dl class="gl-font-sm gl-mb-0">
<dt>{{ __('SHA256') }}</dt>
<dd class="fingerprint" data-testid="key-sha256-fingerprint-content">
- {{ deployKey.fingerprint_sha256 }}
+ {{ deployKey.fingerprintSha256 }}
</dd>
<template v-if="deployKey.fingerprint">
<dt>
@@ -150,10 +152,10 @@ export default {
<gl-badge
v-gl-tooltip
:title="projectTooltipTitle(firstProject)"
- :icon="firstProject.can_push ? 'lock-open' : 'lock'"
+ :icon="firstProject.canPush ? 'lock-open' : 'lock'"
class="deploy-project-label gl-mr-2 gl-mb-2 gl-truncate"
>
- <span class="gl-text-truncate">{{ firstProject.project.full_name }}</span>
+ <span class="gl-text-truncate">{{ projectName(firstProject) }}</span>
</gl-badge>
<gl-badge
@@ -170,14 +172,14 @@ export default {
<gl-badge
v-for="deployKeysProject in restProjects"
v-else-if="isExpanded"
- :key="deployKeysProject.project.full_path"
+ :key="deployKeysProject.project.fullPath"
v-gl-tooltip
- :href="deployKeysProject.project.full_path"
+ :href="deployKeysProject.project.fullPath"
:title="projectTooltipTitle(deployKeysProject)"
- :icon="deployKeysProject.can_push ? 'lock-open' : 'lock'"
+ :icon="deployKeysProject.canPush ? 'lock-open' : 'lock'"
class="deploy-project-label gl-mr-2 gl-mb-2 gl-truncate"
>
- <span class="gl-text-truncate">{{ deployKeysProject.project.full_name }}</span>
+ <span class="gl-text-truncate">{{ projectName(deployKeysProject) }}</span>
</gl-badge>
</template>
<span v-else class="gl-text-secondary">{{ __('None') }}</span>
@@ -188,8 +190,8 @@ export default {
{{ __('Created') }}
</div>
<div class="table-mobile-content gl-text-gray-700 key-created-at">
- <span v-gl-tooltip :title="tooltipTitle(deployKey.created_at)">
- <gl-icon name="calendar" /> <span>{{ timeFormatted(deployKey.created_at) }}</span>
+ <span v-gl-tooltip :title="tooltipTitle(deployKey.createdAt)">
+ <gl-icon name="calendar" /> <span>{{ timeFormatted(deployKey.createdAt) }}</span>
</span>
</div>
</div>
@@ -199,12 +201,12 @@ export default {
</div>
<div class="table-mobile-content gl-text-gray-700 key-expires-at">
<span
- v-if="deployKey.expires_at"
+ v-if="deployKey.expiresAt"
v-gl-tooltip
- :title="tooltipTitle(deployKey.expires_at)"
+ :title="tooltipTitle(deployKey.expiresAt)"
data-testid="expires-at-tooltip"
>
- <gl-icon name="calendar" /> <span>{{ timeFormatted(deployKey.expires_at) }}</span>
+ <gl-icon name="calendar" /> <span>{{ timeFormatted(deployKey.expiresAt) }}</span>
</span>
<span v-else>
<span data-testid="expires-never">{{ __('Never') }}</span>
@@ -213,13 +215,19 @@ export default {
</div>
<div class="table-section section-10 table-button-footer deploy-key-actions">
<div class="btn-group table-action-buttons">
- <action-btn v-if="!isEnabled" :deploy-key="deployKey" type="enable" category="secondary">
+ <action-btn
+ v-if="!isEnabled"
+ :deploy-key="deployKey"
+ :mutation="$options.enableKeyMutation"
+ category="secondary"
+ @error="onEnableError"
+ >
{{ __('Enable') }}
</action-btn>
<gl-button
- v-if="deployKey.can_edit"
+ v-if="deployKey.editPath"
v-gl-tooltip
- :href="editDeployKeyPath"
+ :href="deployKey.editPath"
:title="__('Edit')"
:aria-label="__('Edit')"
data-container="body"
@@ -232,10 +240,10 @@ export default {
:deploy-key="deployKey"
:title="__('Remove')"
:aria-label="__('Remove')"
+ :mutation="$options.confirmDisableMutation"
category="secondary"
variant="danger"
icon="remove"
- type="remove"
data-container="body"
/>
<action-btn
@@ -244,7 +252,7 @@ export default {
:deploy-key="deployKey"
:title="__('Disable')"
:aria-label="__('Disable')"
- type="disable"
+ :mutation="$options.confirmDisableMutation"
data-container="body"
icon="cancel"
category="secondary"
diff --git a/app/assets/javascripts/deploy_keys/components/keys_panel.vue b/app/assets/javascripts/deploy_keys/components/keys_panel.vue
index dac63188aa5..088b85e6093 100644
--- a/app/assets/javascripts/deploy_keys/components/keys_panel.vue
+++ b/app/assets/javascripts/deploy_keys/components/keys_panel.vue
@@ -10,14 +10,6 @@ export default {
type: Array,
required: true,
},
- store: {
- type: Object,
- required: true,
- },
- endpoint: {
- type: String,
- required: true,
- },
projectId: {
type: String,
required: false,
@@ -48,8 +40,6 @@ export default {
v-for="deployKey in keys"
:key="deployKey.id"
:deploy-key="deployKey"
- :store="store"
- :endpoint="endpoint"
:project-id="projectId"
/>
</template>
diff --git a/app/assets/javascripts/deploy_keys/graphql/resolvers.js b/app/assets/javascripts/deploy_keys/graphql/resolvers.js
index 1993801636e..a8693665b90 100644
--- a/app/assets/javascripts/deploy_keys/graphql/resolvers.js
+++ b/app/assets/javascripts/deploy_keys/graphql/resolvers.js
@@ -15,6 +15,8 @@ export const mapDeployKey = (deployKey) => ({
__typename: 'LocalDeployKey',
});
+const DEFAULT_PAGE_SIZE = 5;
+
export const resolvers = (endpoints) => ({
Project: {
deployKeys(_, { scope, page }, { client }) {
@@ -25,19 +27,21 @@ export const resolvers = (endpoints) => ({
endpoint = endpoints.enabledKeysEndpoint;
}
- return axios.get(endpoint, { params: { page } }).then(({ headers, data }) => {
- const normalizedHeaders = normalizeHeaders(headers);
- const pageInfo = {
- ...parseIntPagination(normalizedHeaders),
- __typename: 'LocalPageInfo',
- };
- client.writeQuery({
- query: pageInfoQuery,
- variables: { input: { page, scope } },
- data: { pageInfo },
+ return axios
+ .get(endpoint, { params: { page, per_page: DEFAULT_PAGE_SIZE } })
+ .then(({ headers, data }) => {
+ const normalizedHeaders = normalizeHeaders(headers);
+ const pageInfo = {
+ ...parseIntPagination(normalizedHeaders),
+ __typename: 'LocalPageInfo',
+ };
+ client.writeQuery({
+ query: pageInfoQuery,
+ variables: { input: { page, scope } },
+ data: { pageInfo },
+ });
+ return data?.keys?.map(mapDeployKey) || [];
});
- return data?.keys?.map(mapDeployKey) || [];
- });
},
},
Mutation: {
@@ -48,6 +52,13 @@ export const resolvers = (endpoints) => ({
});
},
currentScope(_, { scope }, { client }) {
+ const key = `${scope}Endpoint`;
+ const { [key]: endpoint } = endpoints;
+
+ if (!endpoint) {
+ throw new Error(`invalid deploy key scope selected: ${scope}`);
+ }
+
client.writeQuery({
query: currentPageQuery,
data: { currentPage: 1 },
diff --git a/app/assets/javascripts/deploy_keys/index.js b/app/assets/javascripts/deploy_keys/index.js
index 83601d5b2e3..673462073f0 100644
--- a/app/assets/javascripts/deploy_keys/index.js
+++ b/app/assets/javascripts/deploy_keys/index.js
@@ -1,24 +1,26 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import DeployKeysApp from './components/app.vue';
+import { createApolloProvider } from './graphql/client';
-export default () =>
- new Vue({
- el: document.getElementById('js-deploy-keys'),
- components: {
- DeployKeysApp,
- },
- data() {
- return {
- endpoint: this.$options.el.dataset.endpoint,
- projectId: this.$options.el.dataset.projectId,
- };
- },
+Vue.use(VueApollo);
+
+export default () => {
+ const el = document.getElementById('js-deploy-keys');
+ return new Vue({
+ el,
+ apolloProvider: createApolloProvider({
+ enabledKeysEndpoint: el.dataset.enabledEndpoint,
+ availableProjectKeysEndpoint: el.dataset.availableProjectEndpoint,
+ availablePublicKeysEndpoint: el.dataset.availablePublicEndpoint,
+ }),
render(createElement) {
- return createElement('deploy-keys-app', {
+ return createElement(DeployKeysApp, {
props: {
- endpoint: this.endpoint,
- projectId: this.projectId,
+ projectId: el.dataset.projectId,
+ projectPath: el.dataset.projectPath,
},
});
},
});
+};
diff --git a/app/assets/javascripts/deploy_keys/service/index.js b/app/assets/javascripts/deploy_keys/service/index.js
deleted file mode 100644
index 2837fc8ed88..00000000000
--- a/app/assets/javascripts/deploy_keys/service/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import axios from '~/lib/utils/axios_utils';
-
-export default class DeployKeysService {
- constructor(endpoint) {
- this.endpoint = endpoint;
- }
-
- getKeys() {
- return axios.get(this.endpoint).then((response) => response.data);
- }
-
- enableKey(id) {
- return axios.put(`${this.endpoint}/${id}/enable`).then((response) => response.data);
- }
-
- disableKey(id) {
- return axios.put(`${this.endpoint}/${id}/disable`).then((response) => response.data);
- }
-}
diff --git a/app/assets/javascripts/deploy_keys/store/index.js b/app/assets/javascripts/deploy_keys/store/index.js
deleted file mode 100644
index dcd77e921cd..00000000000
--- a/app/assets/javascripts/deploy_keys/store/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default class DeployKeysStore {
- constructor() {
- this.keys = {};
- }
-
- isEnabled(id) {
- return this.keys.enabled_keys.some((key) => key.id === id);
- }
-}
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue
new file mode 100644
index 00000000000..32f05d0e298
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/packages_protection_rules.vue
@@ -0,0 +1,111 @@
+<script>
+import { GlCard, GlTable, GlLoadingIcon } from '@gitlab/ui';
+import packagesProtectionRuleQuery from '~/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql';
+import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
+import { s__ } from '~/locale';
+
+const PAGINATION_DEFAULT_PER_PAGE = 10;
+
+export default {
+ components: {
+ SettingsBlock,
+ GlCard,
+ GlTable,
+ GlLoadingIcon,
+ },
+ inject: ['projectPath'],
+ i18n: {
+ settingBlockTitle: s__('PackageRegistry|Protected packages'),
+ settingBlockDescription: s__(
+ 'PackageRegistry|When a package is protected then only certain user roles are able to update and delete the protected package. This helps to avoid tampering with the package.',
+ ),
+ },
+ data() {
+ return {
+ fetchSettingsError: false,
+ packageProtectionRules: [],
+ };
+ },
+ computed: {
+ tableItems() {
+ return this.packageProtectionRules.map((packagesProtectionRule) => {
+ return {
+ col_1_package_name_pattern: packagesProtectionRule.packageNamePattern,
+ col_2_package_type: packagesProtectionRule.packageType,
+ col_3_push_protected_up_to_access_level:
+ packagesProtectionRule.pushProtectedUpToAccessLevel,
+ };
+ });
+ },
+ totalItems() {
+ return this.packageProtectionRules.length;
+ },
+ },
+ apollo: {
+ packageProtectionRules: {
+ query: packagesProtectionRuleQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ first: PAGINATION_DEFAULT_PER_PAGE,
+ };
+ },
+ update: (data) => {
+ return data.project?.packagesProtectionRules?.nodes || [];
+ },
+ error(e) {
+ this.fetchSettingsError = e;
+ },
+ },
+ },
+ fields: [
+ {
+ key: 'col_1_package_name_pattern',
+ label: s__('PackageRegistry|Package name pattern'),
+ },
+ { key: 'col_2_package_type', label: s__('PackageRegistry|Package type') },
+ {
+ key: 'col_3_push_protected_up_to_access_level',
+ label: s__('PackageRegistry|Push protected up to access level'),
+ },
+ ],
+};
+</script>
+
+<template>
+ <settings-block>
+ <template #title>{{ $options.i18n.settingBlockTitle }}</template>
+
+ <template #description>
+ {{ $options.i18n.settingBlockDescription }}
+ </template>
+
+ <template #default>
+ <gl-card
+ class="gl-new-card"
+ header-class="gl-new-card-header"
+ body-class="gl-new-card-body gl-px-0"
+ >
+ <template #header>
+ <div class="gl-new-card-title-wrapper gl-justify-content-space-between">
+ <h3 class="gl-new-card-title">{{ $options.i18n.settingBlockTitle }}</h3>
+ </div>
+ </template>
+
+ <template #default>
+ <gl-table
+ :items="tableItems"
+ :fields="$options.fields"
+ show-empty
+ stacked="md"
+ class="mb-3"
+ >
+ <template #table-busy>
+ <gl-loading-icon size="sm" class="gl-my-5" />
+ </template>
+ </gl-table>
+ </template>
+ </gl-card>
+ </template>
+ </settings-block>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
index 06af69ff250..8e4c50b199b 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
@@ -8,6 +8,7 @@ import {
} from '~/packages_and_registries/settings/project/constants';
import ContainerExpirationPolicy from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue';
import PackagesCleanupPolicy from '~/packages_and_registries/settings/project/components/packages_cleanup_policy.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@@ -18,7 +19,10 @@ export default {
),
GlAlert,
PackagesCleanupPolicy,
+ PackagesProtectionRules: () =>
+ import('~/packages_and_registries/settings/project/components/packages_protection_rules.vue'),
},
+ mixins: [glFeatureFlagsMixin()],
inject: [
'showContainerRegistrySettings',
'showPackageRegistrySettings',
@@ -32,6 +36,11 @@ export default {
showAlert: false,
};
},
+ computed: {
+ showProtectedPackagesSettings() {
+ return this.showPackageRegistrySettings && this.glFeatures.packagesProtectedPackages;
+ },
+ },
mounted() {
this.checkAlert();
},
@@ -60,6 +69,7 @@ export default {
>
{{ $options.i18n.UPDATE_SETTINGS_SUCCESS_MESSAGE }}
</gl-alert>
+ <packages-protection-rules v-if="showProtectedPackagesSettings" />
<packages-cleanup-policy v-if="showPackageRegistrySettings" />
<container-expiration-policy v-if="showContainerRegistrySettings" />
<dependency-proxy-packages-settings v-if="showDependencyProxySettings" />
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql
new file mode 100644
index 00000000000..e0a072b93e4
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_packages_protection_rules.query.graphql
@@ -0,0 +1,13 @@
+query getProjectPackageProtectionRules($projectPath: ID!, $first: Int) {
+ project(fullPath: $projectPath) {
+ id
+ packagesProtectionRules(first: $first) {
+ nodes {
+ id
+ packageNamePattern
+ packageType
+ pushProtectedUpToAccessLevel
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/work_items/components/widget_wrapper.vue b/app/assets/javascripts/work_items/components/widget_wrapper.vue
index 27de858fe4e..6feae8dd94e 100644
--- a/app/assets/javascripts/work_items/components/widget_wrapper.vue
+++ b/app/assets/javascripts/work_items/components/widget_wrapper.vue
@@ -57,28 +57,31 @@ export default {
</script>
<template>
- <div :id="widgetName" class="gl-new-card" :aria-expanded="isOpenString">
+ <div :id="widgetName" class="gl-new-card">
<div class="gl-new-card-header">
<div class="gl-new-card-title-wrapper">
- <h3 class="gl-new-card-title">
- <gl-link
- :id="anchorLinkId"
- class="gl-text-decoration-none"
- :href="anchorLink"
- aria-hidden="true"
- />
+ <h2 class="gl-new-card-title">
+ <div aria-hidden="true">
+ <gl-link
+ :id="anchorLinkId"
+ class="gl-text-decoration-none gl-display-none"
+ :href="anchorLink"
+ />
+ </div>
<slot name="header"></slot>
- </h3>
+ </h2>
<slot name="header-suffix"></slot>
</div>
<slot name="header-right"></slot>
<div class="gl-new-card-toggle">
+ <!-- https://www.w3.org/TR/wai-aria-1.2/#aria-expanded -->
<gl-button
category="tertiary"
size="small"
:icon="toggleIcon"
:aria-label="toggleLabel"
data-testid="widget-toggle"
+ :aria-expanded="isOpenString"
@click="toggle"
/>
</div>
diff --git a/app/controllers/projects/settings/packages_and_registries_controller.rb b/app/controllers/projects/settings/packages_and_registries_controller.rb
index 76c9cead360..fd4dbdab95f 100644
--- a/app/controllers/projects/settings/packages_and_registries_controller.rb
+++ b/app/controllers/projects/settings/packages_and_registries_controller.rb
@@ -12,6 +12,7 @@ module Projects
urgency :low
def show
+ push_frontend_feature_flag(:packages_protected_packages, project)
end
def cleanup_tags
diff --git a/app/views/shared/deploy_keys/_index.html.haml b/app/views/shared/deploy_keys/_index.html.haml
index 5188c530672..95c99f20380 100644
--- a/app/views/shared/deploy_keys/_index.html.haml
+++ b/app/views/shared/deploy_keys/_index.html.haml
@@ -13,4 +13,9 @@
.gl-new-card-add-form.gl-m-3.gl-display-none.js-toggle-content
= render @deploy_keys.form_partial_path
- #js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project), project_id: @project.id } }
+ #js-deploy-keys{ data: { project_id: @project.id,
+ project_path: @project.full_path,
+ enabled_endpoint: enabled_keys_project_deploy_keys_path(@project),
+ available_project_endpoint: available_project_keys_project_deploy_keys_path(@project),
+ available_public_endpoint: available_public_keys_project_deploy_keys_path(@project)
+ } }