263 lines
7.0 KiB
Vue
263 lines
7.0 KiB
Vue
<template>
|
|
<div class="p-5" v-if="deploy">
|
|
<AlertAddressableError
|
|
v-if="error"
|
|
class="mb-5"
|
|
:name="error.name"
|
|
:title="error.title"
|
|
@done="$resources.errors.reload()"
|
|
/>
|
|
<AlertBanner
|
|
v-if="alertMessage && !error"
|
|
:title="alertMessage"
|
|
type="warning"
|
|
class="mb-5"
|
|
/>
|
|
<Button :route="{ name: `${object.pagetype} Detail Deploys` }">
|
|
<template #prefix>
|
|
<i-lucide-arrow-left class="inline-block h-4 w-4" />
|
|
</template>
|
|
所有部署
|
|
</Button>
|
|
|
|
<div class="mt-3">
|
|
<div class="flex w-full items-center">
|
|
<h2 class="text-lg font-medium text-gray-900">{{ deploy.name }}</h2>
|
|
<Badge class="ml-2" :label="deploy.status" />
|
|
<div class="ml-auto flex items-center space-x-2">
|
|
<Button
|
|
@click="$resources.deploy.reload()"
|
|
:loading="$resources.deploy.get.loading"
|
|
>
|
|
<template #icon>
|
|
<i-lucide-refresh-ccw class="h-4 w-4" />
|
|
</template>
|
|
</Button>
|
|
<Dropdown v-if="dropdownOptions?.length" :options="dropdownOptions">
|
|
<template v-slot="{ open }">
|
|
<Button>
|
|
<template #icon>
|
|
<i-lucide-more-horizontal class="h-4 w-4" />
|
|
</template>
|
|
</Button>
|
|
</template>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-500">创建时间</div>
|
|
<div class="mt-2 text-sm text-gray-900">
|
|
{{ $format.date(deploy.creation, 'lll') }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-500">创建者</div>
|
|
<div class="mt-2 text-sm text-gray-900">
|
|
{{ deploy.owner }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-500">持续时间</div>
|
|
<div class="mt-2 text-sm text-gray-900">
|
|
{{
|
|
deploy.build_end ? $format.duration(deploy.build_duration) : '-'
|
|
}}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-500">开始时间</div>
|
|
<div class="mt-2 text-sm text-gray-900">
|
|
{{ $format.date(deploy.build_start, 'lll') }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-500">结束时间</div>
|
|
<div class="mt-2 text-sm text-gray-900">
|
|
{{
|
|
deploy.build_end ? $format.date(deploy.build_end, 'lll') : '-'
|
|
}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 构建步骤 -->
|
|
<div :class="deploy.build_error ? 'mt-4' : 'mt-8'" class="space-y-4">
|
|
<JobStep
|
|
v-for="step in deploy.build_steps"
|
|
:step="step"
|
|
:key="step.name"
|
|
/>
|
|
<JobStep v-for="job in deploy.jobs" :step="job" :key="job.name" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import { createResource, getCachedDocumentResource } from 'jingrow-ui';
|
|
import { getObject } from '../objects';
|
|
import JobStep from '../components/JobStep.vue';
|
|
import AlertAddressableError from '../components/AlertAddressableError.vue';
|
|
import AlertBanner from '../components/AlertBanner.vue';
|
|
import dayjs from 'dayjs';
|
|
import { toast } from 'vue-sonner';
|
|
|
|
export default {
|
|
name: 'DeployCandidate',
|
|
props: ['id', 'objectType'],
|
|
components: {
|
|
JobStep,
|
|
AlertBanner,
|
|
AlertAddressableError,
|
|
},
|
|
resources: {
|
|
deploy() {
|
|
return {
|
|
type: 'document',
|
|
pagetype: 'Deploy Candidate',
|
|
name: this.id,
|
|
transform: this.transformDeploy,
|
|
};
|
|
},
|
|
errors() {
|
|
return {
|
|
type: 'list',
|
|
cache: ['Jcloud Notification', 'Error', 'Deploy Candidate', this.id],
|
|
pagetype: 'Jcloud Notification',
|
|
auto: true,
|
|
fields: ['title', 'name'],
|
|
filters: {
|
|
document_type: 'Deploy Candidate',
|
|
document_name: this.id,
|
|
is_actionable: true,
|
|
class: 'Error',
|
|
},
|
|
limit: 1,
|
|
};
|
|
},
|
|
},
|
|
mounted() {
|
|
this.$socket.emit('pg_subscribe', 'Deploy Candidate', this.id);
|
|
this.$socket.on(`bench_deploy:${this.id}:steps`, (data) => {
|
|
if (data.name === this.id && this.$resources.deploy.pg) {
|
|
this.$resources.deploy.pg.build_steps = this.transformDeploy({
|
|
build_steps: data.steps,
|
|
})?.build_steps;
|
|
}
|
|
});
|
|
this.$socket.on(`bench_deploy:${this.id}:finished`, () => {
|
|
const rgDoc = getCachedDocumentResource(
|
|
'Release Group',
|
|
this.$resources.deploy.pg?.group,
|
|
);
|
|
if (rgDoc) rgDoc.reload();
|
|
this.$resources.deploy.reload();
|
|
this.$resources.errors.reload();
|
|
});
|
|
},
|
|
beforeUnmount() {
|
|
this.$socket.emit('pg_unsubscribe', 'Deploy Candidate', this.id);
|
|
this.$socket.off(`bench_deploy:${this.id}:steps`);
|
|
},
|
|
computed: {
|
|
deploy() {
|
|
return this.$resources.deploy.pg;
|
|
},
|
|
object() {
|
|
return getObject(this.objectType);
|
|
},
|
|
error() {
|
|
return this.$resources.errors?.data?.[0] ?? null;
|
|
},
|
|
alertMessage() {
|
|
if (!this.deploy) {
|
|
return null;
|
|
}
|
|
|
|
if (this.deploy.retry_count > 0 && this.deploy.status === 'Scheduled') {
|
|
return '上一次部署失败,将很快尝试重新部署';
|
|
}
|
|
return null;
|
|
},
|
|
dropdownOptions() {
|
|
return [
|
|
{
|
|
label: '在桌面查看',
|
|
icon: 'external-link',
|
|
condition: () => this.$team?.pg?.is_desk_user,
|
|
onClick: () => {
|
|
window.open(
|
|
`${window.location.protocol}//${window.location.host}/app/deploy-candidate/${this.id}`,
|
|
'_blank',
|
|
);
|
|
},
|
|
},
|
|
{
|
|
label: '失败并重新部署',
|
|
icon: 'repeat',
|
|
condition: () => this.showFailAndRedeploy,
|
|
onClick: () => this.failAndRedeploy(),
|
|
},
|
|
].filter((option) => option.condition?.() ?? true);
|
|
},
|
|
showFailAndRedeploy() {
|
|
if (!this.deploy || this.deploy.status !== 'Running') {
|
|
return false;
|
|
}
|
|
|
|
const start = dayjs(this.deploy.build_start);
|
|
const now = dayjs(new Date());
|
|
|
|
return now.diff(start, 'hours') > 2;
|
|
},
|
|
},
|
|
methods: {
|
|
transformDeploy(deploy) {
|
|
for (let step of deploy.build_steps) {
|
|
if (step.status === 'Running') {
|
|
step.isOpen = true;
|
|
} else {
|
|
step.isOpen = this.$resources.deploy?.pg?.build_steps?.find(
|
|
(s) => s.name === step.name,
|
|
)?.isOpen;
|
|
}
|
|
step.title = `${step.stage} - ${step.step}`;
|
|
step.output =
|
|
step.command || step.output
|
|
? `${step.command || ''}\n${step.output || ''}`.trim()
|
|
: '';
|
|
step.duration = ['Success', 'Failure'].includes(step.status)
|
|
? step.cached
|
|
? 'Cached'
|
|
: `${step.duration}s`
|
|
: null;
|
|
}
|
|
return deploy;
|
|
},
|
|
failAndRedeploy() {
|
|
if (!this.deploy) {
|
|
return;
|
|
}
|
|
|
|
const group = this.deploy.group;
|
|
const onError = () => toast.error('无法失败并重新部署');
|
|
const router = this.$router;
|
|
|
|
createResource({
|
|
url: 'jcloud.api.bench.fail_and_redeploy',
|
|
params: { name: group, dc_name: this.deploy.name },
|
|
onSuccess(name) {
|
|
if (!name) {
|
|
onError();
|
|
} else {
|
|
router.push(`/groups/${group}/deploys/${name}`);
|
|
}
|
|
},
|
|
onError,
|
|
}).fetch();
|
|
},
|
|
},
|
|
};
|
|
</script> |