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
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/usage_quotas/storage/components')
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue64
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/project_list.vue208
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/storage_type_help_link.vue36
3 files changed, 305 insertions, 3 deletions
diff --git a/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue b/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue
index a812b90e378..1594e125da3 100644
--- a/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue
+++ b/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue
@@ -1,20 +1,23 @@
<script>
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlKeysetPagination } from '@gitlab/ui';
import StorageUsageStatistics from 'ee_else_ce/usage_quotas/storage/components/storage_usage_statistics.vue';
import SearchAndSortBar from '~/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.vue';
import DependencyProxyUsage from './dependency_proxy_usage.vue';
import ContainerRegistryUsage from './container_registry_usage.vue';
+import ProjectList from './project_list.vue';
export default {
name: 'NamespaceStorageApp',
components: {
GlAlert,
+ GlKeysetPagination,
StorageUsageStatistics,
DependencyProxyUsage,
ContainerRegistryUsage,
SearchAndSortBar,
+ ProjectList,
},
- inject: ['userNamespace', 'namespaceId'],
+ inject: ['userNamespace', 'namespaceId', 'helpLinks', 'defaultPerPage'],
props: {
namespaceLoadingError: {
type: Boolean,
@@ -31,11 +34,26 @@ export default {
required: false,
default: false,
},
+ isNamespaceProjectsLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
namespace: {
type: Object,
required: false,
default: () => ({}),
},
+ projects: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ initialSortBy: {
+ type: String,
+ required: false,
+ default: 'storage',
+ },
},
computed: {
usedStorage() {
@@ -55,6 +73,27 @@ export default {
containerRegistrySizeIsEstimated() {
return this.namespace.rootStorageStatistics?.containerRegistrySizeIsEstimated ?? false;
},
+ projectList() {
+ return this.projects?.nodes ?? [];
+ },
+ pageInfo() {
+ return this.projects?.pageInfo;
+ },
+ showPagination() {
+ return Boolean(this.pageInfo?.hasPreviousPage || this.pageInfo?.hasNextPage);
+ },
+ },
+ methods: {
+ onPrev(before) {
+ if (this.pageInfo?.hasPreviousPage) {
+ this.$emit('fetch-more-projects', { before, last: this.defaultPerPage, first: undefined });
+ }
+ },
+ onNext(after) {
+ if (this.pageInfo?.hasNextPage) {
+ this.$emit('fetch-more-projects', { after, first: this.defaultPerPage });
+ }
+ },
},
};
</script>
@@ -103,7 +142,26 @@ export default {
"
/>
</div>
- <slot name="ee-storage-app"></slot>
+ <project-list
+ :projects="projectList"
+ :is-loading="isNamespaceProjectsLoading"
+ :help-links="helpLinks"
+ :sort-by="initialSortBy"
+ :sort-desc="true"
+ @sortChanged="
+ ($event) => {
+ $emit('sort-changed', $event);
+ }
+ "
+ />
+ <div class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <gl-keyset-pagination
+ v-if="showPagination"
+ v-bind="pageInfo"
+ @prev="onPrev"
+ @next="onNext"
+ />
+ </div>
</section>
</div>
</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_list.vue b/app/assets/javascripts/usage_quotas/storage/components/project_list.vue
new file mode 100644
index 00000000000..c6f9b1fff03
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/components/project_list.vue
@@ -0,0 +1,208 @@
+<script>
+import { GlTable, GlLink, GlSprintf, GlIcon } from '@gitlab/ui';
+import { __ } from '~/locale';
+import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
+import { containerRegistryPopover } from '~/usage_quotas/storage/constants';
+import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
+import HelpPageLink from '~/vue_shared/components/help_page_link/help_page_link.vue';
+import StorageTypeHelpLink from './storage_type_help_link.vue';
+import StorageTypeWarning from './storage_type_warning.vue';
+
+export default {
+ name: 'ProjectList',
+ components: {
+ GlTable,
+ GlLink,
+ GlSprintf,
+ GlIcon,
+ ProjectAvatar,
+ NumberToHumanSize,
+ HelpPageLink,
+ StorageTypeHelpLink,
+ StorageTypeWarning,
+ },
+ inject: ['isUsingProjectEnforcementWithLimits'],
+ props: {
+ projects: {
+ type: Array,
+ required: true,
+ },
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
+ helpLinks: {
+ type: Object,
+ required: true,
+ },
+ sortBy: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ sortDesc: {
+ type: Boolean,
+ required: false,
+ default: undefined,
+ },
+ },
+ created() {
+ this.fields = [
+ { key: 'name', label: __('Project') },
+ { key: 'storage', label: __('Total'), sortable: !this.isUsingProjectEnforcementWithLimits },
+ { key: 'repository', label: __('Repository') },
+ { key: 'snippets', label: __('Snippets') },
+ { key: 'buildArtifacts', label: __('Jobs') },
+ { key: 'lfsObjects', label: __('LFS') },
+ { key: 'packages', label: __('Packages') },
+ { key: 'wiki', label: __('Wiki') },
+ {
+ key: 'containerRegistry',
+ label: __('Containers'),
+ thClass: 'gl-border-l!',
+ tdClass: 'gl-border-l!',
+ },
+ ].map((f) => ({
+ ...f,
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ thClass: `${f.thClass ?? ''} gl-px-3!`,
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ tdClass: `${f.tdClass ?? ''} gl-px-3!`,
+ }));
+ },
+ methods: {
+ /**
+ * Builds a gl-table td cell slot name for particular field
+ * @param {string} key
+ * @returns {string} */
+ getHeaderSlotName(key) {
+ return `head(${key})`;
+ },
+ getUsageQuotasUrl(projectUrl) {
+ return `${projectUrl}/-/usage_quotas`;
+ },
+ /**
+ * Creates a relative path from a full project path.
+ * E.g. input `namespace / subgroup / project`
+ * results in `subgroup / project`
+ */
+ getProjectRelativePath(fullPath) {
+ return fullPath.replace(/.*?\s?\/\s?/, '');
+ },
+ isCostFactored(project) {
+ return project.statistics.storageSize !== project.statistics.costFactoredStorageSize;
+ },
+ },
+ containerRegistryPopover,
+};
+</script>
+
+<template>
+ <gl-table
+ :fields="fields"
+ :items="projects"
+ :busy="isLoading"
+ show-empty
+ :empty-text="s__('UsageQuota|No projects to display.')"
+ small
+ stacked="lg"
+ :sort-by="sortBy"
+ :sort-desc="sortDesc"
+ no-local-sorting
+ no-sort-reset
+ @sort-changed="$emit('sortChanged', $event)"
+ >
+ <template v-for="field in fields" #[getHeaderSlotName(field.key)]>
+ <div :key="field.key" :data-testid="'th-' + field.key">
+ {{ field.label }}
+
+ <storage-type-help-link
+ v-if="field.key in helpLinks"
+ :storage-type="field.key"
+ :help-links="helpLinks"
+ /><storage-type-warning v-if="field.key == 'containerRegistry'">
+ {{ $options.containerRegistryPopover.content }}
+ <gl-link :href="$options.containerRegistryPopover.docsLink" target="_blank">
+ {{ __('Learn more.') }}
+ </gl-link>
+ </storage-type-warning>
+ </div>
+ </template>
+
+ <template #cell(name)="{ item: project }">
+ <project-avatar
+ :project-id="project.id"
+ :project-name="project.name"
+ :project-avatar-url="project.avatarUrl"
+ :size="16"
+ :alt="project.name"
+ class="gl-display-inline-block gl-mr-2 gl-text-center!"
+ />
+
+ <gl-link
+ :href="getUsageQuotasUrl(project.webUrl)"
+ class="gl-text-gray-900! js-project-link gl-word-break-word"
+ data-testid="project-link"
+ >
+ {{ getProjectRelativePath(project.nameWithNamespace) }}
+ </gl-link>
+ </template>
+
+ <template #cell(storage)="{ item: project }">
+ <template v-if="isCostFactored(project)">
+ <number-to-human-size :value="project.statistics.costFactoredStorageSize" />
+
+ <div class="gl-text-gray-600 gl-mt-2 gl-font-sm">
+ <gl-sprintf :message="s__('UsageQuotas|(of %{totalStorageSize})')">
+ <template #totalStorageSize>
+ <number-to-human-size :value="project.statistics.storageSize" />
+ </template>
+ </gl-sprintf>
+ <help-page-link href="user/usage_quotas#view-project-fork-storage-usage" target="_blank">
+ <gl-icon name="question-o" :size="12" />
+ </help-page-link>
+ </div>
+ </template>
+ <template v-else>
+ <number-to-human-size :value="project.statistics.storageSize" />
+ </template>
+ </template>
+
+ <template #cell(repository)="{ item: project }">
+ <number-to-human-size
+ :value="project.statistics.repositorySize"
+ data-testid="project-repository-size"
+ />
+ </template>
+
+ <template #cell(lfsObjects)="{ item: project }">
+ <number-to-human-size :value="project.statistics.lfsObjectsSize" />
+ </template>
+
+ <template #cell(buildArtifacts)="{ item: project }">
+ <number-to-human-size :value="project.statistics.buildArtifactsSize" />
+ </template>
+
+ <template #cell(packages)="{ item: project }">
+ <number-to-human-size :value="project.statistics.packagesSize" />
+ </template>
+
+ <template #cell(wiki)="{ item: project }">
+ <number-to-human-size :value="project.statistics.wikiSize" data-testid="project-wiki-size" />
+ </template>
+
+ <template #cell(snippets)="{ item: project }">
+ <number-to-human-size
+ :value="project.statistics.snippetsSize"
+ data-testid="project-snippets-size"
+ />
+ </template>
+
+ <template #cell(containerRegistry)="{ item: project }">
+ <number-to-human-size
+ :value="project.statistics.containerRegistrySize"
+ data-testid="project-containers-registry-size"
+ />
+ </template>
+ </gl-table>
+</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/components/storage_type_help_link.vue b/app/assets/javascripts/usage_quotas/storage/components/storage_type_help_link.vue
new file mode 100644
index 00000000000..c25b1848124
--- /dev/null
+++ b/app/assets/javascripts/usage_quotas/storage/components/storage_type_help_link.vue
@@ -0,0 +1,36 @@
+<script>
+import { GlLink, GlIcon } from '@gitlab/ui';
+import { sprintf } from '~/locale';
+import { HELP_LINK_ARIA_LABEL } from '~/usage_quotas/storage/constants';
+
+export default {
+ name: 'StorageTypeHelpLink',
+ components: {
+ GlLink,
+ GlIcon,
+ },
+ props: {
+ storageType: {
+ type: String,
+ required: true,
+ },
+ helpLinks: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ ariaLabel() {
+ return sprintf(HELP_LINK_ARIA_LABEL, {
+ linkTitle: this.storageType,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-link :href="helpLinks[storageType]" target="_blank" :aria-label="ariaLabel">
+ <gl-icon name="question-o" :size="12" />
+ </gl-link>
+</template>