Use Vue setup directive (#2165)

This commit is contained in:
qwerty287 2023-08-08 12:22:39 +02:00 committed by GitHub
parent 9cae5709f9
commit 3bdeb47d8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 582 additions and 981 deletions

View file

@ -45,9 +45,9 @@
</Panel> </Panel>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, defineComponent, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -69,74 +69,48 @@ const emptySecret = {
event: [WebhookEvents.Push], event: [WebhookEvents.Push],
}; };
export default defineComponent({ const apiClient = useApiClient();
name: 'AdminSecretsTab', const notifications = useNotifications();
const i18n = useI18n();
components: { const selectedSecret = ref<Partial<Secret>>();
Button, const isEditingSecret = computed(() => !!selectedSecret.value?.id);
Panel,
DocsLink,
SecretList,
SecretEdit,
Warning,
},
setup() { async function loadSecrets(page: number): Promise<Secret[] | null> {
const apiClient = useApiClient(); return apiClient.getGlobalSecretList(page);
const notifications = useNotifications(); }
const i18n = useI18n();
const selectedSecret = ref<Partial<Secret>>(); const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value);
const isEditingSecret = computed(() => !!selectedSecret.value?.id);
async function loadSecrets(page: number): Promise<Secret[] | null> { const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => {
return apiClient.getGlobalSecretList(page); if (!selectedSecret.value) {
} throw new Error("Unexpected: Can't get secret");
}
const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value); if (isEditingSecret.value) {
await apiClient.updateGlobalSecret(selectedSecret.value);
const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => { } else {
if (!selectedSecret.value) { await apiClient.createGlobalSecret(selectedSecret.value);
throw new Error("Unexpected: Can't get secret"); }
} notifications.notify({
title: i18n.t(isEditingSecret.value ? 'admin.settings.secrets.saved' : 'admin.settings.secrets.created'),
if (isEditingSecret.value) { type: 'success',
await apiClient.updateGlobalSecret(selectedSecret.value); });
} else { selectedSecret.value = undefined;
await apiClient.createGlobalSecret(selectedSecret.value); resetPage();
}
notifications.notify({
title: i18n.t(isEditingSecret.value ? 'admin.settings.secrets.saved' : 'admin.settings.secrets.created'),
type: 'success',
});
selectedSecret.value = undefined;
resetPage();
});
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
await apiClient.deleteGlobalSecret(_secret.name);
notifications.notify({ title: i18n.t('admin.settings.secrets.deleted'), type: 'success' });
resetPage();
});
function editSecret(secret: Secret) {
selectedSecret.value = cloneDeep(secret);
}
function showAddSecret() {
selectedSecret.value = cloneDeep(emptySecret);
}
return {
selectedSecret,
secrets,
isDeleting,
isSaving,
showAddSecret,
createSecret,
editSecret,
deleteSecret,
};
},
}); });
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
await apiClient.deleteGlobalSecret(_secret.name);
notifications.notify({ title: i18n.t('admin.settings.secrets.deleted'), type: 'success' });
resetPage();
});
function editSecret(secret: Secret) {
selectedSecret.value = cloneDeep(secret);
}
function showAddSecret() {
selectedSecret.value = cloneDeep(emptySecret);
}
</script> </script>

View file

@ -35,88 +35,59 @@
</button> </button>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, PropType } from 'vue'; import { computed, useAttrs } from 'vue';
import { RouteLocationRaw, useRouter } from 'vue-router'; import { RouteLocationRaw, useRouter } from 'vue-router';
import Icon, { IconNames } from '~/components/atomic/Icon.vue'; import Icon, { IconNames } from '~/components/atomic/Icon.vue';
export default defineComponent({ const props = withDefaults(
name: 'Button', defineProps<{
text: string;
components: { Icon }, title?: string;
disabled?: boolean;
props: { to: RouteLocationRaw | null;
text: { color: 'blue' | 'green' | 'red' | 'gray';
type: String, startIcon: IconNames | null;
default: null, endIcon: IconNames | null;
}, isLoading?: boolean;
}>(),
title: { {
type: String, text: '',
default: null, title: undefined,
}, to: null,
color: 'gray',
disabled: { startIcon: null,
type: Boolean, endIcon: null,
required: false,
},
to: {
type: [String, Object, null] as PropType<RouteLocationRaw | null>,
default: null,
},
color: {
type: String as PropType<'blue' | 'green' | 'red' | 'gray'>,
default: 'gray',
},
startIcon: {
type: String as PropType<IconNames | null>,
default: null,
},
endIcon: {
type: String as PropType<IconNames | null>,
default: null,
},
isLoading: {
type: Boolean,
},
}, },
);
setup(props, { attrs }) { const router = useRouter();
const router = useRouter();
async function doClick() { async function doClick() {
if (props.isLoading) { if (props.isLoading) {
return; return;
} }
if (!props.to) { if (!props.to) {
return; return;
} }
if (typeof props.to === 'string' && props.to.startsWith('http')) { if (typeof props.to === 'string' && props.to.startsWith('http')) {
window.location.href = props.to; window.location.href = props.to;
return; return;
} }
await router.push(props.to); await router.push(props.to);
} }
const passedClasses = computed(() => { const attrs = useAttrs();
const classes: Record<string, boolean> = {}; const passedClasses = computed(() => {
const origClass = (attrs.class as string) || ''; const classes: Record<string, boolean> = {};
origClass.split(' ').forEach((c) => { const origClass = (attrs.class as string) || '';
classes[c] = true; origClass.split(' ').forEach((c) => {
}); classes[c] = true;
return classes; });
}); return classes;
return { doClick, passedClasses };
},
}); });
</script> </script>

View file

@ -1,7 +1,7 @@
<template> <template>
<router-link v-if="to" :to="to" :title="title" :aria-label="title" class="icon-button"> <router-link v-if="to" :to="to" :title="title" :aria-label="title" class="icon-button">
<slot> <slot>
<Icon :name="icon" /> <Icon v-if="icon" :name="icon" />
</slot> </slot>
</router-link> </router-link>
<a <a
@ -14,12 +14,12 @@
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<slot> <slot>
<Icon :name="icon" /> <Icon v-if="icon" :name="icon" />
</slot> </slot>
</a> </a>
<button v-else :disabled="disabled" class="icon-button" type="button" :title="title" :aria-label="title"> <button v-else :disabled="disabled" class="icon-button" type="button" :title="title" :aria-label="title">
<slot> <slot>
<Icon :name="icon" /> <Icon v-if="icon" :name="icon" />
</slot> </slot>
<div v-if="isLoading" class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center"> <div v-if="isLoading" class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center">
<Icon name="loading" class="animate-spin" /> <Icon name="loading" class="animate-spin" />
@ -27,48 +27,27 @@
</button> </button>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, PropType } from 'vue';
import { RouteLocationRaw } from 'vue-router'; import { RouteLocationRaw } from 'vue-router';
import Icon, { IconNames } from '~/components/atomic/Icon.vue'; import Icon, { IconNames } from '~/components/atomic/Icon.vue';
export default defineComponent({ withDefaults(
name: 'IconButton', defineProps<{
icon: IconNames | null;
components: { Icon }, disabled?: boolean;
to: RouteLocationRaw | null;
props: { isLoading?: boolean;
icon: { title: string;
type: String as PropType<IconNames>, href?: string;
default: '', }>(),
}, {
icon: null,
disabled: { to: null,
type: Boolean, title: undefined,
required: false, href: '',
},
to: {
type: [String, Object, null] as PropType<RouteLocationRaw | null>,
default: null,
},
isLoading: {
type: Boolean,
},
title: {
type: String,
required: true,
},
href: {
type: String,
default: '',
},
}, },
}); );
</script> </script>
<style scoped> <style scoped>

View file

@ -6,17 +6,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; defineProps<{
text: string;
export default defineComponent({ }>();
name: 'Warning',
props: {
text: {
type: String,
required: true,
},
},
});
</script> </script>

View file

@ -8,57 +8,34 @@
@click="innerValue = !innerValue" @click="innerValue = !innerValue"
/> />
<div class="flex flex-col ml-4"> <div class="flex flex-col ml-4">
<label v-if="label" class="cursor-pointer text-wp-text-100" :for="`checkbox-${id}`">{{ label }}</label> <label class="cursor-pointer text-wp-text-100" :for="`checkbox-${id}`">{{ label }}</label>
<span v-if="description" class="text-sm text-wp-text-alt-100">{{ description }}</span> <span v-if="description" class="text-sm text-wp-text-alt-100">{{ description }}</span>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, toRef } from 'vue'; import { computed, toRef } from 'vue';
export default defineComponent({ const props = defineProps<{
name: 'Checkbox', modelValue: boolean;
label: string;
description?: string;
}>();
props: { const emit = defineEmits<{
modelValue: { (event: 'update:modelValue', value: boolean): void;
type: Boolean, }>();
required: true,
},
label: { const modelValue = toRef(props, 'modelValue');
type: String, const innerValue = computed({
default: null, get: () => modelValue.value,
}, set: (value) => {
emit('update:modelValue', value);
description: {
type: String,
default: null,
},
},
emits: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'update:modelValue': (_value: boolean): boolean => true,
},
setup: (props, ctx) => {
const modelValue = toRef(props, 'modelValue');
const innerValue = computed({
get: () => modelValue.value,
set: (value) => {
ctx.emit('update:modelValue', value);
},
});
const id = (Math.random() + 1).toString(36).substring(7);
return {
id,
innerValue,
};
}, },
}); });
const id = (Math.random() + 1).toString(36).substring(7);
</script> </script>
<style scoped> <style scoped>

View file

@ -10,55 +10,40 @@
/> />
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, PropType, toRef } from 'vue'; 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';
export default defineComponent({ const props = withDefaults(
name: 'CheckboxesField', defineProps<{
modelValue: CheckboxOption['value'][];
components: { Checkbox }, options: CheckboxOption[];
}>(),
props: { {
modelValue: { modelValue: () => [],
type: Array as PropType<CheckboxOption['value'][]>, options: undefined,
default: () => [],
},
options: {
type: Array as PropType<CheckboxOption[]>,
required: true,
},
}, },
);
emits: { const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/no-unused-vars (event: 'update:modelValue', value: CheckboxOption['value'][]): void;
'update:modelValue': (_value: CheckboxOption['value'][]): boolean => true, }>();
},
setup: (props, ctx) => { 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);
ctx.emit('update:modelValue', value);
},
});
function clickOption(option: CheckboxOption) {
if (innerValue.value.includes(option.value)) {
innerValue.value = innerValue.value.filter((o) => o !== option.value);
} else {
innerValue.value.push(option.value);
}
}
return {
innerValue,
clickOption,
};
}, },
}); });
function clickOption(option: CheckboxOption) {
if (innerValue.value.includes(option.value)) {
innerValue.value = innerValue.value.filter((o) => o !== option.value);
} else {
innerValue.value.push(option.value);
}
}
</script> </script>

View file

@ -2,45 +2,25 @@
<TextField v-model="innerValue" :placeholder="placeholder" type="number" /> <TextField v-model="innerValue" :placeholder="placeholder" type="number" />
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, toRef } from 'vue'; import { computed, toRef } from 'vue';
import TextField from '~/components/form/TextField.vue'; import TextField from '~/components/form/TextField.vue';
export default defineComponent({ const props = defineProps<{
name: 'NumberField', modelValue: number;
placeholder?: string;
}>();
components: { TextField }, const emit = defineEmits<{
(event: 'update:modelValue', value: number): void;
}>();
props: { const modelValue = toRef(props, 'modelValue');
modelValue: { const innerValue = computed({
type: Number, get: () => modelValue.value.toString(),
required: true, set: (value) => {
}, emit('update:modelValue', parseFloat(value));
placeholder: {
type: String,
default: '',
},
},
emits: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'update:modelValue': (_value: number): boolean => true,
},
setup: (props, ctx) => {
const modelValue = toRef(props, 'modelValue');
const innerValue = computed({
get: () => modelValue.value.toString(),
set: (value) => {
ctx.emit('update:modelValue', parseFloat(value));
},
});
return {
innerValue,
};
}, },
}); });
</script> </script>

View file

@ -15,50 +15,29 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, PropType, toRef } from 'vue'; import { computed, toRef } from 'vue';
import { RadioOption } from './form.types'; import { RadioOption } from './form.types';
export default defineComponent({ const props = defineProps<{
name: 'RadioField', modelValue: string;
options: RadioOption[];
}>();
components: {}, const emit = defineEmits<{
(event: 'update:modelValue', value: string): void;
}>();
props: { const modelValue = toRef(props, 'modelValue');
modelValue: { const innerValue = computed({
type: String, get: () => modelValue.value,
required: true, set: (value) => {
}, emit('update:modelValue', value);
options: {
type: Array as PropType<RadioOption[]>,
required: true,
},
},
emits: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
'update:modelValue': (_value: RadioOption['value']): boolean => true,
},
setup: (props, ctx) => {
const modelValue = toRef(props, 'modelValue');
const innerValue = computed({
get: () => modelValue.value,
set: (value) => {
ctx.emit('update:modelValue', value);
},
});
const id = (Math.random() + 1).toString(36).substring(7);
return {
id,
innerValue,
};
}, },
}); });
const id = (Math.random() + 1).toString(36).substring(7);
</script> </script>
<style scoped> <style scoped>

View file

@ -10,48 +10,32 @@
</select> </select>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, PropType, toRef } from 'vue'; import { computed, toRef } from 'vue';
import { SelectOption } from './form.types'; import { SelectOption } from './form.types';
export default defineComponent({ const props = withDefaults(
name: 'SelectField', defineProps<{
modelValue: string;
props: { placeholder: string;
modelValue: { options: SelectOption[];
type: String, }>(),
default: null, {
}, placeholder: '',
options: undefined,
placeholder: {
type: String,
default: null,
},
options: {
type: Array as PropType<SelectOption[]>,
required: true,
},
}, },
);
emits: { const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/no-unused-vars (event: 'update:modelValue', value: string): void;
'update:modelValue': (_value: SelectOption['value'] | null): boolean => true, }>();
},
setup: (props, ctx) => { const modelValue = toRef(props, 'modelValue');
const modelValue = toRef(props, 'modelValue'); const innerValue = computed({
const innerValue = computed({ get: () => modelValue.value,
get: () => modelValue.value, set: (selectedValue) => {
set: (selectedValue) => { emit('update:modelValue', selectedValue);
ctx.emit('update:modelValue', selectedValue);
},
});
return {
innerValue,
};
}, },
}); });
</script> </script>

View file

@ -19,55 +19,34 @@
/> />
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, toRef } from 'vue'; import { computed, toRef } from 'vue';
export default defineComponent({ const props = withDefaults(
name: 'TextField', defineProps<{
modelValue: string;
props: { placeholder: string;
modelValue: { type: string;
type: String, lines: number;
default: '', disabled?: boolean;
}, }>(),
{
placeholder: { modelValue: '',
type: String, placeholder: '',
default: '', type: 'text',
}, lines: 1,
type: {
type: String,
default: 'text',
},
lines: {
type: Number,
default: 1,
},
disabled: {
type: Boolean,
},
}, },
);
emits: { const emit = defineEmits<{
// eslint-disable-next-line @typescript-eslint/no-unused-vars (event: 'update:modelValue', value: string): void;
'update:modelValue': (_value: string): boolean => true, }>();
},
setup: (props, ctx) => { 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);
ctx.emit('update:modelValue', value);
},
});
return {
innerValue,
};
}, },
}); });
</script> </script>

View file

@ -37,9 +37,8 @@
class="navbar-icon" class="navbar-icon"
:title="$t('admin.settings.settings')" :title="$t('admin.settings.settings')"
:to="{ name: 'admin-settings' }" :to="{ name: 'admin-settings' }"
> icon="settings"
<i-clarity-settings-solid /> />
</IconButton>
<!-- Active Pipelines Indicator --> <!-- Active Pipelines Indicator -->
<ActivePipelines v-if="user" class="navbar-icon" /> <ActivePipelines v-if="user" class="navbar-icon" />
@ -53,8 +52,7 @@
</nav> </nav>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import WoodpeckerLogo from '~/assets/logo.svg?component'; import WoodpeckerLogo from '~/assets/logo.svg?component';
@ -66,36 +64,20 @@ import { useDarkMode } from '~/compositions/useDarkMode';
import ActivePipelines from './ActivePipelines.vue'; import ActivePipelines from './ActivePipelines.vue';
export default defineComponent({ const config = useConfig();
name: 'Navbar', const route = useRoute();
const authentication = useAuthentication();
const { user } = authentication;
const { darkMode } = useDarkMode();
const docsUrl = config.docs || undefined;
const apiUrl = `${config.rootPath ?? ''}/swagger/index.html`;
components: { Button, ActivePipelines, IconButton, WoodpeckerLogo }, function doLogin() {
authentication.authenticate(route.fullPath);
}
setup() { const version = config.version?.startsWith('next') ? 'next' : config.version;
const config = useConfig(); const { enableSwagger } = config;
const route = useRoute();
const authentication = useAuthentication();
const { darkMode } = useDarkMode();
const docsUrl = config.docs || undefined;
const apiUrl = `${config.rootPath ?? ''}/swagger/index.html`;
function doLogin() {
authentication.authenticate(route.fullPath);
}
const version = config.version?.startsWith('next') ? 'next' : config.version;
return {
darkMode,
user: authentication.user,
doLogin,
docsUrl,
version,
apiUrl,
enableSwagger: config.enableSwagger,
};
},
});
</script> </script>
<style scoped> <style scoped>

View file

@ -68,9 +68,9 @@
</ListItem> </ListItem>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Tooltip } from 'floating-vue'; import { Tooltip } from 'floating-vue';
import { defineComponent, PropType, toRef } from 'vue'; import { toRef } from 'vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import ListItem from '~/components/atomic/ListItem.vue'; import ListItem from '~/components/atomic/ListItem.vue';
@ -80,23 +80,10 @@ import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vu
import usePipeline from '~/compositions/usePipeline'; import usePipeline from '~/compositions/usePipeline';
import { Pipeline } from '~/lib/api/types'; import { Pipeline } from '~/lib/api/types';
export default defineComponent({ const props = defineProps<{
name: 'PipelineItem', pipeline: Pipeline;
}>();
components: { Icon, PipelineStatusIcon, ListItem, PipelineRunningIcon, Tooltip }, const pipeline = toRef(props, 'pipeline');
const { since, duration, message, prettyRef, created } = usePipeline(pipeline);
props: {
pipeline: {
type: Object as PropType<Pipeline>,
required: true,
},
},
setup(props) {
const pipeline = toRef(props, 'pipeline');
const { since, duration, message, prettyRef, created } = usePipeline(pipeline);
return { since, duration, message, prettyRef, pipelineStatusColors, created };
},
});
</script> </script>

View file

@ -2,60 +2,45 @@
<span v-if="started" class="ml-auto text-sm">{{ duration }}</span> <span v-if="started" class="ml-auto text-sm">{{ duration }}</span>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, PropType, toRef } from 'vue'; import { computed, toRef } from 'vue';
import { useElapsedTime } from '~/compositions/useElapsedTime'; import { useElapsedTime } from '~/compositions/useElapsedTime';
import { PipelineStep, PipelineWorkflow } from '~/lib/api/types'; import { PipelineStep, PipelineWorkflow } from '~/lib/api/types';
import { durationAsNumber } from '~/utils/duration'; import { durationAsNumber } from '~/utils/duration';
export default defineComponent({ const props = defineProps<{
name: 'PipelineStepDuration', step?: PipelineStep;
workflow?: PipelineWorkflow;
}>();
props: { const step = toRef(props, 'step');
step: { const workflow = toRef(props, 'workflow');
type: Object as PropType<PipelineStep>,
default: undefined,
},
workflow: { const durationRaw = computed(() => {
type: Object as PropType<PipelineWorkflow>, const start = (step.value ? step.value?.start_time : workflow.value?.start_time) || 0;
default: undefined, const end = (step.value ? step.value?.end_time : workflow.value?.end_time) || 0;
},
},
setup(props) { if (end === 0 && start === 0) {
const step = toRef(props, 'step'); return undefined;
const workflow = toRef(props, 'workflow'); }
const durationRaw = computed(() => { if (end === 0) {
const start = (step.value ? step.value?.start_time : workflow.value?.start_time) || 0; return Date.now() - start * 1000;
const end = (step.value ? step.value?.end_time : workflow.value?.end_time) || 0; }
if (end === 0 && start === 0) { return (end - start) * 1000;
return undefined;
}
if (end === 0) {
return Date.now() - start * 1000;
}
return (end - start) * 1000;
});
const running = computed(() => (step.value ? step.value?.state : workflow.value?.state) === 'running');
const { time: durationElapsed } = useElapsedTime(running, durationRaw);
const duration = computed(() => {
if (durationElapsed.value === undefined) {
return '-';
}
return durationAsNumber(durationElapsed.value || 0);
});
const started = computed(() => (step.value ? step.value?.start_time : workflow.value?.start_time) !== undefined);
return { started, duration };
},
}); });
const running = computed(() => (step.value ? step.value?.state : workflow.value?.state) === 'running');
const { time: durationElapsed } = useElapsedTime(running, durationRaw);
const duration = computed(() => {
if (durationElapsed.value === undefined) {
return '-';
}
return durationAsNumber(durationElapsed.value || 0);
});
const started = computed(() => (step.value ? step.value?.start_time : workflow.value?.start_time) !== undefined);
</script> </script>

View file

@ -39,9 +39,9 @@
</Panel> </Panel>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { computed, defineComponent, inject, onMounted, Ref, ref, watch } from 'vue'; import { computed, inject, onMounted, Ref, ref, watch } from 'vue';
import { SelectOption } from '~/components/form/form.types'; import { SelectOption } from '~/components/form/form.types';
import InputField from '~/components/form/InputField.vue'; import InputField from '~/components/form/InputField.vue';
@ -52,81 +52,71 @@ import useConfig from '~/compositions/useConfig';
import { usePaginate } from '~/compositions/usePaginate'; import { usePaginate } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types'; import { Repo } from '~/lib/api/types';
export default defineComponent({ const apiClient = useApiClient();
name: 'BadgeTab', const repo = inject<Ref<Repo>>('repo');
components: { Panel, InputField, SelectField }, const badgeType = useStorage('last-badge-type', 'markdown');
setup() { if (!repo) {
const apiClient = useApiClient(); throw new Error('Unexpected: "repo" should be provided at this place');
const repo = inject<Ref<Repo>>('repo'); }
const badgeType = useStorage('last-badge-type', 'markdown'); const defaultBranch = computed(() => repo.value.default_branch);
const branches = ref<SelectOption[]>([]);
const branch = ref<string>('');
if (!repo) { async function loadBranches() {
throw new Error('Unexpected: "repo" should be provided at this place'); if (!repo) {
} throw new Error('Unexpected: "repo" should be provided at this place');
}
const defaultBranch = computed(() => repo.value.default_branch); branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, page)))
const branches = ref<SelectOption[]>([]); .map((b) => ({
const branch = ref<string>(''); value: b,
text: b,
}))
.filter((b) => b.value !== defaultBranch.value);
branches.value.unshift({
value: '',
text: defaultBranch.value,
});
}
async function loadBranches() { const baseUrl = `${window.location.protocol}//${window.location.hostname}${
if (!repo) { window.location.port ? `:${window.location.port}` : ''
throw new Error('Unexpected: "repo" should be provided at this place'); }${useConfig().rootPath}`;
} const badgeUrl = computed(
() => `/api/badges/${repo.value.id}/status.svg${branch.value !== '' ? `?branch=${branch.value}` : ''}`,
);
const repoUrl = computed(
() => `/repos/${repo.value.id}${branch.value !== '' ? `/branches/${encodeURIComponent(branch.value)}` : ''}`,
);
branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, page))) const badgeContent = computed(() => {
.map((b) => ({ if (!repo) {
value: b, throw new Error('Unexpected: "repo" should be provided at this place');
text: b, }
}))
.filter((b) => b.value !== defaultBranch.value);
branches.value.unshift({
value: '',
text: defaultBranch.value,
});
}
const baseUrl = `${window.location.protocol}//${window.location.hostname}${ if (badgeType.value === 'url') {
window.location.port ? `:${window.location.port}` : '' return `${baseUrl}${badgeUrl.value}`;
}${useConfig().rootPath}`; }
const badgeUrl = computed(
() => `/api/badges/${repo.value.id}/status.svg${branch.value !== '' ? `?branch=${branch.value}` : ''}`,
);
const repoUrl = computed(
() => `/repos/${repo.value.id}${branch.value !== '' ? `/branches/${encodeURIComponent(branch.value)}` : ''}`,
);
const badgeContent = computed(() => { if (badgeType.value === 'markdown') {
if (!repo) { return `[![status-badge](${baseUrl}${badgeUrl.value})](${baseUrl}${repoUrl.value})`;
throw new Error('Unexpected: "repo" should be provided at this place'); }
}
if (badgeType.value === 'url') { if (badgeType.value === 'html') {
return `${baseUrl}${badgeUrl.value}`; return `<a href="${baseUrl}${repoUrl.value}" target="_blank">\n <img src="${baseUrl}${badgeUrl.value}" alt="status-badge" />\n</a>`;
} }
if (badgeType.value === 'markdown') { return '';
return `[![status-badge](${baseUrl}${badgeUrl.value})](${baseUrl}${repoUrl.value})`; });
}
if (badgeType.value === 'html') { onMounted(() => {
return `<a href="${baseUrl}${repoUrl.value}" target="_blank">\n <img src="${baseUrl}${badgeUrl.value}" alt="status-badge" />\n</a>`; loadBranches();
} });
return ''; watch(repo, () => {
}); loadBranches();
onMounted(() => {
loadBranches();
});
watch(repo, () => {
loadBranches();
});
return { badgeType, branches, branch, badgeContent, badgeUrl };
},
}); });
</script> </script>

