diff options
Diffstat (limited to 'app/assets/javascripts')
27 files changed, 471 insertions, 150 deletions
diff --git a/app/assets/javascripts/google_cloud/components/deployments_service_table.vue b/app/assets/javascripts/google_cloud/components/deployments_service_table.vue new file mode 100644 index 00000000000..7d27d7cf6b2 --- /dev/null +++ b/app/assets/javascripts/google_cloud/components/deployments_service_table.vue @@ -0,0 +1,61 @@ +<script> +import { GlButton, GlTable } from '@gitlab/ui'; +import { __ } from '~/locale'; + +const i18n = { + cloudRun: __('Cloud Run'), + cloudRunDescription: __('Deploy container based web apps on Google managed clusters'), + cloudStorage: __('Cloud Storage'), + cloudStorageDescription: __('Deploy static assets and resources to Google managed CDN'), + deployments: __('Deployments'), + deploymentsDescription: __( + 'Configure pipelines to deploy web apps, backend services, APIs and static resources to Google Cloud', + ), + configureViaMergeRequest: __('Configure via Merge Request'), + service: __('Service'), + description: __('Description'), +}; + +export default { + components: { GlButton, GlTable }, + props: { + cloudRunUrl: { + type: String, + required: true, + }, + cloudStorageUrl: { + type: String, + required: true, + }, + }, + fields: [ + { key: 'title', label: i18n.service }, + { key: 'description', label: i18n.description }, + { key: 'action', label: '' }, + ], + items: [ + { + title: i18n.cloudRun, + description: i18n.cloudRunDescription, + action: { title: i18n.configureViaMergeRequest, disabled: true }, + }, + { + title: i18n.cloudStorage, + description: i18n.cloudStorageDescription, + action: { title: i18n.configureViaMergeRequest, disabled: true }, + }, + ], + i18n, +}; +</script> +<template> + <div class="gl-mx-3"> + <h2 class="gl-font-size-h2">{{ $options.i18n.deployments }}</h2> + <p>{{ $options.i18n.deploymentsDescription }}</p> + <gl-table :fields="$options.fields" :items="$options.items"> + <template #cell(action)="{ value }"> + <gl-button :disabled="value.disabled">{{ value.title }}</gl-button> + </template> + </gl-table> + </div> +</template> diff --git a/app/assets/javascripts/google_cloud/components/home.vue b/app/assets/javascripts/google_cloud/components/home.vue index 05f39de66ee..8ef110dcf22 100644 --- a/app/assets/javascripts/google_cloud/components/home.vue +++ b/app/assets/javascripts/google_cloud/components/home.vue @@ -1,11 +1,13 @@ <script> import { GlTabs, GlTab } from '@gitlab/ui'; +import DeploymentsServiceTable from './deployments_service_table.vue'; import ServiceAccountsList from './service_accounts_list.vue'; export default { components: { GlTabs, GlTab, + DeploymentsServiceTable, ServiceAccountsList, }, props: { @@ -21,6 +23,14 @@ export default { type: String, required: true, }, + deploymentsCloudRunUrl: { + type: String, + required: true, + }, + deploymentsCloudStorageUrl: { + type: String, + required: true, + }, }, }; </script> @@ -35,7 +45,12 @@ export default { :empty-illustration-url="emptyIllustrationUrl" /> </gl-tab> - <gl-tab :title="__('Deployments')" disabled /> + <gl-tab :title="__('Deployments')"> + <deployments-service-table + :cloud-run-url="deploymentsCloudRunUrl" + :cloud-storage-url="deploymentsCloudStorageUrl" + /> + </gl-tab> <gl-tab :title="__('Services')" disabled /> </gl-tabs> </template> diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js index f255f8a084c..b6a6720e7a1 100644 --- a/app/assets/javascripts/group.js +++ b/app/assets/javascripts/group.js @@ -13,11 +13,8 @@ export default class Group { this.updateGroupPathSlugHandler = this.updateGroupPathSlug.bind(this); this.groupNames.forEach((groupName) => { - if (groupName.value === '') { - groupName.addEventListener('keyup', this.updateHandler); - - groupName.addEventListener('keyup', this.updateGroupPathSlugHandler); - } + groupName.addEventListener('keyup', this.updateHandler); + groupName.addEventListener('keyup', this.updateGroupPathSlugHandler); }); this.groupPaths.forEach((groupPath) => { diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js index e6c197a30dd..ca5bd8d6964 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js @@ -4,6 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils'; import PerformancePlugin from '~/performance/vue_performance_plugin'; import Translate from '~/vue_shared/translate'; import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue'; +import { renderBreadcrumb } from '~/packages_and_registries/shared/utils'; import { apolloProvider } from './graphql/index'; import RegistryExplorer from './pages/index.vue'; import createRouter from './router'; @@ -84,38 +85,8 @@ export default () => { }, }); - const attachBreadcrumb = () => { - const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li'); - const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1]; - const crumbs = [breadCrumbEl.querySelector('h2')]; - const nestedBreadcrumbEl = document.createElement('div'); - breadCrumbEl.replaceChild(nestedBreadcrumbEl, breadCrumbEl.querySelector('h2')); - return new Vue({ - el: nestedBreadcrumbEl, - router, - apolloProvider, - components: { - RegistryBreadcrumb, - }, - render(createElement) { - // FIXME(@tnir): this is a workaround until the MR gets merged: - // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115 - const parentEl = breadCrumbEl.parentElement.parentElement; - if (parentEl) { - parentEl.classList.remove('breadcrumbs-container'); - parentEl.classList.add('gl-display-flex'); - parentEl.classList.add('w-100'); - } - // End of FIXME(@tnir) - return createElement('registry-breadcrumb', { - class: breadCrumbEl.className, - props: { - crumbs, - }, - }); - }, - }); + return { + attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb), + attachMainComponent, }; - - return { attachBreadcrumb, attachMainComponent }; }; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue index 95b09b25678..7479f748a56 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue @@ -26,7 +26,7 @@ export default { GlSprintf, GlFormRadioGroup, }, - inject: ['npmPath'], + inject: ['npmInstanceUrl'], props: { packageEntity: { type: Object, @@ -66,7 +66,9 @@ export default { npmSetupCommand(type, endpointType) { const scope = this.packageEntity.name.substring(0, this.packageEntity.name.indexOf('/')); const npmPathForEndpoint = - endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE ? this.npmPath : this.packageEntity.npmUrl; + endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE + ? this.npmInstanceUrl + : this.packageEntity.npmUrl; if (type === NPM_PACKAGE_MANAGER) { return `echo ${scope}:registry=${npmPathForEndpoint}/ >> .npmrc`; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue index 6fd96c0654f..6222c2e73d7 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; +import { GlButton, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import { @@ -18,7 +18,6 @@ export default { name: 'PackageListRow', components: { GlButton, - GlLink, GlSprintf, GlTruncate, PackageTags, @@ -42,9 +41,8 @@ export default { packageType() { return getPackageTypeLabel(this.packageEntity.packageType); }, - packageLink() { - const { project, id } = this.packageEntity; - return `${project?.webUrl}/-/packages/${getIdFromGraphQLId(id)}`; + packageId() { + return getIdFromGraphQLId(this.packageEntity.id); }, pipeline() { return this.packageEntity?.pipelines?.nodes[0]; @@ -61,6 +59,9 @@ export default { disabledRow() { return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS; }, + routerLinkEvent() { + return this.disabledRow ? '' : 'click'; + }, }, i18n: { erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'), @@ -73,14 +74,15 @@ export default { <list-item data-qa-selector="package_row" :disabled="disabledRow"> <template #left-primary> <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"> - <gl-link - :href="packageLink" + <router-link class="gl-text-body gl-min-w-0" + data-testid="details-link" data-qa-selector="package_link" - :disabled="disabledRow" + :event="routerLinkEvent" + :to="{ name: 'details', params: { id: packageId } }" > <gl-truncate :text="packageEntity.name" /> - </gl-link> + </router-link> <gl-button v-if="showWarningIcon" diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js index af4e586231c..c4d331fa384 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -74,6 +74,7 @@ export const FETCH_PACKAGE_DETAILS_ERROR_MESSAGE = s__( ); export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully'); +export const PACKAGE_REGISTRY_TITLE = __('Package Registry'); export const PACKAGE_ERROR_STATUS = 'ERROR'; export const PACKAGE_DEFAULT_STATUS = 'DEFAULT'; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/index.js b/app/assets/javascripts/packages_and_registries/package_registry/index.js index 7ec931ff9a0..6680e612985 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/index.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/index.js @@ -2,29 +2,59 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index'; import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue'; +import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue'; +import { renderBreadcrumb } from '~/packages_and_registries/shared/utils'; import createRouter from './router'; Vue.use(Translate); export default () => { const el = document.getElementById('js-vue-packages-list'); - const { endpoint, resourceId, fullPath, pageType, emptyListIllustration } = el.dataset; - const router = createRouter(endpoint); + const { + endpoint, + resourceId, + fullPath, + pageType, + emptyListIllustration, + npmInstanceUrl, + projectListUrl, + groupListUrl, + } = el.dataset; const isGroupPage = pageType === 'groups'; - return new Vue({ - el, - router, - apolloProvider, - provide: { - resourceId, - fullPath, - emptyListIllustration, - isGroupPage, - }, - render(createElement) { - return createElement(PackageRegistry); + // This is a mini state to help the breadcrumb have the correct name in the details page + const breadCrumbState = Vue.observable({ + name: '', + updateName(value) { + this.name = value; }, }); + + const router = createRouter(endpoint, breadCrumbState); + + const attachMainComponent = () => + new Vue({ + el, + router, + apolloProvider, + provide: { + resourceId, + fullPath, + emptyListIllustration, + isGroupPage, + npmInstanceUrl, + projectListUrl, + groupListUrl, + breadCrumbState, + }, + render(createElement) { + return createElement(PackageRegistry); + }, + }); + + return { + attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb), + attachMainComponent, + }; }; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js deleted file mode 100644 index d94bbd21035..00000000000 --- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js +++ /dev/null @@ -1,27 +0,0 @@ -import Vue from 'vue'; -import { parseBoolean } from '~/lib/utils/common_utils'; -import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue'; -import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index'; -import Translate from '~/vue_shared/translate'; - -Vue.use(Translate); - -export default () => { - const el = document.getElementById('js-vue-packages-detail-new'); - if (!el) { - return null; - } - - const { canDelete, ...datasetOptions } = el.dataset; - return new Vue({ - el, - apolloProvider, - provide: { - canDelete: parseBoolean(canDelete), - ...datasetOptions, - }, - render(createElement) { - return createElement(PackagesApp); - }, - }); -}; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue index 52fea4bebbe..162b420a784 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue @@ -68,7 +68,7 @@ export default { GlModal: GlModalDirective, }, mixins: [Tracking.mixin()], - inject: ['packageId', 'svgPath', 'projectListUrl', 'groupListUrl'], + inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl', 'breadCrumbState'], trackingActions: { DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION, @@ -100,12 +100,20 @@ export default { error, }); }, + result() { + this.breadCrumbState.updateName( + `${this.packageEntity?.name} v ${this.packageEntity?.version}`, + ); + }, }, }, computed: { projectName() { return this.packageEntity.project?.name; }, + packageId() { + return this.$route.params.id; + }, queryVariables() { return { id: convertToGraphQLId('Packages::Package', this.packageId), @@ -229,7 +237,7 @@ export default { 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" + :svg-path="emptyListIllustration" /> <div v-else-if="!isLoading" class="packages-app"> <package-title :package-entity="packageEntity"> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/router.js b/app/assets/javascripts/packages_and_registries/package_registry/router.js index ea5b740e879..c5ef4f70dd9 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/router.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/router.js @@ -1,10 +1,12 @@ import Vue from 'vue'; import VueRouter from 'vue-router'; import List from '~/packages_and_registries/package_registry/pages/list.vue'; +import Details from '~/packages_and_registries/package_registry/pages/details.vue'; +import { PACKAGE_REGISTRY_TITLE } from '~/packages_and_registries/package_registry/constants'; Vue.use(VueRouter); -export default function createRouter(base) { +export default function createRouter(base, breadCrumbState) { const router = new VueRouter({ base, mode: 'history', @@ -13,9 +15,25 @@ export default function createRouter(base) { name: 'list', path: '/', component: List, + meta: { + nameGenerator: () => PACKAGE_REGISTRY_TITLE, + root: true, + }, + }, + { + name: 'details', + path: '/:id', + component: Details, + meta: { + nameGenerator: () => breadCrumbState.name, + }, }, ], }); + router.afterEach(() => { + breadCrumbState.updateName(''); + }); + return router; } diff --git a/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue index e77eda31596..a1e3c06812c 100644 --- a/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue +++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue @@ -20,8 +20,11 @@ export default { isRootRoute() { return this.$route.name === this.rootRoute.name; }, + detailsRouteName() { + return this.detailsRoute.meta.nameGenerator(); + }, isLoaded() { - return this.isRootRoute || this.$store?.state.imageDetails?.name; + return this.isRootRoute || this.detailsRouteName; }, allCrumbs() { const crumbs = [ @@ -32,7 +35,7 @@ export default { ]; if (!this.isRootRoute) { crumbs.push({ - text: this.detailsRoute.meta.nameGenerator(), + text: this.detailsRouteName, href: this.detailsRoute.meta.path, }); } @@ -45,7 +48,9 @@ export default { <template> <gl-breadcrumb :key="isLoaded" :items="allCrumbs"> <template #separator> - <gl-icon name="angle-right" :size="8" /> + <span class="gl-mx-n5"> + <gl-icon name="angle-right" :size="8" /> + </span> </template> </gl-breadcrumb> </template> diff --git a/app/assets/javascripts/packages_and_registries/shared/utils.js b/app/assets/javascripts/packages_and_registries/shared/utils.js index cf18f655e79..7e963cd0b08 100644 --- a/app/assets/javascripts/packages_and_registries/shared/utils.js +++ b/app/assets/javascripts/packages_and_registries/shared/utils.js @@ -1,3 +1,4 @@ +import Vue from 'vue'; import { queryToObject } from '~/lib/utils/url_utility'; import { FILTERED_SEARCH_TERM } from './constants'; @@ -38,3 +39,37 @@ export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGr return `../commit/${pipeline.sha}`; }; + +export const renderBreadcrumb = (router, apolloProvider, RegistryBreadcrumb) => () => { + const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li'); + const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1]; + const lastCrumb = breadCrumbEl.children[0]; + const crumbs = [lastCrumb]; + const nestedBreadcrumbEl = document.createElement('div'); + breadCrumbEl.replaceChild(nestedBreadcrumbEl, lastCrumb); + return new Vue({ + el: nestedBreadcrumbEl, + router, + apolloProvider, + components: { + RegistryBreadcrumb, + }, + render(createElement) { + // FIXME(@tnir): this is a workaround until the MR gets merged: + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115 + const parentEl = breadCrumbEl.parentElement.parentElement; + if (parentEl) { + parentEl.classList.remove('breadcrumbs-container'); + parentEl.classList.add('gl-display-flex'); + parentEl.classList.add('w-100'); + } + // End of FIXME(@tnir) + return createElement('registry-breadcrumb', { + class: breadCrumbEl.className, + props: { + crumbs, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/pages/groups/packages/index.js b/app/assets/javascripts/pages/groups/packages/index.js new file mode 100644 index 00000000000..cbe08565cfa --- /dev/null +++ b/app/assets/javascripts/pages/groups/packages/index.js @@ -0,0 +1,8 @@ +import packageApp from '~/packages_and_registries/package_registry/index'; + +const app = packageApp(); + +if (app) { + app.attachBreadcrumb(); + app.attachMainComponent(); +} diff --git a/app/assets/javascripts/pages/groups/packages/index/index.js b/app/assets/javascripts/pages/groups/packages/index/index.js deleted file mode 100644 index 174973a9fad..00000000000 --- a/app/assets/javascripts/pages/groups/packages/index/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import packageApp from '~/packages_and_registries/package_registry/index'; - -packageApp(); diff --git a/app/assets/javascripts/pages/projects/packages/packages/index.js b/app/assets/javascripts/pages/projects/packages/packages/index.js new file mode 100644 index 00000000000..cbe08565cfa --- /dev/null +++ b/app/assets/javascripts/pages/projects/packages/packages/index.js @@ -0,0 +1,8 @@ +import packageApp from '~/packages_and_registries/package_registry/index'; + +const app = packageApp(); + +if (app) { + app.attachBreadcrumb(); + app.attachMainComponent(); +} diff --git a/app/assets/javascripts/pages/projects/packages/packages/index/index.js b/app/assets/javascripts/pages/projects/packages/packages/index/index.js deleted file mode 100644 index 174973a9fad..00000000000 --- a/app/assets/javascripts/pages/projects/packages/packages/index/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import packageApp from '~/packages_and_registries/package_registry/index'; - -packageApp(); diff --git a/app/assets/javascripts/pages/projects/packages/packages/show/index.js b/app/assets/javascripts/pages/projects/packages/packages/show/index.js deleted file mode 100644 index 2dee87985cb..00000000000 --- a/app/assets/javascripts/pages/projects/packages/packages/show/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import initPackageDetails from '~/packages_and_registries/package_registry/pages/details'; - -initPackageDetails(); diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue index 3366942f6ad..bb2bac531a7 100644 --- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue @@ -9,7 +9,7 @@ import RegistrationDropdown from '../components/registration/registration_dropdo import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; import RunnerName from '../components/runner_name.vue'; -import RunnerOnlineStat from '../components/stat/runner_online_stat.vue'; +import RunnerStats from '../components/stat/runner_stats.vue'; import RunnerPagination from '../components/runner_pagination.vue'; import RunnerTypeTabs from '../components/runner_type_tabs.vue'; @@ -20,6 +20,9 @@ import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE, + STATUS_ONLINE, + STATUS_OFFLINE, + STATUS_STALE, I18N_FETCH_ERROR, } from '../constants'; import getRunnersQuery from '../graphql/get_runners.query.graphql'; @@ -51,7 +54,7 @@ export default { RunnerFilteredSearchBar, RunnerList, RunnerName, - RunnerOnlineStat, + RunnerStats, RunnerPagination, RunnerTypeTabs, }, @@ -60,10 +63,6 @@ export default { type: String, required: true, }, - activeRunnersCount: { - type: String, - required: true, - }, }, data() { return { @@ -130,6 +129,30 @@ export default { }; }, }, + onlineRunnersTotal: { + ...runnersCountSmartQuery, + variables() { + return { + status: STATUS_ONLINE, + }; + }, + }, + offlineRunnersTotal: { + ...runnersCountSmartQuery, + variables() { + return { + status: STATUS_OFFLINE, + }; + }, + }, + staleRunnersTotal: { + ...runnersCountSmartQuery, + variables() { + return { + status: STATUS_STALE, + }; + }, + }, }, computed: { variables() { @@ -205,7 +228,11 @@ export default { </script> <template> <div> - <runner-online-stat class="gl-py-6 gl-px-5" :value="activeRunnersCount" /> + <runner-stats + :online-runners-count="onlineRunnersTotal" + :offline-runners-count="offlineRunnersTotal" + :stale-runners-count="staleRunnersTotal" + /> <div class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0" diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js index 5ca87aa7a68..3b8a8fe9cd1 100644 --- a/app/assets/javascripts/runner/admin_runners/index.js +++ b/app/assets/javascripts/runner/admin_runners/index.js @@ -25,9 +25,7 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { return null; } - // TODO `activeRunnersCount` should be implemented using a GraphQL API - // https://gitlab.com/gitlab-org/gitlab/-/issues/333806 - const { runnerInstallHelpPage, registrationToken, activeRunnersCount } = el.dataset; + const { runnerInstallHelpPage, registrationToken } = el.dataset; const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(), @@ -43,10 +41,6 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { return h(AdminRunnersApp, { props: { registrationToken, - - // Runner counts are returned as formatted - // strings, we do not use `parseInt`. - activeRunnersCount, }, }); }, diff --git a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue index edcbcb2bf69..0e259807f98 100644 --- a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue +++ b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import { GlDropdownItem, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui'; import { createAlert } from '~/flash'; import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; @@ -10,9 +10,17 @@ import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants'; export default { name: 'RunnerRegistrationTokenReset', + i18n: { + modalTitle: __('Reset registration token'), + modalCopy: __('Are you sure you want to reset the registration token?'), + }, components: { GlDropdownItem, GlLoadingIcon, + GlModal, + }, + directives: { + GlModal: GlModalDirective, }, inject: { groupId: { @@ -22,6 +30,7 @@ export default { default: null, }, }, + modalID: 'token-reset-modal', props: { type: { type: String, @@ -59,14 +68,10 @@ export default { }, }, methods: { + handleModalPrimary() { + this.resetToken(); + }, async resetToken() { - // TODO Replace confirmation with gl-modal - // See: https://gitlab.com/gitlab-org/gitlab/-/issues/333810 - // eslint-disable-next-line no-alert - if (!window.confirm(__('Are you sure you want to reset the registration token?'))) { - return; - } - this.loading = true; try { const { @@ -106,8 +111,15 @@ export default { }; </script> <template> - <gl-dropdown-item @click.capture.native.stop="resetToken"> + <gl-dropdown-item v-gl-modal="$options.modalID"> {{ __('Reset registration token') }} + <gl-modal + :modal-id="$options.modalID" + :title="$options.i18n.modalTitle" + @primary="handleModalPrimary" + > + <p>{{ $options.i18n.modalCopy }}</p> + </gl-modal> <gl-loading-icon v-if="loading" inline /> </gl-dropdown-item> </template> diff --git a/app/assets/javascripts/runner/components/stat/runner_online_stat.vue b/app/assets/javascripts/runner/components/stat/runner_online_stat.vue deleted file mode 100644 index b92b9badef0..00000000000 --- a/app/assets/javascripts/runner/components/stat/runner_online_stat.vue +++ /dev/null @@ -1,17 +0,0 @@ -<script> -import { GlSingleStat } from '@gitlab/ui/dist/charts'; - -export default { - components: { - GlSingleStat, - }, -}; -</script> -<template> - <gl-single-stat - v-bind="$attrs" - variant="success" - :title="s__('Runners|Online Runners')" - :meta-text="s__('Runners|online')" - /> -</template> diff --git a/app/assets/javascripts/runner/components/stat/runner_stats.vue b/app/assets/javascripts/runner/components/stat/runner_stats.vue new file mode 100644 index 00000000000..d3693ee593e --- /dev/null +++ b/app/assets/javascripts/runner/components/stat/runner_stats.vue @@ -0,0 +1,49 @@ +<script> +import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants'; +import RunnerStatusStat from './runner_status_stat.vue'; + +export default { + components: { + RunnerStatusStat, + }, + props: { + onlineRunnersCount: { + type: Number, + required: false, + default: null, + }, + offlineRunnersCount: { + type: Number, + required: false, + default: null, + }, + staleRunnersCount: { + type: Number, + required: false, + default: null, + }, + }, + STATUS_ONLINE, + STATUS_OFFLINE, + STATUS_STALE, +}; +</script> +<template> + <div class="gl-display-flex gl-py-6"> + <runner-status-stat + class="gl-px-5" + :status="$options.STATUS_ONLINE" + :value="onlineRunnersCount" + /> + <runner-status-stat + class="gl-px-5" + :status="$options.STATUS_OFFLINE" + :value="offlineRunnersCount" + /> + <runner-status-stat + class="gl-px-5" + :status="$options.STATUS_STALE" + :value="staleRunnersCount" + /> + </div> +</template> diff --git a/app/assets/javascripts/runner/components/stat/runner_status_stat.vue b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue new file mode 100644 index 00000000000..b77bbe15541 --- /dev/null +++ b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue @@ -0,0 +1,65 @@ +<script> +import { GlSingleStat } from '@gitlab/ui/dist/charts'; +import { s__, formatNumber } from '~/locale'; +import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants'; + +export default { + components: { + GlSingleStat, + }, + props: { + value: { + type: Number, + required: false, + default: null, + }, + status: { + type: String, + required: true, + }, + }, + computed: { + formattedValue() { + if (typeof this.value === 'number') { + return formatNumber(this.value); + } + return '-'; + }, + stat() { + switch (this.status) { + case STATUS_ONLINE: + return { + variant: 'success', + title: s__('Runners|Online runners'), + metaText: s__('Runners|online'), + }; + case STATUS_OFFLINE: + return { + variant: 'muted', + title: s__('Runners|Offline runners'), + metaText: s__('Runners|offline'), + }; + case STATUS_STALE: + return { + variant: 'warning', + title: s__('Runners|Stale runners'), + metaText: s__('Runners|stale'), + }; + default: + return { + title: s__('Runners|Runners'), + }; + } + }, + }, +}; +</script> +<template> + <gl-single-stat + v-if="stat" + :value="formattedValue" + :variant="stat.variant" + :title="stat.title" + :meta-text="stat.metaText" + /> +</template> diff --git a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql index 6da9e276f74..f7bcd683718 100644 --- a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql @@ -13,7 +13,7 @@ query getGroupRunners( $sort: CiRunnerSort ) { group(fullPath: $groupFullPath) { - id + id # Apollo required runners( membership: DESCENDANTS before: $before diff --git a/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql new file mode 100644 index 00000000000..554eb09e372 --- /dev/null +++ b/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql @@ -0,0 +1,20 @@ +query getGroupRunnersCount( + $groupFullPath: ID! + $status: CiRunnerStatus + $type: CiRunnerType + $tagList: [String!] + $search: String +) { + group(fullPath: $groupFullPath) { + id # Apollo required + runners( + membership: DESCENDANTS + status: $status + type: $type + tagList: $tagList + search: $search + ) { + count + } + } +} diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue index f33f28c11e3..3a7b58e3dc9 100644 --- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue +++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue @@ -9,7 +9,7 @@ import RegistrationDropdown from '../components/registration/registration_dropdo import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; import RunnerName from '../components/runner_name.vue'; -import RunnerOnlineStat from '../components/stat/runner_online_stat.vue'; +import RunnerStats from '../components/stat/runner_stats.vue'; import RunnerPagination from '../components/runner_pagination.vue'; import RunnerTypeTabs from '../components/runner_type_tabs.vue'; @@ -19,8 +19,12 @@ import { GROUP_FILTERED_SEARCH_NAMESPACE, GROUP_TYPE, GROUP_RUNNER_COUNT_LIMIT, + STATUS_ONLINE, + STATUS_OFFLINE, + STATUS_STALE, } from '../constants'; import getGroupRunnersQuery from '../graphql/get_group_runners.query.graphql'; +import getGroupRunnersCountQuery from '../graphql/get_group_runners_count.query.graphql'; import { fromUrlQueryToSearch, fromSearchToUrl, @@ -28,6 +32,17 @@ import { } from '../runner_search_utils'; import { captureException } from '../sentry_utils'; +const runnersCountSmartQuery = { + query: getGroupRunnersCountQuery, + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + update(data) { + return data?.group?.runners?.count; + }, + error(error) { + this.reportToSentry(error); + }, +}; + export default { name: 'GroupRunnersApp', components: { @@ -36,7 +51,7 @@ export default { RunnerFilteredSearchBar, RunnerList, RunnerName, - RunnerOnlineStat, + RunnerStats, RunnerPagination, RunnerTypeTabs, }, @@ -89,6 +104,33 @@ export default { this.reportToSentry(error); }, }, + onlineRunnersTotal: { + ...runnersCountSmartQuery, + variables() { + return { + groupFullPath: this.groupFullPath, + status: STATUS_ONLINE, + }; + }, + }, + offlineRunnersTotal: { + ...runnersCountSmartQuery, + variables() { + return { + groupFullPath: this.groupFullPath, + status: STATUS_OFFLINE, + }; + }, + }, + staleRunnersTotal: { + ...runnersCountSmartQuery, + variables() { + return { + groupFullPath: this.groupFullPath, + status: STATUS_STALE, + }; + }, + }, }, computed: { variables() { @@ -147,7 +189,11 @@ export default { <template> <div> - <runner-online-stat class="gl-py-6 gl-px-5" :value="groupRunnersCount" /> + <runner-stats + :online-runners-count="onlineRunnersTotal" + :offline-runners-count="offlineRunnersTotal" + :stale-runners-count="staleRunnersTotal" + /> <div class="gl-display-flex gl-align-items-center"> <runner-type-tabs |