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/packages/details/components')
-rw-r--r--app/assets/javascripts/packages/details/components/additional_metadata.vue98
-rw-r--r--app/assets/javascripts/packages/details/components/app.vue289
-rw-r--r--app/assets/javascripts/packages/details/components/code_instruction.vue63
-rw-r--r--app/assets/javascripts/packages/details/components/composer_installation.vue60
-rw-r--r--app/assets/javascripts/packages/details/components/conan_installation.vue56
-rw-r--r--app/assets/javascripts/packages/details/components/dependency_row.vue35
-rw-r--r--app/assets/javascripts/packages/details/components/history_element.vue35
-rw-r--r--app/assets/javascripts/packages/details/components/installation_commands.vue53
-rw-r--r--app/assets/javascripts/packages/details/components/maven_installation.vue84
-rw-r--r--app/assets/javascripts/packages/details/components/npm_installation.vue80
-rw-r--r--app/assets/javascripts/packages/details/components/nuget_installation.vue55
-rw-r--r--app/assets/javascripts/packages/details/components/package_history.vue114
-rw-r--r--app/assets/javascripts/packages/details/components/package_title.vue112
-rw-r--r--app/assets/javascripts/packages/details/components/pypi_installation.vue68
14 files changed, 1202 insertions, 0 deletions
diff --git a/app/assets/javascripts/packages/details/components/additional_metadata.vue b/app/assets/javascripts/packages/details/components/additional_metadata.vue
new file mode 100644
index 00000000000..a3de6dd46c7
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/additional_metadata.vue
@@ -0,0 +1,98 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import DetailsRow from '~/registry/shared/components/details_row.vue';
+import { generateConanRecipe } from '../utils';
+import { PackageType } from '../../shared/constants';
+
+export default {
+ i18n: {
+ sourceText: s__('PackageRegistry|Source project located at %{link}'),
+ licenseText: s__('PackageRegistry|License information located at %{link}'),
+ recipeText: s__('PackageRegistry|Recipe: %{recipe}'),
+ appGroup: s__('PackageRegistry|App group: %{group}'),
+ appName: s__('PackageRegistry|App name: %{name}'),
+ },
+ components: {
+ DetailsRow,
+ GlLink,
+ GlSprintf,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ conanRecipe() {
+ return generateConanRecipe(this.packageEntity);
+ },
+ showMetadata() {
+ const visibilityConditions = {
+ [PackageType.NUGET]: this.packageEntity.nuget_metadatum,
+ [PackageType.CONAN]: this.packageEntity.conan_metadatum,
+ [PackageType.MAVEN]: this.packageEntity.maven_metadatum,
+ };
+ return visibilityConditions[this.packageEntity.package_type];
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="showMetadata">
+ <h3 class="gl-font-lg" data-testid="title">{{ __('Additional Metadata') }}</h3>
+
+ <div class="gl-bg-gray-50 gl-inset-border-1-gray-100 gl-rounded-base" data-testid="main">
+ <template v-if="packageEntity.nuget_metadatum">
+ <details-row icon="project" padding="gl-p-4" dashed data-testid="nuget-source">
+ <gl-sprintf :message="$options.i18n.sourceText">
+ <template #link>
+ <gl-link :href="packageEntity.nuget_metadatum.project_url" target="_blank">{{
+ packageEntity.nuget_metadatum.project_url
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </details-row>
+ <details-row icon="license" padding="gl-p-4" data-testid="nuget-license">
+ <gl-sprintf :message="$options.i18n.licenseText">
+ <template #link>
+ <gl-link :href="packageEntity.nuget_metadatum.license_url" target="_blank">{{
+ packageEntity.nuget_metadatum.license_url
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </details-row>
+ </template>
+
+ <details-row
+ v-else-if="packageEntity.conan_metadatum"
+ icon="information-o"
+ padding="gl-p-4"
+ data-testid="conan-recipe"
+ >
+ <gl-sprintf :message="$options.i18n.recipeText">
+ <template #recipe>{{ conanRecipe }}</template>
+ </gl-sprintf>
+ </details-row>
+
+ <template v-else-if="packageEntity.maven_metadatum">
+ <details-row icon="information-o" padding="gl-p-4" dashed data-testid="maven-app">
+ <gl-sprintf :message="$options.i18n.appName">
+ <template #name>
+ <strong>{{ packageEntity.maven_metadatum.app_name }}</strong>
+ </template>
+ </gl-sprintf>
+ </details-row>
+ <details-row icon="information-o" padding="gl-p-4" data-testid="maven-group">
+ <gl-sprintf :message="$options.i18n.appGroup">
+ <template #group>
+ <strong>{{ packageEntity.maven_metadatum.app_group }}</strong>
+ </template>
+ </gl-sprintf>
+ </details-row>
+ </template>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/app.vue b/app/assets/javascripts/packages/details/components/app.vue
new file mode 100644
index 00000000000..dbb5f7be0a0
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/app.vue
@@ -0,0 +1,289 @@
+<script>
+import {
+ GlBadge,
+ GlButton,
+ GlModal,
+ GlModalDirective,
+ GlTooltipDirective,
+ GlLink,
+ GlEmptyState,
+ GlTab,
+ GlTabs,
+ GlTable,
+ GlSprintf,
+} from '@gitlab/ui';
+import { mapActions, mapState } from 'vuex';
+import Tracking from '~/tracking';
+import PackageHistory from './package_history.vue';
+import PackageTitle from './package_title.vue';
+import PackagesListLoader from '../../shared/components/packages_list_loader.vue';
+import PackageListRow from '../../shared/components/package_list_row.vue';
+import DependencyRow from './dependency_row.vue';
+import AdditionalMetadata from './additional_metadata.vue';
+import InstallationCommands from './installation_commands.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import { __, s__ } from '~/locale';
+import { PackageType, TrackingActions } from '../../shared/constants';
+import { packageTypeToTrackCategory } from '../../shared/utils';
+
+export default {
+ name: 'PackagesApp',
+ components: {
+ GlBadge,
+ GlButton,
+ GlEmptyState,
+ GlLink,
+ GlModal,
+ GlTab,
+ GlTabs,
+ GlTable,
+ FileIcon,
+ GlSprintf,
+ PackageTitle,
+ PackagesListLoader,
+ PackageListRow,
+ DependencyRow,
+ PackageHistory,
+ AdditionalMetadata,
+ InstallationCommands,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ GlModal: GlModalDirective,
+ },
+ mixins: [timeagoMixin, Tracking.mixin()],
+ trackingActions: { ...TrackingActions },
+ computed: {
+ ...mapState([
+ 'projectName',
+ 'packageEntity',
+ 'packageFiles',
+ 'isLoading',
+ 'canDelete',
+ 'destroyPath',
+ 'svgPath',
+ 'npmPath',
+ 'npmHelpPath',
+ ]),
+ isValidPackage() {
+ return Boolean(this.packageEntity.name);
+ },
+ canDeletePackage() {
+ return this.canDelete && this.destroyPath;
+ },
+ filesTableRows() {
+ return this.packageFiles.map(x => ({
+ name: x.file_name,
+ downloadPath: x.download_path,
+ size: this.formatSize(x.size),
+ created: x.created_at,
+ }));
+ },
+ tracking() {
+ return {
+ category: packageTypeToTrackCategory(this.packageEntity.package_type),
+ };
+ },
+ hasVersions() {
+ return this.packageEntity.versions?.length > 0;
+ },
+ packageDependencies() {
+ return this.packageEntity.dependency_links || [];
+ },
+ showDependencies() {
+ return this.packageEntity.package_type === PackageType.NUGET;
+ },
+ showFiles() {
+ return this.packageEntity?.package_type !== PackageType.COMPOSER;
+ },
+ },
+ methods: {
+ ...mapActions(['fetchPackageVersions']),
+ formatSize(size) {
+ return numberToHumanSize(size);
+ },
+ cancelDelete() {
+ this.$refs.deleteModal.hide();
+ },
+ getPackageVersions() {
+ if (!this.packageEntity.versions) {
+ this.fetchPackageVersions();
+ }
+ },
+ },
+ i18n: {
+ deleteModalTitle: s__(`PackageRegistry|Delete Package Version`),
+ deleteModalContent: s__(
+ `PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?`,
+ ),
+ },
+ filesTableHeaderFields: [
+ {
+ key: 'name',
+ label: __('Name'),
+ tdClass: 'd-flex align-items-center',
+ },
+ {
+ key: 'size',
+ label: __('Size'),
+ },
+ {
+ key: 'created',
+ label: __('Created'),
+ class: 'text-right',
+ },
+ ],
+};
+</script>
+
+<template>
+ <gl-empty-state
+ v-if="!isValidPackage"
+ :title="s__('PackageRegistry|Unable to load package')"
+ :description="s__('PackageRegistry|There was a problem fetching the details for this package.')"
+ :svg-path="svgPath"
+ />
+
+ <div v-else class="packages-app">
+ <div class="detail-page-header d-flex justify-content-between flex-column flex-sm-row">
+ <package-title />
+
+ <div class="mt-sm-2">
+ <gl-button
+ v-if="canDeletePackage"
+ v-gl-modal="'delete-modal'"
+ class="js-delete-button"
+ variant="danger"
+ category="primary"
+ data-qa-selector="delete_button"
+ >
+ {{ __('Delete') }}
+ </gl-button>
+ </div>
+ </div>
+
+ <gl-tabs>
+ <gl-tab :title="__('Detail')">
+ <div data-qa-selector="package_information_content">
+ <package-history :package-entity="packageEntity" :project-name="projectName" />
+
+ <installation-commands
+ :package-entity="packageEntity"
+ :npm-path="npmPath"
+ :npm-help-path="npmHelpPath"
+ />
+
+ <additional-metadata :package-entity="packageEntity" />
+ </div>
+
+ <template v-if="showFiles">
+ <h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3>
+ <gl-table
+ :fields="$options.filesTableHeaderFields"
+ :items="filesTableRows"
+ tbody-tr-class="js-file-row"
+ >
+ <template #cell(name)="{ item }">
+ <gl-link
+ :href="item.downloadPath"
+ class="js-file-download gl-relative"
+ @click="track($options.trackingActions.PULL_PACKAGE)"
+ >
+ <file-icon
+ :file-name="item.name"
+ css-classes="gl-relative file-icon"
+ class="gl-mr-1 gl-relative"
+ />
+ <span class="gl-relative">{{ item.name }}</span>
+ </gl-link>
+ </template>
+
+ <template #cell(created)="{ item }">
+ <span v-gl-tooltip :title="tooltipTitle(item.created)">{{
+ timeFormatted(item.created)
+ }}</span>
+ </template>
+ </gl-table>
+ </template>
+ </gl-tab>
+
+ <gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
+ <template #title>
+ <span>{{ __('Dependencies') }}</span>
+ <gl-badge size="sm" data-testid="dependencies-badge">{{
+ packageDependencies.length
+ }}</gl-badge>
+ </template>
+
+ <template v-if="packageDependencies.length > 0">
+ <dependency-row
+ v-for="(dep, index) in packageDependencies"
+ :key="index"
+ :dependency="dep"
+ />
+ </template>
+
+ <p v-else class="gl-mt-3" data-testid="no-dependencies-message">
+ {{ s__('PackageRegistry|This NuGet package has no dependencies.') }}
+ </p>
+ </gl-tab>
+
+ <gl-tab
+ :title="__('Other versions')"
+ title-item-class="js-versions-tab"
+ @click="getPackageVersions"
+ >
+ <template v-if="isLoading && !hasVersions">
+ <packages-list-loader />
+ </template>
+
+ <template v-else-if="hasVersions">
+ <package-list-row
+ v-for="v in packageEntity.versions"
+ :key="v.id"
+ :package-entity="{ name: packageEntity.name, ...v }"
+ :package-link="v.id.toString()"
+ :disable-delete="true"
+ :show-package-type="false"
+ />
+ </template>
+
+ <p v-else class="gl-mt-3" data-testid="no-versions-message">
+ {{ s__('PackageRegistry|There are no other versions of this package.') }}
+ </p>
+ </gl-tab>
+ </gl-tabs>
+
+ <gl-modal ref="deleteModal" class="js-delete-modal" modal-id="delete-modal">
+ <template #modal-title>{{ $options.i18n.deleteModalTitle }}</template>
+ <gl-sprintf :message="$options.i18n.deleteModalContent">
+ <template #version>
+ <strong>{{ packageEntity.version }}</strong>
+ </template>
+
+ <template #name>
+ <strong>{{ packageEntity.name }}</strong>
+ </template>
+ </gl-sprintf>
+
+ <div slot="modal-footer" class="w-100">
+ <div class="float-right">
+ <gl-button @click="cancelDelete()">{{ __('Cancel') }}</gl-button>
+ <gl-button
+ ref="modal-delete-button"
+ data-method="delete"
+ :to="destroyPath"
+ variant="danger"
+ category="primary"
+ data-qa-selector="delete_modal_button"
+ @click="track($options.trackingActions.DELETE_PACKAGE)"
+ >
+ {{ __('Delete') }}
+ </gl-button>
+ </div>
+ </div>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/code_instruction.vue b/app/assets/javascripts/packages/details/components/code_instruction.vue
new file mode 100644
index 00000000000..0719ddfcd2b
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/code_instruction.vue
@@ -0,0 +1,63 @@
+<script>
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import Tracking from '~/tracking';
+import { TrackingLabels } from '../constants';
+
+export default {
+ name: 'CodeInstruction',
+ components: {
+ ClipboardButton,
+ },
+ mixins: [
+ Tracking.mixin({
+ label: TrackingLabels.CODE_INSTRUCTION,
+ }),
+ ],
+ props: {
+ instruction: {
+ type: String,
+ required: true,
+ },
+ copyText: {
+ type: String,
+ required: true,
+ },
+ multiline: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ trackingAction: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ methods: {
+ trackCopy() {
+ if (this.trackingAction) {
+ this.track(this.trackingAction);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="!multiline" class="input-group gl-mb-3">
+ <input
+ :value="instruction"
+ type="text"
+ class="form-control monospace js-instruction-input"
+ readonly
+ @copy="trackCopy"
+ />
+ <span class="input-group-append js-instruction-button" @click="trackCopy">
+ <clipboard-button :text="instruction" :title="copyText" class="input-group-text" />
+ </span>
+ </div>
+
+ <div v-else>
+ <pre class="js-instruction-pre" @copy="trackCopy">{{ instruction }}</pre>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/composer_installation.vue b/app/assets/javascripts/packages/details/components/composer_installation.vue
new file mode 100644
index 00000000000..1934da149ce
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/composer_installation.vue
@@ -0,0 +1,60 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import CodeInstruction from './code_instruction.vue';
+import { TrackingActions } from '../constants';
+
+export default {
+ name: 'ComposerInstallation',
+ components: {
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['composerHelpPath']),
+ ...mapGetters(['composerRegistryInclude', 'composerPackageInclude']),
+ },
+ i18n: {
+ registryInclude: s__('PackageRegistry|composer.json registry include'),
+ copyRegistryInclude: s__('PackageRegistry|Copy registry include'),
+ packageInclude: s__('PackageRegistry|composer.json require package include'),
+ copyPackageInclude: s__('PackageRegistry|Copy require package include'),
+ infoLine: s__(
+ 'PackageRegistry|For more information on Composer packages in GitLab, %{linkStart}see the documentation.%{linkEnd}',
+ ),
+ },
+ trackingActions: { ...TrackingActions },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
+ <h4 class="gl-font-base" data-testid="registry-include-title">
+ {{ $options.i18n.registryInclude }}
+ </h4>
+
+ <code-instruction
+ :instruction="composerRegistryInclude"
+ :copy-text="$options.i18n.copyRegistryInclude"
+ :tracking-action="$options.trackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
+ />
+ <h4 class="gl-font-base" data-testid="package-include-title">
+ {{ $options.i18n.packageInclude }}
+ </h4>
+ <code-instruction
+ :instruction="composerPackageInclude"
+ :copy-text="$options.i18n.copyPackageInclude"
+ :tracking-action="$options.trackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
+ />
+ <span data-testid="help-text">
+ <gl-sprintf :message="$options.i18n.infoLine">
+ <template #link="{ content }">
+ <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/conan_installation.vue b/app/assets/javascripts/packages/details/components/conan_installation.vue
new file mode 100644
index 00000000000..cff7d73f1e8
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/conan_installation.vue
@@ -0,0 +1,56 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import CodeInstruction from './code_instruction.vue';
+import { TrackingActions } from '../constants';
+
+export default {
+ name: 'ConanInstallation',
+ components: {
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['conanHelpPath']),
+ ...mapGetters(['conanInstallationCommand', 'conanSetupCommand']),
+ },
+ i18n: {
+ helpText: s__(
+ 'PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}.',
+ ),
+ },
+ trackingActions: { ...TrackingActions },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
+ <h4 class="gl-font-base">
+ {{ s__('PackageRegistry|Conan Command') }}
+ </h4>
+
+ <code-instruction
+ :instruction="conanInstallationCommand"
+ :copy-text="s__('PackageRegistry|Copy Conan Command')"
+ :tracking-action="$options.trackingActions.COPY_CONAN_COMMAND"
+ />
+
+ <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
+ <h4 class="gl-font-base">
+ {{ s__('PackageRegistry|Add Conan Remote') }}
+ </h4>
+ <code-instruction
+ :instruction="conanSetupCommand"
+ :copy-text="s__('PackageRegistry|Copy Conan Setup Command')"
+ :tracking-action="$options.trackingActions.COPY_CONAN_SETUP_COMMAND"
+ />
+ <gl-sprintf :message="$options.i18n.helpText">
+ <template #link="{ content }">
+ <gl-link :href="conanHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/dependency_row.vue b/app/assets/javascripts/packages/details/components/dependency_row.vue
new file mode 100644
index 00000000000..788673d2881
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/dependency_row.vue
@@ -0,0 +1,35 @@
+<script>
+export default {
+ name: 'DependencyRow',
+ props: {
+ dependency: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ showVersion() {
+ return Boolean(this.dependency.version_pattern);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-responsive-table-row">
+ <div class="table-section section-50">
+ <strong class="gl-text-body">{{ dependency.name }}</strong>
+ <span v-if="dependency.target_framework" data-testid="target-framework"
+ >({{ dependency.target_framework }})</span
+ >
+ </div>
+
+ <div
+ v-if="showVersion"
+ class="table-section section-50 gl-display-flex justify-content-md-end"
+ data-testid="version-pattern"
+ >
+ <span class="gl-text-body">{{ dependency.version_pattern }}</span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/history_element.vue b/app/assets/javascripts/packages/details/components/history_element.vue
new file mode 100644
index 00000000000..8a51c1528cf
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/history_element.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+
+export default {
+ name: 'HistoryElement',
+ components: {
+ GlIcon,
+ TimelineEntryItem,
+ },
+
+ props: {
+ icon: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <timeline-entry-item class="system-note note-wrapper gl-mb-6!">
+ <div class="timeline-icon">
+ <gl-icon :name="icon" />
+ </div>
+ <div class="timeline-content">
+ <div class="note-header">
+ <span>
+ <slot></slot>
+ </span>
+ </div>
+ <div class="note-body"></div>
+ </div>
+ </timeline-entry-item>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/installation_commands.vue b/app/assets/javascripts/packages/details/components/installation_commands.vue
new file mode 100644
index 00000000000..138103020a7
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/installation_commands.vue
@@ -0,0 +1,53 @@
+<script>
+import ConanInstallation from './conan_installation.vue';
+import MavenInstallation from './maven_installation.vue';
+import NpmInstallation from './npm_installation.vue';
+import NugetInstallation from './nuget_installation.vue';
+import PypiInstallation from './pypi_installation.vue';
+import ComposerInstallation from './composer_installation.vue';
+import { PackageType } from '../../shared/constants';
+
+export default {
+ name: 'InstallationCommands',
+ components: {
+ [PackageType.CONAN]: ConanInstallation,
+ [PackageType.MAVEN]: MavenInstallation,
+ [PackageType.NPM]: NpmInstallation,
+ [PackageType.NUGET]: NugetInstallation,
+ [PackageType.PYPI]: PypiInstallation,
+ [PackageType.COMPOSER]: ComposerInstallation,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ npmPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ npmHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ installationComponent() {
+ return this.$options.components[this.packageEntity.package_type];
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="installationComponent">
+ <component
+ :is="installationComponent"
+ :name="packageEntity.name"
+ :registry-url="npmPath"
+ :help-url="npmHelpPath"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/maven_installation.vue b/app/assets/javascripts/packages/details/components/maven_installation.vue
new file mode 100644
index 00000000000..d6641c886a0
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/maven_installation.vue
@@ -0,0 +1,84 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import CodeInstruction from './code_instruction.vue';
+import { TrackingActions } from '../constants';
+
+export default {
+ name: 'MavenInstallation',
+ components: {
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['mavenHelpPath']),
+ ...mapGetters(['mavenInstallationXml', 'mavenInstallationCommand', 'mavenSetupXml']),
+ },
+ i18n: {
+ xmlText: s__(
+ `PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block.`,
+ ),
+ setupText: s__(
+ `PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file.`,
+ ),
+ helpText: s__(
+ 'PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}.',
+ ),
+ },
+ trackingActions: { ...TrackingActions },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
+
+ <h4 class="gl-font-base">
+ {{ s__('PackageRegistry|Maven XML') }}
+ </h4>
+ <p>
+ <gl-sprintf :message="$options.i18n.xmlText">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <code-instruction
+ :instruction="mavenInstallationXml"
+ :copy-text="s__('PackageRegistry|Copy Maven XML')"
+ multiline
+ :tracking-action="$options.trackingActions.COPY_MAVEN_XML"
+ />
+
+ <h4 class="gl-font-base">
+ {{ s__('PackageRegistry|Maven Command') }}
+ </h4>
+ <code-instruction
+ :instruction="mavenInstallationCommand"
+ :copy-text="s__('PackageRegistry|Copy Maven command')"
+ :tracking-action="$options.trackingActions.COPY_MAVEN_COMMAND"
+ />
+
+ <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
+ <p>
+ <gl-sprintf :message="$options.i18n.setupText">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <code-instruction
+ :instruction="mavenSetupXml"
+ :copy-text="s__('PackageRegistry|Copy Maven registry XML')"
+ multiline
+ :tracking-action="$options.trackingActions.COPY_MAVEN_SETUP"
+ />
+ <gl-sprintf :message="$options.i18n.helpText">
+ <template #link="{ content }">
+ <gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/npm_installation.vue b/app/assets/javascripts/packages/details/components/npm_installation.vue
new file mode 100644
index 00000000000..d7ff8428370
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/npm_installation.vue
@@ -0,0 +1,80 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import CodeInstruction from './code_instruction.vue';
+import { NpmManager, TrackingActions } from '../constants';
+
+export default {
+ name: 'NpmInstallation',
+ components: {
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['npmHelpPath']),
+ ...mapGetters(['npmInstallationCommand', 'npmSetupCommand']),
+ npmCommand() {
+ return this.npmInstallationCommand(NpmManager.NPM);
+ },
+ npmSetup() {
+ return this.npmSetupCommand(NpmManager.NPM);
+ },
+ yarnCommand() {
+ return this.npmInstallationCommand(NpmManager.YARN);
+ },
+ yarnSetupCommand() {
+ return this.npmSetupCommand(NpmManager.YARN);
+ },
+ },
+ i18n: {
+ helpText: s__(
+ 'PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more.',
+ ),
+ },
+ trackingActions: { ...TrackingActions },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
+ <h4 class="gl-font-base">{{ s__('PackageRegistry|npm command') }}</h4>
+
+ <code-instruction
+ :instruction="npmCommand"
+ :copy-text="s__('PackageRegistry|Copy npm command')"
+ :tracking-action="$options.trackingActions.COPY_NPM_INSTALL_COMMAND"
+ />
+
+ <h4 class="gl-font-base">{{ s__('PackageRegistry|yarn command') }}</h4>
+ <code-instruction
+ :instruction="yarnCommand"
+ :copy-text="s__('PackageRegistry|Copy yarn command')"
+ :tracking-action="$options.trackingActions.COPY_YARN_INSTALL_COMMAND"
+ />
+
+ <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
+
+ <h4 class="gl-font-base">{{ s__('PackageRegistry|npm command') }}</h4>
+ <code-instruction
+ :instruction="npmSetup"
+ :copy-text="s__('PackageRegistry|Copy npm setup command')"
+ :tracking-action="$options.trackingActions.COPY_NPM_SETUP_COMMAND"
+ />
+
+ <h4 class="gl-font-base">{{ s__('PackageRegistry|yarn command') }}</h4>
+ <code-instruction
+ :instruction="yarnSetupCommand"
+ :copy-text="s__('PackageRegistry|Copy yarn setup command')"
+ :tracking-action="$options.trackingActions.COPY_YARN_SETUP_COMMAND"
+ />
+
+ <gl-sprintf :message="$options.i18n.helpText">
+ <template #link="{ content }">
+ <gl-link :href="npmHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/nuget_installation.vue b/app/assets/javascripts/packages/details/components/nuget_installation.vue
new file mode 100644
index 00000000000..150b6e3ab0f
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/nuget_installation.vue
@@ -0,0 +1,55 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import CodeInstruction from './code_instruction.vue';
+import { TrackingActions } from '../constants';
+
+export default {
+ name: 'NugetInstallation',
+ components: {
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['nugetHelpPath']),
+ ...mapGetters(['nugetInstallationCommand', 'nugetSetupCommand']),
+ },
+ i18n: {
+ helpText: s__(
+ 'PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}.',
+ ),
+ },
+ trackingActions: { ...TrackingActions },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
+ <h4 class="gl-font-base">
+ {{ s__('PackageRegistry|NuGet Command') }}
+ </h4>
+ <code-instruction
+ :instruction="nugetInstallationCommand"
+ :copy-text="s__('PackageRegistry|Copy NuGet Command')"
+ :tracking-action="$options.trackingActions.COPY_NUGET_INSTALL_COMMAND"
+ />
+ <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
+ <h4 class="gl-font-base">
+ {{ s__('PackageRegistry|Add NuGet Source') }}
+ </h4>
+
+ <code-instruction
+ :instruction="nugetSetupCommand"
+ :copy-text="s__('PackageRegistry|Copy NuGet Setup Command')"
+ :tracking-action="$options.trackingActions.COPY_NUGET_SETUP_COMMAND"
+ />
+ <gl-sprintf :message="$options.i18n.helpText">
+ <template #link="{ content }">
+ <gl-link :href="nugetHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/package_history.vue b/app/assets/javascripts/packages/details/components/package_history.vue
new file mode 100644
index 00000000000..96ce106884d
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/package_history.vue
@@ -0,0 +1,114 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import HistoryElement from './history_element.vue';
+
+export default {
+ name: 'PackageHistory',
+ i18n: {
+ createdOn: s__('PackageRegistry|%{name} version %{version} was created %{datetime}'),
+ updatedAtText: s__('PackageRegistry|%{name} version %{version} was updated %{datetime}'),
+ commitText: s__('PackageRegistry|Commit %{link} on branch %{branch}'),
+ pipelineText: s__('PackageRegistry|Pipeline %{link} triggered %{datetime} by %{author}'),
+ publishText: s__('PackageRegistry|Published to the %{project} Package Registry %{datetime}'),
+ },
+ components: {
+ GlLink,
+ GlSprintf,
+ HistoryElement,
+ TimeAgoTooltip,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ projectName: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ showDescription: false,
+ };
+ },
+ computed: {
+ packagePipeline() {
+ return this.packageEntity.pipeline?.id ? this.packageEntity.pipeline : null;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="issuable-discussion">
+ <h3 class="gl-font-lg" data-testid="title">{{ __('History') }}</h3>
+ <ul class="timeline main-notes-list notes gl-mb-4" data-testid="timeline">
+ <history-element icon="clock" data-testid="created-on">
+ <gl-sprintf :message="$options.i18n.createdOn">
+ <template #name>
+ <strong>{{ packageEntity.name }}</strong>
+ </template>
+ <template #version>
+ <strong>{{ packageEntity.version }}</strong>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packageEntity.created_at" />
+ </template>
+ </gl-sprintf>
+ </history-element>
+ <history-element icon="pencil" data-testid="updated-at">
+ <gl-sprintf :message="$options.i18n.updatedAtText">
+ <template #name>
+ <strong>{{ packageEntity.name }}</strong>
+ </template>
+ <template #version>
+ <strong>{{ packageEntity.version }}</strong>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packageEntity.updated_at" />
+ </template>
+ </gl-sprintf>
+ </history-element>
+ <template v-if="packagePipeline">
+ <history-element icon="commit" data-testid="commit">
+ <gl-sprintf :message="$options.i18n.commitText">
+ <template #link>
+ <gl-link :href="packagePipeline.project.commit_url">{{
+ packagePipeline.sha
+ }}</gl-link>
+ </template>
+ <template #branch>
+ <strong>{{ packagePipeline.ref }}</strong>
+ </template>
+ </gl-sprintf>
+ </history-element>
+ <history-element icon="pipeline" data-testid="pipeline">
+ <gl-sprintf :message="$options.i18n.pipelineText">
+ <template #link>
+ <gl-link :href="packagePipeline.project.pipeline_url"
+ >#{{ packagePipeline.id }}</gl-link
+ >
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packagePipeline.created_at" />
+ </template>
+ <template #author>{{ packagePipeline.user.name }}</template>
+ </gl-sprintf>
+ </history-element>
+ </template>
+ <history-element icon="package" data-testid="published">
+ <gl-sprintf :message="$options.i18n.publishText">
+ <template #project>
+ <strong>{{ projectName }}</strong>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packageEntity.created_at" />
+ </template>
+ </gl-sprintf>
+ </history-element>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/package_title.vue b/app/assets/javascripts/packages/details/components/package_title.vue
new file mode 100644
index 00000000000..d07883e3e7a
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/package_title.vue
@@ -0,0 +1,112 @@
+<script>
+import { mapState, mapGetters } from 'vuex';
+import { GlAvatar, GlIcon, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import PackageTags from '../../shared/components/package_tags.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { __ } from '~/locale';
+
+export default {
+ name: 'PackageTitle',
+ components: {
+ GlAvatar,
+ GlIcon,
+ GlLink,
+ GlSprintf,
+ PackageTags,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [timeagoMixin],
+ computed: {
+ ...mapState(['packageEntity', 'packageFiles']),
+ ...mapGetters(['packageTypeDisplay', 'packagePipeline', 'packageIcon']),
+ hasTagsToDisplay() {
+ return Boolean(this.packageEntity.tags && this.packageEntity.tags.length);
+ },
+ totalSize() {
+ return numberToHumanSize(this.packageFiles.reduce((acc, p) => acc + p.size, 0));
+ },
+ },
+ i18n: {
+ packageInfo: __('v%{version} published %{timeAgo}'),
+ },
+};
+</script>
+
+<template>
+ <div class="gl-flex-direction-column">
+ <div class="gl-display-flex">
+ <gl-avatar
+ v-if="packageIcon"
+ :src="packageIcon"
+ shape="rect"
+ class="gl-align-self-center gl-mr-4"
+ data-testid="package-icon"
+ />
+
+ <div class="gl-display-flex gl-flex-direction-column">
+ <h1 class="gl-font-size-h1 gl-mt-3 gl-mb-2">
+ {{ packageEntity.name }}
+ </h1>
+
+ <div class="gl-display-flex gl-align-items-center gl-text-gray-500">
+ <gl-icon name="eye" class="gl-mr-3" />
+ <gl-sprintf :message="$options.i18n.packageInfo">
+ <template #version>
+ {{ packageEntity.version }}
+ </template>
+
+ <template #timeAgo>
+ <span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
+ &nbsp;{{ timeFormatted(packageEntity.created_at) }}
+ </span>
+ </template>
+ </gl-sprintf>
+ </div>
+ </div>
+ </div>
+
+ <div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mb-3">
+ <div v-if="packageTypeDisplay" class="gl-display-flex gl-align-items-center gl-mr-5">
+ <gl-icon name="package" class="gl-text-gray-500 gl-mr-3" />
+ <span data-testid="package-type" class="gl-font-weight-bold">{{ packageTypeDisplay }}</span>
+ </div>
+
+ <div v-if="hasTagsToDisplay" class="gl-display-flex gl-align-items-center gl-mr-5">
+ <package-tags :tag-display-limit="1" :tags="packageEntity.tags" />
+ </div>
+
+ <div v-if="packagePipeline" class="gl-display-flex gl-align-items-center gl-mr-5">
+ <gl-icon name="review-list" class="gl-text-gray-500 gl-mr-3" />
+ <gl-link
+ data-testid="pipeline-project"
+ :href="packagePipeline.project.web_url"
+ class="gl-font-weight-bold text-truncate"
+ >
+ {{ packagePipeline.project.name }}
+ </gl-link>
+ </div>
+
+ <div
+ v-if="packagePipeline"
+ data-testid="package-ref"
+ class="gl-display-flex gl-align-items-center gl-mr-5"
+ >
+ <gl-icon name="branch" class="gl-text-gray-500 gl-mr-3" />
+ <span
+ v-gl-tooltip
+ class="gl-font-weight-bold text-truncate mw-xs"
+ :title="packagePipeline.ref"
+ >{{ packagePipeline.ref }}</span
+ >
+ </div>
+
+ <div class="gl-display-flex gl-align-items-center gl-mr-5">
+ <gl-icon name="disk" class="gl-text-gray-500 gl-mr-3" />
+ <span data-testid="package-size" class="gl-font-weight-bold">{{ totalSize }}</span>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/pypi_installation.vue b/app/assets/javascripts/packages/details/components/pypi_installation.vue
new file mode 100644
index 00000000000..f1c619fd6d3
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/pypi_installation.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import { s__ } from '~/locale';
+import CodeInstruction from './code_instruction.vue';
+import { TrackingActions } from '../constants';
+
+export default {
+ name: 'PyPiInstallation',
+ components: {
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['pypiHelpPath']),
+ ...mapGetters(['pypiPipCommand', 'pypiSetupCommand']),
+ },
+ i18n: {
+ setupText: s__(
+ `PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file.`,
+ ),
+ helpText: s__(
+ 'PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}.',
+ ),
+ },
+ trackingActions: { ...TrackingActions },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
+
+ <h4 class="gl-font-base">
+ {{ s__('PackageRegistry|Pip Command') }}
+ </h4>
+
+ <code-instruction
+ :instruction="pypiPipCommand"
+ :copy-text="s__('PackageRegistry|Copy Pip command')"
+ data-testid="pip-command"
+ :tracking-action="$options.trackingActions.COPY_PIP_INSTALL_COMMAND"
+ />
+
+ <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
+ <p>
+ <gl-sprintf :message="$options.i18n.setupText">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <code-instruction
+ :instruction="pypiSetupCommand"
+ :copy-text="s__('PackageRegistry|Copy .pypirc content')"
+ data-testid="pypi-setup-content"
+ multiline
+ :tracking-action="$options.trackingActions.COPY_PYPI_SETUP_COMMAND"
+ />
+ <gl-sprintf :message="$options.i18n.helpText">
+ <template #link="{ content }">
+ <gl-link :href="pypiHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+</template>