mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-03 15:16:29 +00:00
Add pipeline log output download (#1023)
This commit is contained in:
parent
73ee12a6c3
commit
f654e1f316
6 changed files with 84 additions and 8 deletions
|
@ -178,13 +178,15 @@
|
||||||
"exit_code": "exit code {exitCode}",
|
"exit_code": "exit code {exitCode}",
|
||||||
"loading": "Loading ...",
|
"loading": "Loading ...",
|
||||||
"pipeline": "Pipeline #{buildId}",
|
"pipeline": "Pipeline #{buildId}",
|
||||||
|
"log_download_error": "There was an error while downloading the log file",
|
||||||
|
|
||||||
"actions": {
|
"actions": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"canceled": "This step has been canceled.",
|
"canceled": "This step has been canceled.",
|
||||||
"cancel_success": "Pipeline canceled",
|
"cancel_success": "Pipeline canceled",
|
||||||
"restart_success": "Pipeline restarted"
|
"restart_success": "Pipeline restarted",
|
||||||
|
"log_download": "Download"
|
||||||
},
|
},
|
||||||
"protected": {
|
"protected": {
|
||||||
"awaits": "This pipeline is awaiting approval by some maintainer!",
|
"awaits": "This pipeline is awaiting approval by some maintainer!",
|
||||||
|
|
|
@ -178,13 +178,15 @@
|
||||||
"exit_code": "iziešanas kods {exitCode}",
|
"exit_code": "iziešanas kods {exitCode}",
|
||||||
"loading": "Notiek ielāde...",
|
"loading": "Notiek ielāde...",
|
||||||
"pipeline": "Konvejerdarbs #{buildId}",
|
"pipeline": "Konvejerdarbs #{buildId}",
|
||||||
|
"log_download_error": "Veicot žurnālfaila lejupielādi notika kļūda",
|
||||||
|
|
||||||
"actions": {
|
"actions": {
|
||||||
"cancel": "Atcelt",
|
"cancel": "Atcelt",
|
||||||
"restart": "Pārstartēt",
|
"restart": "Pārstartēt",
|
||||||
"canceled": "Šis solis tika atcelts.",
|
"canceled": "Šis solis tika atcelts.",
|
||||||
"cancel_success": "Konvejerdarbs atcelts",
|
"cancel_success": "Konvejerdarbs atcelts",
|
||||||
"restart_success": "Konvejerdarbs pārstartēts"
|
"restart_success": "Konvejerdarbs pārstartēts",
|
||||||
|
"log_download": "Lejupielādēt"
|
||||||
},
|
},
|
||||||
"protected": {
|
"protected": {
|
||||||
"awaits": "Šim konvejerdarbam ir nepieciešams apstiprinājums no atbildīgajām personām!",
|
"awaits": "Šim konvejerdarbam ir nepieciešams apstiprinājums no atbildīgajām personām!",
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
color === 'red',
|
color === 'red',
|
||||||
...passedClasses,
|
...passedClasses,
|
||||||
}"
|
}"
|
||||||
|
:title="title"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click="doClick"
|
@click="doClick"
|
||||||
>
|
>
|
||||||
|
@ -69,6 +70,11 @@ export default defineComponent({
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
<i-mdi-chevron-right v-else-if="name === 'chevron-right'" class="h-6 w-6" />
|
<i-mdi-chevron-right v-else-if="name === 'chevron-right'" class="h-6 w-6" />
|
||||||
<i-carbon-close-outline v-else-if="name === 'close'" class="h-6 w-6" />
|
<i-carbon-close-outline v-else-if="name === 'close'" class="h-6 w-6" />
|
||||||
<i-ic-baseline-edit v-else-if="name === 'edit'" class="h-6 w-6" />
|
<i-ic-baseline-edit v-else-if="name === 'edit'" class="h-6 w-6" />
|
||||||
|
<i-ic-baseline-download v-else-if="name === 'download'" class="h-6 w-6" />
|
||||||
<div v-else-if="name === 'blank'" class="h-6 w-6" />
|
<div v-else-if="name === 'blank'" class="h-6 w-6" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -80,7 +81,8 @@ export type IconNames =
|
||||||
| 'chevron-right'
|
| 'chevron-right'
|
||||||
| 'turn-off'
|
| 'turn-off'
|
||||||
| 'close'
|
| 'close'
|
||||||
| 'edit';
|
| 'edit'
|
||||||
|
| 'download';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Icon',
|
name: 'Icon',
|
||||||
|
|
|
@ -8,7 +8,21 @@
|
||||||
<Icon name="close" class="ml-auto" />
|
<Icon name="close" class="ml-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-grow flex-col bg-gray-300 dark:bg-dark-gray-700 md:m-2 md:mt-0 md:rounded-md overflow-hidden">
|
<div
|
||||||
|
class="flex flex-grow flex-col bg-gray-300 dark:bg-dark-gray-700 md:m-2 md:mt-0 md:rounded-md overflow-hidden"
|
||||||
|
@mouseover="showActions = true"
|
||||||
|
@mouseleave="showActions = false"
|
||||||
|
>
|
||||||
|
<div v-show="showActions" class="absolute top-0 right-0 z-50 mt-2 mr-4 hidden md:flex">
|
||||||
|
<Button
|
||||||
|
v-if="proc?.end_time !== undefined"
|
||||||
|
:is-loading="downloadInProgress"
|
||||||
|
:title="$t('repo.build.actions.log_download')"
|
||||||
|
start-icon="download"
|
||||||
|
@click="download"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-show="loadedLogs" class="w-full flex-grow p-2">
|
<div v-show="loadedLogs" class="w-full flex-grow p-2">
|
||||||
<div id="terminal" class="w-full h-full" />
|
<div id="terminal" class="w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,20 +61,23 @@ import {
|
||||||
toRef,
|
toRef,
|
||||||
watch,
|
watch,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { Terminal } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
|
|
||||||
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import Icon from '~/components/atomic/Icon.vue';
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import { useDarkMode } from '~/compositions/useDarkMode';
|
import { useDarkMode } from '~/compositions/useDarkMode';
|
||||||
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { Build, Repo } from '~/lib/api/types';
|
import { Build, Repo } from '~/lib/api/types';
|
||||||
import { findProc, isProcFinished, isProcRunning } from '~/utils/helpers';
|
import { findProc, isProcFinished, isProcRunning } from '~/utils/helpers';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'BuildLog',
|
name: 'BuildLog',
|
||||||
|
|
||||||
components: { Icon },
|
components: { Icon, Button },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
build: {
|
build: {
|
||||||
|
@ -82,6 +99,8 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const notifications = useNotifications();
|
||||||
|
const i18n = useI18n();
|
||||||
const build = toRef(props, 'build');
|
const build = toRef(props, 'build');
|
||||||
const procId = toRef(props, 'procId');
|
const procId = toRef(props, 'procId');
|
||||||
const repo = inject<Ref<Repo>>('repo');
|
const repo = inject<Ref<Repo>>('repo');
|
||||||
|
@ -103,6 +122,41 @@ export default defineComponent({
|
||||||
const fitAddon = ref(new FitAddon());
|
const fitAddon = ref(new FitAddon());
|
||||||
const loadedLogs = ref(true);
|
const loadedLogs = ref(true);
|
||||||
const autoScroll = ref(true); // TODO
|
const autoScroll = ref(true); // TODO
|
||||||
|
const showActions = ref(false);
|
||||||
|
const downloadInProgress = ref(false);
|
||||||
|
|
||||||
|
async function download() {
|
||||||
|
if (!repo?.value || !build.value || !proc.value) {
|
||||||
|
throw new Error('The reposiotry, build or proc was undefined');
|
||||||
|
}
|
||||||
|
let logs;
|
||||||
|
try {
|
||||||
|
downloadInProgress.value = true;
|
||||||
|
logs = await apiClient.getLogs(repo.value.owner, repo.value.name, build.value.number, proc.value.pid);
|
||||||
|
} catch (e) {
|
||||||
|
notifications.notifyError(e, i18n.t('repo.build.log_download_error'));
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
downloadInProgress.value = false;
|
||||||
|
}
|
||||||
|
const fileURL = window.URL.createObjectURL(
|
||||||
|
new Blob([logs.map((line) => line.out).join('')], {
|
||||||
|
type: 'text/plain',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const fileLink = document.createElement('a');
|
||||||
|
|
||||||
|
fileLink.href = fileURL;
|
||||||
|
fileLink.setAttribute(
|
||||||
|
'download',
|
||||||
|
`${repo.value.owner}-${repo.value.name}-${build.value.number}-${proc.value.name}.log`,
|
||||||
|
);
|
||||||
|
document.body.appendChild(fileLink);
|
||||||
|
|
||||||
|
fileLink.click();
|
||||||
|
document.body.removeChild(fileLink);
|
||||||
|
window.URL.revokeObjectURL(fileURL);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadLogs() {
|
async function loadLogs() {
|
||||||
if (loadedProcSlug.value === procSlug.value) {
|
if (loadedProcSlug.value === procSlug.value) {
|
||||||
|
@ -231,7 +285,7 @@ export default defineComponent({
|
||||||
window.removeEventListener('resize', resize);
|
window.removeEventListener('resize', resize);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { proc, loadedLogs };
|
return { proc, loadedLogs, showActions, download, downloadInProgress };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
import Notifications, { notify } from '@kyvg/vue3-notification';
|
import Notifications, { NotificationsOptions, notify } from '@kyvg/vue3-notification';
|
||||||
|
|
||||||
export const notifications = Notifications;
|
export const notifications = Notifications;
|
||||||
|
|
||||||
export default () => ({ notify });
|
function notifyError(err: unknown, args: NotificationsOptions | string = {}): void {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
const mArgs = typeof args === 'string' ? { title: args } : args;
|
||||||
|
const title = mArgs?.title || (err as Error)?.message || `${err}`;
|
||||||
|
|
||||||
|
notify({ type: 'error', ...mArgs, title });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => ({ notify, notifyError });
|
||||||
|
|
Loading…
Reference in a new issue