mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-30 05:41:12 +00:00
Fix some ui issues and mobile view (#695)
* fix ui issues and improve mobile view * show proc errors * auto open logs on md screens
This commit is contained in:
parent
b15879d460
commit
549996cbcd
17 changed files with 324 additions and 163 deletions
|
@ -6,7 +6,7 @@
|
||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
py-1
|
py-1
|
||||||
px-4
|
px-2
|
||||||
rounded-md
|
rounded-md
|
||||||
border
|
border
|
||||||
shadow-sm
|
shadow-sm
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
@click="doClick"
|
@click="doClick"
|
||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
<Icon v-if="startIcon" :name="startIcon" class="mr-2" :class="{ invisible: isLoading }" />
|
<Icon v-if="startIcon" :name="startIcon" class="mr-1" :class="{ invisible: isLoading }" />
|
||||||
<span :class="{ invisible: isLoading }">{{ text }}</span>
|
<span :class="{ invisible: isLoading }">{{ text }}</span>
|
||||||
<Icon v-if="endIcon" :name="endIcon" class="ml-2" :class="{ invisible: isLoading }" />
|
<Icon v-if="endIcon" :name="endIcon" class="ml-2" :class="{ invisible: isLoading }" />
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
<i-mdi-sync v-else-if="name === 'sync'" class="h-6 w-6" />
|
<i-mdi-sync v-else-if="name === 'sync'" class="h-6 w-6" />
|
||||||
<i-ic-baseline-healing v-else-if="name === 'heal'" class="h-6 w-6" />
|
<i-ic-baseline-healing v-else-if="name === 'heal'" class="h-6 w-6" />
|
||||||
<i-bx-bx-power-off v-else-if="name === 'turn-off'" class="h-6 w-6" />
|
<i-bx-bx-power-off v-else-if="name === 'turn-off'" 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" />
|
||||||
<div v-else-if="name === 'blank'" class="h-6 w-6" />
|
<div v-else-if="name === 'blank'" class="h-6 w-6" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -70,7 +72,9 @@ export type IconNames =
|
||||||
| 'light'
|
| 'light'
|
||||||
| 'sync'
|
| 'sync'
|
||||||
| 'heal'
|
| 'heal'
|
||||||
| 'turn-off';
|
| 'chevron-right'
|
||||||
|
| 'turn-off'
|
||||||
|
| 'close';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Icon',
|
name: 'Icon',
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<Button
|
<div
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:is-loading="isLoading"
|
|
||||||
:to="to"
|
|
||||||
class="
|
class="
|
||||||
|
relative
|
||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
justify-center
|
justify-center
|
||||||
|
@ -11,30 +10,41 @@
|
||||||
px-1
|
px-1
|
||||||
py-1
|
py-1
|
||||||
rounded-full
|
rounded-full
|
||||||
!bg-transparent
|
bg-transparent
|
||||||
!hover:bg-gray-200
|
hover:bg-gray-200 hover:text-gray-700
|
||||||
!dark:hover:bg-gray-600
|
dark:hover:bg-gray-600 dark:text-gray-500 dark:hover:text-gray-700
|
||||||
hover:text-gray-700
|
cursor-pointer
|
||||||
dark:text-gray-500 dark:hover:text-gray-700
|
transition-all
|
||||||
shadow-none
|
duration-150
|
||||||
border-none
|
focus:outline-none
|
||||||
|
overflow-hidden
|
||||||
|
disabled:opacity-50 disabled:cursor-not-allowed
|
||||||
"
|
"
|
||||||
|
@click="doClick"
|
||||||
>
|
>
|
||||||
<Icon :name="icon" />
|
<Icon :name="icon" />
|
||||||
</Button>
|
<div
|
||||||
|
class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center"
|
||||||
|
:class="{
|
||||||
|
'opacity-100': isLoading,
|
||||||
|
'opacity-0': !isLoading,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<Icon name="loading" class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { RouteLocationRaw } from 'vue-router';
|
import { RouteLocationRaw, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
|
||||||
import Icon, { IconNames } from '~/components/atomic/Icon.vue';
|
import Icon, { IconNames } from '~/components/atomic/Icon.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'IconButton',
|
name: 'IconButton',
|
||||||
|
|
||||||
components: { Button, Icon },
|
components: { Icon },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
icon: {
|
icon: {
|
||||||
|
@ -56,5 +66,28 @@ export default defineComponent({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup(props) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function doClick() {
|
||||||
|
if (props.isLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.to === 'string' && props.to.startsWith('http')) {
|
||||||
|
window.location.href = props.to;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await router.push(props.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { doClick };
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="
|
class="
|
||||||
checkbox
|
checkbox
|
||||||
|
flex-shrink-0
|
||||||
relative
|
relative
|
||||||
border border-gray-400
|
border border-gray-400
|
||||||
dark:border-gray-600
|
dark:border-gray-600
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
class="
|
class="
|
||||||
radio
|
radio
|
||||||
relative
|
relative
|
||||||
|
flex-shrink-0
|
||||||
border border-gray-400
|
border border-gray-400
|
||||||
dark:border-gray-600
|
dark:border-gray-600
|
||||||
cursor-pointer
|
cursor-pointer
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<ListItem v-if="build" clickable class="p-0 w-full">
|
<ListItem v-if="build" clickable class="p-0 w-full">
|
||||||
<div class="flex items-center mr-4">
|
<div class="flex items-center md:mr-4">
|
||||||
<div
|
<div
|
||||||
class="min-h-full w-3"
|
class="min-h-full w-3"
|
||||||
:class="{
|
:class="{
|
||||||
|
@ -11,47 +11,56 @@
|
||||||
'bg-blue-400 dark:bg-blue-900': buildStatusColors[build.status] === 'blue',
|
'bg-blue-400 dark:bg-blue-900': buildStatusColors[build.status] === 'blue',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<div class="w-8 flex">
|
<div class="w-8 flex flex-wrap justify-between items-center h-full">
|
||||||
<BuildRunningIcon v-if="build.status === 'started' || build.status === 'running'" />
|
<BuildRunningIcon v-if="build.status === 'started' || build.status === 'running'" />
|
||||||
<BuildStatusIcon v-else class="mx-3" :build="build" />
|
<BuildStatusIcon v-else class="mx-2 md:mx-3" :build="build" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex py-2 px-4 flex-grow min-w-0">
|
<div class="flex py-2 px-4 flex-grow min-w-0 flex-wrap">
|
||||||
<div class="flex items-center flex-shrink-0"><img class="w-8" :src="build.author_avatar" /></div>
|
<div class="<md:hidden flex items-center flex-shrink-0">
|
||||||
|
<img class="w-8" :src="build.author_avatar" />
|
||||||
<div class="ml-4 flex items-center mx-4 min-w-0">
|
|
||||||
<span class="text-gray-600 dark:text-gray-500 whitespace-nowrap overflow-hidden overflow-ellipsis">{{
|
|
||||||
message
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex ml-auto text-gray-500 py-2 flex-shrink-0">
|
<div class="w-full md:w-auto md:mx-4 flex items-center min-w-0">
|
||||||
<div class="flex flex-col space-y-2 w-42">
|
<span
|
||||||
<div class="flex space-x-2 items-center">
|
class="text-gray-600 <md:underline dark:text-gray-500 whitespace-nowrap overflow-hidden overflow-ellipsis"
|
||||||
<Icon v-if="build.event === 'pull_request'" name="pull_request" />
|
>{{ message }}</span
|
||||||
<Icon v-else-if="build.event === 'deployment'" name="deployment" />
|
>
|
||||||
<Icon v-else-if="build.event === 'tag'" name="tag" />
|
</div>
|
||||||
<Icon v-else name="push" />
|
|
||||||
<span v-if="build.event === 'pull_request'" class="truncate">{{
|
<div
|
||||||
`#${build.ref.replaceAll('refs/pull/', '').replaceAll('/merge', '').replaceAll('/head', '')}`
|
class="
|
||||||
}}</span>
|
grid grid-rows-2 grid-flow-col
|
||||||
<span v-else class="truncate">{{ build.branch }}</span>
|
w-full
|
||||||
</div>
|
md:ml-auto md:w-96
|
||||||
<div class="flex space-x-2 items-center">
|
py-2
|
||||||
<Icon name="commit" />
|
gap-x-4 gap-y-2
|
||||||
<span>{{ build.commit.slice(0, 10) }}</span>
|
flex-shrink-0
|
||||||
</div>
|
text-gray-500
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="flex space-x-2 items-center min-w-0">
|
||||||
|
<Icon v-if="build.event === 'pull_request'" name="pull_request" />
|
||||||
|
<Icon v-else-if="build.event === 'deployment'" name="deployment" />
|
||||||
|
<Icon v-else-if="build.event === 'tag'" name="tag" />
|
||||||
|
<Icon v-else name="push" />
|
||||||
|
<span class="truncate">{{ prettyRef }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col ml-4 space-y-2 w-42">
|
|
||||||
<div class="flex space-x-2 items-center">
|
<div class="flex space-x-2 items-center min-w-0">
|
||||||
<Icon name="duration" />
|
<Icon name="commit" />
|
||||||
<span>{{ duration }}</span>
|
<span class="truncate">{{ build.commit.slice(0, 10) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2 items-center">
|
|
||||||
<Icon name="since" />
|
<div class="flex space-x-2 items-center min-w-0">
|
||||||
<span>{{ since }}</span>
|
<Icon name="duration" />
|
||||||
</div>
|
<span class="truncate">{{ duration }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-2 items-center min-w-0">
|
||||||
|
<Icon name="since" />
|
||||||
|
<span class="truncate">{{ since }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -83,9 +92,9 @@ export default defineComponent({
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const build = toRef(props, 'build');
|
const build = toRef(props, 'build');
|
||||||
const { since, duration, message } = useBuild(build);
|
const { since, duration, message, prettyRef } = useBuild(build);
|
||||||
|
|
||||||
return { since, duration, message, buildStatusColors };
|
return { since, duration, message, prettyRef, buildStatusColors };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="build" class="font-mono bg-gray-700 dark:bg-dark-gray-700 p-4">
|
<div v-if="build" class="font-mono bg-gray-700 pt-14 md:pt-4 dark:bg-dark-gray-700 p-4 overflow-y-scroll">
|
||||||
|
<div
|
||||||
|
class="fixed top-0 left-0 w-full md:hidden flex px-4 py-2 bg-gray-600 dark:bg-dark-gray-800 text-gray-50"
|
||||||
|
@click="$emit('update:proc-id', null)"
|
||||||
|
>
|
||||||
|
<span>{{ proc?.name }}</span>
|
||||||
|
<Icon name="close" class="ml-auto" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-for="logLine in logLines" :key="logLine.pos" class="flex items-center">
|
<div v-for="logLine in logLines" :key="logLine.pos" class="flex items-center">
|
||||||
<div class="text-gray-500 text-sm w-4">{{ (logLine.pos || 0) + 1 }}</div>
|
<div class="text-gray-500 text-sm w-4">{{ (logLine.pos || 0) + 1 }}</div>
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
@ -9,9 +17,10 @@
|
||||||
<div v-if="proc?.end_time !== undefined" class="text-gray-500 text-sm mt-4 ml-8">
|
<div v-if="proc?.end_time !== undefined" class="text-gray-500 text-sm mt-4 ml-8">
|
||||||
exit code {{ proc.exit_code }}
|
exit code {{ proc.exit_code }}
|
||||||
</div>
|
</div>
|
||||||
<template v-if="!proc?.start_time" />
|
|
||||||
<div class="text-gray-300 mx-auto">
|
<div class="text-gray-300 mx-auto">
|
||||||
<span v-if="proc?.state === 'skipped'" class="text-orange-300 dark:text-orange-800"
|
<span v-if="proc?.error" class="text-red-500">{{ proc.error }}</span>
|
||||||
|
<span v-else-if="proc?.state === 'skipped'" class="text-orange-300 dark:text-orange-800"
|
||||||
>This step has been canceled.</span
|
>This step has been canceled.</span
|
||||||
>
|
>
|
||||||
<span v-else-if="!proc?.start_time" class="dark:text-gray-500">This step hasn't started yet.</span>
|
<span v-else-if="!proc?.start_time" class="dark:text-gray-500">This step hasn't started yet.</span>
|
||||||
|
@ -23,6 +32,7 @@
|
||||||
import AnsiConvert from 'ansi-to-html';
|
import AnsiConvert from 'ansi-to-html';
|
||||||
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, PropType, Ref, toRef, watch } from 'vue';
|
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, PropType, Ref, toRef, watch } from 'vue';
|
||||||
|
|
||||||
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
import useBuildProc from '~/compositions/useBuildProc';
|
import useBuildProc from '~/compositions/useBuildProc';
|
||||||
import { Build, Repo } from '~/lib/api/types';
|
import { Build, Repo } from '~/lib/api/types';
|
||||||
import { findProc } from '~/utils/helpers';
|
import { findProc } from '~/utils/helpers';
|
||||||
|
@ -30,7 +40,7 @@ import { findProc } from '~/utils/helpers';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'BuildLog',
|
name: 'BuildLog',
|
||||||
|
|
||||||
components: {},
|
components: { Icon },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
build: {
|
build: {
|
||||||
|
@ -46,6 +56,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
emits: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
'update:proc-id': (procId: number | null) => true,
|
||||||
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const build = toRef(props, 'build');
|
const build = toRef(props, 'build');
|
||||||
const procId = toRef(props, 'procId');
|
const procId = toRef(props, 'procId');
|
||||||
|
|
|
@ -1,26 +1,37 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col w-3/12 text-gray-200 dark:text-gray-400">
|
<div class="flex flex-col w-full md:w-3/12 text-gray-200 dark:text-gray-400 bg-gray-600 dark:bg-dark-gray-800">
|
||||||
<div class="flex py-4 px-2 mx-2 justify-between flex-shrink-0 text-gray-500 border-b-1 dark:border-dark-gray-600">
|
<div
|
||||||
|
class="
|
||||||
|
flex
|
||||||
|
py-4
|
||||||
|
px-2
|
||||||
|
mx-2
|
||||||
|
space-x-1
|
||||||
|
justify-between
|
||||||
|
flex-shrink-0
|
||||||
|
text-gray-500
|
||||||
|
border-b-1
|
||||||
|
dark:border-dark-gray-600
|
||||||
|
"
|
||||||
|
>
|
||||||
<div class="flex space-x-1 items-center flex-shrink-0">
|
<div class="flex space-x-1 items-center flex-shrink-0">
|
||||||
<div class="flex items-center"><img class="w-6" :src="build.author_avatar" /></div>
|
<div class="flex items-center"><img class="w-6" :src="build.author_avatar" /></div>
|
||||||
<span>{{ build.author }}</span>
|
<span>{{ build.author }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-1 items-center">
|
<div class="flex space-x-1 items-center min-w-0">
|
||||||
<Icon v-if="build.event === 'push'" name="push" />
|
<Icon v-if="build.event === 'push'" name="push" />
|
||||||
<Icon v-if="build.event === 'deployment'" name="deployment" />
|
<Icon v-if="build.event === 'deployment'" name="deployment" />
|
||||||
<Icon v-else-if="build.event === 'tag'" name="tag" />
|
<Icon v-else-if="build.event === 'tag'" name="tag" />
|
||||||
<a
|
<a
|
||||||
v-else-if="build.event === 'pull_request'"
|
v-else-if="build.event === 'pull_request'"
|
||||||
class="flex items-center text-link"
|
class="flex items-center space-x-1 text-link min-w-0"
|
||||||
:href="build.link_url"
|
:href="build.link_url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Icon name="pull_request" />
|
<Icon name="pull_request" />
|
||||||
<span>{{
|
<span class="truncate">{{ prettyRef }}</span>
|
||||||
`#${build.ref.replaceAll('refs/pull/', '').replaceAll('refs/merge-requests/', '').replaceAll('/head', '')}`
|
</a>
|
||||||
}}</span></a
|
<span v-if="build.event !== 'pull_request'" class="truncate">{{ build.branch }}</span>
|
||||||
>
|
|
||||||
<span v-if="build.event !== 'pull_request'">{{ build.branch }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center flex-shrink-0">
|
<div class="flex items-center flex-shrink-0">
|
||||||
<template v-if="build.event === 'pull_request'">
|
<template v-if="build.event === 'pull_request'">
|
||||||
|
@ -34,55 +45,64 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="proc in build.procs" :key="proc.id">
|
<div v-if="build.procs === undefined || build.procs.length === 0" class="m-auto">
|
||||||
<div class="p-4 pb-1 flex flex-wrap items-center justify-between">
|
<span>No pipeline steps available!</span>
|
||||||
<span>{{ proc.name }}</span>
|
</div>
|
||||||
<div v-if="proc.environ" class="text-xs">
|
|
||||||
<div v-for="(value, key) in proc.environ" :key="key">
|
<div class="flex flex-grow relative min-h-0 overflow-y-auto">
|
||||||
<span
|
<div class="md:absolute top-0 left-0 w-full">
|
||||||
class="
|
<div v-for="proc in build.procs" :key="proc.id">
|
||||||
pl-2
|
<div class="p-4 pb-1 flex flex-wrap items-center justify-between">
|
||||||
pr-1
|
<span>{{ proc.name }}</span>
|
||||||
py-0.5
|
<div v-if="proc.environ" class="text-xs">
|
||||||
bg-gray-800
|
<div v-for="(value, key) in proc.environ" :key="key">
|
||||||
dark:bg-gray-600
|
<span
|
||||||
border-2 border-gray-800
|
class="
|
||||||
dark:border-gray-600
|
pl-2
|
||||||
rounded-l-full
|
pr-1
|
||||||
"
|
py-0.5
|
||||||
>{{ key }}</span
|
bg-gray-800
|
||||||
>
|
dark:bg-gray-600
|
||||||
<span class="pl-1 pr-2 py-0.5 border-2 border-gray-800 dark:border-gray-600 rounded-r-full">{{
|
border-2 border-gray-800
|
||||||
value
|
dark:border-gray-600
|
||||||
}}</span>
|
rounded-l-full
|
||||||
|
"
|
||||||
|
>{{ key }}</span
|
||||||
|
>
|
||||||
|
<span class="pl-1 pr-2 py-0.5 border-2 border-gray-800 dark:border-gray-600 rounded-r-full">{{
|
||||||
|
value
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="job in proc.children"
|
||||||
|
:key="job.pid"
|
||||||
|
class="flex p-2 pl-6 cursor-pointer items-center hover:bg-gray-700 hover:dark:bg-dark-gray-900"
|
||||||
|
:class="{ 'bg-gray-700 !dark:bg-dark-gray-600': selectedProcId && selectedProcId === job.pid }"
|
||||||
|
@click="$emit('update:selected-proc-id', job.pid)"
|
||||||
|
>
|
||||||
|
<div v-if="['success'].includes(job.state)" class="w-2 h-2 bg-lime-400 rounded-full" />
|
||||||
|
<div v-if="['pending', 'skipped'].includes(job.state)" class="w-2 h-2 bg-gray-400 rounded-full" />
|
||||||
|
<div
|
||||||
|
v-if="['killed', 'error', 'failure', 'blocked', 'declined'].includes(job.state)"
|
||||||
|
class="w-2 h-2 bg-red-400 rounded-full"
|
||||||
|
/>
|
||||||
|
<div v-if="['started', 'running'].includes(job.state)" class="w-2 h-2 bg-blue-400 rounded-full" />
|
||||||
|
<span class="ml-2">{{ job.name }}</span>
|
||||||
|
<BuildProcDuration :proc="job" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-for="job in proc.children"
|
|
||||||
:key="job.pid"
|
|
||||||
class="flex p-2 pl-6 cursor-pointer items-center hover:bg-gray-700 hover:dark:bg-dark-gray-900"
|
|
||||||
:class="{ 'bg-gray-700 !dark:bg-dark-gray-600': selectedProcId && selectedProcId === job.pid }"
|
|
||||||
@click="$emit('update:selected-proc-id', job.pid)"
|
|
||||||
>
|
|
||||||
<div v-if="['success'].includes(job.state)" class="w-2 h-2 bg-lime-400 rounded-full" />
|
|
||||||
<div v-if="['pending', 'skipped'].includes(job.state)" class="w-2 h-2 bg-gray-400 rounded-full" />
|
|
||||||
<div
|
|
||||||
v-if="['killed', 'error', 'failure', 'blocked', 'declined'].includes(job.state)"
|
|
||||||
class="w-2 h-2 bg-red-400 rounded-full"
|
|
||||||
/>
|
|
||||||
<div v-if="['started', 'running'].includes(job.state)" class="w-2 h-2 bg-blue-400 rounded-full" />
|
|
||||||
<span class="ml-2">{{ job.name }}</span>
|
|
||||||
<BuildProcDuration :proc="job" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType, toRef } from 'vue';
|
||||||
|
|
||||||
import BuildProcDuration from '~/components/repo/build/BuildProcDuration.vue';
|
import BuildProcDuration from '~/components/repo/build/BuildProcDuration.vue';
|
||||||
|
import useBuild from '~/compositions/useBuild';
|
||||||
import { Build } from '~/lib/api/types';
|
import { Build } from '~/lib/api/types';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -108,5 +128,12 @@ export default defineComponent({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
'update:selected-proc-id': (selectedProcId: number) => true,
|
'update:selected-proc-id': (selectedProcId: number) => true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup(props) {
|
||||||
|
const build = toRef(props, 'build');
|
||||||
|
const { prettyRef } = useBuild(build);
|
||||||
|
|
||||||
|
return { prettyRef };
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex w-full pt-4">
|
<div class="flex w-full md:pt-4 flex-wrap">
|
||||||
<div
|
<div
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
class="
|
class="
|
||||||
|
w-full
|
||||||
|
py-2
|
||||||
|
md:w-auto md:pt-0 md:pb-2 md:px-8
|
||||||
flex
|
flex
|
||||||
cursor-pointer
|
cursor-pointer
|
||||||
pb-2
|
md:border-b-2
|
||||||
px-8
|
|
||||||
border-b-2
|
|
||||||
text-gray-500
|
text-gray-500
|
||||||
hover:text-gray-700
|
hover:text-gray-700
|
||||||
dark:text-gray-500 dark:hover:text-gray-400
|
dark:text-gray-500 dark:hover:text-gray-400
|
||||||
|
items-center
|
||||||
"
|
"
|
||||||
:class="{
|
:class="{
|
||||||
'border-gray-400 dark:border-gray-600': activeTab === tab.id,
|
'border-gray-400 dark:border-gray-600': activeTab === tab.id,
|
||||||
|
@ -20,6 +22,8 @@
|
||||||
}"
|
}"
|
||||||
@click="selectTab(tab)"
|
@click="selectTab(tab)"
|
||||||
>
|
>
|
||||||
|
<Icon v-if="activeTab === tab.id" name="chevron-right" class="md:hidden" />
|
||||||
|
<Icon v-else name="blank" class="md:hidden" />
|
||||||
<span>{{ tab.title }}</span>
|
<span>{{ tab.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,11 +38,13 @@
|
||||||
import { computed, defineComponent, onMounted, provide, ref, toRef } from 'vue';
|
import { computed, defineComponent, onMounted, provide, ref, toRef } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
|
|
||||||
import { Tab } from './types';
|
import { Tab } from './types';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Tabs',
|
name: 'Tabs',
|
||||||
|
components: { Icon },
|
||||||
props: {
|
props: {
|
||||||
// used by toRef
|
// used by toRef
|
||||||
// eslint-disable-next-line vue/no-unused-properties
|
// eslint-disable-next-line vue/no-unused-properties
|
||||||
|
@ -62,7 +68,6 @@ export default defineComponent({
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const disableHashMode = toRef(props, 'disableHashMode');
|
const disableHashMode = toRef(props, 'disableHashMode');
|
||||||
const modelValue = toRef(props, 'modelValue');
|
const modelValue = toRef(props, 'modelValue');
|
||||||
const tabs = ref<Tab[]>([]);
|
const tabs = ref<Tab[]>([]);
|
||||||
|
@ -72,35 +77,28 @@ export default defineComponent({
|
||||||
'active-tab',
|
'active-tab',
|
||||||
computed(() => activeTab.value),
|
computed(() => activeTab.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
async function selectTab(tab: Tab) {
|
async function selectTab(tab: Tab) {
|
||||||
if (tab.id === undefined) {
|
if (tab.id === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
activeTab.value = tab.id;
|
activeTab.value = tab.id;
|
||||||
emit('update:modelValue', activeTab.value);
|
emit('update:modelValue', activeTab.value);
|
||||||
|
|
||||||
if (!disableHashMode.value) {
|
if (!disableHashMode.value) {
|
||||||
await router.replace({ params: route.params, hash: `#${tab.id}` });
|
await router.replace({ params: route.params, hash: `#${tab.id}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (modelValue.value) {
|
if (modelValue.value) {
|
||||||
activeTab.value = modelValue.value;
|
activeTab.value = modelValue.value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashTab = route.hash.replace(/^#/, '');
|
const hashTab = route.hash.replace(/^#/, '');
|
||||||
if (hashTab) {
|
if (hashTab) {
|
||||||
activeTab.value = hashTab;
|
activeTab.value = hashTab;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
activeTab.value = tabs.value[0].id;
|
activeTab.value = tabs.value[0].id;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { tabs, activeTab, selectTab };
|
return { tabs, activeTab, selectTab };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -76,5 +76,25 @@ export default (build: Ref<Build | undefined>) => {
|
||||||
return convertEmojis(build.value.message);
|
return convertEmojis(build.value.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { since, duration, message };
|
const prettyRef = computed(() => {
|
||||||
|
if (build.value?.event === 'push') {
|
||||||
|
return build.value.branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (build.value?.event === 'tag') {
|
||||||
|
return build.value.ref.replaceAll('refs/tags/', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (build.value?.event === 'pull_request') {
|
||||||
|
return `#${build.value.ref
|
||||||
|
.replaceAll('refs/pull/', '')
|
||||||
|
.replaceAll('refs/merge-requests/', '')
|
||||||
|
.replaceAll('/merge', '')
|
||||||
|
.replaceAll('/head', '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return build.value?.ref;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { since, duration, message, prettyRef };
|
||||||
};
|
};
|
||||||
|
|
|
@ -108,6 +108,7 @@ export type BuildProc = {
|
||||||
start_time?: number;
|
start_time?: number;
|
||||||
end_time?: number;
|
end_time?: number;
|
||||||
machine?: string;
|
machine?: string;
|
||||||
|
error?: string;
|
||||||
children?: BuildProc[];
|
children?: BuildProc[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,26 @@
|
||||||
<div v-if="errorMessage" class="bg-red-400 text-white dark:text-gray-500 p-4 rounded-md text-lg">
|
<div v-if="errorMessage" class="bg-red-400 text-white dark:text-gray-500 p-4 rounded-md text-lg">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
<Panel class="flex flex-col m-8 md:flex-row md:w-3xl md:h-sm p-0 overflow-hidden">
|
|
||||||
<div class="flex bg-lime-500 dark:bg-lime-900 md:w-3/5 justify-center items-center">
|
<div
|
||||||
|
class="
|
||||||
|
flex flex-col
|
||||||
|
w-full
|
||||||
|
overflow-hidden
|
||||||
|
md:m-8 md:rounded-md md:shadow md:border md:bg-white md:dark:bg-dark-gray-700
|
||||||
|
dark:border-dark-200
|
||||||
|
md:flex-row md:w-3xl md:h-sm
|
||||||
|
justify-center
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="flex md:bg-lime-500 md:dark:bg-lime-900 md:w-3/5 justify-center items-center">
|
||||||
<img class="w-48 h-48" src="../assets/logo.svg?url" />
|
<img class="w-48 h-48" src="../assets/logo.svg?url" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:w-2/5 my-8 p-4 items-center justify-center">
|
<div class="flex flex-col my-8 md:w-2/5 p-4 items-center justify-center">
|
||||||
<h1 class="text-xl text-gray-600 dark:text-gray-500">Welcome to Woodpecker</h1>
|
<h1 class="text-xl text-gray-600 dark:text-gray-500">Welcome to Woodpecker</h1>
|
||||||
<Button class="mt-4" @click="doLogin">Login</Button>
|
<Button class="mt-4" @click="doLogin">Login</Button>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,7 +31,6 @@ import { defineComponent, onMounted, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import Panel from '~/components/layout/Panel.vue';
|
|
||||||
import useAuthentication from '~/compositions/useAuthentication';
|
import useAuthentication from '~/compositions/useAuthentication';
|
||||||
|
|
||||||
const authErrorMessages = {
|
const authErrorMessages = {
|
||||||
|
@ -34,7 +44,6 @@ export default defineComponent({
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Button,
|
Button,
|
||||||
Panel,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<FluidContainer class="flex flex-col">
|
<FluidContainer class="flex flex-col">
|
||||||
<div class="flex flex-row border-b pb-4 mb-4 items-center dark:border-dark-200">
|
<div class="flex flex-row flex-wrap border-b pb-4 mb-4 items-center dark:border-dark-200">
|
||||||
<h1 class="text-xl text-gray-500">Repositories</h1>
|
<h1 class="text-xl text-gray-500">Repositories</h1>
|
||||||
<TextField v-model="search" class="w-auto ml-auto" placeholder="Search ..." />
|
<TextField v-model="search" class="w-auto md:ml-auto" placeholder="Search ..." />
|
||||||
<Button class="ml-auto" :to="{ name: 'repo-add' }" start-icon="plus" text="Add repository" />
|
<Button class="md:ml-auto" :to="{ name: 'repo-add' }" start-icon="plus" text="Add repository" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<FluidContainer v-if="repo && repoPermissions && $route.meta.repoHeader">
|
<FluidContainer v-if="repo && repoPermissions && $route.meta.repoHeader">
|
||||||
<div class="flex border-b items-center pb-4 mb-4 dark:border-gray-600">
|
<div class="flex flex-wrap border-b items-center pb-4 mb-4 dark:border-gray-600 justify-center">
|
||||||
<h1 class="text-xl text-gray-500">{{ `${repo.owner} / ${repo.name}` }}</h1>
|
<h1 class="text-xl text-gray-500 w-full md:w-auto text-center mb-4 md:mb-0">
|
||||||
<a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="ml-auto">
|
{{ `${repo.owner} / ${repo.name}` }}
|
||||||
|
</h1>
|
||||||
|
<a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="md:ml-auto">
|
||||||
<img :src="badgeUrl" />
|
<img :src="badgeUrl" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -1,27 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-0 flex flex-col flex-grow">
|
<div class="p-0 flex flex-col flex-grow">
|
||||||
<div v-if="build.status === 'blocked'" class="flex flex-col flex-grow justify-center items-center">
|
<div class="flex w-full min-h-0 flex-grow">
|
||||||
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
|
||||||
<p class="text-xl text-gray-500">This pipeline is awaiting approval by some maintainer!</p>
|
|
||||||
<div v-if="repoPermissions.push" class="flex mt-2 space-x-4">
|
|
||||||
<Button color="green" text="Approve" :is-loading="isApprovingBuild" @click="approveBuild" />
|
|
||||||
<Button color="red" text="Decline" :is-loading="isDecliningBuild" @click="declineBuild" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="build.status === 'declined'" class="flex flex-col flex-grow justify-center items-center">
|
|
||||||
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
|
||||||
<p class="text-xl text-gray-500">This pipeline has been declined!</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="flex w-full bg-gray-600 dark:bg-dark-gray-800 min-h-0 flex-grow">
|
|
||||||
<div v-if="build.error" class="flex flex-col p-4">
|
|
||||||
<span class="text-red-400 font-bold text-xl mb-2">Execution error</span>
|
|
||||||
<span class="text-red-400">{{ build.error }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<BuildProcList v-model:selected-proc-id="selectedProcId" :build="build" />
|
<BuildProcList v-model:selected-proc-id="selectedProcId" :build="build" />
|
||||||
|
|
||||||
<BuildLog v-if="selectedProcId" :build="build" :proc-id="selectedProcId" class="w-9/12 flex-grow" />
|
<div class="flex flex-grow relative">
|
||||||
|
<div v-if="build.error" class="flex flex-col p-4">
|
||||||
|
<span class="text-red-400 font-bold text-xl mb-2">Execution error</span>
|
||||||
|
<span class="text-red-400">{{ build.error }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="build.status === 'blocked'" class="flex flex-col flex-grow justify-center items-center">
|
||||||
|
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
||||||
|
<p class="text-xl text-gray-500">This pipeline is awaiting approval by some maintainer!</p>
|
||||||
|
<div v-if="repoPermissions.push" class="flex mt-2 space-x-4">
|
||||||
|
<Button color="green" text="Approve" :is-loading="isApprovingBuild" @click="approveBuild" />
|
||||||
|
<Button color="red" text="Decline" :is-loading="isDecliningBuild" @click="declineBuild" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="build.status === 'declined'" class="flex flex-col flex-grow justify-center items-center">
|
||||||
|
<Icon name="status-blocked" class="w-32 h-32 text-gray-500" />
|
||||||
|
<p class="text-xl text-gray-500">This pipeline has been declined!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BuildLog
|
||||||
|
v-else-if="selectedProcId"
|
||||||
|
v-model:proc-id="selectedProcId"
|
||||||
|
:build="build"
|
||||||
|
class="fixed top-0 left-0 w-full h-full md:absolute"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -37,7 +45,7 @@ import BuildProcList from '~/components/repo/build/BuildProcList.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { Build, Repo, RepoPermissions } from '~/lib/api/types';
|
import { Build, BuildProc, Repo, RepoPermissions } from '~/lib/api/types';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Build',
|
name: 'Build',
|
||||||
|
@ -72,20 +80,41 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const procId = toRef(props, 'procId');
|
const procId = toRef(props, 'procId');
|
||||||
|
|
||||||
|
const defaultProcId = computed(() => {
|
||||||
|
if (!build.value || !build.value.procs || !build.value.procs[0].children) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return build.value.procs[0].children[0].pid;
|
||||||
|
});
|
||||||
|
|
||||||
const selectedProcId = computed({
|
const selectedProcId = computed({
|
||||||
get() {
|
get() {
|
||||||
if (procId.value) {
|
if (procId.value !== '' && procId.value !== null) {
|
||||||
return parseInt(procId.value, 10);
|
const id = parseInt(procId.value, 10);
|
||||||
|
const proc = build.value?.procs?.reduce(
|
||||||
|
(prev, p) => prev || p.children?.find((c) => c.pid === id),
|
||||||
|
undefined as BuildProc | undefined,
|
||||||
|
);
|
||||||
|
if (proc) {
|
||||||
|
return proc.pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return fallback if proc-id is provided, but proc can not be found
|
||||||
|
return defaultProcId.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!build.value || !build.value.procs || !build.value.procs[0].children) {
|
// is opened on >= md-screen
|
||||||
return null;
|
if (window.innerWidth > 768) {
|
||||||
|
return defaultProcId.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return build.value.procs[0].children[0].pid;
|
return null;
|
||||||
},
|
},
|
||||||
set(_selectedProcId: number | null) {
|
set(_selectedProcId: number | null) {
|
||||||
if (!_selectedProcId) {
|
if (!_selectedProcId) {
|
||||||
|
router.replace({ params: { ...route.params, procId: '' } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<FluidContainer v-if="buildConfigs" class="flex flex-col gap-y-6 text-gray-500 justify-between !py-0">
|
<FluidContainer v-if="buildConfigs" class="flex flex-col gap-y-6 text-gray-500 justify-between !pt-0">
|
||||||
<Panel v-for="buildConfig in buildConfigs" :key="buildConfig.hash" :title="buildConfig.name">
|
<Panel v-for="buildConfig in buildConfigs" :key="buildConfig.hash" :title="buildConfig.name">
|
||||||
<span class="font-mono whitespace-pre">{{ buildConfig.data }}</span>
|
<span class="font-mono whitespace-pre">{{ buildConfig.data }}</span>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
@ -4,8 +4,20 @@
|
||||||
<div class="flex mb-2 items-center">
|
<div class="flex mb-2 items-center">
|
||||||
<IconButton icon="back" class="flex-shrink-0" @click="goBack" />
|
<IconButton icon="back" class="flex-shrink-0" @click="goBack" />
|
||||||
|
|
||||||
<h1 class="text-xl ml-2 text-gray-500 whitespace-nowrap overflow-hidden overflow-ellipsis">
|
<h1
|
||||||
Pipeline #{{ buildId }} - {{ message }}
|
class="
|
||||||
|
order-3
|
||||||
|
w-full
|
||||||
|
md:order-none md:w-auto md:ml-2
|
||||||
|
flex flex-wrap
|
||||||
|
text-center text-xl text-gray-500
|
||||||
|
whitespace-nowrap
|
||||||
|
overflow-hidden overflow-ellipsis
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span class="w-full md:w-auto text-center">Pipeline #{{ buildId }}</span>
|
||||||
|
<span class="<md:hidden mx-2">-</span>
|
||||||
|
<span class="w-full md:w-auto text-center">{{ message }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<BuildStatusIcon :build="build" class="flex flex-shrink-0 ml-auto" />
|
<BuildStatusIcon :build="build" class="flex flex-shrink-0 ml-auto" />
|
||||||
|
@ -29,13 +41,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
||||||
<Tabs v-model="activeTab" disable-hash-mode>
|
<Tabs v-model="activeTab" disable-hash-mode class="order-2 md:order-none">
|
||||||
<Tab id="tasks" title="Tasks" />
|
<Tab id="tasks" title="Tasks" />
|
||||||
<Tab id="config" title="Config" />
|
<Tab id="config" title="Config" />
|
||||||
<Tab id="changed-files" :title="`Changed files (${build.changed_files?.length || 0})`" />
|
<Tab id="changed-files" :title="`Changed files (${build.changed_files?.length || 0})`" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div class="flex justify-between gap-x-4 text-gray-500 flex-shrink-0 ml-auto">
|
<div class="flex justify-between gap-x-4 text-gray-500 flex-shrink-0 pb-2 md:p-0 mx-auto md:mr-0">
|
||||||
<div class="flex space-x-1 items-center flex-shrink-0">
|
<div class="flex space-x-1 items-center flex-shrink-0">
|
||||||
<Icon name="since" />
|
<Icon name="since" />
|
||||||
<span>{{ since }}</span>
|
<span>{{ since }}</span>
|
||||||
|
|
Loading…
Reference in a new issue