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/access_tokens/components/access_token_table_app.vue')
-rw-r--r--app/assets/javascripts/access_tokens/components/access_token_table_app.vue168
1 files changed, 168 insertions, 0 deletions
diff --git a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
new file mode 100644
index 00000000000..944a2ef7f64
--- /dev/null
+++ b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
@@ -0,0 +1,168 @@
+<script>
+import { GlButton, GlIcon, GlLink, GlPagination, GlTable, GlTooltipDirective } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { __, sprintf } from '~/locale';
+import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserDate from '~/vue_shared/components/user_date.vue';
+import { EVENT_SUCCESS, FIELDS, FORM_SELECTOR, INITIAL_PAGE, PAGE_SIZE } from './constants';
+
+export default {
+ EVENT_SUCCESS,
+ FORM_SELECTOR,
+ PAGE_SIZE,
+ name: 'AccessTokenTableApp',
+ components: {
+ DomElementListener,
+ GlButton,
+ GlIcon,
+ GlLink,
+ GlPagination,
+ GlTable,
+ TimeAgoTooltip,
+ UserDate,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ lastUsedHelpLink: helpPagePath('/user/profile/personal_access_tokens.md', {
+ anchor: 'view-the-last-time-a-token-was-used',
+ }),
+ i18n: {
+ emptyField: __('Never'),
+ expired: __('Expired'),
+ header: __('Active %{accessTokenTypePlural} (%{totalAccessTokens})'),
+ modalMessage: __(
+ 'Are you sure you want to revoke this %{accessTokenType}? This action cannot be undone.',
+ ),
+ revokeButton: __('Revoke'),
+ tokenValidity: __('Token valid until revoked'),
+ },
+ inject: [
+ 'accessTokenType',
+ 'accessTokenTypePlural',
+ 'initialActiveAccessTokens',
+ 'noActiveTokensMessage',
+ 'showRole',
+ ],
+ data() {
+ return {
+ activeAccessTokens: this.initialActiveAccessTokens,
+ currentPage: INITIAL_PAGE,
+ };
+ },
+ computed: {
+ filteredFields() {
+ return this.showRole ? FIELDS : FIELDS.filter((field) => field.key !== 'role');
+ },
+ header() {
+ return sprintf(this.$options.i18n.header, {
+ accessTokenTypePlural: this.accessTokenTypePlural,
+ totalAccessTokens: this.activeAccessTokens.length,
+ });
+ },
+ modalMessage() {
+ return sprintf(this.$options.i18n.modalMessage, {
+ accessTokenType: this.accessTokenType,
+ });
+ },
+ showPagination() {
+ return this.activeAccessTokens.length > PAGE_SIZE;
+ },
+ },
+ methods: {
+ onSuccess(event) {
+ const [{ active_access_tokens: activeAccessTokens }] = event.detail;
+ this.activeAccessTokens = convertObjectPropsToCamelCase(activeAccessTokens, { deep: true });
+ this.currentPage = INITIAL_PAGE;
+ },
+ sortingChanged(aRow, bRow, key) {
+ if (['createdAt', 'lastUsedAt', 'expiresAt'].includes(key)) {
+ // Transform `null` value to the latest possible date
+ // https://stackoverflow.com/a/11526569/18428169
+ const maxEpoch = 8640000000000000;
+ const a = new Date(aRow[key] ?? maxEpoch).getTime();
+ const b = new Date(bRow[key] ?? maxEpoch).getTime();
+ return a - b;
+ }
+
+ // For other columns the default sorting works OK
+ return false;
+ },
+ },
+};
+</script>
+
+<template>
+ <dom-element-listener :selector="$options.FORM_SELECTOR" @[$options.EVENT_SUCCESS]="onSuccess">
+ <div>
+ <hr />
+ <h5>{{ header }}</h5>
+
+ <gl-table
+ data-testid="active-tokens"
+ :empty-text="noActiveTokensMessage"
+ :fields="filteredFields"
+ :items="activeAccessTokens"
+ :per-page="$options.PAGE_SIZE"
+ :current-page="currentPage"
+ :sort-compare="sortingChanged"
+ show-empty
+ >
+ <template #cell(createdAt)="{ item: { createdAt } }">
+ <user-date :date="createdAt" />
+ </template>
+
+ <template #head(lastUsedAt)="{ label }">
+ <span>{{ label }}</span>
+ <gl-link :href="$options.lastUsedHelpLink"
+ ><gl-icon name="question-o" /><span class="gl-sr-only">{{
+ s__('AccessTokens|The last time a token was used')
+ }}</span></gl-link
+ >
+ </template>
+
+ <template #cell(lastUsedAt)="{ item: { lastUsedAt } }">
+ <time-ago-tooltip v-if="lastUsedAt" :time="lastUsedAt" />
+ <template v-else> {{ $options.i18n.emptyField }}</template>
+ </template>
+
+ <template #cell(expiresAt)="{ item: { expiresAt, expired, expiresSoon } }">
+ <template v-if="expiresAt">
+ <span v-if="expired" class="text-danger">{{ $options.i18n.expired }}</span>
+ <time-ago-tooltip v-else :class="{ 'text-warning': expiresSoon }" :time="expiresAt" />
+ </template>
+ <span v-else v-gl-tooltip :title="$options.i18n.tokenValidity">{{
+ $options.i18n.emptyField
+ }}</span>
+ </template>
+
+ <template #cell(action)="{ item: { revokePath, expiresAt } }">
+ <gl-button
+ variant="danger"
+ :category="expiresAt ? 'primary' : 'secondary'"
+ :aria-label="$options.i18n.revokeButton"
+ :data-confirm="modalMessage"
+ data-confirm-btn-variant="danger"
+ data-qa-selector="revoke_button"
+ data-method="put"
+ :href="revokePath"
+ icon="remove"
+ />
+ </template>
+ </gl-table>
+ <gl-pagination
+ v-if="showPagination"
+ v-model="currentPage"
+ :per-page="$options.PAGE_SIZE"
+ :total-items="activeAccessTokens.length"
+ :prev-text="__('Prev')"
+ :next-text="__('Next')"
+ :label-next-page="__('Go to next page')"
+ :label-prev-page="__('Go to previous page')"
+ align="center"
+ />
+ </div>
+ </dom-element-listener>
+</template>