diff --git a/locales/en/l10n-devopsProjects-pipeline-details.js b/locales/en/l10n-devopsProjects-pipeline-details.js index 0ac0e88be7e..34986d0275b 100644 --- a/locales/en/l10n-devopsProjects-pipeline-details.js +++ b/locales/en/l10n-devopsProjects-pipeline-details.js @@ -235,6 +235,7 @@ module.exports = { // detail page // run log // task status RUN_LOGS: 'Run Logs', VIEW_FULL_LOG: 'View Full Logs', + VIEW_REAL_TIME_LOG: 'View Real-time Logs', // detail page // run log // task status // pipeline log modal PIPELINE_LOG: 'Pipeline Logs', // detail page // Create Pipeline modal // add step modal diff --git a/locales/es/l10n-devopsProjects-pipeline-details.js b/locales/es/l10n-devopsProjects-pipeline-details.js index ddca12f9939..da40df14f3e 100644 --- a/locales/es/l10n-devopsProjects-pipeline-details.js +++ b/locales/es/l10n-devopsProjects-pipeline-details.js @@ -224,6 +224,7 @@ module.exports = { // detail page // run log // task status RUN_LOGS: 'Run Logs', VIEW_FULL_LOG: 'View Full Logs', + VIEW_REAL_TIME_LOG: 'View Real-time Logs', // detail page // run log // task status // pipeline log modal PIPELINE_LOG: 'Pipeline Logs', // detail page // Create Pipeline modal // add step modal diff --git a/locales/tc/l10n-devopsProjects-pipeline-details.js b/locales/tc/l10n-devopsProjects-pipeline-details.js index 77b7a16a380..f07ec30639c 100644 --- a/locales/tc/l10n-devopsProjects-pipeline-details.js +++ b/locales/tc/l10n-devopsProjects-pipeline-details.js @@ -220,6 +220,7 @@ module.exports = { // detail page // run log // task status RUN_LOGS: 'Run Logs', VIEW_FULL_LOG: 'View Full Logs', + VIEW_REAL_TIME_LOG: 'View Real-time Logs', // detail page // run log // task status // pipeline log modal PIPELINE_LOG: 'Pipeline Logs', // detail page // Create Pipeline modal // add step modal diff --git a/locales/zh/l10n-devopsProjects-pipeline-details.js b/locales/zh/l10n-devopsProjects-pipeline-details.js index f645c60f8e2..a7c0653a65b 100644 --- a/locales/zh/l10n-devopsProjects-pipeline-details.js +++ b/locales/zh/l10n-devopsProjects-pipeline-details.js @@ -219,6 +219,7 @@ module.exports = { // detail page // run log // task status RUN_LOGS: '运行日志', VIEW_FULL_LOG: '查看完整日志', + VIEW_REAL_TIME_LOG: '查看实时日志', // detail page // run log // task status // pipeline log modal PIPELINE_LOG: '流水线日志', // detail page // Create Pipeline modal // add step modal diff --git a/src/pages/devops/components/Pipeline/StepModals/params.jsx b/src/pages/devops/components/Pipeline/StepModals/params.jsx index 6a9e4dd8ff8..435c748deb0 100644 --- a/src/pages/devops/components/Pipeline/StepModals/params.jsx +++ b/src/pages/devops/components/Pipeline/StepModals/params.jsx @@ -84,8 +84,8 @@ const setCredentialType = str => { if (type) { const credentialType = Object.entries(typesDict).find( typeArr => typeArr[1] === type - ) - return credentialType ? credentialType[0] : null + )?.[0] + return credentialType } return null } diff --git a/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/FullLogs/index.jsx b/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/FullLogs/index.jsx index dbd4cc04408..83ac2f27e91 100644 --- a/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/FullLogs/index.jsx +++ b/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/FullLogs/index.jsx @@ -43,6 +43,9 @@ export default class FullLogs extends React.Component { @computed get isLogFinish() { + if (this.store.overflow) { + return true + } const logs = this.store.runDetailLogs.split('\n') let index = logs.length - 1 let start = 0 diff --git a/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/Timer.jsx b/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/Timer.jsx new file mode 100644 index 00000000000..e11b3f59d0a --- /dev/null +++ b/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/Timer.jsx @@ -0,0 +1,50 @@ +/* + * This file is part of KubeSphere Console. + * Copyright (C) 2019 The KubeSphere Console Authors. + * + * KubeSphere Console is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KubeSphere Console is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with KubeSphere Console. If not, see . + */ + +import { useEffect, useRef, useState } from 'react' +import { formatUsedTime } from 'utils/index' + +const TimeCounter = ({ startTime, time }) => { + const [seconds, setSeconds] = useState(0) + + const timerRef = useRef(null) + + useEffect(() => { + if (startTime) { + const interval = setInterval(() => { + const endTime = new Date() + const diff = endTime.getTime() - new Date(startTime).getTime() + setSeconds(diff) + }, 300) + timerRef.current = interval + return () => clearInterval(interval) + } + }, [startTime]) + + useEffect(() => { + if (time && timerRef.current) { + clearInterval(timerRef.current) + } + }, [time]) + + return t('DURATION_VALUE', { + value: startTime ? formatUsedTime(time ?? seconds) : '-', + }) +} + +export default TimeCounter diff --git a/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/index.jsx b/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/index.jsx index af694a61247..19b4fdcd6c6 100644 --- a/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/index.jsx +++ b/src/pages/devops/containers/Pipelines/Detail/PipelineLogDialog/index.jsx @@ -18,16 +18,16 @@ import React from 'react' import classNames from 'classnames' -import { isEmpty, isArray } from 'lodash' -import { action, observable, computed, toJS, reaction } from 'mobx' +import { isArray, isEmpty } from 'lodash' +import { action, computed, observable, reaction, toJS } from 'mobx' import { observer } from 'mobx-react' import { Modal } from 'components/Base' import { Button } from '@kube-design/components' import Status from 'devops/components/Status' import { getPipelineStatus } from 'utils/status' -import { formatUsedTime } from 'utils' import RunStore from 'stores/devops/run' +import TimeCounter from 'devops/containers/Pipelines/Detail/PipelineLogDialog/Timer' import LogItem from './logItem' import styles from './index.scss' import FullLogs from './FullLogs' @@ -40,6 +40,9 @@ export default class PipelineLog extends React.Component { * @type {RunStore} */ this.store = new RunStore() + this.state = { + isDownloading: false, + } this.reaction = reaction( () => this.isEmptySteps, @@ -126,6 +129,16 @@ export default class PipelineLog extends React.Component { // this.refreshFlag = !this.refreshFlag // }, 1000) + handleDownload = async () => { + this.setState({ isDownloading: true }) + await this.store.handleDownloadLogs(this.props.params) + this.setState({ isDownloading: false }) + } + + handleJumpFullLogs = () => { + this.store.handleJumpFullLogs(this.props.params) + } + renderLeftTab(stage, index) { if (Array.isArray(stage)) { return ( @@ -200,6 +213,28 @@ export default class PipelineLog extends React.Component { )) } + get isRunning() { + return this.activeStage?.result && this.activeStage?.result === 'UNKNOWN' + } + + renderLogButton = () => { + if (this.isRunning) { + return ( + + ) + } + return ( + + + + + ) + } + render() { const { nodes } = this.props const _nodes = toJS(nodes) @@ -215,8 +250,7 @@ export default class PipelineLog extends React.Component { // ) // } - const time = this.activeStage?.durationInMillis ?? '' - + // const time = this.activeStage?.durationInMillis ?? '' return ( <>
@@ -226,13 +260,16 @@ export default class PipelineLog extends React.Component {
- {t('DURATION_VALUE', { - value: time ? formatUsedTime(time) : '-', - })} + - + {this.renderLogButton()}
{this.renderLogContent()}
diff --git a/src/stores/devops/log.js b/src/stores/devops/log.js index 62fc445111b..e484e3db25b 100644 --- a/src/stores/devops/log.js +++ b/src/stores/devops/log.js @@ -29,26 +29,47 @@ export default class PipelineRunStore extends BaseStore { } async getStepLog({ devops, cluster, name, branch, runId, nodeId, stepId }) { + const params = this.stepLogData.start + ? `?start=${this.stepLogData.start}` + : '' + const headers = branch + ? {} + : { + 'x-file-size-limit': 1024 * 1024 * 5, + } const result = await request.defaults({ url: `${this.getDevopsUrlV2({ cluster, devops, })}pipelines/${decodeURIComponent(name)}${ branch ? `/branches/${encodeURIComponent(branch)}` : '' - }/runs/${runId}/nodes/${nodeId}/steps/${stepId}/log/?start=${this - .stepLogData.start || 0}`, + }/runs/${runId}/nodes/${nodeId}/steps/${stepId}/log/${params}`, + options: { headers }, handler: resp => { if (resp.status === 200) { return resp.text().then(res => ({ data: res, headers: resp.headers })) } }, }) - const prevLog = this.stepLogData.log + const prevLog = !this.stepLogData.start ? '' : this.stepLogData.log this.stepLogData = { log: prevLog + get(result, 'data', ''), start: result.headers.get('x-text-size'), hasMore: Boolean(result.headers.get('x-more-data')), } + if ( + result.headers.get('X-File-Size-Limit-Out') === 'true' || + this.log?.length > 1024 * 1024 * 5 + ) { + this.stepLogData.hasMore = false + this.stepLogData.log += `\n +***************************************************************** +* * +* The log is too large, please download it to view the details. * +* * +***************************************************************** + ` + } } @action diff --git a/src/stores/devops/run.js b/src/stores/devops/run.js index 1b25249a469..a019457aa0f 100644 --- a/src/stores/devops/run.js +++ b/src/stores/devops/run.js @@ -81,6 +81,9 @@ export default class PipelineRunStore extends BaseStore { @observable logSize = 0 + @observable + overflow = false + @observable hasMore = false @@ -329,7 +332,7 @@ export default class PipelineRunStore extends BaseStore { this.runStartDetailLogs = '' this.hasMore = false } - if (this.logSize >= 1024 * 1024 * 20) { + if (this.overflow) { const result = await request.get( `${this.getRunUrl({ cluster, @@ -350,6 +353,8 @@ export default class PipelineRunStore extends BaseStore { ${result}` } else { + const start = this.logSize + const params = start ? `?start=${start}` : '' const result = await request.get( `${this.getRunUrl({ cluster, @@ -357,33 +362,68 @@ ${result}` name, branch, runId, - })}log/?start=${this.logSize}`, + })}log/${params}`, {}, { headers: { - 'x-file-size-limit': 1024 * 100, + // 'x-file-size-limit': 1024 * 1024 * 10, 'x-with-headers': true, }, } ) - this.logSize += Number(result.headers.get('X-File-Size')) - this.hasMore = result.headers.get('X-File-Size-Limit-Out') !== 'true' + const size = result.headers.get('x-text-size') + if (size) { + this.logSize = Number(size) + this.hasMore = Boolean(result.headers.get('x-more-data')) + } else { + this.logSize += Number(result.headers.get('x-text-size')) + this.hasMore = Boolean(result.headers.get('x-more-data')) + } + this.overflow = + Boolean(result.headers.get('X-File-Size-Limit-Out')) || + this.logSize >= 1024 * 1024 * 10 + result.text().then(text => { - this.runStartDetailLogs += this.removeSameWords( - this.runStartDetailLogs, - text - ) + if (start === 0) { + this.runStartDetailLogs = text + return + } + // console.log(this.runStartDetailLogs.slice(-100).split('\n')) + const arr = this.runStartDetailLogs.slice(-100).split('\n') + if (arr.length >= 2) { + arr.pop() + if (arr.length && arr.pop().startsWith('Finished:')) { + // + } else { + this.runStartDetailLogs += text + } + } else { + this.runStartDetailLogs += text + } }) } } - - removeSameWords = (str1, str2) => { - const end = str1.slice(-100) - const index = str2.indexOf(end) - if (index === -1) { - return str2 - } - return str2.slice(index + end.length) + // removeSameWords = (str1, str2) => { + // const end = str1.slice(-100) + // const index = str2.indexOf(end) + // if (index === -1) { + // return str2 + // } + // return str2.slice(index + end.length) + // } + + handleJumpFullLogs({ devops, name, branch, cluster }) { + name = decodeURIComponent(name) + const url = getClusterUrl( + `${window.location.protocol}//${window.location.host}/${this.getRunUrl({ + cluster, + devops, + name, + branch, + runId: this.runDetail.id, + })}log/?start=0` + ) + window.open(url) } async handleDownloadLogs({ devops, name, branch, cluster }) { @@ -470,4 +510,4 @@ ${result}` } ) } -} +} \ No newline at end of file