fix: streamline release deploy ui

This commit is contained in:
hy 2026-04-24 15:14:13 +08:00
parent 2d81e98b3b
commit 3cf7f6656d
3 changed files with 94 additions and 231 deletions

View File

@ -616,11 +616,6 @@ body.auth-page {
align-items: center;
}
.release-section-nav {
flex: 1 1 auto;
min-width: 0;
}
.taskbar-context {
display: flex;
flex-direction: column;
@ -668,43 +663,6 @@ body.auth-page {
transition: color 180ms ease, opacity 180ms ease, border-color 180ms ease, background 180ms ease;
}
.release-step-link {
border-color: color-mix(in oklab, var(--line) 86%, var(--panel-strong));
background: color-mix(in oklab, var(--panel-strong) 90%, var(--panel-muted));
}
.release-step-index {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border: 1px solid var(--line);
border-radius: 999px;
color: var(--muted);
font-size: 9px;
font-weight: 800;
letter-spacing: 0;
}
.release-step-link.tone-ok .release-step-index {
border-color: color-mix(in oklab, var(--ok) 34%, var(--line));
background: color-mix(in oklab, var(--ok-soft) 46%, var(--panel-strong));
color: color-mix(in oklab, var(--ok) 70%, var(--text));
}
.release-step-link.tone-warn .release-step-index {
border-color: color-mix(in oklab, var(--warn) 38%, var(--line));
background: color-mix(in oklab, var(--warn-soft) 56%, var(--panel-strong));
color: color-mix(in oklab, var(--warn) 82%, var(--text));
}
.release-step-link.tone-bad .release-step-index {
border-color: color-mix(in oklab, var(--bad) 42%, var(--line));
background: color-mix(in oklab, var(--bad-soft) 58%, var(--panel-strong));
color: color-mix(in oklab, var(--bad) 82%, var(--text));
}
.section-link:hover {
color: var(--text);
border-color: color-mix(in oklab, var(--accent) 18%, var(--line));
@ -715,10 +673,6 @@ body.auth-page {
margin-left: 0;
}
.release-kind-select {
min-width: 98px;
}
.control-group-spread {
justify-content: flex-end;
flex: 1 1 auto;
@ -3191,9 +3145,9 @@ body.auth-page {
justify-content: space-between;
gap: 12px;
align-items: center;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid color-mix(in oklab, var(--line) 92%, var(--panel-strong));
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.release-command-actions .refresh {
@ -3307,23 +3261,24 @@ body.auth-page {
}
.release-service-title-line {
display: flex;
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
gap: 5px;
flex-wrap: nowrap;
align-items: center;
}
.release-service-title strong {
overflow: hidden;
flex: 1 1 auto;
grid-column: 1 / -1;
overflow: visible;
font-size: 13px;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.25;
overflow-wrap: anywhere;
text-overflow: clip;
white-space: normal;
}
.release-service-title-line .badge {
flex: 0 0 auto;
width: fit-content;
min-height: 22px;
padding: 0 6px;
font-size: 9.5px;
@ -4035,17 +3990,6 @@ body.auth-page {
background: linear-gradient(180deg, color-mix(in oklab, var(--panel-muted) 98%, var(--panel-strong)) 0%, var(--panel-muted) 100%);
}
.release-section-nav {
display: flex;
gap: 6px;
align-items: center;
padding: 4px;
border: 1px solid color-mix(in oklab, var(--line) 92%, var(--panel-strong));
border-radius: 14px;
background: linear-gradient(180deg, color-mix(in oklab, var(--panel-muted) 96%, var(--panel-strong)) 0%, var(--panel-strong) 100%);
overflow: auto;
}
.mongo-admin-layout {
align-items: start;
}
@ -4736,6 +4680,20 @@ body.auth-page {
box-shadow: 0 24px 64px rgba(29, 42, 68, 0.24);
}
.build-params-dialog {
grid-template-rows: auto auto auto;
width: min(900px, 100%);
}
.build-params-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: 0;
}
.build-params-toggle {
justify-content: flex-end;
}
.log-viewer-head {
display: flex;
justify-content: space-between;

View File

@ -56,12 +56,6 @@ const PRIMARY_FILTERS = [
{ id: "infra", label: "基础设施" },
];
const BUSINESS_GROUPS = new Set(["gateway", "app", "pay"]);
const RELEASE_SECTIONS = [
{ id: "release-build", label: "构建参数" },
{ id: "release-targets", label: "更新目标" },
{ id: "release-log", label: "执行日志" },
{ id: "release-restart", label: "滚动重启" },
];
const CONFIG_TABS = [
{ id: "nacos", label: "Nacos" },
{ id: "go", label: "Go Env" },
@ -420,7 +414,6 @@ createApp({
: DATABASE_ENGINES.filter((engine) => engine.id === "mongo"),
commonToolTabs: COMMON_TOOL_TABS,
primaryFilters: PRIMARY_FILTERS,
releaseSections: RELEASE_SECTIONS,
configTabs: CONFIG_TABS,
infraTabs: INFRA_TABS,
mongoAdminTabs: MONGO_ADMIN_TABS,
@ -436,7 +429,7 @@ createApp({
keyword: "",
infraKeyword: "",
releaseKeyword: "",
releaseKindFilter: "all",
buildParamsOpen: false,
configTab: "nacos",
configEditorMode: "diff",
goEditorMode: "diff",
@ -1743,9 +1736,6 @@ createApp({
const keyword = this.normalizedReleaseKeyword;
return this.allReleaseTargetRows
.filter((item) => {
if (this.releaseKindFilter !== "all" && item.kind !== this.releaseKindFilter) {
return false;
}
if (keyword && !item.searchText.includes(keyword)) {
return false;
}
@ -1766,9 +1756,6 @@ createApp({
].join(" ").toLowerCase(),
}))
.filter((item) => {
if (this.releaseKindFilter !== "all" && item.kind !== this.releaseKindFilter) {
return false;
}
if (keyword && !item.searchText.includes(keyword)) {
return false;
}
@ -2385,14 +2372,6 @@ createApp({
}
return text;
},
scrollToSection(sectionId) {
this.$nextTick(() => {
const node = document.getElementById(sectionId);
if (node) {
node.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
},
imageMeta(imageRef) {
const raw = String(imageRef || "").trim();
if (!raw) {
@ -2490,36 +2469,30 @@ createApp({
return this.copiedToken === token;
},
runReleasePrecheck() {
const selectedKinds = new Set(
(this.updateOps.items || [])
.filter((item) => this.updateOps.selectedServices.includes(item.service))
.map((item) => item.kind)
);
const checks = [
{
label: "更新目标",
ok: this.updateOps.items.length > 0,
detail: this.updateOps.items.length > 0 ? `${this.updateOps.items.length} 项可更新服务` : this.explainError(this.updateOps.error) || "暂无更新目标",
detail: this.updateOps.items.length > 0 ? `${this.updateOps.items.length} 项可更新服务` : this.explainError(this.updateOps.error) || "暂无可更新服务",
},
{
label: "更新选择",
ok: this.updateOps.selectedServices.length > 0,
detail: this.updateOps.selectedServices.length > 0 ? this.updateOps.selectedServices.join(", ") : "未选择更新服务",
},
{
label: "滚动重启目标",
ok: this.restartOps.items.length > 0,
detail: this.restartOps.items.length > 0 ? `${this.restartOps.items.length} 项可重启服务` : (this.restartOps.error || "暂无重启目标"),
},
{
label: "构建参数",
ok: (() => {
const selectedKinds = new Set(
(this.updateOps.items || [])
.filter((item) => this.updateOps.selectedServices.includes(item.service))
.map((item) => item.kind)
);
return (
(!selectedKinds.has("java") || Boolean(this.updateOps.javaGitRef.trim()))
&& (!selectedKinds.has("golang") || Boolean(this.updateOps.goGitRef.trim()))
&& (!selectedKinds.has("frontend") || Boolean(this.updateOps.adminGitRef.trim()))
);
})(),
ok: (
(!selectedKinds.has("java") || Boolean(this.updateOps.javaGitRef.trim()))
&& (!selectedKinds.has("golang") || Boolean(this.updateOps.goGitRef.trim()))
&& (!selectedKinds.has("frontend") || Boolean(this.updateOps.adminGitRef.trim()))
),
detail: `Java ${this.updateOps.javaGitRef || "-"} / Go ${this.updateOps.goGitRef || "-"} / Admin ${this.updateOps.adminGitRef || "-"}`,
},
];
@ -2529,9 +2502,9 @@ createApp({
ranAt: new Date().toISOString(),
tone: ok ? "ok" : "warn",
title: ok ? "预检查通过" : "预检查未通过",
checks,
checks: [],
};
this.recordReleaseHistory("precheck", this.releasePrecheck.title, checks.map((item) => `${item.label}: ${item.detail}`).join(" | "), ok ? "ok" : "warn");
this.recordReleaseHistory("precheck", this.releasePrecheck.title, checks.map((item) => item.detail).join(" | "), ok ? "ok" : "warn");
},
async refreshReleaseTargets() {
await this.ensureReleaseWorkbenchLoaded({ force: true });
@ -3319,33 +3292,6 @@ createApp({
}
return this.releaseIsRunning() ? `上次${this.releasePrecheck.title}` : this.releasePrecheck.title;
},
releaseStepTone(sectionId) {
if (sectionId === "release-build") {
return this.updateOps.selectedServices.length > 0 || this.updateOps.status ? "ok" : "neutral";
}
if (sectionId === "release-targets") {
return this.updateOps.selectedServices.length > 0 || this.restartOps.selectedServices.length > 0 ? "ok" : "neutral";
}
if (sectionId === "release-log") {
if (this.updateOps.error || this.updateOps.status === "failed") {
return "bad";
}
if (this.updateOps.running || this.updateOps.status === "queued" || this.updateOps.status === "running") {
return "warn";
}
return this.updateOps.status === "success" ? "ok" : "neutral";
}
if (sectionId === "release-restart") {
if (this.restartOps.error) {
return "bad";
}
if (this.restartOps.running) {
return "warn";
}
return this.restartOps.result?.ok || this.restartOps.selectedServices.length > 0 ? "ok" : "neutral";
}
return "neutral";
},
syncRefreshSettings() {
const settings = this.payload.settings || {};
const options = Array.isArray(settings.refreshIntervalOptions)
@ -5995,10 +5941,10 @@ createApp({
this.restartOps.items = Array.isArray(payload.items) ? payload.items : [];
const available = new Set(this.restartOps.items.map((item) => item.service));
this.restartOps.selectedServices = this.restartOps.selectedServices.filter((service) => available.has(service));
this.recordReleaseHistory("restart", "刷新滚动重启目标", `${this.restartOps.items.length}`, "neutral");
this.recordReleaseHistory("restart", "刷新重启列表", `${this.restartOps.items.length}`, "neutral");
} catch (error) {
this.restartOps.error = error instanceof Error ? error.message : String(error);
this.recordReleaseHistory("restart", "刷新滚动重启目标失败", this.explainError(this.restartOps.error), "bad");
this.recordReleaseHistory("restart", "刷新重启列表失败", this.explainError(this.restartOps.error), "bad");
} finally {
this.restartOps.loaded = true;
this.restartOps.loading = false;
@ -6041,10 +5987,10 @@ createApp({
const available = new Set(this.updateOps.items.map((item) => item.service));
this.updateOps.selectedServices = this.updateOps.selectedServices.filter((service) => available.has(service));
this.recordReleaseHistory("update", "刷新更新目标", `${this.updateOps.items.length}`, "neutral");
this.recordReleaseHistory("update", "刷新服务列表", `${this.updateOps.items.length}`, "neutral");
} catch (error) {
this.updateOps.error = error instanceof Error ? error.message : String(error);
this.recordReleaseHistory("update", "刷新更新目标失败", this.explainError(this.updateOps.error), "bad");
this.recordReleaseHistory("update", "刷新服务列表失败", this.explainError(this.updateOps.error), "bad");
} finally {
this.updateOps.loaded = true;
this.updateOps.loading = false;

View File

@ -305,38 +305,6 @@
</section>
</template>
<template v-else-if="activeView === 'release'">
<section class="control-bar task-control-bar">
<div class="taskbar-context">
<strong>发布</strong>
</div>
<div class="control-group release-section-nav">
<button
v-for="(section, index) in releaseSections"
:key="section.id"
:class="['section-link', 'release-step-link', `tone-${releaseStepTone(section.id)}`]"
@click="scrollToSection(section.id)"
>
<span class="release-step-index">{{ index + 1 }}</span>
<span>{{ section.label }}</span>
</button>
</div>
<div class="control-group control-group-spread">
<label class="interval-field">
<span>类型</span>
<select v-model="releaseKindFilter" class="interval-select release-kind-select">
<option value="all">全部</option>
<option value="java">Java</option>
<option value="golang">Golang</option>
<option value="frontend">前端</option>
</select>
</label>
</div>
</section>
</template>
<p v-if="error" class="toolbar-error">加载失败:{{ error }}</p>
<template v-if="!showLoadingSkeleton && (activeView === 'overview' || activeView === 'services')">
@ -3134,13 +3102,6 @@
<template v-if="!showLoadingSkeleton && activeView === 'release'">
<section class="panel release-status-strip sticky-release-strip">
<div class="release-task-head">
<div class="release-task-context">
<strong>发布工作台</strong>
<span>构建参数 → 更新目标 → 执行日志 → 滚动重启</span>
</div>
</div>
<div class="release-command-row" aria-label="发布操作">
<div class="release-action-summary compact">
<span :class="['badge', releasePrimaryStatusTone()]">{{ releasePrimaryStatusLabel() }}</span>
@ -3151,6 +3112,7 @@
</span>
</div>
<div class="action-group release-command-actions">
<button class="refresh ghost" @click="buildParamsOpen = true">构建参数</button>
<template v-if="releaseIsRunning()">
<button class="refresh" @click="openUpdateLogViewer" :disabled="!updateOps.operationId && !updateOps.logs.length">查看日志</button>
<button class="refresh ghost" @click="fetchUpdateOperationStatus(updateOps.operationId)" :disabled="!updateOps.operationId || updateOps.statusRequestInFlight">
@ -3197,63 +3159,12 @@
<div v-if="releasePrecheck.ranAt" class="precheck-strip release-check-strip">
<span :class="['badge', releasePrecheck.tone]">{{ releasePrecheckBadgeLabel() }}</span>
<span class="badge neutral">执行于 {{ formatDateTime(releasePrecheck.ranAt) }}</span>
<span
v-for="check in releasePrecheck.checks"
:key="'precheck-' + check.label"
:class="['badge', check.ok ? 'ok' : 'warn']"
:title="check.detail"
>
{{ check.label }}
</span>
</div>
<p v-if="updateOps.error" class="metric-error">{{ explainError(updateOps.error) }}</p>
<p v-else-if="updateOps.message" :class="updateOps.running ? 'warn-text' : updateOps.result && updateOps.result.ok ? 'ok-text' : 'metric-error'">{{ updateOps.message }}</p>
</section>
<section id="release-build" class="panel infra-panel">
<header class="panel-header">
<div class="panel-title">
<h2>构建参数</h2>
</div>
<span class="badge neutral">平台 {{ updateOps.defaults.dockerPlatform }}</span>
</header>
<div class="metadata-strip">
<span class="mono">Harbor {{ updateOps.builder.harborRegistry }} / {{ updateOps.builder.harborProject }}</span>
<span class="mono">Java {{ truncateMiddle(updateOps.builder.javaRepoRoot, 16, 12) }}</span>
<span class="mono">Go {{ truncateMiddle(updateOps.builder.golangRepoRoot, 16, 12) }}</span>
<span class="mono">Admin {{ truncateMiddle(updateOps.builder.frontendRepoRoot || updateOps.builder.frontendRepoUrl, 16, 12) }}</span>
</div>
<div class="update-form-grid">
<label class="update-field">
<span>Java Git Ref</span>
<input v-model.trim="updateOps.javaGitRef" class="search" placeholder="main / branch / tag / commit" />
</label>
<label class="update-field">
<span>Go Git Ref</span>
<input v-model.trim="updateOps.goGitRef" class="search" placeholder="main / branch / tag / commit" />
</label>
<label class="update-field">
<span>Admin Git Ref</span>
<input v-model.trim="updateOps.adminGitRef" class="search" placeholder="main / branch / tag / commit" />
</label>
<label class="update-field">
<span>Image Tag</span>
<input v-model.trim="updateOps.imageTag" class="search" placeholder="可选,不填则自动生成" />
</label>
<div class="update-field update-field-toggle">
<span>构建选项</span>
<label class="update-check">
<input v-model="updateOps.skipMavenBuild" type="checkbox" />
<strong>跳过 Maven</strong>
<small>仅影响 Java 构建阶段</small>
</label>
</div>
</div>
</section>
<section id="release-targets" class="panel infra-panel">
<header class="panel-header">
<div class="panel-title">
@ -3323,7 +3234,7 @@
</header>
</article>
</div>
<div v-else class="empty-panel compact">{{ updateOps.loading ? '加载更新目标中...' : '当前没有可展示的更新目标' }}</div>
<div v-else class="empty-panel compact">{{ updateOps.loading ? '加载服务中...' : '当前没有可展示的服务' }}</div>
</section>
<section id="release-log" class="panel infra-panel">
@ -3531,6 +3442,54 @@
</template>
<div v-if="buildParamsOpen" class="log-viewer-overlay" @click.self="buildParamsOpen = false">
<section class="log-viewer build-params-dialog" role="dialog" aria-modal="true" aria-labelledby="build-params-title">
<header class="log-viewer-head">
<div class="panel-title">
<h2 id="build-params-title">构建参数</h2>
</div>
<div class="action-group">
<span class="badge neutral">平台 {{ updateOps.defaults.dockerPlatform }}</span>
<button class="refresh ghost" @click="buildParamsOpen = false">关闭</button>
</div>
</header>
<div class="metadata-strip">
<span class="mono">Harbor {{ updateOps.builder.harborRegistry }} / {{ updateOps.builder.harborProject }}</span>
<span class="mono">Java {{ truncateMiddle(updateOps.builder.javaRepoRoot, 18, 14) }}</span>
<span class="mono">Go {{ truncateMiddle(updateOps.builder.golangRepoRoot, 18, 14) }}</span>
<span class="mono">Admin {{ truncateMiddle(updateOps.builder.frontendRepoRoot || updateOps.builder.frontendRepoUrl, 18, 14) }}</span>
</div>
<div class="update-form-grid build-params-grid">
<label class="update-field">
<span>Java Git Ref</span>
<input v-model.trim="updateOps.javaGitRef" class="search" placeholder="main / branch / tag / commit" />
</label>
<label class="update-field">
<span>Go Git Ref</span>
<input v-model.trim="updateOps.goGitRef" class="search" placeholder="main / branch / tag / commit" />
</label>
<label class="update-field">
<span>Admin Git Ref</span>
<input v-model.trim="updateOps.adminGitRef" class="search" placeholder="main / branch / tag / commit" />
</label>
<label class="update-field">
<span>Image Tag</span>
<input v-model.trim="updateOps.imageTag" class="search" placeholder="可选,不填则自动生成" />
</label>
<div class="update-field update-field-toggle build-params-toggle">
<span>构建选项</span>
<label class="update-check">
<input v-model="updateOps.skipMavenBuild" type="checkbox" />
<strong>跳过 Maven</strong>
<small>仅影响 Java 构建阶段</small>
</label>
</div>
</div>
</section>
</div>
<div v-if="logViewer.open" class="log-viewer-overlay" @click.self="closeLogViewer">
<section class="log-viewer" role="dialog" aria-modal="true" :aria-labelledby="'log-viewer-title'">
<header class="log-viewer-head">