feat: 初始化DevOps平台前端

This commit is contained in:
湛兮
2026-06-11 20:49:59 +08:00
commit 60931e2c21
38 changed files with 7033 additions and 0 deletions
+297
View File
@@ -0,0 +1,297 @@
<script setup lang="ts">
import StatusPill from '../components/StatusPill.vue'
import { usePlatformStore } from '../stores/platform'
import type { IntegrationConfigState } from '../types/devops'
const platform = usePlatformStore()
const configStatusMeta: Record<
IntegrationConfigState,
{ label: string; type: 'success' | 'warning' | 'danger' }
> = {
configured: { label: '已配置', type: 'success' },
partial: { label: '部分配置', type: 'warning' },
missing: { label: '未配置', type: 'danger' },
}
function configStatusLabel(status: IntegrationConfigState): string {
return configStatusMeta[status]?.label ?? '未配置'
}
function configStatusType(
status: IntegrationConfigState,
): 'success' | 'warning' | 'danger' {
return configStatusMeta[status]?.type ?? 'danger'
}
function formatCheckedAt(value: string): string {
if (!value) {
return '-'
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return value
}
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
}).format(date)
}
</script>
<template>
<div class="page-shell">
<div class="page-header">
<div>
<h1>系统配置</h1>
<p>展示后端集成配置状态API 契约来源BPMN 映射Agent 边界和通知维护状态</p>
</div>
</div>
<section class="panel">
<div class="panel-header">
<h2>后端集成配置状态</h2>
<span>检查时间{{ formatCheckedAt(platform.integrationConfig.checkedAt) }}</span>
</div>
<div class="panel-body">
<div class="secret-boundary">
这里只展示环境变量名和配置状态Jenkins tokenGitea token通知 webhookLLM key数据库连接串不会进入浏览器
</div>
<div class="config-list">
<article
v-for="item in platform.integrationConfig.integrations"
:key="item.key"
class="config-row"
>
<div class="config-row-main">
<div>
<h3>{{ item.name }}</h3>
<p v-if="item.note">{{ item.note }}</p>
</div>
<ElTag :type="configStatusType(item.status)" effect="light" round>
{{ configStatusLabel(item.status) }}
</ElTag>
</div>
<div class="variable-grid">
<div class="variable-group">
<strong>必需变量</strong>
<div class="variable-tags">
<ElTag v-for="name in item.required" :key="name" class="mono" effect="plain">
{{ name }}
</ElTag>
</div>
</div>
<div class="variable-group">
<strong>已识别</strong>
<div class="variable-tags">
<ElTag
v-for="name in item.configured"
:key="name"
class="mono"
type="success"
effect="plain"
>
{{ name }}
</ElTag>
<span v-if="item.configured.length === 0" class="empty-value">暂无</span>
</div>
</div>
<div class="variable-group">
<strong>缺失</strong>
<div class="variable-tags">
<ElTag
v-for="name in item.missing"
:key="name"
class="mono"
type="danger"
effect="plain"
>
{{ name }}
</ElTag>
<span v-if="item.missing.length === 0" class="empty-value"></span>
</div>
</div>
<div class="variable-group">
<strong>可选变量</strong>
<div class="variable-tags">
<ElTag
v-for="name in item.optional ?? []"
:key="name"
class="mono"
type="info"
effect="plain"
>
{{ name }}
</ElTag>
<span v-if="!item.optional?.length" class="empty-value">暂无</span>
</div>
</div>
</div>
</article>
</div>
</div>
</section>
<div class="section-grid">
<section v-for="item in platform.settings" :key="item.key" class="panel">
<div class="panel-header">
<h2>{{ item.name }}</h2>
<StatusPill :state="item.status" />
</div>
<div class="panel-body setting-card">
<dl>
<dt>责任方</dt>
<dd>{{ item.owner }}</dd>
<dt>契约</dt>
<dd>{{ item.contract }}</dd>
<dt>边界</dt>
<dd>{{ item.boundary }}</dd>
</dl>
</div>
</section>
</div>
<section class="panel">
<div class="panel-header">
<h2>前端环境变量</h2>
<span>第一阶段仅保留 API 地址</span>
</div>
<div class="panel-body compact-list">
<div class="list-row">
<span>
<strong class="mono">VITE_API_BASE_URL</strong>
<small>联调时指向后端网关未配置时默认 /api</small>
</span>
<ElTag type="info" round>{{ platform.dataSource === 'backend' ? '后端已联通' : '/api 或 mock 降级' }}</ElTag>
</div>
<div class="list-row">
<span>
<strong>密钥策略</strong>
<small>Jenkins tokenGitea token通知 webhookLLM key 均不得进入前端</small>
</span>
<ElTag type="success" round>前端隔离</ElTag>
</div>
</div>
</section>
</div>
</template>
<style scoped>
.setting-card dl {
display: grid;
grid-template-columns: 74px minmax(0, 1fr);
gap: 10px 12px;
margin: 0;
font-size: 13px;
}
.setting-card dt {
color: #7b8798;
}
.setting-card dd {
margin: 0;
color: #172033;
line-height: 1.6;
}
.secret-boundary {
margin-bottom: 14px;
padding: 12px 14px;
border: 1px solid #dbeafe;
border-radius: 8px;
background: #eff6ff;
color: #25508f;
font-size: 13px;
line-height: 1.6;
}
.config-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.config-row {
padding: 14px;
border: 1px solid #edf1f7;
border-radius: 8px;
background: #fbfcff;
}
.config-row-main {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.config-row-main h3 {
margin: 0;
color: #182033;
font-size: 15px;
font-weight: 700;
letter-spacing: 0;
}
.config-row-main p {
margin: 6px 0 0;
color: #748093;
font-size: 12px;
line-height: 1.5;
}
.variable-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.variable-group {
min-width: 0;
}
.variable-group strong {
display: block;
margin-bottom: 8px;
color: #4b5565;
font-size: 12px;
}
.variable-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
min-height: 24px;
}
.empty-value {
color: #9aa4b2;
font-size: 12px;
line-height: 24px;
}
@media (max-width: 1080px) {
.variable-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 680px) {
.config-row-main {
flex-direction: column;
}
.variable-grid {
grid-template-columns: 1fr;
}
}
</style>