feat: update dashboard and release UI
This commit is contained in:
parent
e1519a77f4
commit
39ebdb4178
@ -34,6 +34,10 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -44,9 +48,7 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: "IBM Plex Sans", "PingFang SC", "Noto Sans SC", sans-serif;
|
font-family: "IBM Plex Sans", "PingFang SC", "Noto Sans SC", sans-serif;
|
||||||
background:
|
background: linear-gradient(180deg, var(--bg-soft) 0%, var(--bg) 100%);
|
||||||
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%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body::before {
|
body::before {
|
||||||
@ -71,17 +73,16 @@ body::before {
|
|||||||
|
|
||||||
.boot-indicator {
|
.boot-indicator {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 50%;
|
right: 24px;
|
||||||
top: 22px;
|
top: 18px;
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
transform: translateX(-50%);
|
|
||||||
border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong));
|
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%);
|
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);
|
box-shadow: 0 16px 32px rgba(33, 46, 76, 0.1);
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@ -390,7 +391,7 @@ body.auth-page {
|
|||||||
.control-bar,
|
.control-bar,
|
||||||
.panel {
|
.panel {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 18px;
|
border-radius: 10px;
|
||||||
background: linear-gradient(180deg, var(--panel-tint) 0%, var(--panel) 100%);
|
background: linear-gradient(180deg, var(--panel-tint) 0%, var(--panel) 100%);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
@ -400,8 +401,8 @@ body.auth-page {
|
|||||||
top: 10px;
|
top: 10px;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(200px, 260px) minmax(0, 1fr) auto;
|
grid-template-columns: minmax(188px, 250px) minmax(0, max-content) minmax(420px, 1fr);
|
||||||
gap: 14px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
padding: 9px 14px;
|
padding: 9px 14px;
|
||||||
@ -452,7 +453,7 @@ body.auth-page {
|
|||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
padding: 0 9px;
|
padding: 0 9px;
|
||||||
border: 1px solid color-mix(in oklab, var(--accent) 34%, var(--line-strong));
|
border: 1px solid color-mix(in oklab, var(--accent) 34%, var(--line-strong));
|
||||||
border-radius: 999px;
|
border-radius: 7px;
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -473,7 +474,7 @@ body.auth-page {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong));
|
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%);
|
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);
|
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;
|
justify-content: center;
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
padding: 0 12px;
|
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%);
|
background: linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 98%, var(--panel-muted)) 0%, var(--panel-strong) 100%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -535,13 +536,21 @@ body.auth-page {
|
|||||||
|
|
||||||
.topbar-status {
|
.topbar-status {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
flex-wrap: nowrap;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong));
|
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%);
|
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);
|
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 {
|
.control-bar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 72px;
|
top: 72px;
|
||||||
@ -636,15 +645,26 @@ body.auth-page {
|
|||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
padding: 0 11px;
|
padding: 0 11px;
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
background: color-mix(in oklab, var(--panel-muted) 88%, var(--bg-soft));
|
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 {
|
.search::placeholder {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
opacity: 0.88;
|
opacity: 0.88;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search:disabled {
|
||||||
|
opacity: 0.58;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.interval-field {
|
.interval-field {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -657,7 +677,7 @@ body.auth-page {
|
|||||||
min-width: 82px;
|
min-width: 82px;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 11px;
|
padding: 0 11px;
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
background: var(--panel-strong);
|
background: var(--panel-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -959,7 +979,7 @@ body.auth-page {
|
|||||||
|
|
||||||
.status-band-item.tone-ok {
|
.status-band-item.tone-ok {
|
||||||
background:
|
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 {
|
.status-band-item.tone-warn {
|
||||||
@ -2458,6 +2478,17 @@ body.auth-page {
|
|||||||
font-size: 11px;
|
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 {
|
.infra-inline-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@ -3019,6 +3050,28 @@ body.auth-page {
|
|||||||
margin-top: 8px;
|
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 {
|
.release-card-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
@ -4527,6 +4580,7 @@ body.auth-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.badge.warn,
|
.badge.warn,
|
||||||
|
.warn-text,
|
||||||
.tone-warn {
|
.tone-warn {
|
||||||
color: var(--warn);
|
color: var(--warn);
|
||||||
}
|
}
|
||||||
@ -4640,6 +4694,7 @@ body.auth-page {
|
|||||||
.topbar-status {
|
.topbar-status {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-panel {
|
.detail-panel {
|
||||||
@ -4722,8 +4777,10 @@ body.auth-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.boot-indicator {
|
.boot-indicator {
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
width: calc(100% - 24px);
|
width: auto;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4844,6 +4901,7 @@ body.auth-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.release-action-bar,
|
.release-action-bar,
|
||||||
|
.release-bottom-action-bar,
|
||||||
.release-task-head,
|
.release-task-head,
|
||||||
.log-viewer-head,
|
.log-viewer-head,
|
||||||
.compact-workbench-head,
|
.compact-workbench-head,
|
||||||
|
|||||||
154
static/app.js
154
static/app.js
@ -639,6 +639,160 @@ createApp({
|
|||||||
}
|
}
|
||||||
return this.formatDateTime(this.payload.updatedAt);
|
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() {
|
hasRenderableData() {
|
||||||
return (
|
return (
|
||||||
Boolean(this.payload.updatedAt)
|
Boolean(this.payload.updatedAt)
|
||||||
|
|||||||
@ -38,6 +38,8 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="topbar-status">
|
<div class="topbar-status">
|
||||||
|
<span :class="['badge', globalHealthTone]">{{ globalHealthLabel }}</span>
|
||||||
|
<input v-model.trim="globalSearchModel" class="search topbar-search" :placeholder="globalSearchPlaceholder" :disabled="globalSearchDisabled" />
|
||||||
<span v-if="auth.username" class="badge neutral mono">{{ auth.username }}</span>
|
<span v-if="auth.username" class="badge neutral mono">{{ auth.username }}</span>
|
||||||
<span :class="['badge', loading ? 'warn' : 'neutral']">{{ loading ? '刷新中' : '在线' }}</span>
|
<span :class="['badge', loading ? 'warn' : 'neutral']">{{ loading ? '刷新中' : '在线' }}</span>
|
||||||
<span v-if="error" class="badge bad">接口异常</span>
|
<span v-if="error" class="badge bad">接口异常</span>
|
||||||
@ -70,8 +72,6 @@
|
|||||||
<button :class="['filter-chip', densityMode === 'compact' ? 'active' : '']" @click="densityMode = 'compact'">紧凑</button>
|
<button :class="['filter-chip', densityMode === 'compact' ? 'active' : '']" @click="densityMode = 'compact'">紧凑</button>
|
||||||
<button :class="['filter-chip', densityMode === 'detail' ? 'active' : '']" @click="densityMode = 'detail'">详情</button>
|
<button :class="['filter-chip', densityMode === 'detail' ? 'active' : '']" @click="densityMode = 'detail'">详情</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input v-model.trim="keyword" class="search" placeholder="搜索主机 / 服务 / IP" />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@ -153,11 +153,6 @@
|
|||||||
>
|
>
|
||||||
{{ infraMongoDetailsOpen ? '收起次要指标' : '展开次要指标' }}
|
{{ infraMongoDetailsOpen ? '收起次要指标' : '展开次要指标' }}
|
||||||
</button>
|
</button>
|
||||||
<input
|
|
||||||
v-model.trim="infraKeyword"
|
|
||||||
class="search"
|
|
||||||
:placeholder="infraTab === 'mongo' ? '搜索数据库 / 指标' : infraTab === 'tat' ? '搜索主机 / IP / 节点' : '搜索服务 / namespace / IP'"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@ -192,7 +187,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group control-group-spread">
|
<div class="control-group control-group-spread">
|
||||||
<input v-model.trim="databaseKeywordModel" class="search" :placeholder="databaseSearchPlaceholder" />
|
|
||||||
<button class="refresh ghost" @click="refreshDatabaseTree" :disabled="databaseTreeLoading">
|
<button class="refresh ghost" @click="refreshDatabaseTree" :disabled="databaseTreeLoading">
|
||||||
{{ databaseTreeLoading ? '刷新中...' : '刷新树' }}
|
{{ databaseTreeLoading ? '刷新中...' : '刷新树' }}
|
||||||
</button>
|
</button>
|
||||||
@ -227,7 +221,6 @@
|
|||||||
|
|
||||||
<div class="control-group control-group-spread">
|
<div class="control-group control-group-spread">
|
||||||
<template v-if="commonTool === 'mysql'">
|
<template v-if="commonTool === 'mysql'">
|
||||||
<input v-model.trim="mySqlAdmin.keyword" class="search" placeholder="搜索数据库 / 表" />
|
|
||||||
<button class="refresh ghost" @click="refreshMySqlDatabases({ preserveSelection: true })" :disabled="mySqlAdmin.loading">
|
<button class="refresh ghost" @click="refreshMySqlDatabases({ preserveSelection: true })" :disabled="mySqlAdmin.loading">
|
||||||
{{ mySqlAdmin.loading ? '刷新中...' : '刷新树' }}
|
{{ mySqlAdmin.loading ? '刷新中...' : '刷新树' }}
|
||||||
</button>
|
</button>
|
||||||
@ -240,7 +233,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input v-model.trim="cdnAdmin.keyword" class="search" placeholder="搜索 taskId / URL / 状态" />
|
|
||||||
<button class="refresh ghost" @click="refreshCdnOverview()" :disabled="cdnAdmin.loading">
|
<button class="refresh ghost" @click="refreshCdnOverview()" :disabled="cdnAdmin.loading">
|
||||||
{{ cdnAdmin.loading ? '刷新中...' : '刷新记录' }}
|
{{ cdnAdmin.loading ? '刷新中...' : '刷新记录' }}
|
||||||
</button>
|
</button>
|
||||||
@ -271,13 +263,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group control-group-spread">
|
<div class="control-group control-group-spread">
|
||||||
<input
|
<div v-if="configTab === 'go'" class="taskbar-inline">
|
||||||
v-if="configTab !== 'go'"
|
|
||||||
v-model.trim="nacosConfig.keyword"
|
|
||||||
class="search"
|
|
||||||
:placeholder="configTab === 'history' ? '搜索操作 / 结果 / 时间' : '搜索 dataId / group / appName'"
|
|
||||||
/>
|
|
||||||
<div v-else class="taskbar-inline">
|
|
||||||
<span class="badge neutral">{{ goConfig.hosts.length }} 台主机</span>
|
<span class="badge neutral">{{ goConfig.hosts.length }} 台主机</span>
|
||||||
<span :class="['badge', goConfig.diffKeys.length > 0 ? 'warn' : 'neutral']">{{ goConfig.diffKeys.length }} 差异 key</span>
|
<span :class="['badge', goConfig.diffKeys.length > 0 ? 'warn' : 'neutral']">{{ goConfig.diffKeys.length }} 差异 key</span>
|
||||||
</div>
|
</div>
|
||||||
@ -337,7 +323,6 @@
|
|||||||
<option value="frontend">前端</option>
|
<option value="frontend">前端</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<input v-model.trim="releaseKeyword" class="search" placeholder="搜索服务 / 主机 / 镜像" />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@ -874,6 +859,10 @@
|
|||||||
<strong>{{ item.value }}</strong>
|
<strong>{{ item.value }}</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="infra-freshness">
|
||||||
|
<span>最近 {{ formattedUpdatedAt }}</span>
|
||||||
|
<span :class="dataFreshnessTone === 'warn' ? 'warn-text' : ''">延迟 {{ dataFreshnessLabel }}</span>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -1266,6 +1255,8 @@
|
|||||||
<span>主机 {{ summary.hostTotal || 0 }}</span>
|
<span>主机 {{ summary.hostTotal || 0 }}</span>
|
||||||
<span>正常 {{ summary.hostMetricOk || 0 }}</span>
|
<span>正常 {{ summary.hostMetricOk || 0 }}</span>
|
||||||
<span :class="(summary.hostMetricDown || 0) > 0 ? 'bad-text' : 'ok-text'">异常 {{ summary.hostMetricDown || 0 }}</span>
|
<span :class="(summary.hostMetricDown || 0) > 0 ? 'bad-text' : 'ok-text'">异常 {{ summary.hostMetricDown || 0 }}</span>
|
||||||
|
<span>最近 {{ formattedUpdatedAt }}</span>
|
||||||
|
<span :class="dataFreshnessTone === 'warn' ? 'warn-text' : ''">延迟 {{ dataFreshnessLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
@ -1642,7 +1633,7 @@
|
|||||||
<div class="action-group action-group-tight">
|
<div class="action-group action-group-tight">
|
||||||
<button class="refresh ghost inline-button" @click="loadMySqlRowForEdit(row)">编辑</button>
|
<button class="refresh ghost inline-button" @click="loadMySqlRowForEdit(row)">编辑</button>
|
||||||
<button class="refresh ghost inline-button" @click="copyMySqlRowToInsert(row)">复制</button>
|
<button class="refresh ghost inline-button" @click="copyMySqlRowToInsert(row)">复制</button>
|
||||||
<button class="refresh ghost inline-button" @click="deleteMySqlRow(row)">删除</button>
|
<button class="refresh danger inline-button" @click="deleteMySqlRow(row)">删除</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
@ -1757,7 +1748,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="refresh ghost inline-button" @click="dropMySqlColumn(column.name)" :disabled="mySqlAdmin.columnForm.saving">删除</button>
|
<button class="refresh danger inline-button" @click="dropMySqlColumn(column.name)" :disabled="mySqlAdmin.columnForm.saving">删除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="mySqlAdmin.schema.columns.length === 0">
|
<tr v-if="mySqlAdmin.schema.columns.length === 0">
|
||||||
@ -1898,7 +1889,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="refresh ghost inline-button" @click="dropMySqlIndex(index.name)" :disabled="mySqlAdmin.indexForm.saving">删除</button>
|
<button class="refresh danger inline-button" @click="dropMySqlIndex(index.name)" :disabled="mySqlAdmin.indexForm.saving">删除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="mySqlAdmin.schema.indexes.length === 0">
|
<tr v-if="mySqlAdmin.schema.indexes.length === 0">
|
||||||
@ -2288,7 +2279,7 @@
|
|||||||
<button class="refresh ghost inline-button" @click="copyText(item.jsonText, 'mongo-doc-' + item.key)">
|
<button class="refresh ghost inline-button" @click="copyText(item.jsonText, 'mongo-doc-' + item.key)">
|
||||||
{{ isCopied('mongo-doc-' + item.key) ? '已复制' : '复制' }}
|
{{ isCopied('mongo-doc-' + item.key) ? '已复制' : '复制' }}
|
||||||
</button>
|
</button>
|
||||||
<button class="refresh ghost inline-button" @click="deleteMongoDocument(item)">删除</button>
|
<button class="refresh danger inline-button" @click="deleteMongoDocument(item)">删除</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="mono">{{ item.idText || '-' }}</td>
|
<td class="mono">{{ item.idText || '-' }}</td>
|
||||||
@ -2621,7 +2612,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="refresh ghost inline-button" @click="dropMongoIndex(index.name)" :disabled="mongoAdmin.indexForm.saving || index.name === '_id_'">删除</button>
|
<button class="refresh danger inline-button" @click="dropMongoIndex(index.name)" :disabled="mongoAdmin.indexForm.saving || index.name === '_id_'">删除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="mongoAdmin.collectionDetail.indexes.length === 0">
|
<tr v-if="mongoAdmin.collectionDetail.indexes.length === 0">
|
||||||
@ -2736,7 +2727,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="action-group action-group-stack">
|
<div class="action-group action-group-stack">
|
||||||
<button class="refresh ghost" @click="dropMongoSecondaryIndexes" :disabled="mongoAdmin.indexForm.dropAllSaving">
|
<button class="refresh danger" @click="dropMongoSecondaryIndexes" :disabled="mongoAdmin.indexForm.dropAllSaving">
|
||||||
{{ mongoAdmin.indexForm.dropAllSaving ? '执行中...' : '删除二级索引' }}
|
{{ mongoAdmin.indexForm.dropAllSaving ? '执行中...' : '删除二级索引' }}
|
||||||
</button>
|
</button>
|
||||||
<button class="refresh danger" @click="dropMongoCollection" :disabled="mongoAdmin.collectionOps.dropRunning">
|
<button class="refresh danger" @click="dropMongoCollection" :disabled="mongoAdmin.collectionOps.dropRunning">
|
||||||
@ -2980,7 +2971,12 @@
|
|||||||
{{ nacosConfig.validating ? '校验中...' : '校验' }}
|
{{ nacosConfig.validating ? '校验中...' : '校验' }}
|
||||||
</button>
|
</button>
|
||||||
<button class="refresh ghost" @click="restoreNacosContent" :disabled="!nacosConfigDirty || nacosConfig.saving || nacosConfig.detailLoading">恢复</button>
|
<button class="refresh ghost" @click="restoreNacosContent" :disabled="!nacosConfigDirty || nacosConfig.saving || nacosConfig.detailLoading">恢复</button>
|
||||||
<button class="refresh" @click="saveNacosConfig" :disabled="nacosConfig.saving || nacosConfig.detailLoading">
|
<button
|
||||||
|
class="refresh"
|
||||||
|
@click="saveNacosConfig"
|
||||||
|
:disabled="nacosConfig.saving || nacosConfig.detailLoading || (nacosConfigDirty && configEditorMode !== 'diff')"
|
||||||
|
:title="nacosConfigDirty && configEditorMode !== 'diff' ? '保存前先切到 Diff' : ''"
|
||||||
|
>
|
||||||
{{ nacosConfig.saving ? '保存中...' : '保存' }}
|
{{ nacosConfig.saving ? '保存中...' : '保存' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -3064,7 +3060,12 @@
|
|||||||
{{ goConfig.validating ? '校验中...' : '校验' }}
|
{{ goConfig.validating ? '校验中...' : '校验' }}
|
||||||
</button>
|
</button>
|
||||||
<button class="refresh ghost" @click="restoreGoContent" :disabled="!goConfigDirty || goConfig.loading || goConfig.saving">恢复</button>
|
<button class="refresh ghost" @click="restoreGoContent" :disabled="!goConfigDirty || goConfig.loading || goConfig.saving">恢复</button>
|
||||||
<button class="refresh" @click="saveGoConfig" :disabled="goConfig.saving || goConfig.loading">
|
<button
|
||||||
|
class="refresh"
|
||||||
|
@click="saveGoConfig"
|
||||||
|
:disabled="goConfig.saving || goConfig.loading || (goConfigDirty && goEditorMode !== 'diff')"
|
||||||
|
:title="goConfigDirty && goEditorMode !== 'diff' ? '保存前先切到 Diff' : ''"
|
||||||
|
>
|
||||||
{{ goConfig.saving ? '保存中...' : '保存' }}
|
{{ goConfig.saving ? '保存中...' : '保存' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -3129,18 +3130,6 @@
|
|||||||
<strong>发布工作台</strong>
|
<strong>发布工作台</strong>
|
||||||
<span>构建参数 → 更新目标 → 执行日志 → 滚动重启</span>
|
<span>构建参数 → 更新目标 → 执行日志 → 滚动重启</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-group">
|
|
||||||
<button class="refresh ghost" @click="refreshReleaseTargets" :disabled="updateOps.loading || restartOps.loading || updateOps.running || restartOps.running">
|
|
||||||
{{ updateOps.loading || restartOps.loading ? '刷新中...' : '刷新目标' }}
|
|
||||||
</button>
|
|
||||||
<button class="refresh ghost" @click="runReleasePrecheck" :disabled="updateOps.running || restartOps.running">预检查</button>
|
|
||||||
<button class="refresh ghost" @click="openUpdateLogViewer" :disabled="!updateOps.operationId && !updateOps.logs.length">
|
|
||||||
查看日志
|
|
||||||
</button>
|
|
||||||
<button class="refresh danger" @click="runServiceUpdate" :disabled="updateOps.running || updateOps.selectedServices.length === 0">
|
|
||||||
{{ updateOps.running ? '更新中...' : `执行更新 ${updateOps.selectedServices.length}` }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="release-action-summary">
|
<div class="release-action-summary">
|
||||||
@ -3413,9 +3402,6 @@
|
|||||||
<span :class="['badge', restartOps.error ? 'bad' : restartOps.running ? 'warn' : 'ok']">
|
<span :class="['badge', restartOps.error ? 'bad' : restartOps.running ? 'warn' : 'ok']">
|
||||||
{{ restartOps.error ? '存在错误' : restartOps.running ? '执行中' : '可操作' }}
|
{{ restartOps.error ? '存在错误' : restartOps.running ? '执行中' : '可操作' }}
|
||||||
</span>
|
</span>
|
||||||
<button class="refresh danger" @click="runRollingRestart" :disabled="restartOps.running || restartOps.selectedServices.length === 0">
|
|
||||||
{{ restartOps.running ? '重启中...' : `执行重启 ${restartOps.selectedServices.length}` }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -3491,6 +3477,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="release-bottom-action-bar" aria-label="发布操作">
|
||||||
|
<div class="release-action-summary compact">
|
||||||
|
<span :class="['badge', updateStatusBadgeClass()]">{{ updateStatusLabel() }}</span>
|
||||||
|
<span class="badge neutral">更新 {{ updateOps.selectedServices.length }} / {{ releaseTargetRows.length }}</span>
|
||||||
|
<span class="badge neutral">重启 {{ restartOps.selectedServices.length }} / {{ restartTargetRows.length }}</span>
|
||||||
|
<span :class="['badge', releasePrecheck.ok ? 'ok' : releasePrecheck.ranAt ? 'warn' : 'neutral']">
|
||||||
|
{{ releasePrecheck.ranAt ? releasePrecheck.title : '未预检查' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="action-group">
|
||||||
|
<button class="refresh ghost" @click="refreshReleaseTargets" :disabled="updateOps.loading || restartOps.loading || updateOps.running || restartOps.running">
|
||||||
|
{{ updateOps.loading || restartOps.loading ? '刷新中...' : '刷新目标' }}
|
||||||
|
</button>
|
||||||
|
<button class="refresh ghost" @click="runReleasePrecheck" :disabled="updateOps.running || restartOps.running">预检查</button>
|
||||||
|
<button class="refresh ghost" @click="openUpdateLogViewer" :disabled="!updateOps.operationId && !updateOps.logs.length">查看日志</button>
|
||||||
|
<button class="refresh danger" @click="runServiceUpdate" :disabled="updateOps.running || updateOps.selectedServices.length === 0">
|
||||||
|
{{ updateOps.running ? '更新中...' : `执行更新 ${updateOps.selectedServices.length}` }}
|
||||||
|
</button>
|
||||||
|
<button class="refresh danger" @click="runRollingRestart" :disabled="restartOps.running || restartOps.selectedServices.length === 0">
|
||||||
|
{{ restartOps.running ? '重启中...' : `执行重启 ${restartOps.selectedServices.length}` }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="logViewer.open" class="log-viewer-overlay" @click.self="closeLogViewer">
|
<div v-if="logViewer.open" class="log-viewer-overlay" @click.self="closeLogViewer">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user