diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index e5742157d882..80ccecbce162 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -80,6 +80,7 @@ milestones = Milestones ok = OK cancel = Cancel rerun = Re-run +rerun_all = Re-run all jobs save = Save add = Add add_all = Add All diff --git a/templates/repo/actions/status.tmpl b/templates/repo/actions/status.tmpl index ab2ee8482cf0..cdc747aa8b0b 100644 --- a/templates/repo/actions/status.tmpl +++ b/templates/repo/actions/status.tmpl @@ -1,5 +1,6 @@ <!-- This template should be kept the same as web_src/js/components/ActionRunStatus.vue Please also update the vue file above if this template is modified. + action status accepted: success, skipped, waiting, blocked, running, failure, cancelled, unknown --> {{- $size := 16 -}} {{- if .size -}} @@ -11,7 +12,7 @@ {{- $className = .className -}} {{- end -}} -<span data-tooltip-content="{{.locale.Tr (printf "actions.status.%s" .status)}}"> +<span class="gt-df gt-ac" data-tooltip-content="{{.locale.Tr (printf "actions.status.%s" .status)}}"> {{if eq .status "success"}} {{svg "octicon-check-circle-fill" $size (printf "text green %s" $className)}} {{else if eq .status "skipped"}} @@ -22,7 +23,7 @@ {{svg "octicon-blocked" $size (printf "text yellow %s" $className)}} {{else if eq .status "running"}} {{svg "octicon-meter" $size (printf "text yellow job-status-rotate %s" $className)}} -{{else}} +{{else if or (eq .status "failure") or (eq .status "cancelled") or (eq .status "unknown")}} {{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}} {{end}} </span> diff --git a/templates/repo/actions/view.tmpl b/templates/repo/actions/view.tmpl index 8d6559ee9800..3a3a069cbc8a 100644 --- a/templates/repo/actions/view.tmpl +++ b/templates/repo/actions/view.tmpl @@ -9,6 +9,7 @@ data-locale-approve="{{.locale.Tr "repo.diff.review.approve"}}" data-locale-cancel="{{.locale.Tr "cancel"}}" data-locale-rerun="{{.locale.Tr "rerun"}}" + data-locale-rerun-all="{{.locale.Tr "rerun_all"}}" data-locale-status-unknown="{{.locale.Tr "actions.status.unknown"}}" data-locale-status-waiting="{{.locale.Tr "actions.status.waiting"}}" data-locale-status-running="{{.locale.Tr "actions.status.running"}}" diff --git a/web_src/css/base.css b/web_src/css/base.css index eb12ffef7a6c..4b9f1eef2249 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -71,6 +71,7 @@ /* console colors */ --color-console-fg: #ffffff; --color-console-bg: #171717; + --color-console-hover-bg: #ffffff16; /* named colors */ --color-red: #db2828; --color-orange: #f2711c; diff --git a/web_src/js/components/ActionRunStatus.vue b/web_src/js/components/ActionRunStatus.vue index 0786cb60a9a4..bddf307a1b10 100644 --- a/web_src/js/components/ActionRunStatus.vue +++ b/web_src/js/components/ActionRunStatus.vue @@ -1,14 +1,15 @@ <!-- This vue should be kept the same as templates/repo/actions/status.tmpl Please also update the template file above if this vue is modified. + action status accepted: success, skipped, waiting, blocked, running, failure, cancelled, unknown --> <template> - <span :data-tooltip-content="localeStatus" v-if="status"> + <span class="gt-df gt-ac" :data-tooltip-content="localeStatus" v-if="status"> <SvgIcon name="octicon-check-circle-fill" class="text green" :size="size" :class-name="className" v-if="status === 'success'"/> <SvgIcon name="octicon-skip" class="text grey" :size="size" :class-name="className" v-else-if="status === 'skipped'"/> <SvgIcon name="octicon-clock" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'waiting'"/> <SvgIcon name="octicon-blocked" class="text yellow" :size="size" :class-name="className" v-else-if="status === 'blocked'"/> <SvgIcon name="octicon-meter" class="text yellow" :size="size" :class-name="'job-status-rotate ' + className" v-else-if="status === 'running'"/> - <SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else/> + <SvgIcon name="octicon-x-circle-fill" class="text red" :size="size" v-else-if="['failure', 'cancelled', 'unknown'].includes(status)" /> </span> </template> diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 28adfbc6eca7..b2fd63dd18e9 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -1,28 +1,30 @@ <template> - <div class="action-view-container"> + <div class="ui container action-view-container"> <div class="action-view-header"> <div class="action-info-summary"> - <ActionRunStatus :locale-status="locale.status[run.status]" :status="run.status" :size="20"/> - <div class="action-title"> - {{ run.title }} + <div class="action-info-summary-title"> + <ActionRunStatus :locale-status="locale.status[run.status]" :status="run.status" :size="20"/> + <h2 class="action-info-summary-title-text"> + {{ run.title }} + </h2> </div> <button class="ui basic small compact button primary" @click="approveRun()" v-if="run.canApprove"> - <SvgIcon class="gt-mr-2" name="octicon-play" :size="20"/> {{ locale.approve }} + {{ locale.approve }} </button> <button class="ui basic small compact button red" @click="cancelRun()" v-else-if="run.canCancel"> - <SvgIcon class="gt-mr-2" name="octicon-x-circle-fill" :size="20"/> {{ locale.cancel }} + {{ locale.cancel }} </button> <button class="ui basic small compact button secondary" @click="rerun()" v-else-if="run.canRerun"> - <SvgIcon class="gt-mr-2" name="octicon-sync" :size="20"/> {{ locale.rerun }} + {{ locale.rerun_all }} </button> </div> <div class="action-commit-summary"> {{ run.commit.localeCommit }} <a :href="run.commit.link">{{ run.commit.shortSHA }}</a> - <span class="ui label" v-if="run.commit.shortSHA"> + <span class="ui label" v-if="run.commit.shortSHA"> <a :href="run.commit.branch.link">{{ run.commit.branch.name }}</a> </span> - {{ run.commit.localePushedBy }} + {{ run.commit.localePushedBy }} <a :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a> </div> </div> @@ -30,15 +32,15 @@ <div class="action-view-left"> <div class="job-group-section"> <div class="job-brief-list"> - <div class="job-brief-item" v-for="(job, index) in run.jobs" :key="job.id"> + <div class="job-brief-item" :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-link" :href="run.link+'/jobs/'+index"> <ActionRunStatus :locale-status="locale.status[job.status]" :status="job.status"/> - <span class="ui text gt-mx-3">{{ job.name }}</span> + <span class="job-brief-name gt-mx-3 gt-ellipsis">{{ job.name }}</span> </a> - <span class="step-summary-duration">{{ job.duration }}</span> - <button :data-tooltip-content="locale.rerun" class="job-brief-rerun" @click="rerunJob(index)" v-if="job.canRerun"> - <SvgIcon name="octicon-sync" class="ui text black"/> - </button> + <span class="job-brief-info"> + <span class="step-summary-duration">{{ job.duration }}</span> + <SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun gt-mx-3" @click="rerunJob(index)" v-if="job.canRerun && onHoverRerunIndex === job.id"/> + </span> </div> </div> </div> @@ -58,21 +60,24 @@ <div class="action-view-right"> <div class="job-info-header"> - <div class="job-info-header-title"> + <h3 class="job-info-header-title"> {{ currentJob.title }} - </div> - <div class="job-info-header-detail"> + </h3> + <p class="job-info-header-detail"> {{ currentJob.detail }} - </div> + </p> </div> <div class="job-step-container"> <div class="job-step-section" v-for="(jobStep, i) in currentJob.steps" :key="i"> - <div class="job-step-summary" @click.stop="toggleStepLogs(i)"> - <SvgIcon :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" class="gt-mr-3"/> - + <div class="job-step-summary" @click.stop="toggleStepLogs(i)" :class="currentJobStepsStates[i].expanded ? 'selected' : ''"> + <!-- 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="gt-mr-3 job-status-rotate"/> + <SvgIcon v-else :name="currentJobStepsStates[i].expanded ? 'octicon-chevron-down': 'octicon-chevron-right'" class="gt-mr-3"/> <ActionRunStatus :status="jobStep.status" class="gt-mr-3"/> - <span class="step-summary-msg">{{ jobStep.summary }}</span> + <span class="step-summary-msg gt-ellipsis">{{ jobStep.summary }}</span> <span class="step-summary-duration">{{ jobStep.duration }}</span> </div> @@ -115,6 +120,7 @@ const sfc = { intervalID: null, currentJobStepsStates: [], artifacts: [], + onHoverRerunIndex: -1, // provided by backend run: { @@ -295,6 +301,7 @@ const sfc = { // sync the currentJobStepsStates to store the job step states for (let i = 0; i < this.currentJob.steps.length; i++) { if (!this.currentJobStepsStates[i]) { + // initial states for job steps this.currentJobStepsStates[i] = {cursor: null, expanded: false}; } } @@ -325,6 +332,10 @@ const sfc = { body, }); }, + + isDone(status) { + return ['success', 'skipped', 'failure', 'cancelled'].includes(status); + } }, }; @@ -348,6 +359,7 @@ export function initRepositoryActionView() { cancel: el.getAttribute('data-locale-cancel'), rerun: el.getAttribute('data-locale-rerun'), artifactsTitle: el.getAttribute('data-locale-artifacts-title'), + rerun_all: el.getAttribute('data-locale-rerun-all'), status: { unknown: el.getAttribute('data-locale-status-unknown'), waiting: el.getAttribute('data-locale-status-waiting'), @@ -417,24 +429,30 @@ export function ansiLogToHTML(line) { /* action view header */ .action-view-header { - margin: 0 20px 20px 20px; + margin: 20px 0px; } .action-info-summary { - font-size: 150%; - height: 20px; display: flex; align-items: center; margin-top: 1rem; + justify-content: space-between; } -.action-info-summary .action-title { - padding: 0 5px; +.action-info-summary-title { + display: flex; +} + +.action-info-summary-title-text { + font-size: 20px; + margin: 0 0 0 5px; flex: 1; } .action-commit-summary { - padding: 10px 10px; + display: flex; + gap: 5px; + margin: 10px 0px 10px 25px; } /* ================ */ @@ -444,7 +462,6 @@ export function ansiLogToHTML(line) { width: 30%; max-width: 400px; overflow-y: scroll; - margin-left: 10px; } .job-group-section .job-group-summary { @@ -473,42 +490,64 @@ export function ansiLogToHTML(line) { padding-right: 3px; } -.job-group-section .job-brief-list .job-brief-item { +.job-brief-item { margin: 5px 0; padding: 10px; background: var(--color-info-bg); border-radius: 5px; text-decoration: none; display: flex; - justify-items: center; flex-wrap: nowrap; + justify-content: space-between; + align-items: center; } -.job-group-section .job-brief-list .job-brief-item .job-brief-rerun { - float: right; - border: none; - background-color: transparent; - outline: none; +.job-brief-item:hover { + background-color: var(--color-secondary); +} + +.job-brief-item.selected { + font-weight: var(--font-weight-bold); + background-color: var(--color-secondary-dark-1); +} + +.job-brief-item:first-of-type { + margin-top: 0; +} + +.job-brief-item .job-brief-rerun { cursor: pointer; transition: transform 0.2s; } -.job-group-section .job-brief-list .job-brief-item .job-brief-rerun:hover { +.job-brief-item .job-brief-rerun:hover { transform: scale(130%); } -.job-group-section .job-brief-list .job-brief-item .job-brief-link { - flex-grow: 1; +.job-brief-item .job-brief-link { display: flex; + width: 100%; } -.job-group-section .job-brief-list .job-brief-item .job-brief-link span { +.job-brief-item .job-brief-link span { display: flex; align-items: center; } -.job-group-section .job-brief-list .job-brief-item:hover { - background-color: var(--color-secondary); +.job-brief-item .job-brief-link .job-brief-name { + display: block; + width: 70%; + color: var(--color-text); +} + +.job-brief-item .job-brief-link:hover { + text-decoration: none; +} + +.job-brief-item .job-brief-info { + display: flex; + align-items: center; + width: 55px; } /* ================ */ @@ -517,21 +556,27 @@ export function ansiLogToHTML(line) { .action-view-right { flex: 1; background-color: var(--color-console-bg); - color: var(--color-console-fg); + color: var(--color-secondary-dark-2); max-height: 100%; - margin-right: 10px; + width: 70%; display: flex; flex-direction: column; } -.job-info-header .job-info-header-title { - font-size: 150%; +.job-info-header { padding: 10px; + border-bottom: 1px solid var(--color-grey); +} + +.job-info-header .job-info-header-title { + color: var(--color-console-fg); + font-size: 16px; + margin: 0; } .job-info-header .job-info-header-detail { - padding: 0 10px 10px; - border-bottom: 1px solid var(--color-grey); + color: var(--color-secondary-dark-3); + font-size: 12px; } .job-step-container { @@ -543,6 +588,8 @@ export function ansiLogToHTML(line) { cursor: pointer; padding: 5px 10px; display: flex; + align-items: center; + user-select: none; } .job-step-container .job-step-summary .step-summary-msg { @@ -553,8 +600,25 @@ export function ansiLogToHTML(line) { margin-left: 16px; } -.job-step-container .job-step-summary:hover { +.job-step-container .job-step-summary:hover, +.job-step-container .job-step-summary.selected { + color: var(--color-console-fg); background-color: var(--color-black-light); + border-radius: 5px; +} + +@media (max-width: 768px) { + .action-view-body { + flex-direction: column; + } + .action-view-left, .action-view-right { + width: 100%; + } + + .action-view-left { + max-width: none; + overflow-y: hidden; + } } </style> @@ -576,12 +640,19 @@ export function ansiLogToHTML(line) { .job-step-section .job-step-logs { font-family: monospace, monospace; + margin: 8px 0px; + font-size: 12px; } .job-step-section .job-step-logs .job-log-line { display: flex; } +.job-step-section .job-step-logs .job-log-line:hover { + color: var(--color-console-fg); + background-color: var(--color-console-hover-bg); +} + .job-step-section .job-step-logs .job-log-line .line-num { width: 48px; color: var(--color-grey-light);