mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-03 14:18:42 +00:00
UI cleanups and improvements (#2548)
This commit is contained in:
parent
5bad63556a
commit
284fb99194
17 changed files with 106 additions and 157 deletions
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
|
@ -21,6 +21,7 @@ declare module 'vue' {
|
||||||
Button: typeof import('./src/components/atomic/Button.vue')['default']
|
Button: typeof import('./src/components/atomic/Button.vue')['default']
|
||||||
Checkbox: typeof import('./src/components/form/Checkbox.vue')['default']
|
Checkbox: typeof import('./src/components/form/Checkbox.vue')['default']
|
||||||
CheckboxesField: typeof import('./src/components/form/CheckboxesField.vue')['default']
|
CheckboxesField: typeof import('./src/components/form/CheckboxesField.vue')['default']
|
||||||
|
Container: typeof import('./src/components/layout/Container.vue')['default']
|
||||||
CronTab: typeof import('./src/components/repo/settings/CronTab.vue')['default']
|
CronTab: typeof import('./src/components/repo/settings/CronTab.vue')['default']
|
||||||
DeployPipelinePopup: typeof import('./src/components/layout/popups/DeployPipelinePopup.vue')['default']
|
DeployPipelinePopup: typeof import('./src/components/layout/popups/DeployPipelinePopup.vue')['default']
|
||||||
DocsLink: typeof import('./src/components/atomic/DocsLink.vue')['default']
|
DocsLink: typeof import('./src/components/atomic/DocsLink.vue')['default']
|
||||||
|
|
|
@ -489,5 +489,6 @@
|
||||||
"oauth_error": "Error while authenticating against OAuth provider",
|
"oauth_error": "Error while authenticating against OAuth provider",
|
||||||
"internal_error": "Some internal error occurred",
|
"internal_error": "Some internal error occurred",
|
||||||
"access_denied": "You are not allowed to login"
|
"access_denied": "You are not allowed to login"
|
||||||
}
|
},
|
||||||
|
"default": "default"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="to === null ? 'button' : httpLink ? 'a' : 'router-link'"
|
:is="to === undefined ? 'button' : httpLink ? 'a' : 'router-link'"
|
||||||
v-bind="btnAttrs"
|
v-bind="btnAttrs"
|
||||||
class="relative flex items-center py-1 px-2 rounded-md border shadow-sm cursor-pointer transition-all duration-150 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
|
class="relative flex items-center py-1 px-2 rounded-md border shadow-sm cursor-pointer transition-all duration-150 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
:class="{
|
:class="{
|
||||||
|
@ -19,10 +19,9 @@
|
||||||
<span :class="{ invisible: isLoading }">{{ text }}</span>
|
<span :class="{ invisible: isLoading }">{{ text }}</span>
|
||||||
<Icon v-if="endIcon" :name="endIcon" class="ml-2 w-6 h-6" :class="{ invisible: isLoading }" />
|
<Icon v-if="endIcon" :name="endIcon" class="ml-2 w-6 h-6" :class="{ invisible: isLoading }" />
|
||||||
<div
|
<div
|
||||||
|
v-if="isLoading"
|
||||||
class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center"
|
class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center"
|
||||||
:class="{
|
:class="{
|
||||||
'opacity-100': isLoading,
|
|
||||||
'opacity-0': !isLoading,
|
|
||||||
'bg-wp-control-neutral-200': color === 'gray',
|
'bg-wp-control-neutral-200': color === 'gray',
|
||||||
'bg-wp-control-ok-200': color === 'green',
|
'bg-wp-control-ok-200': color === 'green',
|
||||||
'bg-wp-control-info-200': color === 'blue',
|
'bg-wp-control-info-200': color === 'blue',
|
||||||
|
@ -43,22 +42,22 @@ import Icon, { IconNames } from '~/components/atomic/Icon.vue';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
text: string;
|
text?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
to: RouteLocationRaw | null;
|
to?: RouteLocationRaw;
|
||||||
color: 'blue' | 'green' | 'red' | 'gray';
|
color?: 'blue' | 'green' | 'red' | 'gray';
|
||||||
startIcon: IconNames | null;
|
startIcon?: IconNames;
|
||||||
endIcon: IconNames | null;
|
endIcon?: IconNames;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
text: '',
|
text: undefined,
|
||||||
title: undefined,
|
title: undefined,
|
||||||
to: null,
|
to: undefined,
|
||||||
color: 'gray',
|
color: 'gray',
|
||||||
startIcon: null,
|
startIcon: undefined,
|
||||||
endIcon: null,
|
endIcon: undefined,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -32,22 +32,14 @@ import { RouteLocationRaw } from 'vue-router';
|
||||||
|
|
||||||
import Icon, { IconNames } from '~/components/atomic/Icon.vue';
|
import Icon, { IconNames } from '~/components/atomic/Icon.vue';
|
||||||
|
|
||||||
withDefaults(
|
defineProps<{
|
||||||
defineProps<{
|
icon?: IconNames;
|
||||||
icon: IconNames | null;
|
disabled?: boolean;
|
||||||
disabled?: boolean;
|
to?: RouteLocationRaw;
|
||||||
to: RouteLocationRaw | null;
|
isLoading?: boolean;
|
||||||
isLoading?: boolean;
|
title?: string;
|
||||||
title: string;
|
href?: string;
|
||||||
href?: string;
|
}>();
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
icon: null,
|
|
||||||
to: null,
|
|
||||||
title: undefined,
|
|
||||||
href: '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -16,24 +16,17 @@ import { computed, toRef } from 'vue';
|
||||||
import Checkbox from './Checkbox.vue';
|
import Checkbox from './Checkbox.vue';
|
||||||
import { CheckboxOption } from './form.types';
|
import { CheckboxOption } from './form.types';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = defineProps<{
|
||||||
defineProps<{
|
modelValue?: CheckboxOption['value'][];
|
||||||
modelValue: CheckboxOption['value'][];
|
options?: CheckboxOption[];
|
||||||
options: CheckboxOption[];
|
}>();
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
modelValue: () => [],
|
|
||||||
options: undefined,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'update:modelValue', value: CheckboxOption['value'][]): void;
|
(event: 'update:modelValue', value: CheckboxOption['value'][]): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modelValue = toRef(props, 'modelValue');
|
const modelValue = toRef(props, 'modelValue');
|
||||||
const innerValue = computed({
|
const innerValue = computed({
|
||||||
get: () => modelValue.value,
|
get: () => modelValue.value || [],
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
emit('update:modelValue', value);
|
emit('update:modelValue', value);
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,17 +15,11 @@ import { computed, toRef } from 'vue';
|
||||||
|
|
||||||
import { SelectOption } from './form.types';
|
import { SelectOption } from './form.types';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = defineProps<{
|
||||||
defineProps<{
|
modelValue: string;
|
||||||
modelValue: string;
|
placeholder?: string;
|
||||||
placeholder: string;
|
options: SelectOption[];
|
||||||
options: SelectOption[];
|
}>();
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
placeholder: '',
|
|
||||||
options: undefined,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'update:modelValue', value: string): void;
|
(event: 'update:modelValue', value: string): void;
|
||||||
|
|
|
@ -24,10 +24,10 @@ import { computed, toRef } from 'vue';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue: string;
|
modelValue?: string;
|
||||||
placeholder: string;
|
placeholder?: string;
|
||||||
type: string;
|
type?: string;
|
||||||
lines: number;
|
lines?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
export interface Props {
|
defineProps<{
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
}
|
}>();
|
||||||
|
|
||||||
defineProps<Props>();
|
|
||||||
</script>
|
</script>
|
|
@ -36,15 +36,10 @@ import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import Icon from '~/components/atomic/Icon.vue';
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = defineProps<{
|
||||||
defineProps<{
|
title?: string;
|
||||||
title?: string;
|
collapsable?: boolean;
|
||||||
collapsable?: boolean;
|
}>();
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _collapsed is used to store the internal state of the panel, but is
|
* _collapsed is used to store the internal state of the panel, but is
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class="bg-wp-background-100 border-b-1 border-wp-background-400 dark:border-wp-background-100 dark:bg-wp-background-300 text-wp-text-100"
|
class="bg-wp-background-100 border-b-1 border-wp-background-400 dark:border-wp-background-100 dark:bg-wp-background-300 text-wp-text-100"
|
||||||
:class="{ 'md:px-4': fullWidth }"
|
:class="{ 'md:px-4': fullWidth }"
|
||||||
>
|
>
|
||||||
<FluidContainer :full-width="fullWidth" class="!py-0">
|
<Container :full-width="fullWidth" class="!py-0">
|
||||||
<div class="flex w-full md:items-center flex-col py-3 gap-2 md:gap-10 md:flex-row md:justify-between">
|
<div class="flex w-full md:items-center flex-col py-3 gap-2 md:gap-10 md:flex-row md:justify-between">
|
||||||
<div
|
<div
|
||||||
class="flex items-center content-start"
|
class="flex items-center content-start"
|
||||||
|
@ -46,13 +46,13 @@
|
||||||
<slot name="tabActions" />
|
<slot name="tabActions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FluidContainer>
|
</Container>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TextField from '~/components/form/TextField.vue';
|
import TextField from '~/components/form/TextField.vue';
|
||||||
import FluidContainer from '~/components/layout/FluidContainer.vue';
|
import Container from '~/components/layout/Container.vue';
|
||||||
|
|
||||||
import Tabs from './Tabs.vue';
|
import Tabs from './Tabs.vue';
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
:go-back="goBack"
|
:go-back="goBack"
|
||||||
:enable-tabs="enableTabs"
|
:enable-tabs="enableTabs"
|
||||||
:search="search"
|
:search="search"
|
||||||
:full-width="fullWidth"
|
:full-width="fullWidthHeader"
|
||||||
@update:search="(value) => $emit('update:search', value)"
|
@update:search="(value) => $emit('update:search', value)"
|
||||||
>
|
>
|
||||||
<template #title><slot name="title" /></template>
|
<template #title><slot name="title" /></template>
|
||||||
|
@ -11,48 +11,39 @@
|
||||||
<template v-if="$slots.tabActions" #tabActions><slot name="tabActions" /></template>
|
<template v-if="$slots.tabActions" #tabActions><slot name="tabActions" /></template>
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<FluidContainer v-if="fluidContent">
|
<slot v-if="fluidContent" />
|
||||||
|
<Container v-else>
|
||||||
<slot />
|
<slot />
|
||||||
</FluidContainer>
|
</Container>
|
||||||
<slot v-else />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { toRef } from 'vue';
|
import { toRef } from 'vue';
|
||||||
|
|
||||||
import FluidContainer from '~/components/layout/FluidContainer.vue';
|
import Container from '~/components/layout/Container.vue';
|
||||||
import { useTabsProvider } from '~/compositions/useTabs';
|
import { useTabsProvider } from '~/compositions/useTabs';
|
||||||
|
|
||||||
import Header from './Header.vue';
|
import Header from './Header.vue';
|
||||||
|
|
||||||
export interface Props {
|
const props = defineProps<{
|
||||||
// Header
|
// Header
|
||||||
goBack?: () => void;
|
goBack?: () => void;
|
||||||
search?: string;
|
search?: string;
|
||||||
|
fullWidthHeader?: boolean;
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
enableTabs?: boolean;
|
enableTabs?: boolean;
|
||||||
disableHashMode?: boolean;
|
disableHashMode?: boolean;
|
||||||
activeTab: string;
|
activeTab?: string;
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
fluidContent?: boolean;
|
fluidContent?: boolean;
|
||||||
fullWidth?: boolean;
|
}>();
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const emit = defineEmits<{
|
||||||
goBack: undefined,
|
(event: 'update:activeTab', value: string): void;
|
||||||
search: undefined,
|
(event: 'update:search', value: string): void;
|
||||||
// eslint-disable-next-line vue/no-boolean-default
|
}>();
|
||||||
disableHashMode: false,
|
|
||||||
// eslint-disable-next-line vue/no-boolean-default
|
|
||||||
enableTabs: false,
|
|
||||||
activeTab: '',
|
|
||||||
// eslint-disable-next-line vue/no-boolean-default
|
|
||||||
fluidContent: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:activeTab', 'update:search']);
|
|
||||||
|
|
||||||
if (props.enableTabs) {
|
if (props.enableTabs) {
|
||||||
useTabsProvider({
|
useTabsProvider({
|
||||||
|
|
|
@ -275,7 +275,7 @@ async function loadLogs() {
|
||||||
if (loadedStepSlug.value === stepSlug.value) {
|
if (loadedStepSlug.value === stepSlug.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadedStepSlug.value = stepSlug.value;
|
|
||||||
log.value = undefined;
|
log.value = undefined;
|
||||||
logBuffer.value = [];
|
logBuffer.value = [];
|
||||||
ansiUp.value = new AnsiUp();
|
ansiUp.value = new AnsiUp();
|
||||||
|
@ -294,12 +294,12 @@ async function loadLogs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStepFinished(step.value)) {
|
if (isStepFinished(step.value)) {
|
||||||
|
loadedStepSlug.value = stepSlug.value;
|
||||||
const logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
|
const logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
|
||||||
logs?.forEach((line) => writeLog({ index: line.line, text: b64DecodeUnicode(line.data), time: line.time }));
|
logs?.forEach((line) => writeLog({ index: line.line, text: b64DecodeUnicode(line.data), time: line.time }));
|
||||||
flushLogs(false);
|
flushLogs(false);
|
||||||
}
|
} else if (isStepRunning(step.value)) {
|
||||||
|
loadedStepSlug.value = stepSlug.value;
|
||||||
if (isStepRunning(step.value)) {
|
|
||||||
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
|
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
|
||||||
writeLog({ index: line.line, text: b64DecodeUnicode(line.data), time: line.time });
|
writeLog({ index: line.line, text: b64DecodeUnicode(line.data), time: line.time });
|
||||||
flushLogs(true);
|
flushLogs(true);
|
||||||
|
@ -308,18 +308,22 @@ async function loadLogs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loadLogs();
|
await loadLogs();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(stepSlug, () => {
|
watch(stepSlug, async () => {
|
||||||
loadLogs();
|
await loadLogs();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(step, (oldStep, newStep) => {
|
watch(step, async (newStep, oldStep) => {
|
||||||
if (oldStep && oldStep.name === newStep?.name && oldStep?.end_time !== newStep?.end_time) {
|
if (oldStep?.name === newStep?.name) {
|
||||||
if (autoScroll.value) {
|
if (oldStep?.end_time !== newStep?.end_time && autoScroll.value) {
|
||||||
scrollDown();
|
scrollDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldStep?.state !== newStep?.state) {
|
||||||
|
await loadLogs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function useTabsProvider({
|
||||||
disableHashMode,
|
disableHashMode,
|
||||||
updateActiveTabProp,
|
updateActiveTabProp,
|
||||||
}: {
|
}: {
|
||||||
activeTabProp: Ref<string>;
|
activeTabProp: Ref<string | undefined>;
|
||||||
updateActiveTabProp: (tab: string) => void;
|
updateActiveTabProp: (tab: string) => void;
|
||||||
disableHashMode: Ref<boolean>;
|
disableHashMode: Ref<boolean>;
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -39,53 +39,33 @@ export default class ApiClient {
|
||||||
this.csrf = csrf;
|
this.csrf = csrf;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _request(method: string, path: string, data: unknown): Promise<unknown> {
|
private async _request(method: string, path: string, data: unknown): Promise<unknown> {
|
||||||
const endpoint = `${this.server}${path}`;
|
const res = await fetch(`${this.server}${path}`, {
|
||||||
const xhr = new XMLHttpRequest();
|
method,
|
||||||
xhr.open(method, endpoint, true);
|
headers: {
|
||||||
|
...(method !== 'GET' && this.csrf ? { 'X-CSRF-TOKEN': this.csrf } : {}),
|
||||||
if (this.token) {
|
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
|
||||||
xhr.setRequestHeader('Authorization', `Bearer ${this.token}`);
|
},
|
||||||
}
|
body: data ? JSON.stringify(data) : undefined,
|
||||||
|
|
||||||
if (method !== 'GET' && this.csrf) {
|
|
||||||
xhr.setRequestHeader('X-CSRF-TOKEN', this.csrf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
xhr.onload = () => {
|
|
||||||
if (xhr.readyState === 4) {
|
|
||||||
if (xhr.status >= 300) {
|
|
||||||
const error: ApiError = {
|
|
||||||
status: xhr.status,
|
|
||||||
message: xhr.response,
|
|
||||||
};
|
|
||||||
if (this.onerror) {
|
|
||||||
this.onerror(error);
|
|
||||||
}
|
|
||||||
reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const contentType = xhr.getResponseHeader('Content-Type');
|
|
||||||
if (contentType && contentType.startsWith('application/json')) {
|
|
||||||
resolve(JSON.parse(xhr.response));
|
|
||||||
} else {
|
|
||||||
resolve(xhr.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = (e) => {
|
|
||||||
reject(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
||||||
xhr.send(JSON.stringify(data));
|
|
||||||
} else {
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const error: ApiError = {
|
||||||
|
status: res.status,
|
||||||
|
message: res.statusText,
|
||||||
|
};
|
||||||
|
if (this.onerror) {
|
||||||
|
this.onerror(error);
|
||||||
|
}
|
||||||
|
throw new Error(res.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = res.headers.get('Content-Type');
|
||||||
|
if (contentType && contentType.startsWith('application/json')) {
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
_get(path: string) {
|
_get(path: string) {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
:to="{ name: 'repo-branch', params: { branch } }"
|
:to="{ name: 'repo-branch', params: { branch } }"
|
||||||
>
|
>
|
||||||
{{ branch }}
|
{{ branch }}
|
||||||
|
<Badge v-if="branch === repo?.default_branch" :label="$t('default')" class="ml-auto" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, Ref, watch } from 'vue';
|
import { inject, Ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import Badge from '~/components/atomic/Badge.vue';
|
||||||
import ListItem from '~/components/atomic/ListItem.vue';
|
import ListItem from '~/components/atomic/ListItem.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
import { usePagination } from '~/compositions/usePaginate';
|
import { usePagination } from '~/compositions/usePaginate';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<FluidContainer full-width class="flex flex-col flex-grow md:min-h-xs">
|
<Container full-width class="flex flex-col flex-grow md:min-h-xs">
|
||||||
<div class="flex w-full min-h-0 flex-grow">
|
<div class="flex w-full min-h-0 flex-grow">
|
||||||
<PipelineStepList
|
<PipelineStepList
|
||||||
v-if="pipeline?.workflows?.length || 0 > 0"
|
v-if="pipeline?.workflows?.length || 0 > 0"
|
||||||
|
@ -25,7 +25,6 @@
|
||||||
color="blue"
|
color="blue"
|
||||||
:start-icon="forge ?? 'repo'"
|
:start-icon="forge ?? 'repo'"
|
||||||
:text="$t('repo.pipeline.protected.review')"
|
:text="$t('repo.pipeline.protected.review')"
|
||||||
:is-loading="isApprovingPipeline"
|
|
||||||
:to="pipeline.link_url"
|
:to="pipeline.link_url"
|
||||||
:title="message"
|
:title="message"
|
||||||
/>
|
/>
|
||||||
|
@ -57,7 +56,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FluidContainer>
|
</Container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -67,7 +66,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import Button from '~/components/atomic/Button.vue';
|
import Button from '~/components/atomic/Button.vue';
|
||||||
import Icon from '~/components/atomic/Icon.vue';
|
import Icon from '~/components/atomic/Icon.vue';
|
||||||
import FluidContainer from '~/components/layout/FluidContainer.vue';
|
import Container from '~/components/layout/Container.vue';
|
||||||
import PipelineLog from '~/components/repo/pipeline/PipelineLog.vue';
|
import PipelineLog from '~/components/repo/pipeline/PipelineLog.vue';
|
||||||
import PipelineStepList from '~/components/repo/pipeline/PipelineStepList.vue';
|
import PipelineStepList from '~/components/repo/pipeline/PipelineStepList.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
enable-tabs
|
enable-tabs
|
||||||
disable-hash-mode
|
disable-hash-mode
|
||||||
:go-back="goBack"
|
:go-back="goBack"
|
||||||
:fluid-content="activeTab !== 'tasks'"
|
:fluid-content="activeTab === 'tasks'"
|
||||||
:full-width="true"
|
full-width-header
|
||||||
>
|
>
|
||||||
<template #title>{{ repo.full_name }}</template>
|
<template #title>{{ repo.full_name }}</template>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue