diff --git a/.env.example b/.env.example index a6320a5..5e4ceba 100644 --- a/.env.example +++ b/.env.example @@ -52,14 +52,14 @@ UPDATE_DEFAULT_GO_GIT_REF=main UPDATE_DEFAULT_ADMIN_GIT_REF=main JAVA_REPO_ROOT=/opt/deploy/sources/chatapp3-java GOLANG_REPO_ROOT=/opt/deploy/sources/chatapp3-golang -ADMIN_FRONTEND_ENABLED=0 +ADMIN_FRONTEND_ENABLED=1 ADMIN_FRONTEND_SERVICE_NAME=admin ADMIN_FRONTEND_REPO_URL=git@gitea.haiyihy.com:hy/chatapp3-admin.git ADMIN_FRONTEND_REPO_ROOT=/opt/deploy/sources/chatapp3-admin -ADMIN_FRONTEND_HOST= -ADMIN_FRONTEND_IP= -ADMIN_FRONTEND_INSTANCE_ID= -ADMIN_FRONTEND_SSH_HOST= +ADMIN_FRONTEND_HOST=admin-platform +ADMIN_FRONTEND_IP=10.2.1.2 +ADMIN_FRONTEND_INSTANCE_ID=ins-p11begwe +ADMIN_FRONTEND_SSH_HOST=10.2.1.2 ADMIN_FRONTEND_SSH_USER=root ADMIN_FRONTEND_SSH_PORT=22 ADMIN_FRONTEND_SSH_USE_SUDO=0 @@ -70,7 +70,7 @@ ADMIN_FRONTEND_COMPOSE_SERVICE=caddy ADMIN_FRONTEND_RELEASE_META_PATH=/opt/yumi-admin/frontend-admin/.hy-app-monitor-admin-release.json ADMIN_FRONTEND_BUILD_SCRIPT=build:antdv-next ADMIN_FRONTEND_DIST_DIR=apps/dist -ADMIN_FRONTEND_PUBLIC_URL= +ADMIN_FRONTEND_PUBLIC_URL=https://yumi-admin.haiyihy.com MONGO_URI= MONGO_DB_NAME=tarab_all MONGO_INSTANCE_ID=ins-o9mmob66 diff --git a/static/app.css b/static/app.css index 5e6d493..ca1f3cf 100644 --- a/static/app.css +++ b/static/app.css @@ -391,9 +391,9 @@ body.auth-page { .control-bar, .panel { border: 1px solid var(--line); - border-radius: 10px; + border-radius: 8px; background: linear-gradient(180deg, var(--panel-tint) 0%, var(--panel) 100%); - box-shadow: var(--shadow); + box-shadow: none; } .topbar { @@ -401,14 +401,14 @@ body.auth-page { top: 10px; z-index: 20; display: grid; - grid-template-columns: minmax(188px, 250px) minmax(0, max-content) minmax(420px, 1fr); - gap: 10px; + grid-template-columns: minmax(170px, 230px) minmax(0, max-content) minmax(360px, 1fr); + gap: 8px; align-items: center; min-height: 56px; - padding: 9px 14px; + padding: 8px 12px; backdrop-filter: blur(10px); box-shadow: - 0 16px 32px rgba(33, 46, 76, 0.08), + 0 10px 22px rgba(33, 46, 76, 0.05), inset 0 1px 0 color-mix(in oklab, var(--panel-strong) 76%, transparent); } @@ -467,12 +467,52 @@ body.auth-page { font-size: 11px; } -.topbar-nav { - display: flex; - gap: 10px; +.app-switcher { + display: inline-flex; align-items: center; flex-wrap: wrap; - padding: 4px; + gap: 3px; + width: fit-content; + padding: 2px; + border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); + border-radius: 8px; + background: color-mix(in oklab, var(--panel-muted) 94%, var(--panel-strong)); +} + +.app-switch-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 24px; + padding: 0 8px; + border: 1px solid transparent; + border-radius: 6px; + color: var(--muted); + font-size: 10.5px; + font-weight: 700; + text-decoration: none; + white-space: nowrap; + transition: color 180ms ease, border-color 180ms ease, background 180ms ease; +} + +.app-switch-link:hover { + color: var(--text); + border-color: color-mix(in oklab, var(--accent) 20%, var(--line)); + background: color-mix(in oklab, var(--panel-strong) 92%, var(--accent-soft)); +} + +.app-switch-link.active { + border-color: color-mix(in oklab, var(--accent) 38%, var(--line-strong)); + background: color-mix(in oklab, var(--accent-soft) 62%, var(--panel-strong)); + color: var(--accent); +} + +.topbar-nav { + display: flex; + gap: 6px; + align-items: center; + flex-wrap: wrap; + padding: 3px; border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); 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%); @@ -537,7 +577,8 @@ body.auth-page { .topbar-status { justify-content: flex-end; flex-wrap: nowrap; - padding: 4px; + gap: 6px; + padding: 3px; border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); 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%); @@ -551,6 +592,12 @@ body.auth-page { white-space: nowrap; } +.topbar-nav .nav-chip, +.topbar-status .refresh, +.topbar-status .interval-select { + min-height: 30px; +} + .control-bar { position: sticky; top: 72px; @@ -561,9 +608,7 @@ body.auth-page { margin-top: 8px; padding: 8px 14px; backdrop-filter: blur(8px); - box-shadow: - 0 12px 26px rgba(33, 46, 76, 0.06), - 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); } .task-control-bar { @@ -608,6 +653,7 @@ body.auth-page { .section-link { display: inline-flex; align-items: center; + gap: 7px; min-height: 28px; padding: 0 10px; border: 1px solid transparent; @@ -622,6 +668,43 @@ 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)); @@ -651,8 +734,8 @@ body.auth-page { .topbar-search { flex: 1 1 220px; - min-width: min(24vw, 300px); - max-width: 360px; + min-width: min(22vw, 260px); + max-width: 320px; } .search::placeholder { @@ -723,6 +806,7 @@ body.auth-page { .nav-chip:focus-visible, .filter-chip:focus-visible, +.app-switch-link:focus-visible, .refresh:focus-visible, .search:focus-visible, .interval-select:focus-visible, @@ -745,9 +829,7 @@ body.auth-page { position: relative; padding: 14px; overflow: hidden; - box-shadow: - var(--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); } .matrix-panel, @@ -784,11 +866,11 @@ body.auth-page { .summary-strip { margin-top: 8px; padding: 8px 10px; - background: - linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 95%, var(--accent-soft)) 0%, var(--panel) 100%); + background: var(--panel); } .summary-strip .status-band-list { + border-radius: 8px; box-shadow: none; } @@ -1041,6 +1123,20 @@ body.auth-page { background: color-mix(in oklab, var(--ok-soft) 70%, var(--panel-strong)); } +.summary-strip .status-band-item.tone-ok { + background: + linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 99%, var(--accent-soft)) 0%, color-mix(in oklab, var(--panel-muted) 96%, var(--panel-strong)) 100%); +} + +.summary-strip .status-band-item.tone-ok::before { + background: color-mix(in oklab, var(--ok) 28%, var(--line-strong)); + opacity: 0.55; +} + +.summary-strip .status-band-item.tone-ok strong { + color: color-mix(in oklab, var(--text) 74%, var(--ok)); +} + .summary-tile.tone-warn, .matrix-state.tone-warn, .metric-chip.tone-warn, @@ -1179,27 +1275,27 @@ body.auth-page { .matrix-stacks { display: grid; - gap: 12px; + gap: 8px; } .matrix-stack { --group-color: var(--accent); display: grid; - gap: 10px; - padding: 10px 0 0; + gap: 6px; + padding: 6px 0 0; border: 1px solid color-mix(in oklab, var(--line) 92%, var(--panel-strong)); - border-radius: 16px; + border-radius: 8px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-muted) 96%, 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) 76%, transparent); + box-shadow: none; } .matrix-stack::before { content: ""; display: block; - width: 64px; - height: 3px; - border-radius: 999px; + width: 54px; + height: 2px; + border-radius: 0 999px 999px 0; background: linear-gradient(90deg, var(--group-color) 0%, color-mix(in oklab, var(--group-color) 50%, transparent) 100%); } @@ -1208,7 +1304,7 @@ body.auth-page { justify-content: space-between; gap: 10px; align-items: center; - padding: 0 12px; + padding: 0 10px 4px; } .matrix-stack-headline { @@ -1262,18 +1358,20 @@ body.auth-page { } .matrix-table { - min-width: 520px; + width: max-content; + table-layout: fixed; + min-width: 460px; } .matrix-table th, .matrix-table td, .table th, .table td { - padding: 6px 9px; + padding: 5px 8px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: middle; - font-size: 11px; + font-size: 10.5px; } .matrix-table th, @@ -1287,6 +1385,10 @@ body.auth-page { white-space: nowrap; } +.matrix-table th:not(.matrix-sticky-col) { + width: 92px; +} + .table tbody tr:nth-child(even), .matrix-table tbody tr:nth-child(even) { background: color-mix(in oklab, var(--accent-soft) 10%, var(--panel-strong)); @@ -1321,19 +1423,20 @@ body.auth-page { } .matrix-service strong { - font-size: 12px; + font-size: 11.5px; line-height: 1.3; } .matrix-kind { color: var(--muted); - font-size: 11px; + font-size: 10px; } .matrix-state { - min-width: 34px; - height: 24px; - padding: 0 4px; + min-width: 28px; + width: 92px; + height: 22px; + padding: 0 3px; border-left: 1px solid var(--line); text-align: center; vertical-align: middle; @@ -1341,12 +1444,12 @@ body.auth-page { .matrix-state-marker { display: block; - width: 9px; - height: 9px; + width: 7px; + height: 7px; margin: 0 auto; border-radius: 999px; background: currentColor; - box-shadow: 0 0 0 3px color-mix(in oklab, currentColor 12%, transparent); + box-shadow: 0 0 0 2px color-mix(in oklab, currentColor 12%, transparent); } .matrix-state-code { @@ -1366,8 +1469,8 @@ body.auth-page { } .matrix-table.is-detail .matrix-state { - height: 40px; - padding: 4px 5px; + height: 34px; + padding: 3px 4px; } .matrix-table.is-detail .matrix-state-code, @@ -1401,7 +1504,8 @@ body.auth-page { z-index: 3; background: color-mix(in oklab, var(--panel-strong) 94%, var(--panel-muted)); box-shadow: 1px 0 0 var(--line); - min-width: 118px; + min-width: 108px; + width: 136px; } .matrix-table thead .matrix-sticky-col { @@ -2379,6 +2483,7 @@ body.auth-page { .config-workbench-panel { position: relative; + padding: 10px 12px; background: var(--panel); box-shadow: none; } @@ -2388,8 +2493,8 @@ body.auth-page { } .config-head-strip { - margin-bottom: 12px; - padding: 0 0 10px; + margin-bottom: 8px; + padding: 0 0 7px; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; @@ -2401,8 +2506,8 @@ body.auth-page { } .config-inline-strip { - margin-bottom: 10px; - padding: 0 0 8px; + margin-bottom: 8px; + padding: 0 0 6px; border: 0; border-bottom: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); border-radius: 0; @@ -2589,11 +2694,11 @@ body.auth-page { gap: 4px; align-self: start; position: sticky; - top: 116px; + top: 0; max-height: calc(100vh - 128px); padding: 6px; border: 1px solid var(--line); - border-radius: 14px; + border-radius: 8px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-muted) 96%, var(--panel-strong)) 0%, var(--panel-muted) 100%); box-shadow: inset 0 1px 0 color-mix(in oklab, var(--panel-strong) 72%, transparent); } @@ -2601,7 +2706,7 @@ body.auth-page { .config-only-layout > .config-sidebar { gap: 6px; max-height: calc(100vh - 124px); - padding: 0 10px 0 0; + padding: 0 8px 0 0; border: 0; border-right: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); border-radius: 0; @@ -2625,7 +2730,7 @@ body.auth-page { } .config-list-ide { - gap: 4px; + gap: 2px; padding-right: 2px; } @@ -2746,7 +2851,7 @@ body.auth-page { .config-jump-group { display: inline-flex; - gap: 8px; + gap: 6px; align-items: center; } @@ -2754,9 +2859,9 @@ body.auth-page { display: grid; gap: 2px; min-width: 0; - padding: 4px 7px; + padding: 3px 6px; border: 1px solid var(--line); - border-radius: 11px; + border-radius: 7px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-strong) 96%, var(--accent-soft)) 0%, var(--panel-strong) 100%); } @@ -2778,33 +2883,64 @@ body.auth-page { .editor-toolbar { position: relative; - display: flex; - justify-content: space-between; - gap: 12px; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; align-items: center; - padding: 6px; + padding: 5px; border: 1px solid color-mix(in oklab, var(--line) 92%, var(--panel-strong)); - border-radius: 14px; + border-radius: 8px; background: linear-gradient(180deg, color-mix(in oklab, var(--panel-muted) 96%, var(--panel-strong)) 0%, var(--panel-muted) 100%); box-shadow: inset 0 1px 0 color-mix(in oklab, var(--panel-strong) 72%, transparent); } .sticky-toolbar { position: sticky; - top: 124px; + top: 0; z-index: 8; } .editor-toolbar-main { display: flex; - gap: 8px; + gap: 6px; align-items: center; flex-wrap: wrap; } +.editor-toolbar-meta { + grid-column: 1 / -1; + display: flex; + gap: 8px; + flex-wrap: wrap; + padding-top: 4px; + border-top: 1px solid color-mix(in oklab, var(--line) 82%, var(--panel-strong)); + color: var(--muted); + font-size: 9.5px; +} + +.editor-toolbar-meta span { + overflow: hidden; + max-width: 220px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.editor-toolbar .filter-chip, +.editor-toolbar .refresh.ghost { + min-height: 28px; + padding: 0 9px; + border-color: color-mix(in oklab, var(--line) 80%, transparent); + background: transparent; + box-shadow: none; +} + +.editor-toolbar .refresh:not(.ghost) { + min-height: 30px; +} + .diff-panel { border: 1px solid var(--line); - border-radius: 16px; + border-radius: 8px; background: oklch(100% 0 0 / 0.86); overflow: auto; box-shadow: inset 0 1px 0 color-mix(in oklab, var(--panel-strong) 76%, transparent); @@ -2946,7 +3082,7 @@ body.auth-page { .release-status-strip { margin-top: 8px; - padding: 12px; + padding: 10px 12px; background: var(--panel); box-shadow: none; } @@ -2962,7 +3098,7 @@ body.auth-page { justify-content: space-between; gap: 14px; align-items: flex-start; - padding-bottom: 10px; + padding-bottom: 8px; border-bottom: 1px solid var(--line); } @@ -3000,11 +3136,11 @@ body.auth-page { gap: 8px; flex-wrap: wrap; align-items: center; - margin-top: 10px; + margin-top: 8px; } .release-meta-strip { - margin-top: 10px; + margin-top: 8px; align-items: stretch; } @@ -3050,10 +3186,28 @@ body.auth-page { margin-top: 8px; } +.release-command-row { + display: flex; + 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)); +} + +.release-command-actions .refresh { + min-height: 30px; +} + +.release-command-actions .refresh.ghost { + background: transparent; + box-shadow: none; +} + .release-bottom-action-bar { - position: sticky; - bottom: 12px; - z-index: 16; + position: static; + z-index: auto; display: flex; justify-content: space-between; gap: 12px; @@ -3061,11 +3215,9 @@ body.auth-page { 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); + border-radius: 8px; + background: var(--panel-strong); + box-shadow: none; } .release-action-summary.compact { @@ -3074,19 +3226,19 @@ body.auth-page { .release-card-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 10px; - margin-top: 10px; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 7px; + margin-top: 8px; } .release-service-card { --release-kind-color: var(--accent); position: relative; display: grid; - gap: 10px; - padding: 10px 12px; + gap: 6px; + padding: 8px 10px; border: 1px solid var(--line); - border-radius: 12px; + border-radius: 8px; background: var(--panel-strong); cursor: pointer; transition: border-color 180ms ease, background 180ms ease, box-shadow 180ms ease; @@ -3097,8 +3249,8 @@ body.auth-page { content: ""; position: absolute; left: 0; - top: 8px; - bottom: 8px; + top: 7px; + bottom: 7px; width: 3px; border-radius: 999px; background: var(--release-kind-color); @@ -3143,52 +3295,59 @@ body.auth-page { .release-service-head { display: flex; justify-content: space-between; - gap: 12px; + gap: 8px; align-items: flex-start; } .release-service-title { display: grid; - gap: 6px; + gap: 4px; flex: 1 1 auto; min-width: 0; } .release-service-title-line { display: flex; - gap: 8px; - flex-wrap: wrap; + gap: 5px; + flex-wrap: nowrap; align-items: center; } .release-service-title strong { + overflow: hidden; + flex: 1 1 auto; font-size: 13px; line-height: 1.3; + text-overflow: ellipsis; + white-space: nowrap; } .release-service-title-line .badge { + flex: 0 0 auto; min-height: 22px; - padding: 0 8px; - font-size: 10px; + padding: 0 6px; + font-size: 9.5px; } .release-service-meta { - display: flex; - flex-wrap: wrap; - gap: 6px 12px; + display: grid; + gap: 2px; color: var(--muted); - font-size: 11px; - line-height: 1.45; + font-size: 10px; + line-height: 1.25; } .release-service-meta span { + overflow: hidden; min-width: 0; - word-break: break-word; + text-overflow: ellipsis; + white-space: nowrap; + word-break: normal; } .release-service-actions { display: grid; - gap: 6px; + gap: 3px; justify-items: end; align-self: center; } @@ -3198,18 +3357,22 @@ body.auth-page { } .release-service-action-row { - display: flex; + display: none; gap: 8px; align-items: center; justify-content: flex-end; flex-wrap: wrap; } +.release-service-card[data-kind="frontend"] .release-service-action-row { + display: flex; +} + .release-select-state { display: inline-flex; align-items: center; - min-height: 24px; - padding: 0 9px; + min-height: 22px; + padding: 0 7px; border: 1px solid color-mix(in oklab, var(--line) 90%, var(--panel-strong)); border-radius: 999px; background: color-mix(in oklab, var(--panel-muted) 94%, var(--panel-strong)); @@ -3226,8 +3389,7 @@ body.auth-page { } .release-service-note { - color: var(--muted); - margin: 0; + display: none; } .precheck-strip { @@ -3655,8 +3817,8 @@ body.auth-page { .config-layout { display: grid; - grid-template-columns: minmax(208px, 236px) minmax(0, 1fr); - gap: 10px; + grid-template-columns: minmax(176px, 216px) minmax(0, 1fr); + gap: 8px; } .infra-panel > .panel-header { @@ -3719,6 +3881,53 @@ body.auth-page { margin-top: 6px; } +.config-list-ide .config-item-ide { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 6px; + align-items: center; + min-height: 28px; + padding: 4px 6px 4px 8px; + border: 0; + border-radius: 6px; + background: transparent; + box-shadow: none; +} + +.config-list-ide .config-item-ide:hover { + transform: none; + background: color-mix(in oklab, var(--accent-soft) 16%, var(--panel-strong)); +} + +.config-list-ide .config-item-ide.active { + border-color: transparent; + background: color-mix(in oklab, var(--accent-soft) 56%, var(--panel-strong)); + box-shadow: inset 2px 0 0 var(--accent); +} + +.config-list-ide .config-item-ide strong { + overflow: hidden; + color: var(--text); + font-size: 10.5px; + line-height: 1.25; + text-overflow: ellipsis; + white-space: nowrap; + word-break: normal; +} + +.config-list-ide .config-item-ide small { + display: none; +} + +.config-list-ide .config-item-ide span { + margin-top: 0; + color: var(--muted); + font-size: 9px; + line-height: 1; + letter-spacing: 0.04em; + text-transform: uppercase; +} + .config-description { display: flex; gap: 10px; @@ -3728,7 +3937,11 @@ body.auth-page { .config-editor { display: flex; flex-direction: column; - gap: 12px; + gap: 8px; +} + +.config-editor-meta { + margin-bottom: 0; } .config-empty, @@ -3771,10 +3984,10 @@ body.auth-page { .config-textarea { width: 100%; - min-height: clamp(320px, 60vh, 680px); + min-height: clamp(360px, 66vh, 760px); padding: 12px 14px; resize: vertical; - border-radius: 14px; + border-radius: 8px; background: oklch(100% 0 0 / 0.86); box-shadow: inset 0 1px 2px rgba(52, 71, 103, 0.08); font-size: 12px; @@ -4685,11 +4898,30 @@ body.auth-page { } } +@media (max-width: 1280px) { + .topbar { + grid-template-columns: minmax(160px, 220px) minmax(0, 1fr); + } + + .topbar-status { + grid-column: 1 / -1; + justify-content: flex-start; + } + + .topbar-search { + max-width: none; + } +} + @media (max-width: 1040px) { .control-bar { position: static; } + .topbar { + grid-template-columns: 1fr; + } + .topbar-nav, .topbar-status { width: 100%; @@ -4901,6 +5133,7 @@ body.auth-page { } .release-action-bar, + .release-command-row, .release-bottom-action-bar, .release-task-head, .log-viewer-head, diff --git a/static/app.js b/static/app.js index 953296a..902923e 100644 --- a/static/app.js +++ b/static/app.js @@ -6,6 +6,11 @@ const APP_CONTEXTS = [ { id: "common", basePath: "/common", mode: "common", title: "HY App Monitor 通用" }, { id: "mysql", basePath: "/mysql", mode: "common", title: "HY App Monitor 通用" }, ]; +const APP_SWITCH_LINKS = [ + { id: "yumi", label: "Yumi", href: "/yumi/" }, + { id: "haiyi", label: "HaiYi", href: "/haiyi/" }, + { id: "common", label: "通用", href: "/common/" }, +]; const STORAGE_KEY = "hy-app-monitor-refresh-interval"; const UPDATE_OPERATION_STORAGE_KEY = "hy-app-monitor-service-update-operation"; const GROUP_LABELS = { @@ -402,6 +407,8 @@ createApp({ const isCommonMode = CURRENT_APP_CONTEXT.mode === "common"; return { activeView: isCommonMode ? "common" : "overview", + appSwitchLinks: APP_SWITCH_LINKS, + currentAppEntryId: CURRENT_APP_CONTEXT.id === "mysql" ? "common" : CURRENT_APP_CONTEXT.id, databaseEngine: isCommonMode ? "mysql" : "mongo", commonTool: "mysql", densityMode: "compact", @@ -1727,47 +1734,14 @@ createApp({ goConfigDirty() { return this.goConfig.editorContent !== this.goConfig.loadedContent; }, + allReleaseTargetRows() { + return (this.updateOps.items || []) + .map((item) => this.decorateReleaseTargetItem(item)) + .sort((left, right) => left.service.localeCompare(right.service, "zh-CN")); + }, releaseTargetRows() { const keyword = this.normalizedReleaseKeyword; - return (this.updateOps.items || []) - .map((item) => { - const images = Array.isArray(item.currentImages) ? item.currentImages : []; - const primaryImage = images[0] || ""; - const imageMeta = this.imageMeta(primaryImage); - const imageSummary = images.length > 1 - ? `${images.length} 个版本` - : primaryImage || "-"; - const currentLabel = item.currentLabel || (images.length > 1 ? "多版本" : (imageMeta.tag || "-")); - const currentTitle = item.currentTitle || imageSummary; - const detailLabel = item.detailLabel || (images.length > 1 ? `${images.length} 项` : imageMeta.digestShort); - const detailTitle = item.detailTitle || primaryImage || "-"; - const copyValue = item.copyValue || primaryImage || ""; - return { - ...item, - hostCount: (item.hosts || []).length, - hostsLabel: (item.hosts || []).map((host) => host.host).join(" / "), - imageTitle: images.join("\n"), - imageSummary, - currentLabel, - currentTitle, - detailLabel, - detailTitle, - copyValue, - digestFull: imageMeta.digestFull, - repositoryShort: item.repository ? this.truncateMiddle(item.repository, 18, 10) : imageMeta.repositoryShort, - currentImagePrimary: primaryImage, - currentImageCount: images.length, - searchText: [ - item.service, - item.kind, - item.repository, - currentLabel, - detailLabel, - ...(item.hosts || []).flatMap((host) => [host.host, host.ip, host.path, host.port]), - ...images, - ].join(" ").toLowerCase(), - }; - }) + return this.allReleaseTargetRows .filter((item) => { if (this.releaseKindFilter !== "all" && item.kind !== this.releaseKindFilter) { return false; @@ -1776,8 +1750,7 @@ createApp({ return false; } return true; - }) - .sort((left, right) => left.service.localeCompare(right.service, "zh-CN")); + }); }, restartTargetRows() { const keyword = this.normalizedReleaseKeyword; @@ -1816,13 +1789,13 @@ createApp({ }, { label: "最近结果", - value: this.updateStatusLabel(), - tone: this.updateStatusBadgeClass(), + value: this.releasePrimaryStatusLabel(), + tone: this.releasePrimaryStatusTone(), }, { label: "失败阶段", - value: this.updateOps.error ? (this.updateOps.stage || "failed") : "-", - tone: this.updateOps.error ? "bad" : "neutral", + value: this.updateOps.error || this.restartOps.error ? (this.updateOps.stage || "failed") : "-", + tone: this.updateOps.error || this.restartOps.error ? "bad" : "neutral", }, { label: "影响服务", @@ -2446,6 +2419,44 @@ createApp({ digestShort: digestFull ? this.truncateMiddle(digestFull, 10, 6) : "-", }; }, + decorateReleaseTargetItem(item) { + const images = Array.isArray(item.currentImages) ? item.currentImages : []; + const primaryImage = images[0] || ""; + const imageMeta = this.imageMeta(primaryImage); + const imageSummary = images.length > 1 + ? `${images.length} 个版本` + : primaryImage || "-"; + const currentLabel = item.currentLabel || (images.length > 1 ? "多版本" : (imageMeta.tag || "-")); + const currentTitle = item.currentTitle || imageSummary; + const detailLabel = item.detailLabel || (images.length > 1 ? `${images.length} 项` : imageMeta.digestShort); + const detailTitle = item.detailTitle || primaryImage || "-"; + const copyValue = item.copyValue || primaryImage || ""; + return { + ...item, + hostCount: (item.hosts || []).length, + hostsLabel: (item.hosts || []).map((host) => host.host).join(" / "), + imageTitle: images.join("\n"), + imageSummary, + currentLabel, + currentTitle, + detailLabel, + detailTitle, + copyValue, + digestFull: imageMeta.digestFull, + repositoryShort: item.repository ? this.truncateMiddle(item.repository, 18, 10) : imageMeta.repositoryShort, + currentImagePrimary: primaryImage, + currentImageCount: images.length, + searchText: [ + item.service, + item.kind, + item.repository, + currentLabel, + detailLabel, + ...(item.hosts || []).flatMap((host) => [host.host, host.ip, host.path, host.port]), + ...images, + ].join(" ").toLowerCase(), + }; + }, truncateMiddle(value, start = 12, end = 10) { const text = String(value || ""); if (!text) { @@ -3258,6 +3269,83 @@ createApp({ } return "未开始"; }, + releaseIsRunning() { + return Boolean( + this.restartOps.running + || this.updateOps.running + || this.updateOps.status === "queued" + || this.updateOps.status === "running" + ); + }, + releasePrimaryStatusLabel() { + if (this.restartOps.running) { + return "重启执行中"; + } + if (this.updateOps.status === "queued") { + return "更新排队中"; + } + if (this.updateOps.running || this.updateOps.status === "running") { + return "更新执行中"; + } + if (this.restartOps.error) { + return "重启失败"; + } + if (this.updateOps.error || this.updateOps.status === "failed") { + return "更新失败"; + } + if (this.restartOps.result?.ok) { + return "重启已完成"; + } + if (this.updateOps.status === "success") { + return "更新已完成"; + } + return "待操作"; + }, + releasePrimaryStatusTone() { + if (this.updateOps.error || this.restartOps.error || this.updateOps.status === "failed") { + return "bad"; + } + if (this.releaseIsRunning()) { + return "warn"; + } + if (this.updateOps.status === "success" || this.restartOps.result?.ok) { + return "ok"; + } + return "neutral"; + }, + releasePrecheckBadgeLabel() { + if (!this.releasePrecheck.ranAt) { + return "未预检查"; + } + 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) @@ -5974,11 +6062,14 @@ createApp({ } this.updateOps.selectedServices = [...next]; }, - async runServiceUpdate() { - if (this.updateOps.selectedServices.length === 0) { + async runServiceUpdate(serviceNames = this.updateOps.selectedServices) { + const requestedServices = Array.isArray(serviceNames) ? serviceNames : this.updateOps.selectedServices; + const services = [...new Set((requestedServices || []).map((service) => String(service || "").trim()).filter(Boolean))]; + if (services.length === 0) { return; } + this.updateOps.selectedServices = services; this.stopUpdatePolling(); this.updateOps.requestEpoch += 1; this.updateOps.statusRequestInFlight = false; @@ -5995,7 +6086,7 @@ createApp({ this.updateOps.logs = []; this.updateOps.result = null; this.persistUpdateOperationId(); - this.recordReleaseHistory("update", "提交服务更新", this.updateOps.selectedServices.join(", "), "warn"); + this.recordReleaseHistory("update", "提交服务更新", services.join(", "), "warn"); try { const { response, payload } = await this.requestJson("/api/ops/update-services", { @@ -6004,7 +6095,7 @@ createApp({ "Content-Type": "application/json", }, body: JSON.stringify({ - services: this.updateOps.selectedServices, + services, javaGitRef: this.updateOps.javaGitRef, goGitRef: this.updateOps.goGitRef, adminGitRef: this.updateOps.adminGitRef, diff --git a/static/haiyi.html b/static/haiyi.html index ebe82a3..947e8df 100644 --- a/static/haiyi.html +++ b/static/haiyi.html @@ -37,7 +37,9 @@
- 返回入口 + Yumi + 通用 + 选择入口
diff --git a/static/index.html b/static/index.html index 4010be9..f6de37b 100644 --- a/static/index.html +++ b/static/index.html @@ -24,6 +24,16 @@ DEPLOY

最后刷新 {{ formattedUpdatedAt }}

+