View file

@ -88,8 +88,8 @@
</Panel> </Panel>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, inject, onMounted, Ref, ref } from 'vue'; import { inject, onMounted, Ref, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -108,101 +108,84 @@ import useNotifications from '~/compositions/useNotifications';
import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types'; import { Repo, RepoSettings, RepoVisibility, WebhookEvents } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos'; import { useRepoStore } from '~/store/repos';
export default defineComponent({ const apiClient = useApiClient();
name: 'GeneralTab', const notifications = useNotifications();
const { user } = useAuthentication();
const repoStore = useRepoStore();
const i18n = useI18n();
components: { Button, Panel, InputField, TextField, RadioField, NumberField, Checkbox, CheckboxesField }, const repo = inject<Ref<Repo>>('repo');
const repoSettings = ref<RepoSettings>();
setup() { function loadRepoSettings() {
const apiClient = useApiClient(); if (!repo) {
const notifications = useNotifications(); throw new Error('Unexpected: Repo should be set');
const { user } = useAuthentication(); }
const repoStore = useRepoStore();
const i18n = useI18n();
const repo = inject<Ref<Repo>>('repo'); repoSettings.value = {
const repoSettings = ref<RepoSettings>(); config_file: repo.value.config_file,
timeout: repo.value.timeout,
visibility: repo.value.visibility,
gated: repo.value.gated,
trusted: repo.value.trusted,
allow_pr: repo.value.allow_pr,
cancel_previous_pipeline_events: repo.value.cancel_previous_pipeline_events || [],
netrc_only_trusted: repo.value.netrc_only_trusted,
};
}
function loadRepoSettings() { async function loadRepo() {
if (!repo) { if (!repo) {
throw new Error('Unexpected: Repo should be set'); throw new Error('Unexpected: Repo should be set');
} }
repoSettings.value = { await repoStore.loadRepo(repo.value.id);
config_file: repo.value.config_file, loadRepoSettings();
timeout: repo.value.timeout, }
visibility: repo.value.visibility,
gated: repo.value.gated,
trusted: repo.value.trusted,
allow_pr: repo.value.allow_pr,
cancel_previous_pipeline_events: repo.value.cancel_previous_pipeline_events || [],
netrc_only_trusted: repo.value.netrc_only_trusted,
};
}
async function loadRepo() { const { doSubmit: saveRepoSettings, isLoading: isSaving } = useAsyncAction(async () => {
if (!repo) { if (!repo) {
throw new Error('Unexpected: Repo should be set'); throw new Error('Unexpected: Repo should be set');
} }
await repoStore.loadRepo(repo.value.id); if (!repoSettings.value) {
loadRepoSettings(); throw new Error('Unexpected: Repo-Settings should be set');
} }
const { doSubmit: saveRepoSettings, isLoading: isSaving } = useAsyncAction(async () => { await apiClient.updateRepo(repo.value.id, repoSettings.value);
if (!repo) { await loadRepo();
throw new Error('Unexpected: Repo should be set'); notifications.notify({ title: i18n.t('repo.settings.general.success'), type: 'success' });
}
if (!repoSettings.value) {
throw new Error('Unexpected: Repo-Settings should be set');
}
await apiClient.updateRepo(repo.value.id, repoSettings.value);
await loadRepo();
notifications.notify({ title: i18n.t('repo.settings.general.success'), type: 'success' });
});
onMounted(() => {
loadRepoSettings();
});
const projectVisibilityOptions: RadioOption[] = [
{
value: RepoVisibility.Public,
text: i18n.t('repo.settings.general.visibility.public.public'),
description: i18n.t('repo.settings.general.visibility.public.desc'),
},
{
value: RepoVisibility.Internal,
text: i18n.t('repo.settings.general.visibility.internal.internal'),
description: i18n.t('repo.settings.general.visibility.internal.desc'),
},
{
value: RepoVisibility.Private,
text: i18n.t('repo.settings.general.visibility.private.private'),
description: i18n.t('repo.settings.general.visibility.private.desc'),
},
];
const cancelPreviousPipelineEventsOptions: CheckboxOption[] = [
{ value: WebhookEvents.Push, text: i18n.t('repo.pipeline.event.push') },
{ value: WebhookEvents.Tag, text: i18n.t('repo.pipeline.event.tag') },
{
value: WebhookEvents.PullRequest,
text: i18n.t('repo.pipeline.event.pr'),
},
{ value: WebhookEvents.Deploy, text: i18n.t('repo.pipeline.event.deploy') },
];
return {
user,
repoSettings,
isSaving,
saveRepoSettings,
projectVisibilityOptions,
cancelPreviousPipelineEventsOptions,
};
},
}); });
onMounted(() => {
loadRepoSettings();
});
const projectVisibilityOptions: RadioOption[] = [
{
value: RepoVisibility.Public,
text: i18n.t('repo.settings.general.visibility.public.public'),
description: i18n.t('repo.settings.general.visibility.public.desc'),
},
{
value: RepoVisibility.Internal,
text: i18n.t('repo.settings.general.visibility.internal.internal'),
description: i18n.t('repo.settings.general.visibility.internal.desc'),
},
{
value: RepoVisibility.Private,
text: i18n.t('repo.settings.general.visibility.private.private'),
description: i18n.t('repo.settings.general.visibility.private.desc'),
},
];
const cancelPreviousPipelineEventsOptions: CheckboxOption[] = [
{ value: WebhookEvents.Push, text: i18n.t('repo.pipeline.event.push') },
{ value: WebhookEvents.Tag, text: i18n.t('repo.pipeline.event.tag') },
{
value: WebhookEvents.PullRequest,
text: i18n.t('repo.pipeline.event.pr'),
},
{ value: WebhookEvents.Deploy, text: i18n.t('repo.pipeline.event.deploy') },
];
</script> </script>

View file

@ -83,8 +83,8 @@
</Panel> </Panel>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, inject, Ref, ref } from 'vue'; import { computed, inject, Ref, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -101,74 +101,56 @@ import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types'; import { Repo } from '~/lib/api/types';
import { Registry } from '~/lib/api/types/registry'; import { Registry } from '~/lib/api/types/registry';
export default defineComponent({ const apiClient = useApiClient();
name: 'RegistriesTab', const notifications = useNotifications();
const i18n = useI18n();
components: { const repo = inject<Ref<Repo>>('repo');
Button, const selectedRegistry = ref<Partial<Registry>>();
Panel, const isEditingRegistry = computed(() => !!selectedRegistry.value?.id);
ListItem,
IconButton,
InputField,
TextField,
DocsLink,
},
setup() { async function loadRegistries(page: number): Promise<Registry[] | null> {
const apiClient = useApiClient(); if (!repo?.value) {
const notifications = useNotifications(); throw new Error("Unexpected: Can't load repo");
const i18n = useI18n(); }
const repo = inject<Ref<Repo>>('repo'); return apiClient.getRegistryList(repo.value.id, page);
const selectedRegistry = ref<Partial<Registry>>(); }
const isEditingRegistry = computed(() => !!selectedRegistry.value?.id);
async function loadRegistries(page: number): Promise<Registry[] | null> { const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getRegistryList(repo.value.id, page); const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => {
} if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value); if (!selectedRegistry.value) {
throw new Error("Unexpected: Can't get registry");
}
const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => { if (isEditingRegistry.value) {
if (!repo?.value) { await apiClient.updateRegistry(repo.value.id, selectedRegistry.value);
throw new Error("Unexpected: Can't load repo"); } else {
} await apiClient.createRegistry(repo.value.id, selectedRegistry.value);
}
notifications.notify({
title: i18n.t(
isEditingRegistry.value ? 'repo.settings.registries.saved' : i18n.t('repo.settings.registries.created'),
),
type: 'success',
});
selectedRegistry.value = undefined;
resetPage();
});
if (!selectedRegistry.value) { const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
throw new Error("Unexpected: Can't get registry"); if (!repo?.value) {
} throw new Error("Unexpected: Can't load repo");
}
if (isEditingRegistry.value) { const registryAddress = encodeURIComponent(_registry.address);
await apiClient.updateRegistry(repo.value.id, selectedRegistry.value); await apiClient.deleteRegistry(repo.value.id, registryAddress);
} else { notifications.notify({ title: i18n.t('repo.settings.registries.deleted'), type: 'success' });
await apiClient.createRegistry(repo.value.id, selectedRegistry.value); resetPage();
}
notifications.notify({
title: i18n.t(
isEditingRegistry.value ? 'repo.settings.registries.saved' : i18n.t('repo.settings.registries.created'),
),
type: 'success',
});
selectedRegistry.value = undefined;
resetPage();
});
const { doSubmit: deleteRegistry, isLoading: isDeleting } = useAsyncAction(async (_registry: Registry) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
const registryAddress = encodeURIComponent(_registry.address);
await apiClient.deleteRegistry(repo.value.id, registryAddress);
notifications.notify({ title: i18n.t('repo.settings.registries.deleted'), type: 'success' });
resetPage();
});
return { selectedRegistry, registries, isEditingRegistry, isSaving, isDeleting, createRegistry, deleteRegistry };
},
}); });
</script> </script>

