diff --git a/static/app.css b/static/app.css index b404748..5e6d493 100644 --- a/static/app.css +++ b/static/app.css @@ -34,6 +34,10 @@ display: none; } +[hidden] { + display: none !important; +} + html, body { margin: 0; @@ -44,9 +48,7 @@ body { position: relative; color: var(--text); font-family: "IBM Plex Sans", "PingFang SC", "Noto Sans SC", sans-serif; - background: - radial-gradient(circle at top right, color-mix(in oklab, var(--accent) 8%, transparent), transparent 30%), - linear-gradient(180deg, var(--bg-soft) 0%, var(--bg) 100%); + background: linear-gradient(180deg, var(--bg-soft) 0%, var(--bg) 100%); } body::before { @@ -71,17 +73,16 @@ body::before { .boot-indicator { position: fixed; - left: 50%; - top: 22px; + right: 24px; + top: 18px; z-index: 60; display: inline-flex; align-items: center; gap: 10px; min-height: 40px; padding: 0 14px; - transform: translateX(-50%); border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); - border-radius: 14px; + border-radius: 8px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 96%, var(--accent-soft)) 0%, var(--panel) 100%); box-shadow: 0 16px 32px rgba(33, 46, 76, 0.1); color: var(--muted); @@ -390,7 +391,7 @@ body.auth-page { .control-bar, .panel { border: 1px solid var(--line); - border-radius: 18px; + border-radius: 10px; background: linear-gradient(180deg, var(--panel-tint) 0%, var(--panel) 100%); box-shadow: var(--shadow); } @@ -400,8 +401,8 @@ body.auth-page { top: 10px; z-index: 20; display: grid; - grid-template-columns: minmax(200px, 260px) minmax(0, 1fr) auto; - gap: 14px; + grid-template-columns: minmax(188px, 250px) minmax(0, max-content) minmax(420px, 1fr); + gap: 10px; align-items: center; min-height: 56px; padding: 9px 14px; @@ -452,7 +453,7 @@ body.auth-page { min-height: 24px; padding: 0 9px; border: 1px solid color-mix(in oklab, var(--accent) 34%, var(--line-strong)); - border-radius: 999px; + border-radius: 7px; background: var(--accent-soft); color: var(--accent); font-size: 11px; @@ -473,7 +474,7 @@ body.auth-page { flex-wrap: wrap; padding: 4px; border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); - border-radius: 16px; + border-radius: 10px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-muted) 94%, var(--panel-strong)) 0%, color-mix(in oklab, var(--panel-strong) 96%, var(--panel-muted)) 100%); box-shadow: inset 0 1px 0 color-mix(in oklab, var(--panel-strong) 72%, transparent); } @@ -497,7 +498,7 @@ body.auth-page { justify-content: center; min-height: 34px; padding: 0 12px; - border-radius: 11px; + border-radius: 8px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 98%, var(--panel-muted)) 0%, var(--panel-strong) 100%); cursor: pointer; font-size: 11px; @@ -535,13 +536,21 @@ body.auth-page { .topbar-status { justify-content: flex-end; + flex-wrap: nowrap; padding: 4px; border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); - border-radius: 16px; + border-radius: 10px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-muted) 94%, var(--panel-strong)) 0%, color-mix(in oklab, var(--panel-strong) 96%, var(--panel-muted)) 100%); box-shadow: inset 0 1px 0 color-mix(in oklab, var(--panel-strong) 72%, transparent); } +.topbar-status .badge, +.topbar-status .refresh, +.topbar-status .interval-field { + flex: 0 0 auto; + white-space: nowrap; +} + .control-bar { position: sticky; top: 72px; @@ -636,15 +645,26 @@ body.auth-page { min-width: 320px; min-height: 34px; padding: 0 11px; - border-radius: 12px; + border-radius: 8px; background: color-mix(in oklab, var(--panel-muted) 88%, var(--bg-soft)); } +.topbar-search { + flex: 1 1 220px; + min-width: min(24vw, 300px); + max-width: 360px; +} + .search::placeholder { color: var(--muted); opacity: 0.88; } +.search:disabled { + opacity: 0.58; + cursor: not-allowed; +} + .interval-field { display: inline-flex; align-items: center; @@ -657,7 +677,7 @@ body.auth-page { min-width: 82px; min-height: 32px; padding: 0 11px; - border-radius: 12px; + border-radius: 8px; background: var(--panel-strong); } @@ -959,7 +979,7 @@ body.auth-page { .status-band-item.tone-ok { background: - linear-gradient(180deg, color-mix(in oklab, var(--ok-soft) 52%, var(--panel-strong)) 0%, color-mix(in oklab, var(--ok-soft) 74%, var(--panel-muted)) 100%); + linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 98%, var(--ok-soft)) 0%, color-mix(in oklab, var(--panel-muted) 94%, var(--panel-strong)) 100%); } .status-band-item.tone-warn { @@ -2458,6 +2478,17 @@ body.auth-page { font-size: 11px; } +.infra-freshness { + display: flex; + gap: 8px; + flex-wrap: wrap; + padding-top: 7px; + border-top: 1px solid color-mix(in oklab, var(--line) 86%, var(--panel-strong)); + color: var(--muted); + font-size: 10px; + font-weight: 700; +} + .infra-inline-list { display: flex; gap: 8px; @@ -3019,6 +3050,28 @@ body.auth-page { margin-top: 8px; } +.release-bottom-action-bar { + position: sticky; + bottom: 12px; + z-index: 16; + display: flex; + justify-content: space-between; + gap: 12px; + align-items: center; + margin-top: 12px; + padding: 8px 10px; + border: 1px solid var(--line-strong); + border-radius: 10px; + background: linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 98%, var(--accent-soft)) 0%, var(--panel-strong) 100%); + box-shadow: + 0 14px 30px rgba(33, 46, 76, 0.12), + inset 0 1px 0 color-mix(in oklab, var(--panel-strong) 78%, transparent); +} + +.release-action-summary.compact { + margin-top: 0; +} + .release-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); @@ -4527,6 +4580,7 @@ body.auth-page { } .badge.warn, +.warn-text, .tone-warn { color: var(--warn); } @@ -4640,6 +4694,7 @@ body.auth-page { .topbar-status { width: 100%; justify-content: flex-start; + flex-wrap: wrap; } .detail-panel { @@ -4722,8 +4777,10 @@ body.auth-page { } .boot-indicator { + left: 12px; + right: 12px; top: 14px; - width: calc(100% - 24px); + width: auto; justify-content: center; } @@ -4844,6 +4901,7 @@ body.auth-page { } .release-action-bar, + .release-bottom-action-bar, .release-task-head, .log-viewer-head, .compact-workbench-head, diff --git a/static/app.js b/static/app.js index bee1092..953296a 100644 --- a/static/app.js +++ b/static/app.js @@ -639,6 +639,160 @@ createApp({ } return this.formatDateTime(this.payload.updatedAt); }, + globalHealthTone() { + if (this.error) { + return "bad"; + } + if (this.loading && !this.hasRenderableData) { + return "warn"; + } + if (CURRENT_APP_CONTEXT.mode === "common") { + if (this.commonTool === "mysql" && this.mySqlAdmin.error) { + return "bad"; + } + if (this.commonTool === "cdn" && this.cdnAdmin.error) { + return "bad"; + } + return "neutral"; + } + + const nacosDown = !(this.payload.nacos && this.payload.nacos.ok); + const mongoDown = !(this.payload.mongo && this.payload.mongo.ok); + const serviceDown = Number(this.summary.down || 0) > 0; + const hostMetricDown = Number(this.summary.hostMetricDown || 0) > 0; + const nacosUnhealthy = Number(this.summary.nacosUnhealthyInstanceTotal || 0) > 0; + + if (serviceDown || nacosDown || mongoDown) { + return "bad"; + } + if (hostMetricDown || nacosUnhealthy) { + return "warn"; + } + return "ok"; + }, + globalHealthLabel() { + if (this.error) { + return "全局异常"; + } + if (this.loading && !this.hasRenderableData) { + return "加载中"; + } + if (CURRENT_APP_CONTEXT.mode === "common") { + return this.globalHealthTone === "bad" ? "通用异常" : "通用在线"; + } + if (this.globalHealthTone === "bad") { + return `全局异常 ${this.summary.down || 0}`; + } + if (this.globalHealthTone === "warn") { + return "需要关注"; + } + return "全局正常"; + }, + globalSearchModel: { + get() { + if (["overview", "services", "hosts"].includes(this.activeView)) { + return this.keyword; + } + if (this.activeView === "infra") { + return this.infraKeyword; + } + if (this.activeView === "database") { + return this.databaseKeywordModel; + } + if (this.activeView === "config") { + return this.configTab === "go" ? "" : this.nacosConfig.keyword; + } + if (this.activeView === "release") { + return this.releaseKeyword; + } + if (this.activeView === "common") { + return this.commonTool === "mysql" ? this.mySqlAdmin.keyword : this.cdnAdmin.keyword; + } + return ""; + }, + set(value) { + const nextValue = String(value || ""); + if (["overview", "services", "hosts"].includes(this.activeView)) { + this.keyword = nextValue; + return; + } + if (this.activeView === "infra") { + this.infraKeyword = nextValue; + return; + } + if (this.activeView === "database") { + this.databaseKeywordModel = nextValue; + return; + } + if (this.activeView === "config") { + if (this.configTab !== "go") { + this.nacosConfig.keyword = nextValue; + } + return; + } + if (this.activeView === "release") { + this.releaseKeyword = nextValue; + return; + } + if (this.activeView === "common") { + if (this.commonTool === "mysql") { + this.mySqlAdmin.keyword = nextValue; + } else { + this.cdnAdmin.keyword = nextValue; + } + } + }, + }, + globalSearchPlaceholder() { + if (["overview", "services", "hosts"].includes(this.activeView)) { + return "搜索主机 / 服务 / IP"; + } + if (this.activeView === "infra") { + return this.infraTab === "mongo" ? "搜索数据库 / 指标" : this.infraTab === "tat" ? "搜索主机 / IP / 节点" : "搜索服务 / namespace / IP"; + } + if (this.activeView === "database") { + return this.databaseSearchPlaceholder; + } + if (this.activeView === "config") { + return this.configTab === "history" ? "搜索操作 / 结果 / 时间" : this.configTab === "go" ? "Go Env 当前无搜索" : "搜索 dataId / group / appName"; + } + if (this.activeView === "release") { + return "搜索服务 / 主机 / 镜像"; + } + if (this.activeView === "common") { + return this.commonTool === "mysql" ? "搜索数据库 / 表" : "搜索 taskId / URL / 状态"; + } + return "全局搜索"; + }, + globalSearchDisabled() { + return this.activeView === "config" && this.configTab === "go"; + }, + dataFreshnessLabel() { + const updatedAt = Date.parse(this.payload.updatedAt || ""); + if (Number.isNaN(updatedAt)) { + return "未知"; + } + const seconds = Math.max(0, Math.round((Date.now() - updatedAt) / 1000)); + if (seconds < 60) { + return `${seconds}s`; + } + const minutes = Math.floor(seconds / 60); + if (minutes < 60) { + return `${minutes}m`; + } + return `${Math.floor(minutes / 60)}h`; + }, + dataFreshnessTone() { + const updatedAt = Date.parse(this.payload.updatedAt || ""); + if (Number.isNaN(updatedAt)) { + return "warn"; + } + const seconds = Math.max(0, Math.round((Date.now() - updatedAt) / 1000)); + if (seconds > 180) { + return "warn"; + } + return "neutral"; + }, hasRenderableData() { return ( Boolean(this.payload.updatedAt) diff --git a/static/index.html b/static/index.html index 5deb8cb..4010be9 100644 --- a/static/index.html +++ b/static/index.html @@ -38,6 +38,8 @@
+ {{ globalHealthLabel }} + {{ auth.username }} {{ loading ? '刷新中' : '在线' }} 接口异常 @@ -70,8 +72,6 @@
- - @@ -153,11 +153,6 @@ > {{ infraMongoDetailsOpen ? '收起次要指标' : '展开次要指标' }} - @@ -192,7 +187,6 @@
- @@ -227,7 +221,6 @@
@@ -874,6 +859,10 @@ {{ item.value }}
+
+ 最近 {{ formattedUpdatedAt }} + 延迟 {{ dataFreshnessLabel }} +
@@ -1266,6 +1255,8 @@ 主机 {{ summary.hostTotal || 0 }} 正常 {{ summary.hostMetricOk || 0 }} 异常 {{ summary.hostMetricDown || 0 }} + 最近 {{ formattedUpdatedAt }} + 延迟 {{ dataFreshnessLabel }}
@@ -1642,7 +1633,7 @@
- +
- + @@ -1898,7 +1889,7 @@
- + @@ -2288,7 +2279,7 @@ - + {{ item.idText || '-' }} @@ -2621,7 +2612,7 @@ - + @@ -2736,7 +2727,7 @@
- -
@@ -3064,7 +3060,12 @@ {{ goConfig.validating ? '校验中...' : '校验' }} - @@ -3129,18 +3130,6 @@ 发布工作台 构建参数 → 更新目标 → 执行日志 → 滚动重启 -
- - - - -
@@ -3413,9 +3402,6 @@ {{ restartOps.error ? '存在错误' : restartOps.running ? '执行中' : '可操作' }} -
@@ -3491,6 +3477,30 @@ + +
+
+ {{ updateStatusLabel() }} + 更新 {{ updateOps.selectedServices.length }} / {{ releaseTargetRows.length }} + 重启 {{ restartOps.selectedServices.length }} / {{ restartTargetRows.length }} + + {{ releasePrecheck.ranAt ? releasePrecheck.title : '未预检查' }} + +
+
+ + + + + +
+