1
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-04-18 00:47:48 -04:00
gitea/web_src/js/components/RepoBuildView.vue
2022-11-25 17:48:44 +08:00

366 lines
9.8 KiB
Vue

<template>
<div class="build-view-container">
<div class="build-view-left">
<div class="build-info-summary">{{ buildInfo.title }}</div>
<div class="job-group-section" v-for="(jobGroup, i) in allJobGroups" :key="i">
<div class="job-group-summary">
{{ jobGroup.summary }}
</div>
<div class="job-brief-list">
<a class="job-brief-item" v-for="job in jobGroup.jobs" :key="job.id">
<SvgIcon name="octicon-check-circle-fill" class="green" v-if="job.status === 'success'"/>
<SvgIcon name="octicon-skip" class="ui text grey" v-else-if="job.status === 'skipped'"/>
<SvgIcon name="octicon-clock" class="ui text yellow" v-else-if="job.status === 'waiting'"/>
<SvgIcon name="octicon-x-circle-fill" class="red" v-else/>
{{ job.name }}
</a>
</div>
</div>
</div>
<div class="build-view-right">
<div class="job-info-header">
<div class="job-info-header-title">
{{ currentJobInfo.title }}
</div>
<div class="job-info-header-detail">
{{ currentJobInfo.detail }}
</div>
</div>
<div class="job-step-container">
<div class="job-step-section" v-for="(jobStep, i) in currentJobSteps" :key="i">
<div class="job-step-summary" @click.stop="toggleStepLogs(i)">
<SvgIcon name="octicon-chevron-down" class="mr-3" v-show="currentJobStepsStates[i].expanded"/>
<SvgIcon name="octicon-chevron-right" class="mr-3" v-show="!currentJobStepsStates[i].expanded"/>
<SvgIcon name="octicon-check-circle-fill" class="green mr-3 " v-if="jobStep.status === 'success'"/>
<SvgIcon name="octicon-skip" class="ui text grey mr-3 " v-else-if="jobStep.status === 'skipped'"/>
<SvgIcon name="octicon-clock" class="ui text yellow mr-3 " v-else-if="jobStep.status === 'waiting'"/>
<SvgIcon name="octicon-x-circle-fill" class="red mr-3 " v-else/>
<span class="step-summary-msg">{{ jobStep.summary }}</span>
<span class="step-summary-dur">{{ Math.round(jobStep.duration/1000) }}s</span>
</div>
<!-- the log elements could be a lot, do not use v-if to destroy/reconstruct the DOM -->
<div class="job-step-logs" ref="elJobStepLogs" v-show="currentJobStepsStates[i].expanded">
<!--
<div class="job-log-group">
<div class="job-log-group-summary"></div>
<div class="job-log-list">
<div class="job-log-line"></div>
</div>
</div>
-->
<!--
<div class="job-log-line"></div>
-->
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {SvgIcon} from '../svg.js';
import Vue from 'vue';
const sfc = {
name: 'RepoBuildView',
components: {
SvgIcon,
},
data() {
return {
// internal state
loading: false,
currentJobStepsStates: [],
// provided by backend
buildInfo: {},
allJobGroups: [],
currentJobInfo: {},
currentJobSteps: [],
};
},
mounted() {
const elBodyDiv = document.querySelector('body > div.full.height');
elBodyDiv.style.height = '100%';
this.loadJobData();
},
methods: {
stepLogsGetActiveContainer(idx) {
const el = this.$refs.elJobStepLogs[idx];
return el._stepLogsActiveContainer ?? el;
},
stepLogsGroupBegin(idx) {
const el = this.$refs.elJobStepLogs[idx];
const elJobLogGroup = document.createElement('div');
elJobLogGroup.classList.add('job-log-group');
const elJobLogGroupSummary = document.createElement('div');
elJobLogGroupSummary.classList.add('job-log-group-summary');
const elJobLogList = document.createElement('div');
elJobLogList.classList.add('job-log-list');
elJobLogGroup.appendChild(elJobLogGroupSummary);
elJobLogGroup.appendChild(elJobLogList);
el._stepLogsActiveContainer = elJobLogList;
},
stepLogsGroupEnd(idx) {
const el = this.$refs.elJobStepLogs[idx];
el._stepLogsActiveContainer = null;
},
toggleStepLogs(idx) {
this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded;
},
createLogLine(line) {
const el = document.createElement('div');
el.classList.add('job-log-line');
el._jobLogTime = line.t;
const elLineNum = document.createElement('line-num');
elLineNum.innerText = line.ln;
el.appendChild(elLineNum);
const elLogTime = document.createElement('log-time');
elLogTime.innerText = new Date(line.t).toUTCString();
el.appendChild(elLogTime);
const elLogMsg = document.createElement('log-msg');
elLogMsg.innerText = line.m;
el.appendChild(elLogMsg);
return el;
},
appendLogs(stepIndex, logLines) {
for (const line of logLines) {
// TODO: group support: ##[group]GroupTitle , ##[endgroup]
const el = this.stepLogsGetActiveContainer(stepIndex);
el.append(this.createLogLine(line));
}
},
fetchMockData(reqData) {
const stateData = {
buildInfo: {title: 'The Demo Build'},
allJobGroups: [
{summary: 'Job Group Foo', jobs: [{id: 1, name: 'Job A', status: 'success'}, {id: 2, name: 'Job B', status: 'error'}]},
{summary: 'Job Group Bar', jobs: [{id: 3, name: 'Job X', status: 'skipped'}, {id: 4, name: 'Job Y', status: 'waiting'}]},
],
currentJobInfo: {title: 'the job title', detail: ' succeeded 3 hours ago in 11s'},
currentJobSteps: [
{summary: 'Job Step 1', duration: 3000, status: 'success'},
{summary: 'Job Step 2', duration: 3000, status: 'error'},
{summary: 'Job Step 3', duration: 3000, status: 'skipped'},
{summary: 'Job Step 4', duration: 3000, status: 'waiting'},
],
};
const logsData = {streamingLogs: []};
for (const reqCursor of reqData.stepLogCursors) {
if (!reqCursor.expanded) continue;
// if (reqCursor.cursor > 100) continue;
const stepIndex = reqCursor.stepIndex;
let cursor = reqCursor.cursor;
const lines = [];
for (let i = 0; i < 110; i++) {
lines.push({
ln: cursor+0, // line number
m: `hello world ${Date.now()}, cursor: ${cursor}`,
t: Date.now(),
d: 3000, // duration
});
cursor++;
}
logsData.streamingLogs.push({stepIndex, cursor, lines});
}
return {stateData, logsData};
},
async fetchJobData(reqData) {
const resp = await fetch(`?job_id=${this.jobId}`, {method: 'POST', body: JSON.stringify(reqData)});
return await resp.json();
},
async loadJobData() {
try {
if (this.loading) return;
this.loading = true;
const stepLogCursors = this.currentJobStepsStates.map((it, idx) => {return {stepIndex: idx, cursor: it.cursor, expanded: it.expanded}});
const reqData = {stepLogCursors};
// const data = await this.fetchJobData();
const data = this.fetchMockData(reqData);
console.log('loadJobData', data);
for (const [key, value] of Object.entries(data.stateData)) {
this[key] = value;
}
for (let i = 0; i < this.currentJobSteps.length; i++) {
if (!this.currentJobStepsStates[i]) {
this.$set(this.currentJobStepsStates, i, {cursor: null, expanded: false});
}
}
for (const [_, logs] of data.logsData.streamingLogs.entries()) {
this.currentJobStepsStates[logs.stepIndex].cursor = logs.cursor;
this.appendLogs(logs.stepIndex, logs.lines);
}
} finally {
this.loading = false;
setTimeout(() => this.loadJobData(), 1000);
}
}
},
};
export default sfc;
export function initRepositoryBuildView() {
const el = document.getElementById('repo-build-view');
if (!el) return;
const View = Vue.extend({
render: (createElement) => createElement(sfc),
});
new View().$mount(el);
}
</script>
<style scoped lang="less">
.build-view-container {
display: flex;
height: 100%;
}
// ================
// build view left
.build-view-left {
width: 20%;
overflow-y: scroll;
margin-left: 10px;
}
.build-info-summary {
font-size: 150%;
margin: 5px 0;
}
.job-group-section {
.job-group-summary {
margin: 5px 0;
padding: 10px;
}
.job-brief-list {
a.job-brief-item {
display: block;
margin: 5px 0;
padding: 10px;
background: #f8f8f8;
border-radius: 5px;
text-decoration: none;
}
}
}
// ================
// build view right
.build-view-right {
flex: 1;
background-color: #262626;
color: #d6d6d6;
max-height: 100%;
display: flex;
flex-direction: column;
}
.job-info-header {
.job-info-header-title {
color: #fdfdfd;
font-size: 150%;
padding: 10px;
}
.job-info-header-detail {
padding: 0 10px 10px;
border-bottom: 1px solid #666;
}
}
.job-step-container {
max-height: 100%;
overflow: auto;
.job-step-summary {
cursor: pointer;
padding: 5px 10px;
display: flex;
.step-summary-msg {
flex: 1;
}
.step-summary-dur {
margin-left: 16px;
}
}
.job-step-summary:hover {
background-color: #333;
}
}
</style>
<style lang="less">
// some elements are not managed by vue, so we need to use global style
.job-step-section {
margin: 10px;
.job-step-logs {
.job-log-line {
display: flex;
line-num {
width: 48px;
color: #555;
text-align: right;
}
log-time {
color: #777;
margin-left: 16px;
}
log-msg {
flex: 1;
margin-left: 16px;
}
}
// TODO: group support
.job-log-group {
}
.job-log-group-summary {
}
.job-log-list {
}
}
}
</style>