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:
Anbraten 2022-01-16 18:42:10 +01:00 committed by GitHub
parent b15879d460
commit 549996cbcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 324 additions and 163 deletions

View file

@ -6,7 +6,7 @@
flex
items-center
py-1
px-4
px-2
rounded-md
border
shadow-sm
@ -32,7 +32,7 @@
@click="doClick"
>
<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>
<Icon v-if="endIcon" :name="endIcon" class="ml-2" :class="{ invisible: isLoading }" />
<div

View file

@ -31,6 +31,8 @@
<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-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" />
</template>
@ -70,7 +72,9 @@ export type IconNames =
| 'light'
| 'sync'
| 'heal'
| 'turn-off';
| 'chevron-right'
| 'turn-off'
| 'close';
export default defineComponent({
name: 'Icon',

View file

@ -1,9 +1,8 @@
<template>
<Button
<div
:disabled="disabled"
:is-loading="isLoading"
:to="to"
class="
relative
flex
items-center
justify-center
@ -11,30 +10,41 @@
px-1
py-1
rounded-full
!bg-transparent
!hover:bg-gray-200
!dark:hover:bg-gray-600
hover:text-gray-700
dark:text-gray-500 dark:hover:text-gray-700
shadow-none
border-none
bg-transparent
hover:bg-gray-200 hover:text-gray-700
dark:hover:bg-gray-600 dark:text-gray-500 dark:hover:text-gray-700
cursor-pointer
transition-all
duration-150
focus:outline-none
overflow-hidden
disabled:opacity-50 disabled:cursor-not-allowed
"
@click="doClick"
>
<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>
<script lang="ts">
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';
export default defineComponent({
name: 'IconButton',
components: { Button, Icon },
components: { Icon },
props: {
icon: {
@ -56,5 +66,28 @@ export default defineComponent({
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>

View file

@ -5,6 +5,7 @@
type="checkbox"
class="
checkbox
flex-shrink-0
relative
border border-gray-400
dark:border-gray-600

View file

@ -6,6 +6,7 @@
class="
radio
relative
flex-shrink-0
border border-gray-400
dark:border-gray-600
cursor-pointer

View file

@ -1,6 +1,6 @@
<template>
<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
class="min-h-full w-3"
:class="{
@ -11,47 +11,56 @@
'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'" />
<BuildStatusIcon v-else class="mx-3" :build="build" />
<BuildStatusIcon v-else class="mx-2 md:mx-3" :build="build" />
</div>
</div>
<div class="flex py-2 px-4 flex-grow min-w-0">
<div class="flex items-center flex-shrink-0"><img class="w-8" :src="build.author_avatar" /></div>
<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 class="flex py-2 px-4 flex-grow min-w-0 flex-wrap">
<div class="<md:hidden flex items-center flex-shrink-0">
<img class="w-8" :src="build.author_avatar" />
</div>
<div class="flex ml-auto text-gray-500 py-2 flex-shrink-0">
<div class="flex flex-col space-y-2 w-42">
<div class="flex space-x-2 items-center">
<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 v-if="build.event === 'pull_request'" class="truncate">{{
`#${build.ref.replaceAll('refs/pull/', '').replaceAll('/merge', '').replaceAll('/head', '')}`
}}</span>
<span v-else class="truncate">{{ build.branch }}</span>
</div>
<div class="flex space-x-2 items-center">
<Icon name="commit" />
<span>{{ build.commit.slice(0, 10) }}</span>
</div>
<div class="w-full md:w-auto md:mx-4 flex items-center min-w-0">
<span
class="text-gray-600 <md:underline dark:text-gray-500 whitespace-nowrap overflow-hidden overflow-ellipsis"
>{{ message }}</span
>
</div>
<div
class="
grid grid-rows-2 grid-flow-col
w-full
md:ml-auto md:w-96
py-2
gap-x-4 gap-y-2
flex-shrink-0
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 class="flex flex-col ml-4 space-y-2 w-42">
<div class="flex space-x-2 items-center">
<Icon name="duration" />
<span>{{ duration }}</span>
</div>
<div class="flex space-x-2 items-center">
<Icon name="since" />
<span>{{ since }}</span>
</div>
<div class="flex space-x-2 items-center min-w-0">
<Icon name="commit" />
<span class="truncate">{{ build.commit.slice(0, 10) }}</span>
</div>
<div class="flex space-x-2 items-center min-w-0">
<Icon name="duration" />
<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>
@ -83,9 +92,9 @@ export default defineComponent({
setup(props) {
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>

View file

@ -1,5 +1,13 @@
<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 class="text-gray-500 text-sm w-4">{{ (logLine.pos || 0) + 1 }}</div>
<!-- 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">
exit code {{ proc.exit_code }}
</div>
<template v-if="!proc?.start_time" />
<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
>
<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 { computed, defineComponent, inject, onBeforeUnmount, onMounted, PropType, Ref, toRef, watch } from 'vue';
import Icon from '~/components/atomic/Icon.vue';
import useBuildProc from '~/compositions/useBuildProc';
import { Build, Repo } from '~/lib/api/types';
import { findProc } from '~/utils/helpers';
@ -30,7 +40,7 @@ import { findProc } from '~/utils/helpers';
export default defineComponent({
name: 'BuildLog',
components: {},
components: { Icon },
props: {
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) {
const build = toRef(props, 'build');
const procId = toRef(props, 'procId');

View file

@ -1,26 +1,37 @@
<template>
<div class="flex flex-col w-3/12 text-gray-200 dark:text-gray-400">
<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 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
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 items-center"><img class="w-6" :src="build.author_avatar" /></div>
<span>{{ build.author }}</span>
</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 === 'deployment'" name="deployment" />
<Icon v-else-if="build.event === 'tag'" name="tag" />
<a
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"
target="_blank"
>
<Icon name="pull_request" />
<span>{{
`#${build.ref.replaceAll('refs/pull/', '').replaceAll('refs/merge-requests/', '').replaceAll('/head', '')}`
}}</span></a
>
<span v-if="build.event !== 'pull_request'">{{ build.branch }}</span>
<span class="truncate">{{ prettyRef }}</span>
</a>
<span v-if="build.event !== 'pull_request'" class="truncate">{{ build.branch }}</span>
</div>
<div class="flex items-center flex-shrink-0">
<template v-if="build.event === 'pull_request'">
@ -34,55 +45,64 @@
</div>
</div>
<div v-for="proc in build.procs" :key="proc.id">
<div class="p-4 pb-1 flex flex-wrap items-center justify-between">
<span>{{ proc.name }}</span>
<div v-if="proc.environ" class="text-xs">
<div v-for="(value, key) in proc.environ" :key="key">
<span
class="
pl-2
pr-1
py-0.5
bg-gray-800
dark:bg-gray-600
border-2 border-gray-800
dark:border-gray-600
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 v-if="build.procs === undefined || build.procs.length === 0" class="m-auto">
<span>No pipeline steps available!</span>
</div>
<div class="flex flex-grow relative min-h-0 overflow-y-auto">
<div class="md:absolute top-0 left-0 w-full">
<div v-for="proc in build.procs" :key="proc.id">
<div class="p-4 pb-1 flex flex-wrap items-center justify-between">
<span>{{ proc.name }}</span>
<div v-if="proc.environ" class="text-xs">
<div v-for="(value, key) in proc.environ" :key="key">
<span
class="
pl-2
pr-1
py-0.5
bg-gray-800
dark:bg-gray-600
border-2 border-gray-800
dark:border-gray-600
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
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>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { defineComponent, PropType, toRef } from 'vue';
import BuildProcDuration from '~/components/repo/build/BuildProcDuration.vue';
import useBuild from '~/compositions/useBuild';
import { Build } from '~/lib/api/types';
export default defineComponent({
@ -108,5 +128,12 @@ export default defineComponent({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'update:selected-proc-id': (selectedProcId: number) => true,
},
setup(props) {
const build = toRef(props, 'build');
const { prettyRef } = useBuild(build);
return { prettyRef };
},
});
</script>

View file

@ -1,18 +1,20 @@
<template>
<div class="flex flex-col">
<div class="flex w-full pt-4">
<div class="flex w-full md:pt-4 flex-wrap">
<div
v-for="tab in tabs"
:key="tab.id"
class="
w-full
py-2
md:w-auto md:pt-0 md:pb-2 md:px-8
flex
cursor-pointer
pb-2
px-8
border-b-2
md:border-b-2
text-gray-500
hover:text-gray-700
dark:text-gray-500 dark:hover:text-gray-400
items-center
"
:class="{
'border-gray-400 dark:border-gray-600': activeTab === tab.id,
@ -20,6 +22,8 @@
}"
@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>
</div>
</div>
@ -34,11 +38,13 @@
import { computed, defineComponent, onMounted, provide, ref, toRef } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import Icon from '~/components/atomic/Icon.vue';
import { Tab } from './types';
export default defineComponent({
name: 'Tabs',
components: { Icon },
props: {
// used by toRef
// eslint-disable-next-line vue/no-unused-properties
@ -62,7 +68,6 @@ export default defineComponent({
setup(props, { emit }) {
const router = useRouter();
const route = useRoute();
const disableHashMode = toRef(props, 'disableHashMode');
const modelValue = toRef(props, 'modelValue');
const tabs = ref<Tab[]>([]);
@ -72,35 +77,28 @@ export default defineComponent({
'active-tab',
computed(() => activeTab.value),
);
async function selectTab(tab: Tab) {
if (tab.id === undefined) {
return;
}
activeTab.value = tab.id;
emit('update:modelValue', activeTab.value);
if (!disableHashMode.value) {
await router.replace({ params: route.params, hash: `#${tab.id}` });
}
}
onMounted(() => {
if (modelValue.value) {
activeTab.value = modelValue.value;
return;
}
const hashTab = route.hash.replace(/^#/, '');
if (hashTab) {
activeTab.value = hashTab;
return;
}
activeTab.value = tabs.value[0].id;
});
return { tabs, activeTab, selectTab };
},
});

View file

@ -76,5 +76,25 @@ export default (build: Ref<Build | undefined>) => {
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 };
};

View file

@ -108,6 +108,7 @@ export type BuildProc = {
start_time?: number;
end_time?: number;
machine?: string;
error?: string;
children?: BuildProc[];
};

View file

@ -3,15 +3,26 @@
<div v-if="errorMessage" class="bg-red-400 text-white dark:text-gray-500 p-4 rounded-md text-lg">
{{ errorMessage }}
</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" />
</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>
<Button class="mt-4" @click="doLogin">Login</Button>
</div>
</Panel>
</div>
</div>
</template>
@ -20,7 +31,6 @@ import { defineComponent, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import Button from '~/components/atomic/Button.vue';
import Panel from '~/components/layout/Panel.vue';
import useAuthentication from '~/compositions/useAuthentication';
const authErrorMessages = {
@ -34,7 +44,6 @@ export default defineComponent({
components: {
Button,
Panel,
},
setup() {

View file

@ -1,9 +1,9 @@
<template>
<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>
<TextField v-model="search" class="w-auto ml-auto" placeholder="Search ..." />
<Button class="ml-auto" :to="{ name: 'repo-add' }" start-icon="plus" text="Add repository" />
<TextField v-model="search" class="w-auto md:ml-auto" placeholder="Search ..." />
<Button class="md:ml-auto" :to="{ name: 'repo-add' }" start-icon="plus" text="Add repository" />
</div>
<div class="space-y-4">

View file

@ -1,8 +1,10 @@
<template>
<FluidContainer v-if="repo && repoPermissions && $route.meta.repoHeader">
<div class="flex border-b items-center pb-4 mb-4 dark:border-gray-600">
<h1 class="text-xl text-gray-500">{{ `${repo.owner} / ${repo.name}` }}</h1>
<a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="ml-auto">
<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 w-full md:w-auto text-center mb-4 md:mb-0">
{{ `${repo.owner} / ${repo.name}` }}
</h1>
<a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="md:ml-auto">
<img :src="badgeUrl" />
</a>
<a

View file

@ -1,27 +1,35 @@
<template>
<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">
<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>
<div class="flex w-full min-h-0 flex-grow">
<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>
</template>
@ -37,7 +45,7 @@ import BuildProcList from '~/components/repo/build/BuildProcList.vue';
import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
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({
name: 'Build',
@ -72,20 +80,41 @@ export default defineComponent({
}
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({
get() {
if (procId.value) {
return parseInt(procId.value, 10);
if (procId.value !== '' && procId.value !== null) {
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) {
return null;
// is opened on >= md-screen
if (window.innerWidth > 768) {
return defaultProcId.value;
}
return build.value.procs[0].children[0].pid;
return null;
},
set(_selectedProcId: number | null) {
if (!_selectedProcId) {
router.replace({ params: { ...route.params, procId: '' } });
return;
}

View file

@ -1,5 +1,5 @@
<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">
<span class="font-mono whitespace-pre">{{ buildConfig.data }}</span>
</Panel>

View file

@ -4,8 +4,20 @@
<div class="flex mb-2 items-center">
<IconButton icon="back" class="flex-shrink-0" @click="goBack" />
<h1 class="text-xl ml-2 text-gray-500 whitespace-nowrap overflow-hidden overflow-ellipsis">
Pipeline #{{ buildId }} - {{ message }}
<h1
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>
<BuildStatusIcon :build="build" class="flex flex-shrink-0 ml-auto" />
@ -29,13 +41,13 @@
</div>
<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="config" title="Config" />
<Tab id="changed-files" :title="`Changed files (${build.changed_files?.length || 0})`" />
</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">
<Icon name="since" />
<span>{{ since }}</span>