View file

@ -38,9 +38,9 @@
</Panel> </Panel>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { computed, defineComponent, inject, Ref, ref } from 'vue'; import { computed, inject, Ref, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
@ -61,86 +61,61 @@ const emptySecret = {
event: [WebhookEvents.Push], event: [WebhookEvents.Push],
}; };
export default defineComponent({ const apiClient = useApiClient();
name: 'SecretsTab', const notifications = useNotifications();
const i18n = useI18n();
components: { const repo = inject<Ref<Repo>>('repo');
Button, const selectedSecret = ref<Partial<Secret>>();
Panel, const isEditingSecret = computed(() => !!selectedSecret.value?.id);
DocsLink,
SecretList,
SecretEdit,
},
setup() { async function loadSecrets(page: number): Promise<Secret[] | null> {
const apiClient = useApiClient(); if (!repo?.value) {
const notifications = useNotifications(); throw new Error("Unexpected: Can't load repo");
const i18n = useI18n(); }
const repo = inject<Ref<Repo>>('repo'); return apiClient.getSecretList(repo.value.id, page);
const selectedSecret = ref<Partial<Secret>>(); }
const isEditingSecret = computed(() => !!selectedSecret.value?.id);
async function loadSecrets(page: number): Promise<Secret[] | null> { const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value);
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
return apiClient.getSecretList(repo.value.id, page); const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => {
} if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value); if (!selectedSecret.value) {
throw new Error("Unexpected: Can't get secret");
}
const { doSubmit: createSecret, isLoading: isSaving } = useAsyncAction(async () => { if (isEditingSecret.value) {
if (!repo?.value) { await apiClient.updateSecret(repo.value.id, selectedSecret.value);
throw new Error("Unexpected: Can't load repo"); } else {
} await apiClient.createSecret(repo.value.id, selectedSecret.value);
}
if (!selectedSecret.value) { notifications.notify({
throw new Error("Unexpected: Can't get secret"); title: i18n.t(isEditingSecret.value ? 'repo.settings.secrets.saved' : 'repo.settings.secrets.created'),
} type: 'success',
});
if (isEditingSecret.value) { selectedSecret.value = undefined;
await apiClient.updateSecret(repo.value.id, selectedSecret.value); resetPage();
} else {
await apiClient.createSecret(repo.value.id, selectedSecret.value);
}
notifications.notify({
title: i18n.t(isEditingSecret.value ? 'repo.settings.secrets.saved' : 'repo.settings.secrets.created'),
type: 'success',
});
selectedSecret.value = undefined;
resetPage();
});
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteSecret(repo.value.id, _secret.name);
notifications.notify({ title: i18n.t('repo.settings.secrets.deleted'), type: 'success' });
resetPage();
});
function editSecret(secret: Secret) {
selectedSecret.value = cloneDeep(secret);
}
function showAddSecret() {
selectedSecret.value = cloneDeep(emptySecret);
}
return {
selectedSecret,
secrets,
isDeleting,
isSaving,
showAddSecret,
createSecret,
editSecret,
deleteSecret,
};
},
}); });
const { doSubmit: deleteSecret, isLoading: isDeleting } = useAsyncAction(async (_secret: Secret) => {
if (!repo?.value) {
throw new Error("Unexpected: Can't load repo");
}
await apiClient.deleteSecret(repo.value.id, _secret.name);
notifications.notify({ title: i18n.t('repo.settings.secrets.deleted'), type: 'success' });
resetPage();
});
function editSecret(secret: Secret) {
selectedSecret.value = cloneDeep(secret);
}
function showAddSecret() {
selectedSecret.value = cloneDeep(emptySecret);
}
</script> </script>

View file

@ -6,7 +6,7 @@ declare global {
WOODPECKER_DOCS: string | undefined; WOODPECKER_DOCS: string | undefined;
WOODPECKER_VERSION: string | undefined; WOODPECKER_VERSION: string | undefined;
WOODPECKER_CSRF: string | undefined; WOODPECKER_CSRF: string | undefined;
WOODPECKER_FORGE: string | undefined; WOODPECKER_FORGE: 'github' | 'gitlab' | 'gitea' | 'bitbucket' | undefined;
WOODPECKER_ROOT_PATH: string | undefined; WOODPECKER_ROOT_PATH: string | undefined;
WOODPECKER_ENABLE_SWAGGER: boolean | undefined; WOODPECKER_ENABLE_SWAGGER: boolean | undefined;
} }

View file

@ -22,8 +22,8 @@
</main> </main>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
@ -31,48 +31,32 @@ import WoodpeckerLogo from '~/assets/logo.svg?component';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
import useAuthentication from '~/compositions/useAuthentication'; import useAuthentication from '~/compositions/useAuthentication';
export default defineComponent({ const route = useRoute();
name: 'Login', const router = useRouter();
const authentication = useAuthentication();
const errorMessage = ref<string>();
const i18n = useI18n();
components: { function doLogin() {
Button, const url = typeof route.query.url === 'string' ? route.query.url : '';
WoodpeckerLogo, authentication.authenticate(url);
}, }
setup() { const authErrorMessages = {
const route = useRoute(); oauth_error: i18n.t('user.oauth_error'),
const router = useRouter(); internal_error: i18n.t('user.internal_error'),
const authentication = useAuthentication(); access_denied: i18n.t('user.access_denied'),
const errorMessage = ref<string>(); };
const i18n = useI18n();
function doLogin() { onMounted(async () => {
const url = typeof route.query.url === 'string' ? route.query.url : ''; if (authentication.isAuthenticated) {
authentication.authenticate(url); await router.replace({ name: 'home' });
} return;
}
const authErrorMessages = { if (route.query.code) {
oauth_error: i18n.t('user.oauth_error'), const code = route.query.code as keyof typeof authErrorMessages;
internal_error: i18n.t('user.internal_error'), errorMessage.value = authErrorMessages[code];
access_denied: i18n.t('user.access_denied'), }
};
onMounted(async () => {
if (authentication.isAuthenticated) {
await router.replace({ name: 'home' });
return;
}
if (route.query.code) {
const code = route.query.code as keyof typeof authErrorMessages;
errorMessage.value = authErrorMessages[code];
}
});
return {
doLogin,
errorMessage,
};
},
}); });
</script> </script>

View file

@ -8,11 +8,3 @@
> >
</div> </div>
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NotFound',
});
</script>

View file

@ -5,38 +5,25 @@
<PipelineList :pipelines="pipelines" :repo="repo" /> <PipelineList :pipelines="pipelines" :repo="repo" />
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, inject, Ref, toRef } from 'vue'; import { computed, inject, Ref, toRef } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
export default defineComponent({ const props = defineProps<{
name: 'RepoBranch', branch: string;
}>();
components: { PipelineList }, const branch = toRef(props, 'branch');
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
props: { const allPipelines = inject<Ref<Pipeline[]>>('pipelines');
branch: { const pipelines = computed(() =>
type: String, allPipelines?.value.filter((b) => b.branch === branch.value && b.event !== 'pull_request'),
required: true, );
},
},
setup(props) {
const branch = toRef(props, 'branch');
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const allPipelines = inject<Ref<Pipeline[]>>('pipelines');
const pipelines = computed(() =>
allPipelines?.value.filter((b) => b.branch === branch.value && b.event !== 'pull_request'),
);
return { pipelines, repo };
},
});
</script> </script>

