aboutsummaryrefslogtreecommitdiffstats
path: root/web_src/js/components/RepoActionView.vue
diff options
context:
space:
mode:
Diffstat (limited to 'web_src/js/components/RepoActionView.vue')
-rw-r--r--web_src/js/components/RepoActionView.vue122
1 files changed, 58 insertions, 64 deletions
diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue
index 79b43a3746..2eb2211269 100644
--- a/web_src/js/components/RepoActionView.vue
+++ b/web_src/js/components/RepoActionView.vue
@@ -6,6 +6,8 @@ import {createElementFromAttrs, toggleElem} from '../utils/dom.ts';
import {formatDatetime} from '../utils/time.ts';
import {renderAnsi} from '../render/ansi.ts';
import {POST, DELETE} from '../modules/fetch.ts';
+import type {IntervalId} from '../types.ts';
+import {toggleFullScreen} from '../utils.ts';
// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
@@ -24,6 +26,20 @@ type LogLineCommand = {
prefix: string,
}
+type Job = {
+ id: number;
+ name: string;
+ status: RunStatus;
+ canRerun: boolean;
+ duration: string;
+}
+
+type Step = {
+ summary: string,
+ duration: string,
+ status: RunStatus,
+}
+
function parseLineCommand(line: LogLine): LogLineCommand | null {
for (const prefix of LogLinePrefixesGroup) {
if (line.message.startsWith(prefix)) {
@@ -77,7 +93,7 @@ export default defineComponent({
default: '',
},
locale: {
- type: Object as PropType<Record<string, string>>,
+ type: Object as PropType<Record<string, any>>,
default: null,
},
},
@@ -86,11 +102,10 @@ export default defineComponent({
const {autoScroll, expandRunning} = getLocaleStorageOptions();
return {
// internal state
- loadingAbortController: null,
- intervalID: null,
- currentJobStepsStates: [],
- artifacts: [],
- onHoverRerunIndex: -1,
+ loadingAbortController: null as AbortController | null,
+ intervalID: null as IntervalId | null,
+ currentJobStepsStates: [] as Array<Record<string, any>>,
+ artifacts: [] as Array<Record<string, any>>,
menuVisible: false,
isFullScreen: false,
timeVisible: {
@@ -105,7 +120,7 @@ export default defineComponent({
link: '',
title: '',
titleHTML: '',
- status: 'unknown' as RunStatus,
+ status: '' as RunStatus, // do not show the status before initialized, otherwise it would show an incorrect "error" icon
canCancel: false,
canApprove: false,
canRerun: false,
@@ -122,7 +137,7 @@ export default defineComponent({
// canRerun: false,
// duration: '',
// },
- ],
+ ] as Array<Job>,
commit: {
localeCommit: '',
localePushedBy: '',
@@ -148,7 +163,7 @@ export default defineComponent({
// duration: '',
// status: '',
// }
- ],
+ ] as Array<Step>,
},
};
},
@@ -162,7 +177,7 @@ export default defineComponent({
},
},
- async mounted() { // eslint-disable-line @typescript-eslint/no-misused-promises
+ async mounted() {
// load job data and then auto-reload periodically
// need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
await this.loadJob();
@@ -194,7 +209,7 @@ export default defineComponent({
// get the job step logs container ('.job-step-logs')
getJobStepLogsContainer(stepIndex: number): HTMLElement {
- return this.$refs.logs[stepIndex];
+ return (this.$refs.logs as any)[stepIndex];
},
// get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
@@ -205,7 +220,7 @@ export default defineComponent({
},
// begin a log group
beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
- const el = this.$refs.logs[stepIndex];
+ const el = (this.$refs.logs as any)[stepIndex];
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
this.createLogLine(stepIndex, startTime, {
index: line.index,
@@ -223,7 +238,7 @@ export default defineComponent({
},
// end a log group
endLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
- const el = this.$refs.logs[stepIndex];
+ const el = (this.$refs.logs as any)[stepIndex];
el._stepLogsActiveContainer = null;
el.append(this.createLogLine(stepIndex, startTime, {
index: line.index,
@@ -393,7 +408,7 @@ export default defineComponent({
if (this.menuVisible) this.menuVisible = false;
},
- toggleTimeDisplay(type: string) {
+ toggleTimeDisplay(type: 'seconds' | 'stamp') {
this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`];
for (const el of (this.$refs.steps as HTMLElement).querySelectorAll(`.log-time-${type}`)) {
toggleElem(el, this.timeVisible[`log-time-${type}`]);
@@ -402,29 +417,16 @@ export default defineComponent({
toggleFullScreen() {
this.isFullScreen = !this.isFullScreen;
- const fullScreenEl = document.querySelector('.action-view-right');
- const outerEl = document.querySelector('.full.height');
- const actionBodyEl = document.querySelector('.action-view-body');
- const headerEl = document.querySelector('#navbar');
- const contentEl = document.querySelector('.page-content');
- const footerEl = document.querySelector('.page-footer');
- toggleElem(headerEl, !this.isFullScreen);
- toggleElem(contentEl, !this.isFullScreen);
- toggleElem(footerEl, !this.isFullScreen);
- // move .action-view-right to new parent
- if (this.isFullScreen) {
- outerEl.append(fullScreenEl);
- } else {
- actionBodyEl.append(fullScreenEl);
- }
+ toggleFullScreen('.action-view-right', this.isFullScreen, '.action-view-body');
},
async hashChangeListener() {
const selectedLogStep = window.location.hash;
if (!selectedLogStep) return;
const [_, step, _line] = selectedLogStep.split('-');
- if (!this.currentJobStepsStates[step]) return;
- if (!this.currentJobStepsStates[step].expanded && this.currentJobStepsStates[step].cursor === null) {
- this.currentJobStepsStates[step].expanded = true;
+ const stepNum = Number(step);
+ if (!this.currentJobStepsStates[stepNum]) return;
+ if (!this.currentJobStepsStates[stepNum].expanded && this.currentJobStepsStates[stepNum].cursor === null) {
+ this.currentJobStepsStates[stepNum].expanded = true;
// need to await for load job if the step log is loaded for the first time
// so logline can be selected by querySelector
await this.loadJob();
@@ -437,7 +439,8 @@ export default defineComponent({
});
</script>
<template>
- <div class="ui container action-view-container">
+ <!-- make the view container full width to make users easier to read logs -->
+ <div class="ui fluid container">
<div class="action-view-header">
<div class="action-info-summary">
<div class="action-info-summary-title">
@@ -476,13 +479,13 @@ export default defineComponent({
<div class="action-view-left">
<div class="job-group-section">
<div class="job-brief-list">
- <a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id" @mouseenter="onHoverRerunIndex = job.id" @mouseleave="onHoverRerunIndex = -1">
+ <a class="job-brief-item" :href="run.link+'/jobs/'+index" :class="parseInt(jobIndex) === index ? 'selected' : ''" v-for="(job, index) in run.jobs" :key="job.id">
<div class="job-brief-item-left">
<ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/>
<span class="job-brief-name tw-mx-2 gt-ellipsis">{{ job.name }}</span>
</div>
<span class="job-brief-item-right">
- <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun && onHoverRerunIndex === job.id"/>
+ <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun tw-mx-2 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun"/>
<span class="step-summary-duration">{{ job.duration }}</span>
</span>
</a>
@@ -493,14 +496,24 @@ export default defineComponent({
{{ locale.artifactsTitle }}
</div>
<ul class="job-artifacts-list">
- <li class="job-artifacts-item" v-for="artifact in artifacts" :key="artifact.name">
- <a class="job-artifacts-link" target="_blank" :href="run.link+'/artifacts/'+artifact.name">
- <SvgIcon name="octicon-file" class="ui text black job-artifacts-icon"/>{{ artifact.name }}
- </a>
- <a v-if="run.canDeleteArtifact" @click="deleteArtifact(artifact.name)" class="job-artifacts-delete">
- <SvgIcon name="octicon-trash" class="ui text black job-artifacts-icon"/>
- </a>
- </li>
+ <template v-for="artifact in artifacts" :key="artifact.name">
+ <li class="job-artifacts-item">
+ <template v-if="artifact.status !== 'expired'">
+ <a class="flex-text-inline" target="_blank" :href="run.link+'/artifacts/'+artifact.name">
+ <SvgIcon name="octicon-file" class="text black"/>
+ <span class="gt-ellipsis">{{ artifact.name }}</span>
+ </a>
+ <a v-if="run.canDeleteArtifact" @click="deleteArtifact(artifact.name)">
+ <SvgIcon name="octicon-trash" class="text black"/>
+ </a>
+ </template>
+ <span v-else class="flex-text-inline text light grey">
+ <SvgIcon name="octicon-file"/>
+ <span class="gt-ellipsis">{{ artifact.name }}</span>
+ <span class="ui label text light grey tw-flex-shrink-0">{{ locale.artifactExpired }}</span>
+ </span>
+ </li>
+ </template>
</ul>
</div>
</div>
@@ -559,7 +572,7 @@ export default defineComponent({
<!-- If the job is done and the job step log is loaded for the first time, show the loading icon
currentJobStepsStates[i].cursor === null means the log is loaded for the first time
-->
- <SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="tw-mr-2 job-status-rotate"/>
+ <SvgIcon v-if="isDone(run.status) && currentJobStepsStates[i].expanded && currentJobStepsStates[i].cursor === null" name="octicon-sync" class="tw-mr-2 circular-spin"/>
<SvgIcon v-else :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" :class="['tw-mr-2', !isExpandable(jobStep.status) && 'tw-invisible']"/>
<ActionRunStatus :status="jobStep.status" class="tw-mr-2"/>
@@ -662,6 +675,7 @@ export default defineComponent({
padding: 6px;
display: flex;
justify-content: space-between;
+ align-items: center;
}
.job-artifacts-list {
@@ -669,10 +683,6 @@ export default defineComponent({
list-style: none;
}
-.job-artifacts-icon {
- padding-right: 3px;
-}
-
.job-brief-list {
display: flex;
flex-direction: column;
@@ -705,11 +715,6 @@ export default defineComponent({
.job-brief-item .job-brief-rerun {
cursor: pointer;
- transition: transform 0.2s;
-}
-
-.job-brief-item .job-brief-rerun:hover {
- transform: scale(130%);
}
.job-brief-item .job-brief-item-left {
@@ -886,16 +891,6 @@ export default defineComponent({
<style> /* eslint-disable-line vue-scoped-css/enforce-style-type */
/* some elements are not managed by vue, so we need to use global style */
-.job-status-rotate {
- animation: job-status-rotate-keyframes 1s linear infinite;
-}
-
-@keyframes job-status-rotate-keyframes {
- 100% {
- transform: rotate(-360deg);
- }
-}
-
.job-step-section {
margin: 10px;
}
@@ -945,7 +940,6 @@ export default defineComponent({
.job-step-logs .job-log-line .log-msg {
flex: 1;
- word-break: break-all;
white-space: break-spaces;
margin-left: 10px;
overflow-wrap: anywhere;