summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/clusters
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/clusters
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
downloadgitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/clusters')
-rw-r--r--app/assets/javascripts/clusters/agents/components/activity_events_list.vue176
-rw-r--r--app/assets/javascripts/clusters/agents/components/activity_history_item.vue79
-rw-r--r--app/assets/javascripts/clusters/agents/components/show.vue16
-rw-r--r--app/assets/javascripts/clusters/agents/components/token_table.vue13
-rw-r--r--app/assets/javascripts/clusters/agents/constants.js37
-rw-r--r--app/assets/javascripts/clusters/agents/graphql/fragments/cluster_agent_token.fragment.graphql2
-rw-r--r--app/assets/javascripts/clusters/agents/graphql/queries/get_agent_activity_events.query.graphql25
-rw-r--r--app/assets/javascripts/clusters/agents/graphql/queries/get_cluster_agent.query.graphql2
-rw-r--r--app/assets/javascripts/clusters/agents/index.js3
9 files changed, 344 insertions, 9 deletions
diff --git a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
new file mode 100644
index 00000000000..6567ce203bc
--- /dev/null
+++ b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
@@ -0,0 +1,176 @@
+<script>
+import {
+ GlLoadingIcon,
+ GlEmptyState,
+ GlLink,
+ GlIcon,
+ GlAlert,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { n__, s__, __ } from '~/locale';
+import { formatDate, getDayDifference, isToday } from '~/lib/utils/datetime_utility';
+import { EVENTS_STORED_DAYS } from '../constants';
+import getAgentActivityEventsQuery from '../graphql/queries/get_agent_activity_events.query.graphql';
+import ActivityHistoryItem from './activity_history_item.vue';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlEmptyState,
+ GlAlert,
+ GlLink,
+ GlIcon,
+ ActivityHistoryItem,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ i18n: {
+ emptyText: s__(
+ 'ClusterAgents|See Agent activity updates such as tokens created or revoked and clusters connected or not connected.',
+ ),
+ emptyTooltip: s__('ClusterAgents|What is GitLab Agent activity?'),
+ error: s__(
+ 'ClusterAgents|An error occurred while retrieving GitLab Agent activity. Reload the page to try again.',
+ ),
+ today: __('Today'),
+ yesterday: __('Yesterday'),
+ },
+ emptyHelpLink: helpPagePath('user/clusters/agent/install/index', {
+ anchor: 'view-agent-activity',
+ }),
+ borderClasses: 'gl-border-b-1 gl-border-b-solid gl-border-b-gray-100',
+ apollo: {
+ agentEvents: {
+ query: getAgentActivityEventsQuery,
+ variables() {
+ return {
+ agentName: this.agentName,
+ projectPath: this.projectPath,
+ };
+ },
+ update: (data) => data?.project?.clusterAgent?.activityEvents?.nodes,
+ error() {
+ this.isError = true;
+ },
+ },
+ },
+ inject: ['agentName', 'projectPath', 'activityEmptyStateImage'],
+ data() {
+ return {
+ isError: false,
+ };
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.agentEvents?.loading;
+ },
+ emptyStateTitle() {
+ return n__(
+ "ClusterAgents|There's no activity from the past day",
+ "ClusterAgents|There's no activity from the past %d days",
+ EVENTS_STORED_DAYS,
+ );
+ },
+ eventsList() {
+ const list = this.agentEvents;
+ const listByDates = {};
+
+ if (!list?.length) {
+ return listByDates;
+ }
+
+ list.forEach((event) => {
+ const dateName = this.getFormattedDate(event.recordedAt);
+ if (!listByDates[dateName]) {
+ listByDates[dateName] = [];
+ }
+ listByDates[dateName].push(event);
+ });
+
+ return listByDates;
+ },
+ hasEvents() {
+ return Object.keys(this.eventsList).length;
+ },
+ },
+ methods: {
+ isYesterday(date) {
+ const today = new Date();
+ return getDayDifference(today, date) === -1;
+ },
+ getFormattedDate(dateString) {
+ const date = new Date(dateString);
+ let dateName;
+ if (isToday(date)) {
+ dateName = this.$options.i18n.today;
+ } else if (this.isYesterday(date)) {
+ dateName = this.$options.i18n.yesterday;
+ } else {
+ dateName = formatDate(date, 'yyyy-mm-dd');
+ }
+ return dateName;
+ },
+ isLast(dateEvents, idx) {
+ return idx === dateEvents.length - 1;
+ },
+ getBodyClasses(dateEvents, idx) {
+ return !this.isLast(dateEvents, idx) ? this.$options.borderClasses : '';
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-loading-icon v-if="isLoading" size="md" />
+
+ <div v-else-if="hasEvents">
+ <div
+ v-for="(dateEvents, key) in eventsList"
+ :key="key"
+ class="agent-activity-list issuable-discussion"
+ >
+ <h4
+ class="gl-pb-4 gl-ml-5"
+ :class="$options.borderClasses"
+ data-testid="activity-section-title"
+ >
+ {{ key }}
+ </h4>
+
+ <ul class="notes main-notes-list timeline">
+ <activity-history-item
+ v-for="(event, idx) in dateEvents"
+ :key="idx"
+ :event="event"
+ :body-class="getBodyClasses(dateEvents, idx)"
+ />
+ </ul>
+ </div>
+ </div>
+
+ <gl-alert v-else-if="isError" variant="danger" :dismissible="false" class="gl-mt-3">
+ {{ $options.i18n.error }}
+ </gl-alert>
+
+ <gl-empty-state
+ v-else
+ :title="emptyStateTitle"
+ :svg-path="activityEmptyStateImage"
+ :svg-height="150"
+ >
+ <template #description
+ >{{ $options.i18n.emptyText }}
+ <gl-link
+ v-gl-tooltip
+ :href="$options.emptyHelpLink"
+ :title="$options.i18n.emptyTooltip"
+ :aria-label="$options.i18n.emptyTooltip"
+ ><gl-icon name="question" :size="14"
+ /></gl-link>
+ </template>
+ </gl-empty-state>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters/agents/components/activity_history_item.vue b/app/assets/javascripts/clusters/agents/components/activity_history_item.vue
new file mode 100644
index 00000000000..7792d89a575
--- /dev/null
+++ b/app/assets/javascripts/clusters/agents/components/activity_history_item.vue
@@ -0,0 +1,79 @@
+<script>
+import { GlLink, GlIcon, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
+import { EVENT_DETAILS, DEFAULT_ICON } from '../constants';
+
+export default {
+ i18n: {
+ defaultBodyText: s__('ClusterAgents|Event occurred'),
+ },
+ components: {
+ GlLink,
+ GlIcon,
+ GlSprintf,
+ TimeAgoTooltip,
+ HistoryItem,
+ },
+ props: {
+ event: {
+ required: true,
+ type: Object,
+ },
+ bodyClass: {
+ required: false,
+ default: '',
+ type: String,
+ },
+ },
+ computed: {
+ eventDetails() {
+ const defaultEvent = {
+ eventTypeIcon: DEFAULT_ICON,
+ title: this.event.kind,
+ body: this.$options.i18n.defaultBodyText,
+ };
+
+ const eventDetails = EVENT_DETAILS[this.event.kind] || defaultEvent;
+ const { eventTypeIcon, title, body, titleIcon } = eventDetails;
+ const resultEvent = { ...this.event, eventTypeIcon, title, body, titleIcon };
+
+ return resultEvent;
+ },
+ },
+};
+</script>
+<template>
+ <history-item :icon="eventDetails.eventTypeIcon" class="gl-my-0! gl-pr-0!">
+ <strong>
+ <gl-sprintf :message="eventDetails.title"
+ ><template v-if="eventDetails.titleIcon" #titleIcon
+ ><gl-icon
+ class="gl-mr-2"
+ :name="eventDetails.titleIcon.name"
+ :size="12"
+ :class="eventDetails.titleIcon.class"
+ />
+ </template>
+ <template #tokenName>{{ eventDetails.agentToken.name }}</template></gl-sprintf
+ >
+ </strong>
+
+ <template #body>
+ <p class="gl-mt-2 gl-mb-0 gl-pb-2" :class="bodyClass">
+ <gl-sprintf :message="eventDetails.body">
+ <template #userName>
+ <span class="gl-font-weight-bold">{{ eventDetails.user.name }}</span>
+ <gl-link :href="eventDetails.user.webUrl">@{{ eventDetails.user.username }}</gl-link>
+ </template>
+
+ <template #strong="{ content }">
+ <span class="gl-font-weight-bold"> {{ content }} </span>
+ </template>
+ </gl-sprintf>
+ <time-ago-tooltip :time="eventDetails.recordedAt" />
+ </p>
+ </template>
+ </history-item>
+</template>
diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue
index afbba9d1f7c..9109c010500 100644
--- a/app/assets/javascripts/clusters/agents/components/show.vue
+++ b/app/assets/javascripts/clusters/agents/components/show.vue
@@ -8,11 +8,12 @@ import {
GlTab,
GlTabs,
} from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { MAX_LIST_COUNT } from '../constants';
import getClusterAgentQuery from '../graphql/queries/get_cluster_agent.query.graphql';
import TokenTable from './token_table.vue';
+import ActivityEvents from './activity_events_list.vue';
export default {
i18n: {
@@ -20,6 +21,7 @@ export default {
loadingError: s__('ClusterAgents|An error occurred while loading your agent'),
tokens: s__('ClusterAgents|Access tokens'),
unknownUser: s__('ClusterAgents|Unknown user'),
+ activity: __('Activity'),
},
apollo: {
clusterAgent: {
@@ -47,6 +49,7 @@ export default {
GlTabs,
TimeAgoTooltip,
TokenTable,
+ ActivityEvents,
},
props: {
agentName: {
@@ -127,9 +130,14 @@ export default {
</gl-sprintf>
</p>
- <gl-tabs>
+ <gl-tabs sync-active-tab-with-query-params lazy>
+ <gl-tab :title="$options.i18n.activity" query-param-value="activity">
+ <activity-events :agent-name="agentName" :project-path="projectPath" />
+ </gl-tab>
+
<slot name="ee-security-tab"></slot>
- <gl-tab>
+
+ <gl-tab query-param-value="tokens">
<template #title>
<span data-testid="cluster-agent-token-count">
{{ $options.i18n.tokens }}
@@ -143,7 +151,7 @@ export default {
<gl-loading-icon v-if="isLoading" size="md" class="gl-m-3" />
<div v-else>
- <TokenTable :tokens="tokens" />
+ <token-table :tokens="tokens" />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination v-bind="tokenPageInfo" @prev="prevPage" @next="nextPage" />
diff --git a/app/assets/javascripts/clusters/agents/components/token_table.vue b/app/assets/javascripts/clusters/agents/components/token_table.vue
index 70ed2566134..019fac531d1 100644
--- a/app/assets/javascripts/clusters/agents/components/token_table.vue
+++ b/app/assets/javascripts/clusters/agents/components/token_table.vue
@@ -62,8 +62,8 @@ export default {
];
},
learnMoreUrl() {
- return helpPagePath('user/clusters/agent/index.md', {
- anchor: 'create-an-agent-record-in-gitlab',
+ return helpPagePath('user/clusters/agent/install/index', {
+ anchor: 'register-an-agent-with-gitlab',
});
},
},
@@ -83,7 +83,14 @@ export default {
</gl-link>
</div>
- <gl-table :items="tokens" :fields="fields" fixed stacked="md">
+ <gl-table
+ :items="tokens"
+ :fields="fields"
+ fixed
+ stacked="md"
+ head-variant="white"
+ thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
+ >
<template #cell(lastUsed)="{ item }">
<time-ago-tooltip v-if="item.lastUsedAt" :time="item.lastUsedAt" />
<span v-else>{{ $options.i18n.neverUsed }}</span>
diff --git a/app/assets/javascripts/clusters/agents/constants.js b/app/assets/javascripts/clusters/agents/constants.js
index bbc4630f83b..315c7662755 100644
--- a/app/assets/javascripts/clusters/agents/constants.js
+++ b/app/assets/javascripts/clusters/agents/constants.js
@@ -1 +1,38 @@
+import { s__ } from '~/locale';
+
export const MAX_LIST_COUNT = 25;
+
+export const EVENTS_STORED_DAYS = 7;
+
+export const EVENT_DETAILS = {
+ token_created: {
+ eventTypeIcon: 'token',
+ title: s__('ClusterAgents|%{tokenName} created'),
+ body: s__('ClusterAgents|Token created by %{userName}'),
+ },
+ token_revoked: {
+ eventTypeIcon: 'token',
+ title: s__('ClusterAgents|%{tokenName} revoked'),
+ body: s__('ClusterAgents|Token revoked by %{userName}'),
+ },
+ agent_connected: {
+ eventTypeIcon: 'connected',
+ title: s__('ClusterAgents|%{titleIcon}Connected'),
+ body: s__('ClusterAgents|Agent %{strongStart}connected%{strongEnd}'),
+ titleIcon: {
+ name: 'status-success',
+ class: 'text-success-500',
+ },
+ },
+ agent_disconnected: {
+ eventTypeIcon: 'connected',
+ title: s__('ClusterAgents|%{titleIcon}Not connected'),
+ body: s__('ClusterAgents|Agent %{strongStart}disconnected%{strongEnd}'),
+ titleIcon: {
+ name: 'severity-critical',
+ class: 'text-danger-800',
+ },
+ },
+};
+
+export const DEFAULT_ICON = 'token';
diff --git a/app/assets/javascripts/clusters/agents/graphql/fragments/cluster_agent_token.fragment.graphql b/app/assets/javascripts/clusters/agents/graphql/fragments/cluster_agent_token.fragment.graphql
index 1e9187e8ad1..7deb057ede9 100644
--- a/app/assets/javascripts/clusters/agents/graphql/fragments/cluster_agent_token.fragment.graphql
+++ b/app/assets/javascripts/clusters/agents/graphql/fragments/cluster_agent_token.fragment.graphql
@@ -4,8 +4,8 @@ fragment Token on ClusterAgentToken {
description
lastUsedAt
name
-
createdByUser {
+ id
name
}
}
diff --git a/app/assets/javascripts/clusters/agents/graphql/queries/get_agent_activity_events.query.graphql b/app/assets/javascripts/clusters/agents/graphql/queries/get_agent_activity_events.query.graphql
new file mode 100644
index 00000000000..0d7ff029387
--- /dev/null
+++ b/app/assets/javascripts/clusters/agents/graphql/queries/get_agent_activity_events.query.graphql
@@ -0,0 +1,25 @@
+query getAgentActivityEvents($projectPath: ID!, $agentName: String!) {
+ project(fullPath: $projectPath) {
+ id
+ clusterAgent(name: $agentName) {
+ id
+ activityEvents {
+ nodes {
+ kind
+ level
+ recordedAt
+ agentToken {
+ id
+ name
+ }
+ user {
+ id
+ name
+ username
+ webUrl
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/clusters/agents/graphql/queries/get_cluster_agent.query.graphql b/app/assets/javascripts/clusters/agents/graphql/queries/get_cluster_agent.query.graphql
index d01db8f0a6a..3662e925261 100644
--- a/app/assets/javascripts/clusters/agents/graphql/queries/get_cluster_agent.query.graphql
+++ b/app/assets/javascripts/clusters/agents/graphql/queries/get_cluster_agent.query.graphql
@@ -10,11 +10,13 @@ query getClusterAgent(
$beforeToken: String
) {
project(fullPath: $projectPath) {
+ id
clusterAgent(name: $agentName) {
id
createdAt
createdByUser {
+ id
name
}
diff --git a/app/assets/javascripts/clusters/agents/index.js b/app/assets/javascripts/clusters/agents/index.js
index 426d8d83847..5796c9e308d 100644
--- a/app/assets/javascripts/clusters/agents/index.js
+++ b/app/assets/javascripts/clusters/agents/index.js
@@ -13,11 +13,12 @@ export default () => {
}
const defaultClient = createDefaultClient();
- const { agentName, projectPath } = el.dataset;
+ const { agentName, projectPath, activityEmptyStateImage } = el.dataset;
return new Vue({
el,
apolloProvider: new VueApollo({ defaultClient }),
+ provide: { agentName, projectPath, activityEmptyStateImage },
render(createElement) {
return createElement(AgentShowPage, {
props: {