mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-04-15 01:41:56 +00:00
Access repos by their ids (#1691)
closes #1295 closes #648 # TODO - [x] add new routes with `:repoID` - [x] load repo in middleware using `:repoID` if present - [x] update UI routes `:owner/:name` to `:repoID` - [x] load repos using id in UI - [x] add lookup endpoint `:owner/:name` to `:repoID` - [x] redirect `:owner/:name` to `:repoID` in UI - [x] use badge with `:repoID` route in UI - [x] update `woodpecker-go` - [x] check cli - [x] add migrations / deprecation notes - [x] check if #648 got solved directly - [x] Test - [x] create repo - [x] repo pages - [x] ui redirects - [x] forge status links
This commit is contained in:
@@ -99,12 +99,7 @@ function deleteVar(key: string) {
|
||||
const pipelineNumber = toRef(props, 'pipelineNumber');
|
||||
async function triggerDeployPipeline() {
|
||||
loading.value = true;
|
||||
const newPipeline = await apiClient.deployPipeline(
|
||||
repo.value.owner,
|
||||
repo.value.name,
|
||||
pipelineNumber.value,
|
||||
payload.value,
|
||||
);
|
||||
const newPipeline = await apiClient.deployPipeline(repo.value.id, pipelineNumber.value, payload.value);
|
||||
|
||||
emit('close');
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ const newPipelineVariable = ref<{ name: string; value: string }>({ name: '', val
|
||||
|
||||
const loading = ref(true);
|
||||
onMounted(async () => {
|
||||
const data = await usePaginate((page) => apiClient.getRepoBranches(repo.value.owner, repo.value.name, page));
|
||||
const data = await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, page));
|
||||
branches.value = data.map((e) => ({
|
||||
text: e,
|
||||
value: e,
|
||||
@@ -103,7 +103,7 @@ function deleteVar(key: string) {
|
||||
|
||||
async function triggerManualPipeline() {
|
||||
loading.value = true;
|
||||
const pipeline = await apiClient.createPipeline(repo.value.owner, repo.value.name, payload.value);
|
||||
const pipeline = await apiClient.createPipeline(repo.value.id, payload.value);
|
||||
|
||||
emit('close');
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div v-if="pipeline" class="flex text-color w-full">
|
||||
<PipelineStatusIcon :status="pipeline.status" class="flex items-center" />
|
||||
<div class="flex flex-col ml-4 min-w-0">
|
||||
<span class="underline">{{ pipeline.owner }} / {{ pipeline.name }}</span>
|
||||
<span class="underline">{{ repo?.owner }} / {{ repo?.name }}</span>
|
||||
<span class="whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message }}</span>
|
||||
<div class="flex flex-col mt-2">
|
||||
<div class="flex space-x-2 items-center">
|
||||
@@ -23,32 +23,24 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { Tooltip } from 'floating-vue';
|
||||
import { defineComponent, PropType, toRef } from 'vue';
|
||||
import { computed, toRef } from 'vue';
|
||||
|
||||
import Icon from '~/components/atomic/Icon.vue';
|
||||
import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vue';
|
||||
import usePipeline from '~/compositions/usePipeline';
|
||||
import { PipelineFeed } from '~/lib/api/types';
|
||||
import { useRepoStore } from '~/store/repos';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PipelineFeedItem',
|
||||
const props = defineProps<{
|
||||
pipeline: PipelineFeed;
|
||||
}>();
|
||||
|
||||
components: { PipelineStatusIcon, Icon, Tooltip },
|
||||
const repoStore = useRepoStore();
|
||||
|
||||
props: {
|
||||
pipeline: {
|
||||
type: Object as PropType<PipelineFeed>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
const pipeline = toRef(props, 'pipeline');
|
||||
const repo = repoStore.getRepo(computed(() => pipeline.value.repo_id));
|
||||
|
||||
setup(props) {
|
||||
const pipeline = toRef(props, 'pipeline');
|
||||
const { since, duration, message, created } = usePipeline(pipeline);
|
||||
|
||||
return { since, duration, message, created };
|
||||
},
|
||||
});
|
||||
const { since, duration, message, created } = usePipeline(pipeline);
|
||||
</script>
|
||||
|
||||
@@ -1,43 +1,29 @@
|
||||
<template>
|
||||
<aside
|
||||
v-if="isPipelineFeedOpen"
|
||||
v-if="isOpen"
|
||||
class="flex flex-col z-50 overflow-y-auto items-center bg-white dark:bg-dark-gray-800 dark:border-dark-500"
|
||||
:aria-label="$t('pipeline_feed')"
|
||||
>
|
||||
<router-link
|
||||
v-for="pipeline in sortedPipelineFeed"
|
||||
v-for="pipeline in sortedPipelines"
|
||||
:key="pipeline.id"
|
||||
:to="{
|
||||
name: 'repo-pipeline',
|
||||
params: { repoOwner: pipeline.owner, repoName: pipeline.name, pipelineId: pipeline.number },
|
||||
params: { repoId: pipeline.repo_id, pipelineId: pipeline.number },
|
||||
}"
|
||||
class="flex border-b py-4 px-2 w-full hover:bg-light-300 dark:hover:bg-dark-gray-900 dark:border-dark-gray-600 hover:shadow-sm"
|
||||
>
|
||||
<PipelineFeedItem :pipeline="pipeline" />
|
||||
</router-link>
|
||||
|
||||
<span v-if="sortedPipelineFeed.length === 0" class="text-color m-4">{{ $t('repo.pipeline.no_pipelines') }}</span>
|
||||
<span v-if="sortedPipelines.length === 0" class="text-color m-4">{{ $t('repo.pipeline.no_pipelines') }}</span>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PipelineFeedItem from '~/components/pipeline-feed/PipelineFeedItem.vue';
|
||||
import usePipelineFeed from '~/compositions/usePipelineFeed';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PipelineFeedSidebar',
|
||||
|
||||
components: { PipelineFeedItem },
|
||||
|
||||
setup() {
|
||||
const pipelineFeed = usePipelineFeed();
|
||||
|
||||
return {
|
||||
isPipelineFeedOpen: pipelineFeed.isOpen,
|
||||
sortedPipelineFeed: pipelineFeed.sortedPipelines,
|
||||
};
|
||||
},
|
||||
});
|
||||
const pipelineFeed = usePipelineFeed();
|
||||
const { isOpen, sortedPipelines } = pipelineFeed;
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:key="pipeline.id"
|
||||
:to="{
|
||||
name: 'repo-pipeline',
|
||||
params: { repoOwner: repo.owner, repoName: repo.name, pipelineId: pipeline.number },
|
||||
params: { pipelineId: pipeline.number },
|
||||
}"
|
||||
:pipeline="pipeline"
|
||||
/>
|
||||
@@ -15,28 +15,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
import PipelineItem from '~/components/repo/pipeline/PipelineItem.vue';
|
||||
import { Pipeline, Repo } from '~/lib/api/types';
|
||||
import { Pipeline } from '~/lib/api/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PipelineList',
|
||||
|
||||
components: { Panel, PipelineItem },
|
||||
|
||||
props: {
|
||||
repo: {
|
||||
type: Object as PropType<Repo>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
pipelines: {
|
||||
type: Object as PropType<Pipeline[] | undefined>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
defineProps<{
|
||||
pipelines: Pipeline[] | undefined;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -190,7 +190,7 @@ async function download() {
|
||||
let logs;
|
||||
try {
|
||||
downloadInProgress.value = true;
|
||||
logs = await apiClient.getLogs(repo.value.owner, repo.value.name, pipeline.value.number, step.value.id);
|
||||
logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
|
||||
} catch (e) {
|
||||
notifications.notifyError(e, i18n.t('repo.pipeline.log_download_error'));
|
||||
return;
|
||||
@@ -239,22 +239,16 @@ async function loadLogs() {
|
||||
}
|
||||
|
||||
if (isStepFinished(step.value)) {
|
||||
const logs = await apiClient.getLogs(repo.value.owner, repo.value.name, pipeline.value.number, step.value.id);
|
||||
const logs = await apiClient.getLogs(repo.value.id, pipeline.value.number, step.value.id);
|
||||
logs?.forEach((line) => writeLog({ index: line.line, text: atob(line.data), time: line.time }));
|
||||
flushLogs(false);
|
||||
}
|
||||
|
||||
if (isStepRunning(step.value)) {
|
||||
stream.value = apiClient.streamLogs(
|
||||
repo.value.owner,
|
||||
repo.value.name,
|
||||
pipeline.value.number,
|
||||
step.value.id,
|
||||
(line) => {
|
||||
writeLog({ index: line.line, text: atob(line.data), time: line.time });
|
||||
flushLogs(true);
|
||||
},
|
||||
);
|
||||
stream.value = apiClient.streamLogs(repo.value.id, pipeline.value.number, step.value.id, (line) => {
|
||||
writeLog({ index: line.line, text: atob(line.data), time: line.time });
|
||||
flushLogs(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, Ref } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, Ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
@@ -57,74 +57,56 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { Repo } from '~/lib/api/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ActionsTab',
|
||||
const apiClient = useApiClient();
|
||||
const router = useRouter();
|
||||
const notifications = useNotifications();
|
||||
const i18n = useI18n();
|
||||
|
||||
components: { Button, Panel },
|
||||
const repo = inject<Ref<Repo>>('repo');
|
||||
|
||||
setup() {
|
||||
const apiClient = useApiClient();
|
||||
const router = useRouter();
|
||||
const notifications = useNotifications();
|
||||
const i18n = useI18n();
|
||||
const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
const repo = inject<Ref<Repo>>('repo');
|
||||
|
||||
const { doSubmit: repairRepo, isLoading: isRepairingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
await apiClient.repairRepo(repo.value.owner, repo.value.name);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), type: 'success' });
|
||||
});
|
||||
|
||||
const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
// TODO use proper dialog
|
||||
// eslint-disable-next-line no-alert, no-restricted-globals
|
||||
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.deleteRepo(repo.value.owner, repo.value.name);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.delete.success'), type: 'success' });
|
||||
await router.replace({ name: 'repos' });
|
||||
});
|
||||
|
||||
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
await apiClient.activateRepo(repo.value.owner, repo.value.name);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.enable.success'), type: 'success' });
|
||||
});
|
||||
|
||||
const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
await apiClient.deleteRepo(repo.value.owner, repo.value.name, false);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' });
|
||||
await router.replace({ name: 'repos' });
|
||||
});
|
||||
|
||||
return {
|
||||
isActive: repo?.value.active,
|
||||
isRepairingRepo,
|
||||
isDeletingRepo,
|
||||
isDeactivatingRepo,
|
||||
isActivatingRepo,
|
||||
deleteRepo,
|
||||
repairRepo,
|
||||
deactivateRepo,
|
||||
activateRepo,
|
||||
};
|
||||
},
|
||||
await apiClient.repairRepo(repo.value.id);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.repair.success'), type: 'success' });
|
||||
});
|
||||
|
||||
const { doSubmit: deleteRepo, isLoading: isDeletingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
// TODO use proper dialog
|
||||
// eslint-disable-next-line no-alert, no-restricted-globals
|
||||
if (!confirm(i18n.t('repo.settings.actions.delete.confirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.deleteRepo(repo.value.id);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.delete.success'), type: 'success' });
|
||||
await router.replace({ name: 'repos' });
|
||||
});
|
||||
|
||||
const { doSubmit: activateRepo, isLoading: isActivatingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
await apiClient.activateRepo(repo.value.forge_remote_id);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.enable.success'), type: 'success' });
|
||||
});
|
||||
|
||||
const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
await apiClient.deleteRepo(repo.value.id, false);
|
||||
notifications.notify({ title: i18n.t('repo.settings.actions.disable.success'), type: 'success' });
|
||||
await router.replace({ name: 'repos' });
|
||||
});
|
||||
|
||||
const isActive = computed(() => repo?.value.active);
|
||||
</script>
|
||||
|
||||
@@ -75,7 +75,7 @@ export default defineComponent({
|
||||
throw new Error('Unexpected: "repo" should be provided at this place');
|
||||
}
|
||||
|
||||
branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.owner, repo.value.name, page)))
|
||||
branches.value = (await usePaginate((page) => apiClient.getRepoBranches(repo.value.id, page)))
|
||||
.map((b) => ({
|
||||
value: b,
|
||||
text: b,
|
||||
@@ -91,14 +91,9 @@ export default defineComponent({
|
||||
window.location.port ? `:${window.location.port}` : ''
|
||||
}`;
|
||||
const badgeUrl = computed(
|
||||
() =>
|
||||
`/api/badges/${repo.value.owner}/${repo.value.name}/status.svg${
|
||||
branch.value !== '' ? `?branch=${branch.value}` : ''
|
||||
}`,
|
||||
);
|
||||
const repoUrl = computed(
|
||||
() => `/${repo.value.owner}/${repo.value.name}${branch.value !== '' ? `/branches/${branch.value}` : ''}`,
|
||||
() => `/api/badges/${repo.value.id}/status.svg${branch.value !== '' ? `?branch=${branch.value}` : ''}`,
|
||||
);
|
||||
const repoUrl = computed(() => `/${repo.value.id}${branch.value !== '' ? `/branches/${branch.value}` : ''}`);
|
||||
|
||||
const badgeContent = computed(() => {
|
||||
if (!repo) {
|
||||
|
||||
@@ -121,7 +121,7 @@ async function loadCrons(page: number): Promise<Cron[] | null> {
|
||||
throw new Error("Unexpected: Can't load repo");
|
||||
}
|
||||
|
||||
return apiClient.getCronList(repo.value.owner, repo.value.name, page);
|
||||
return apiClient.getCronList(repo.value.id, page);
|
||||
}
|
||||
|
||||
const { resetPage, data: crons } = usePagination(loadCrons, () => !selectedCron.value);
|
||||
@@ -136,9 +136,9 @@ const { doSubmit: createCron, isLoading: isSaving } = useAsyncAction(async () =>
|
||||
}
|
||||
|
||||
if (isEditingCron.value) {
|
||||
await apiClient.updateCron(repo.value.owner, repo.value.name, selectedCron.value);
|
||||
await apiClient.updateCron(repo.value.id, selectedCron.value);
|
||||
} else {
|
||||
await apiClient.createCron(repo.value.owner, repo.value.name, selectedCron.value);
|
||||
await apiClient.createCron(repo.value.id, selectedCron.value);
|
||||
}
|
||||
notifications.notify({
|
||||
title: i18n.t(isEditingCron.value ? 'repo.settings.crons.saved' : i18n.t('repo.settings.crons.created')),
|
||||
@@ -153,7 +153,7 @@ const { doSubmit: deleteCron, isLoading: isDeleting } = useAsyncAction(async (_c
|
||||
throw new Error("Unexpected: Can't load repo");
|
||||
}
|
||||
|
||||
await apiClient.deleteCron(repo.value.owner, repo.value.name, _cron.id);
|
||||
await apiClient.deleteCron(repo.value.id, _cron.id);
|
||||
notifications.notify({ title: i18n.t('repo.settings.crons.deleted'), type: 'success' });
|
||||
resetPage();
|
||||
});
|
||||
@@ -163,12 +163,10 @@ const { doSubmit: runCron } = useAsyncAction(async (_cron: Cron) => {
|
||||
throw new Error("Unexpected: Can't load repo");
|
||||
}
|
||||
|
||||
const pipeline = await apiClient.runCron(repo.value.owner, repo.value.name, _cron.id);
|
||||
const pipeline = await apiClient.runCron(repo.value.id, _cron.id);
|
||||
await router.push({
|
||||
name: 'repo-pipeline',
|
||||
params: {
|
||||
repoOwner: repo.value.owner,
|
||||
repoName: repo.value.name,
|
||||
pipelineId: pipeline.number,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -148,7 +148,7 @@ export default defineComponent({
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
await repoStore.loadRepo(repo.value.owner, repo.value.name);
|
||||
await repoStore.loadRepo(repo.value.id);
|
||||
loadRepoSettings();
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ export default defineComponent({
|
||||
throw new Error('Unexpected: Repo-Settings should be set');
|
||||
}
|
||||
|
||||
await apiClient.updateRepo(repo.value.owner, repo.value.name, repoSettings.value);
|
||||
await apiClient.updateRepo(repo.value.id, repoSettings.value);
|
||||
await loadRepo();
|
||||
notifications.notify({ title: i18n.t('repo.settings.general.success'), type: 'success' });
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ export default defineComponent({
|
||||
throw new Error("Unexpected: Can't load repo");
|
||||
}
|
||||
|
||||
return apiClient.getRegistryList(repo.value.owner, repo.value.name, page);
|
||||
return apiClient.getRegistryList(repo.value.id, page);
|
||||
}
|
||||
|
||||
const { resetPage, data: registries } = usePagination(loadRegistries, () => !selectedRegistry.value);
|
||||
@@ -139,9 +139,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (isEditingRegistry.value) {
|
||||
await apiClient.updateRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
|
||||
await apiClient.updateRegistry(repo.value.id, selectedRegistry.value);
|
||||
} else {
|
||||
await apiClient.createRegistry(repo.value.owner, repo.value.name, selectedRegistry.value);
|
||||
await apiClient.createRegistry(repo.value.id, selectedRegistry.value);
|
||||
}
|
||||
notifications.notify({
|
||||
title: i18n.t(
|
||||
@@ -159,7 +159,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const registryAddress = encodeURIComponent(_registry.address);
|
||||
await apiClient.deleteRegistry(repo.value.owner, repo.value.name, registryAddress);
|
||||
await apiClient.deleteRegistry(repo.value.id, registryAddress);
|
||||
notifications.notify({ title: i18n.t('repo.settings.registries.deleted'), type: 'success' });
|
||||
resetPage();
|
||||
});
|
||||
|
||||
@@ -86,7 +86,7 @@ export default defineComponent({
|
||||
throw new Error("Unexpected: Can't load repo");
|
||||
}
|
||||
|
||||
return apiClient.getSecretList(repo.value.owner, repo.value.name, page);
|
||||
return apiClient.getSecretList(repo.value.id, page);
|
||||
}
|
||||
|
||||
const { resetPage, data: secrets } = usePagination(loadSecrets, () => !selectedSecret.value);
|
||||
@@ -101,9 +101,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (isEditingSecret.value) {
|
||||
await apiClient.updateSecret(repo.value.owner, repo.value.name, selectedSecret.value);
|
||||
await apiClient.updateSecret(repo.value.id, selectedSecret.value);
|
||||
} else {
|
||||
await apiClient.createSecret(repo.value.owner, repo.value.name, selectedSecret.value);
|
||||
await apiClient.createSecret(repo.value.id, selectedSecret.value);
|
||||
}
|
||||
notifications.notify({
|
||||
title: i18n.t(isEditingSecret.value ? 'repo.settings.secrets.saved' : 'repo.settings.secrets.created'),
|
||||
@@ -118,7 +118,7 @@ export default defineComponent({
|
||||
throw new Error("Unexpected: Can't load repo");
|
||||
}
|
||||
|
||||
await apiClient.deleteSecret(repo.value.owner, repo.value.name, _secret.name);
|
||||
await apiClient.deleteSecret(repo.value.id, _secret.name);
|
||||
notifications.notify({ title: i18n.t('repo.settings.secrets.deleted'), type: 'success' });
|
||||
resetPage();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user