mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-04-26 21:44:44 +00:00
Use Vue setup directive (#2165)
This commit is contained in:
parent
9cae5709f9
commit
3bdeb47d8c
26 changed files with 582 additions and 981 deletions
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 `[](${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 `[](${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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -8,11 +8,3 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'NotFound',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue