mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 03:41:01 +00:00
Add button to trigger deployments (#1415)
Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Anbraten <anton@ju60.de> Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
5d1ec770c9
commit
cfb288201f
5 changed files with 168 additions and 1 deletions
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
|
@ -15,6 +15,7 @@ declare module '@vue/runtime-core' {
|
||||||
Checkbox: typeof import('./src/components/form/Checkbox.vue')['default']
|
Checkbox: typeof import('./src/components/form/Checkbox.vue')['default']
|
||||||
CheckboxesField: typeof import('./src/components/form/CheckboxesField.vue')['default']
|
CheckboxesField: typeof import('./src/components/form/CheckboxesField.vue')['default']
|
||||||
CronTab: typeof import('./src/components/repo/settings/CronTab.vue')['default']
|
CronTab: typeof import('./src/components/repo/settings/CronTab.vue')['default']
|
||||||
|
DeployPipelinePopup: typeof import('./src/components/layout/popups/DeployPipelinePopup.vue')['default']
|
||||||
DocsLink: typeof import('./src/components/atomic/DocsLink.vue')['default']
|
DocsLink: typeof import('./src/components/atomic/DocsLink.vue')['default']
|
||||||
FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default']
|
FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default']
|
||||||
GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default']
|
GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default']
|
||||||
|
|
|
@ -41,6 +41,18 @@
|
||||||
"value": "Variable value"
|
"value": "Variable value"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"deploy_pipeline": {
|
||||||
|
"title": "Trigger deployment event for current pipeline #{pipelineId}",
|
||||||
|
"enter_target": "Target deployment environment",
|
||||||
|
"trigger": "Deploy",
|
||||||
|
"variables": {
|
||||||
|
"add": "Add variable",
|
||||||
|
"title": "Additional pipeline variables",
|
||||||
|
"desc": "Specify additional variables to use in your pipeline. Variables with the same name will be overwritten.",
|
||||||
|
"name": "Variable name",
|
||||||
|
"value": "Variable value"
|
||||||
|
}
|
||||||
|
},
|
||||||
"activity": "Activity",
|
"activity": "Activity",
|
||||||
"branches": "Branches",
|
"branches": "Branches",
|
||||||
"add": "Add repository",
|
"add": "Add repository",
|
||||||
|
@ -218,6 +230,7 @@
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"canceled": "This step has been canceled.",
|
"canceled": "This step has been canceled.",
|
||||||
"cancel_success": "Pipeline canceled",
|
"cancel_success": "Pipeline canceled",
|
||||||
|
"deploy": "Deploy",
|
||||||
"restart_success": "Pipeline restarted",
|
"restart_success": "Pipeline restarted",
|
||||||
"log_download": "Download",
|
"log_download": "Download",
|
||||||
"log_auto_scroll": "Automatically scroll down",
|
"log_auto_scroll": "Automatically scroll down",
|
||||||
|
|
120
web/src/components/layout/popups/DeployPipelinePopup.vue
Normal file
120
web/src/components/layout/popups/DeployPipelinePopup.vue
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<template>
|
||||||
|
<Popup :open="open" @close="$emit('close')">
|
||||||
|
<Panel v-if="!loading">
|
||||||
|
<form @submit.prevent="triggerDeployPipeline">
|
||||||
|
<span class="text-xl text-color">{{ $t('repo.deploy_pipeline.title', { pipelineId: pipelineNumber }) }}</span>
|
||||||
|
<InputField :label="$t('repo.deploy_pipeline.enter_target')">
|
||||||
|
<TextField v-model="payload.environment" required />
|
||||||
|
</InputField>
|
||||||
|
<InputField :label="$t('repo.deploy_pipeline.variables.title')">
|
||||||
|
<span class="text-sm text-color-alt mb-2">{{ $t('repo.deploy_pipeline.variables.desc') }}</span>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div v-for="(value, name) in payload.variables" :key="name" class="flex gap-4">
|
||||||
|
<TextField :model-value="name" disabled />
|
||||||
|
<TextField :model-value="value" disabled />
|
||||||
|
<div class="w-34 flex-shrink-0">
|
||||||
|
<Button color="red" class="ml-auto" @click="deleteVar(name)">
|
||||||
|
<i-la-times />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form class="flex gap-4" @submit.prevent="addPipelineVariable">
|
||||||
|
<TextField
|
||||||
|
v-model="newPipelineVariable.name"
|
||||||
|
:placeholder="$t('repo.deploy_pipeline.variables.name')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
v-model="newPipelineVariable.value"
|
||||||
|
:placeholder="$t('repo.deploy_pipeline.variables.value')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
class="w-34 flex-shrink-0"
|
||||||
|
start-icon="plus"
|
||||||
|
type="submit"
|
||||||
|
:text="$t('repo.deploy_pipeline.variables.add')"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</InputField>
|
||||||
|
<Button type="submit" :text="$t('repo.deploy_pipeline.trigger')" />
|
||||||
|
</form>
|
||||||
|
</Panel>
|
||||||
|
</Popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, toRef } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import Button from '~/components/atomic/Button.vue';
|
||||||
|
import InputField from '~/components/form/InputField.vue';
|
||||||
|
import TextField from '~/components/form/TextField.vue';
|
||||||
|
import Panel from '~/components/layout/Panel.vue';
|
||||||
|
import Popup from '~/components/layout/Popup.vue';
|
||||||
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
|
import { inject } from '~/compositions/useInjectProvide';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
pipelineNumber: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'close'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const apiClient = useApiClient();
|
||||||
|
|
||||||
|
const repo = inject('repo');
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const payload = ref<{ id: string; environment: string; variables: Record<string, string> }>({
|
||||||
|
id: '',
|
||||||
|
environment: '',
|
||||||
|
variables: {},
|
||||||
|
});
|
||||||
|
const newPipelineVariable = ref<{ name: string; value: string }>({ name: '', value: '' });
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
function addPipelineVariable() {
|
||||||
|
if (!newPipelineVariable.value.name || !newPipelineVariable.value.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
payload.value.variables[newPipelineVariable.value.name] = newPipelineVariable.value.value;
|
||||||
|
newPipelineVariable.value.name = '';
|
||||||
|
newPipelineVariable.value.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteVar(key: string) {
|
||||||
|
delete payload.value.variables[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
emit('close');
|
||||||
|
|
||||||
|
await router.push({
|
||||||
|
name: 'repo-pipeline',
|
||||||
|
params: {
|
||||||
|
pipelineId: newPipeline.number,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -23,6 +23,13 @@ type PipelineOptions = {
|
||||||
branch: string;
|
branch: string;
|
||||||
variables: Record<string, string>;
|
variables: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DeploymentOptions = {
|
||||||
|
id: string;
|
||||||
|
environment: string;
|
||||||
|
variables: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
export default class WoodpeckerClient extends ApiClient {
|
export default class WoodpeckerClient extends ApiClient {
|
||||||
getRepoList(opts?: RepoListOptions): Promise<Repo[]> {
|
getRepoList(opts?: RepoListOptions): Promise<Repo[]> {
|
||||||
const query = encodeQueryString(opts);
|
const query = encodeQueryString(opts);
|
||||||
|
@ -62,6 +69,18 @@ export default class WoodpeckerClient extends ApiClient {
|
||||||
return this._post(`/api/repos/${owner}/${repo}/pipelines`, options) as Promise<Pipeline>;
|
return this._post(`/api/repos/${owner}/${repo}/pipelines`, options) as Promise<Pipeline>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deploy triggers a deployment for an existing pipeline using the
|
||||||
|
// specified target environment.
|
||||||
|
deployPipeline(owner: string, repo: string, number: number, options: DeploymentOptions): Promise<Pipeline> {
|
||||||
|
const vars = {
|
||||||
|
...options.variables,
|
||||||
|
event: 'deployment',
|
||||||
|
deploy_to: options.environment,
|
||||||
|
};
|
||||||
|
const query = encodeQueryString(vars);
|
||||||
|
return this._post(`/api/repos/${owner}/${repo}/pipelines/${number}?${query}`) as Promise<Pipeline>;
|
||||||
|
}
|
||||||
|
|
||||||
getPipelineList(owner: string, repo: string, opts?: Record<string, string | number | boolean>): Promise<Pipeline[]> {
|
getPipelineList(owner: string, repo: string, opts?: Record<string, string | number | boolean>): Promise<Pipeline[]> {
|
||||||
const query = encodeQueryString(opts);
|
const query = encodeQueryString(opts);
|
||||||
return this._get(`/api/repos/${owner}/${repo}/pipelines?${query}`) as Promise<Pipeline[]>;
|
return this._get(`/api/repos/${owner}/${repo}/pipelines?${query}`) as Promise<Pipeline[]>;
|
||||||
|
|
|
@ -31,6 +31,17 @@
|
||||||
:is-loading="isRestartingPipeline"
|
:is-loading="isRestartingPipeline"
|
||||||
@click="restartPipeline"
|
@click="restartPipeline"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
v-if="pipeline.status === 'success'"
|
||||||
|
class="flex-shrink-0"
|
||||||
|
:text="$t('repo.pipeline.actions.deploy')"
|
||||||
|
@click="showDeployPipelinePopup = true"
|
||||||
|
/>
|
||||||
|
<DeployPipelinePopup
|
||||||
|
:pipeline-number="pipelineId"
|
||||||
|
:open="showDeployPipelinePopup"
|
||||||
|
@close="showDeployPipelinePopup = false"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -66,7 +77,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Tooltip } from 'floating-vue';
|
import { Tooltip } from 'floating-vue';
|
||||||
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, provide, Ref, toRef, watch } from 'vue';
|
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, provide, Ref, 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';
|
||||||
|
|
||||||
|
@ -135,6 +146,8 @@ export default defineComponent({
|
||||||
|
|
||||||
const { message } = usePipeline(pipeline);
|
const { message } = usePipeline(pipeline);
|
||||||
|
|
||||||
|
const showDeployPipelinePopup = ref(false);
|
||||||
|
|
||||||
async function loadPipeline(): Promise<void> {
|
async function loadPipeline(): Promise<void> {
|
||||||
if (!repo) {
|
if (!repo) {
|
||||||
throw new Error('Unexpected: Repo is undefined');
|
throw new Error('Unexpected: Repo is undefined');
|
||||||
|
@ -216,6 +229,7 @@ export default defineComponent({
|
||||||
message,
|
message,
|
||||||
isCancelingPipeline,
|
isCancelingPipeline,
|
||||||
isRestartingPipeline,
|
isRestartingPipeline,
|
||||||
|
showDeployPipelinePopup,
|
||||||
activeTab,
|
activeTab,
|
||||||
since,
|
since,
|
||||||
duration,
|
duration,
|
||||||
|
|
Loading…
Reference in a new issue