View file

@ -2,27 +2,17 @@
<PipelineList :pipelines="pipelines" :repo="repo" /> <PipelineList :pipelines="pipelines" :repo="repo" />
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, inject, Ref } from 'vue'; import { inject, Ref } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
export default defineComponent({ const repo = inject<Ref<Repo>>('repo');
name: 'RepoPipelines', const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
components: { PipelineList }, const pipelines = inject<Ref<Pipeline[]>>('pipelines');
setup() {
const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');
if (!repo || !repoPermissions) {
throw new Error('Unexpected: "repo" & "repoPermissions" should be provided at this place');
}
const pipelines = inject<Ref<Pipeline[]>>('pipelines');
return { pipelines, repo };
},
});
</script> </script>

View file

@ -11,12 +11,9 @@ import { computed, inject, Ref, toRef } from 'vue';
import PipelineList from '~/components/repo/pipeline/PipelineList.vue'; import PipelineList from '~/components/repo/pipeline/PipelineList.vue';
import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types'; import { Pipeline, Repo, RepoPermissions } from '~/lib/api/types';
const props = defineProps({ const props = defineProps<{
pullRequest: { pullRequest: string;
type: String, }>();
required: true,
},
});
const pullRequest = toRef(props, 'pullRequest'); const pullRequest = toRef(props, 'pullRequest');
const repo = inject<Ref<Repo>>('repo'); const repo = inject<Ref<Repo>>('repo');
const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions'); const repoPermissions = inject<Ref<RepoPermissions>>('repo-permissions');

View file

@ -17,13 +17,7 @@
<a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="ml-2"> <a v-if="badgeUrl" :href="badgeUrl" target="_blank" class="ml-2">
<img :src="badgeUrl" /> <img :src="badgeUrl" />
</a> </a>
<IconButton :href="repo.link_url" :title="$t('repo.open_in_forge')"> <IconButton :href="repo.link_url" :title="$t('repo.open_in_forge')" :icon="forge ?? 'repo'" />
<Icon v-if="forge === 'github'" name="github" />
<Icon v-else-if="forge === 'gitea'" name="gitea" />
<Icon v-else-if="forge === 'gitlab'" name="gitlab" />
<Icon v-else-if="forge === 'bitbucket'" name="bitbucket" />
<Icon v-else name="repo" />
</IconButton>
<IconButton <IconButton
v-if="repoPermissions.admin" v-if="repoPermissions.admin"
:to="{ name: 'repo-settings' }" :to="{ name: 'repo-settings' }"
@ -59,7 +53,6 @@ import { computed, onMounted, provide, ref, toRef, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import Icon from '~/components/atomic/Icon.vue';
import IconButton from '~/components/atomic/IconButton.vue'; import IconButton from '~/components/atomic/IconButton.vue';
import ManualPipelinePopup from '~/components/layout/popups/ManualPipelinePopup.vue'; import ManualPipelinePopup from '~/components/layout/popups/ManualPipelinePopup.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue'; import Scaffold from '~/components/layout/scaffold/Scaffold.vue';

View file

@ -9,26 +9,14 @@
</Panel> </Panel>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, inject, Ref } from 'vue'; import { inject, Ref } from 'vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import { Pipeline } from '~/lib/api/types'; import { Pipeline } from '~/lib/api/types';
export default defineComponent({ const pipeline = inject<Ref<Pipeline>>('pipeline');
name: 'PipelineChangedFiles', if (!pipeline) {
throw new Error('Unexpected: "pipeline" should be provided at this place');
components: { }
Panel,
},
setup() {
const pipeline = inject<Ref<Pipeline>>('pipeline');
if (!pipeline) {
throw new Error('Unexpected: "pipeline" should be provided at this place');
}
return { pipeline };
},
});
</script> </script>

View file

@ -11,51 +11,38 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, inject, onMounted, Ref, ref, watch } from 'vue'; import { inject, onMounted, Ref, ref, watch } from 'vue';
import SyntaxHighlight from '~/components/atomic/SyntaxHighlight'; import SyntaxHighlight from '~/components/atomic/SyntaxHighlight';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { Pipeline, PipelineConfig, Repo } from '~/lib/api/types'; import { Pipeline, PipelineConfig, Repo } from '~/lib/api/types';
export default defineComponent({ const pipeline = inject<Ref<Pipeline>>('pipeline');
name: 'PipelineConfig', const apiClient = useApiClient();
const repo = inject<Ref<Repo>>('repo');
if (!repo || !pipeline) {
throw new Error('Unexpected: "repo" & "pipeline" should be provided at this place');
}
components: { const pipelineConfigs = ref<PipelineConfig[]>();
Panel, async function loadPipelineConfig() {
SyntaxHighlight, if (!repo || !pipeline) {
}, throw new Error('Unexpected: "repo" & "pipeline" should be provided at this place');
}
setup() { pipelineConfigs.value = (await apiClient.getPipelineConfig(repo.value.id, pipeline.value.number)).map((i) => ({
const pipeline = inject<Ref<Pipeline>>('pipeline'); ...i,
const apiClient = useApiClient(); data: atob(i.data),
const repo = inject<Ref<Repo>>('repo'); }));
if (!repo || !pipeline) { }
throw new Error('Unexpected: "repo" & "pipeline" should be provided at this place');
}
const pipelineConfigs = ref<PipelineConfig[]>(); onMounted(() => {
async function loadPipelineConfig() { loadPipelineConfig();
if (!repo || !pipeline) { });
throw new Error('Unexpected: "repo" & "pipeline" should be provided at this place');
}
pipelineConfigs.value = (await apiClient.getPipelineConfig(repo.value.id, pipeline.value.number)).map((i) => ({ watch(pipeline, () => {
...i, loadPipelineConfig();
data: atob(i.data),
}));
}
onMounted(() => {
loadPipelineConfig();
});
watch(pipeline, () => {
loadPipelineConfig();
});
return { pipelineConfigs };
},
}); });
</script> </script>