This commit is contained in:
@@ -111,9 +111,9 @@
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>历史更新记录</h2>
|
||||
<p>按版本汇总获取、下载、重启安装和最终执行结果。</p>
|
||||
<p>按版本展示签名 Release 提供的更新日志与最终结果。</p>
|
||||
</div>
|
||||
<span>最近 {{ historyTimeline.length }} 个版本 · {{ historyRows.length }} 条记录</span>
|
||||
<span>已记录 {{ historyTimeline.length }} 个版本</span>
|
||||
</div>
|
||||
|
||||
<t-alert v-if="historyError" theme="error" :close-btn="false">
|
||||
@@ -146,37 +146,19 @@
|
||||
<t-tag :theme="resultTheme(item.resultCode)" variant="light">{{ resultLabel(item.resultCode) }}</t-tag>
|
||||
</div>
|
||||
|
||||
<div class="timeline-card__summary">
|
||||
<span class="timeline-card__label">执行流程</span>
|
||||
<span>{{ item.operations.join(' · ') }}</span>
|
||||
<span class="timeline-card__count">{{ item.steps.length }} 条记录</span>
|
||||
<div class="timeline-card__notes">
|
||||
<span>更新日志</span>
|
||||
<p>{{ item.releaseNotes || '该版本的签名 Release 未提供更新日志。' }}</p>
|
||||
</div>
|
||||
<p class="timeline-card__reason">{{ item.reason || '已记录本次版本更新操作。' }}</p>
|
||||
|
||||
<div v-if="item.releaseNotes" class="timeline-card__notes">
|
||||
<span>版本说明</span>
|
||||
<p>{{ item.releaseNotes }}</p>
|
||||
</div>
|
||||
|
||||
<div class="timeline-steps" aria-label="更新步骤">
|
||||
<div v-for="step in item.steps" :key="step.row.publicId" class="timeline-step">
|
||||
<span class="timeline-step__dot" :class="`timeline-step__dot--${resultTheme(step.row.resultCode)}`" />
|
||||
<div class="timeline-step__body">
|
||||
<div class="timeline-step__heading">
|
||||
<strong>{{ actionLabel(step.row.actionCode) }}</strong>
|
||||
<t-tag size="small" :theme="resultTheme(step.row.resultCode)" variant="light">
|
||||
{{ resultLabel(step.row.resultCode) }}
|
||||
</t-tag>
|
||||
<time>{{ formatDateTime(step.row.occurredAt) }}</time>
|
||||
</div>
|
||||
<p>{{ step.row.reason || '操作已记录' }} · {{ step.row.username || '系统' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="timeline-card__reason">{{ item.reason || '更新结果已记录。' }}</p>
|
||||
<div class="timeline-card__published">
|
||||
<span v-if="item.publishedAt">发布时间:{{ formatDateTime(item.publishedAt) }}</span>
|
||||
<span>记录时间:{{ formatDateTime(item.occurredAt) }} · {{ item.username || '系统' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="timeline-card__footer">
|
||||
<span>最后更新:{{ formatDateTime(item.occurredAt) }}</span>
|
||||
<t-button variant="text" size="small" @click="openHistoryDetail(item)">查看完整记录</t-button>
|
||||
<span>目标版本 {{ item.version }}</span>
|
||||
<t-button variant="text" size="small" @click="openHistoryDetail(item)">查看更新日志</t-button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -283,23 +265,17 @@
|
||||
{{ resultLabel(selectedHistory.resultCode) }}
|
||||
</t-tag>
|
||||
</div>
|
||||
<p class="history-detail__reason">{{ selectedHistory.reason || '已记录本次版本更新操作。' }}</p>
|
||||
<div v-if="selectedHistory.releaseNotes" class="history-detail__notes">
|
||||
<span>版本说明</span>
|
||||
<p>{{ selectedHistory.releaseNotes }}</p>
|
||||
<div class="history-detail__metadata">
|
||||
<span v-if="selectedHistory.publishedAt">发布时间:{{ formatDateTime(selectedHistory.publishedAt) }}</span>
|
||||
<span
|
||||
>记录时间:{{ formatDateTime(selectedHistory.occurredAt) }} · {{ selectedHistory.username || '系统' }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="history-detail__steps" aria-label="更新日志">
|
||||
<div v-for="step in selectedHistory.steps" :key="step.row.publicId" class="history-detail__step">
|
||||
<div>
|
||||
<strong>{{ actionLabel(step.row.actionCode) }}</strong>
|
||||
<t-tag size="small" :theme="resultTheme(step.row.resultCode)" variant="light">
|
||||
{{ resultLabel(step.row.resultCode) }}
|
||||
</t-tag>
|
||||
</div>
|
||||
<time>{{ formatDateTime(step.row.occurredAt) }} · {{ step.row.username || '系统' }}</time>
|
||||
<p>{{ step.row.reason || '操作已记录' }}</p>
|
||||
</div>
|
||||
<div class="history-detail__notes">
|
||||
<span>更新日志</span>
|
||||
<p>{{ selectedHistory.releaseNotes || '该版本的签名 Release 未提供更新日志。' }}</p>
|
||||
</div>
|
||||
<p class="history-detail__reason">{{ selectedHistory.reason || '更新结果已记录。' }}</p>
|
||||
</div>
|
||||
</t-dialog>
|
||||
</div>
|
||||
@@ -322,10 +298,6 @@ import {
|
||||
defineOptions({ name: 'SystemUpdatePage' });
|
||||
|
||||
type TagTheme = 'default' | 'primary' | 'success' | 'warning' | 'danger';
|
||||
interface HistoryTimelineStep {
|
||||
row: AuditLog;
|
||||
}
|
||||
|
||||
interface HistoryTimelineItem {
|
||||
key: string;
|
||||
version: string;
|
||||
@@ -333,9 +305,8 @@ interface HistoryTimelineItem {
|
||||
username: string;
|
||||
resultCode: string;
|
||||
reason: string;
|
||||
operations: string[];
|
||||
releaseNotes: string;
|
||||
steps: HistoryTimelineStep[];
|
||||
publishedAt: string;
|
||||
}
|
||||
|
||||
const UPDATE_PENDING_KEY = 'kaidi-system-update-pending';
|
||||
@@ -498,9 +469,8 @@ const historyTimeline = computed<HistoryTimelineItem[]>(() => {
|
||||
const newestFirst = rows.slice().sort((left, right) => compareHistoryDates(right.occurredAt, left.occurredAt));
|
||||
const terminal = newestFirst.find((row) => isTerminalHistoryAction(row.actionCode));
|
||||
const representative = terminal || newestFirst[0];
|
||||
const chronological = newestFirst.slice().reverse();
|
||||
const operations = [...new Set(chronological.map((row) => actionLabel(row.actionCode)).filter(Boolean))];
|
||||
const releaseNotes = newestFirst.map((row) => historyReleaseNotes(row)).find(Boolean) || '';
|
||||
const publishedAt = newestFirst.map((row) => historyPublishedAt(row)).find(Boolean) || '';
|
||||
|
||||
return {
|
||||
key,
|
||||
@@ -509,9 +479,8 @@ const historyTimeline = computed<HistoryTimelineItem[]>(() => {
|
||||
username: representative?.username || '',
|
||||
resultCode: representative?.resultCode || 'UNKNOWN',
|
||||
reason: representative?.reason || '',
|
||||
operations,
|
||||
releaseNotes,
|
||||
steps: chronological.map((row) => ({ row })),
|
||||
publishedAt,
|
||||
};
|
||||
})
|
||||
.sort((left, right) => compareHistoryDates(right.occurredAt, left.occurredAt));
|
||||
@@ -571,7 +540,7 @@ async function loadHistory(silent = false) {
|
||||
objectType: 'SYSTEM_UPDATE',
|
||||
sort: 'occurredAt,desc',
|
||||
page: 1,
|
||||
size: 20,
|
||||
size: 100,
|
||||
});
|
||||
historyRows.value = result.items;
|
||||
} catch (error) {
|
||||
@@ -859,7 +828,24 @@ function historyVersion(row: AuditLog) {
|
||||
function historyReleaseNotes(row: AuditLog) {
|
||||
for (const source of [row.afterJson, row.beforeJson]) {
|
||||
const parsed = parseHistoryPayload(source);
|
||||
if (typeof parsed?.releaseNotes === 'string' && parsed.releaseNotes.trim()) return parsed.releaseNotes;
|
||||
for (const key of ['releaseNotes', 'changelog', 'updateLog', 'notes']) {
|
||||
const value = parsed?.[key];
|
||||
if (typeof value === 'string' && value.trim()) return value.trim();
|
||||
if (Array.isArray(value)) {
|
||||
const lines = value
|
||||
.filter((item): item is string => typeof item === 'string' && Boolean(item.trim()))
|
||||
.map((item) => item.trim());
|
||||
if (lines.length) return lines.join('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function historyPublishedAt(row: AuditLog) {
|
||||
for (const source of [row.afterJson, row.beforeJson]) {
|
||||
const parsed = parseHistoryPayload(source);
|
||||
if (typeof parsed?.publishedAt === 'string' && parsed.publishedAt.trim()) return parsed.publishedAt;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -869,19 +855,6 @@ function openHistoryDetail(item: HistoryTimelineItem) {
|
||||
historyDetailVisible.value = true;
|
||||
}
|
||||
|
||||
function actionLabel(action: string) {
|
||||
return (
|
||||
{
|
||||
SYSTEM_UPDATE_CHECK: '获取版本',
|
||||
SYSTEM_UPDATE_DOWNLOAD_REQUEST: '下载更新包',
|
||||
SYSTEM_UPDATE_REQUEST: '立即更新并重启',
|
||||
SYSTEM_UPDATE_SUCCEEDED: '更新完成',
|
||||
SYSTEM_UPDATE_FAILED: '更新失败',
|
||||
SYSTEM_UPDATE_RECOVERY_REQUIRED: '更新需人工恢复',
|
||||
}[action] || action
|
||||
);
|
||||
}
|
||||
|
||||
function resultLabel(result: string) {
|
||||
return { SUCCESS: '成功', FAILED: '失败', DENIED: '已拒绝', BLOCKED: '已阻断' }[result] || result;
|
||||
}
|
||||
@@ -1155,9 +1128,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.timeline-card__header,
|
||||
.timeline-card__summary,
|
||||
.timeline-card__footer,
|
||||
.timeline-step__heading,
|
||||
.history-detail__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1188,35 +1159,13 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.timeline-card__identity span,
|
||||
.timeline-card__count,
|
||||
.timeline-card__footer,
|
||||
.timeline-step__heading time,
|
||||
.history-detail__header span,
|
||||
.history-detail__step time {
|
||||
.history-detail__metadata {
|
||||
color: var(--td-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.timeline-card__summary {
|
||||
flex-wrap: wrap;
|
||||
margin-top: 12px;
|
||||
padding: 9px 10px;
|
||||
color: var(--td-text-color-primary);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.timeline-card__label {
|
||||
color: var(--td-text-color-secondary);
|
||||
}
|
||||
|
||||
.timeline-card__count {
|
||||
margin-left: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timeline-card__reason,
|
||||
.history-detail__reason {
|
||||
margin: 12px 0 0;
|
||||
@@ -1250,65 +1199,15 @@ onBeforeUnmount(() => {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.timeline-steps {
|
||||
.timeline-card__published,
|
||||
.history-detail__metadata {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.timeline-step {
|
||||
display: grid;
|
||||
grid-template-columns: 10px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.timeline-step__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-top: 7px;
|
||||
background: var(--td-component-border);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.timeline-step__dot--success {
|
||||
background: var(--td-success-color);
|
||||
}
|
||||
|
||||
.timeline-step__dot--danger {
|
||||
background: var(--td-error-color);
|
||||
}
|
||||
|
||||
.timeline-step__dot--warning {
|
||||
background: var(--td-warning-color);
|
||||
}
|
||||
|
||||
.timeline-step__body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.timeline-step__heading {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.timeline-step__heading strong {
|
||||
color: var(--td-text-color-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.timeline-step__heading time {
|
||||
margin-left: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timeline-step__body p {
|
||||
margin: 3px 0 0;
|
||||
gap: 6px 16px;
|
||||
margin-top: 10px;
|
||||
color: var(--td-text-color-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.timeline-card__footer {
|
||||
@@ -1336,53 +1235,6 @@ onBeforeUnmount(() => {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.history-detail__steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-height: 420px;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
border: 1px solid var(--td-component-border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.history-detail__step {
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--td-component-border);
|
||||
}
|
||||
|
||||
.history-detail__step:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.history-detail__step > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.history-detail__step strong {
|
||||
color: var(--td-text-color-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.history-detail__step time {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.history-detail__step p {
|
||||
margin: 4px 0 0;
|
||||
color: var(--td-text-color-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
@@ -1552,16 +1404,6 @@ onBeforeUnmount(() => {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.timeline-card__count {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.timeline-step__heading time {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.timeline-card__notes,
|
||||
.history-detail__notes {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user