所属产品:LightApollo · 信息截止:2026-09-07(deep-rev)

1. 页面概览

1.1 是什么

「Git 仓库」是 LightApollo 的 GitOps 配置同步管理页(对应前端源码 action/web/src/views/apollo/GitReposPage.vue,对齐 TAD-02 GitOps)。它的职责是:把「仓库」里声明的配置,按「同步目标(sync target)」的定义同步成 LightApollo 的期望状态(DesiredState),落到指定环境,并检测实际状态与期望之间的漂移。页面以仓库为入口,一张表管仓库、一层展开管仓库下的同步目标,目标行再挂出同步 / 回滚 / 暂停 / 恢复 / 历史 / 漂移等操作。

需要先说清一个贯穿全局的实现前提:这里的「Git 仓库」是本地目录模拟。TAD-02 采用自研轻量实现,仓库 url 指向的是后端所在机器的磁盘目录(如 C:/configs/app-config),后端直接读目录里的文件,不真正走 git 协议拉取、也没有远端凭据交换;「commit」是由目录内容算出来的模拟 SHA(见 5.4)。因此本页所说的「同步」本质是「读目录 → 解析声明 → 在 hub 建期望状态」,与真实 GitOps 的 git 语义有差距,操作与演示不受影响。

页面在 LightApollo 发布链中的位置是「配置入口」:同步目标把仓库内 git_path 前缀目录下的配置文件(支持 k8s_manifest / helm / kustomize 三种声明类型)解析为期望状态,经过创建(draft)→(视同步策略)激活(active)后,期望状态就成为「部署与漂移」页的发布对象;目标环境正是「环境管理」页登记的环境(target_environment_id)。漂移策略(alert / auto_fix)决定发现差异后是仅告警还是自动调和。一句话:本页把「仓库内声明 → 目标环境的期望状态」这段 GitOps 链路做成可点按钮的界面,并留下同步历史与漂移事件供审计回溯。

本页涉及两种「状态」不要混淆:一是仓库/同步目标的登记状态(status:仓库 active/paused/error,目标 active/syncing/paused/error,徽标展示在列表);二是每次同步产出的同步历史行(SyncHistory:running → success/failed)。登记状态决定「同步引擎放不放行」,历史行则记录「某一次同步做成/做败了什么」。后面第 4、5 章所有按钮的成功与失败,最终都能落到这两类状态上被看到。

三种同步策略(sync_policy)的实际差异在 5.4 讲透,这里先给一句话印象:manual 策略只在被点「同步/刷新」时才跑一次;auto 策略除被手动/webhook 触发外,同步出的新声明还会走自动激活与安全门禁;scheduled 策略额外依赖目标上的 cron 定时表达式(sync_cron)到点自动触发。仓库级「同步间隔」字段(sync_interval_seconds)目前仅是记录,尚未接成真正的轮询触发(见 8 章)——不要指望填了它就会自动同步。

1.2 核心价值表

能力说明对应页面操作
仓库列表展示登记的全部 Git 仓库:ID/名称/URL/默认分支/状态/同步间隔/最后同步时间进入页面自动加载
仓库管理新建/编辑/删除仓库,登记本地目录路径与认证方式等元数据页头「新建仓库」与行操作「编辑」「删除」
测试连接校验仓库 url 指向的本地目录存在且可读行操作「测试连接」
刷新同步对仓库下全部 active 同步目标各触发一次手动同步行操作「刷新」
同步目标管理行展开查看某仓库的同步目标,含新建/编辑/删除「▸ 同步目标」行展开 + 子区按钮
手动同步 / 回滚立即同步一次声明到目标环境;按最近一次同步结果回滚目标行「同步」「回滚」
暂停 / 恢复暂停后同步引擎拒绝该目标的同步(自动/webhook 都不再触发)目标行「暂停 / 恢复」
历史与漂移弹窗查看同步历史(含 commit 与操作摘要)与漂移事件(期望/实际 diff)目标行「历史」「漂移」

1.3 一句话总结

Git 仓库页把仓库内 k8s_manifest / helm / kustomize 配置同步为指定环境的期望状态,并对每次同步留痕、对差异做漂移管理。

2. 访问入口

2.1 路由与菜单

路由 path/apollo/git-repos
路由 nameApolloGitRepos
meta.titleApollo Git 仓库
侧边栏位置ApolloLayout 侧边栏「Git 仓库」(位于「环境管理」之后、「制品管理」之前)
前端源码action/web/src/views/apollo/GitReposPage.vue
API 客户端action/web/src/api/apolloClient.js
路由注册action/web/src/router/index.js:import 于第 23 行,children 注册于第 427~432 行

2.2 认证与权限

2.3 端口与 API 前缀

3. 界面布局

┌───────────────────────────────────────────────────────────────┐
│ Apollo Git 仓库与同步管理                         [ 新建仓库 ]      │
│ [ 说明 alert:Git 仓库为"本地目录"模拟… ]                        │
│ [ 操作结果提示条(可关闭)]                                      │
│ [ 仓库列表卡片 ]                                                │
│  ▸同步目标 ID 名称 URL(本地目录) 默认分支 状态 同步间隔 最后同步 操作 │
│  操作列:测试连接 / 刷新 / 编辑 / 删除                            │
│ ┌─ 同步目标子区(仓库行展开,每仓库一块)───────────────────────┐  │
│ │ 同步目标(N)— 仓库 {name}                    [ 新建同步目标 ]  │  │
│ │ ID Git路径 配置类型 同步策略 漂移策略 目标环境 状态 操作        │  │
│ │ 操作列:同步/回滚/暂停|恢复/历史/漂移/编辑/删除                  │  │
│ └──────────────────────────────────────────────────────────────┘  │
│ [ 新建/编辑仓库弹窗 ]  [ 新建/编辑同步目标弹窗 ]                   │
│ [ 同步历史弹窗 ]  [ 漂移事件弹窗 ]                                 │
└───────────────────────────────────────────────────────────────┘
板块职责
说明 alert常驻说明本页为本地目录模拟与同步机制,帮助操作者理解 url 语义
操作结果提示条展示成功/失败/信息提示,右侧「关闭」可收起
仓库列表卡片加载中「加载中...」;空数据「暂无 Git 仓库,点击"新建仓库"添加(项目 #1,url 填本地目录路径)。」;有数据渲染表格,首列是展开按钮
同步目标子区点「▸ 同步目标」展开;每仓库一块,含标题行与操作列,展开结果有前端缓存
弹窗区仓库表单、同步目标表单、同步历史(只读表格)、漂移事件(只读表格)

页面横向较宽:仓库表一共 8 列(含最左侧展开钮占位与最右操作列),首列收起时只有展开符号、无其余文案;同步目标子区使用缩进块嵌套在仓库行内,看起来像「表套表」。两级表的行高与列宽由仓库行的展开状态决定——展开后该仓库行下会插入一块独立区域,不改变其它仓库行的排版,因此仓库很多时页面会很长(无分页,见 8 章)。

进入页面的初始请求序列(见 4.14):并行发起两个只读请求——拉仓库列表(渲染主体)+ 拉环境列表(仅备用,喂给同步目标弹窗的下拉),两者互不阻塞;仓库列表失败会红条提示且整页无数据,环境列表失败则静默降级。首屏没有任何写请求。

4. 交互元素

4.1 说明 alert 与全局 busy

顶部常驻一条 alert alert-info,文字为:「Git 仓库为"本地目录"模拟(url 指向磁盘目录,TAD-02 自研轻量实现);同步目标将仓库内 git_path 目录下的配置(k8s_manifest / helm / kustomize)同步到目标环境,并按漂移策略检测差异。」这条提示不可关闭,目的是让每个进入页面的人先知道 url 填的是目录。

页面互斥位 busy 比较特殊:它存的是字符串操作键而不是布尔位(空闲为 '',进行中为 saveRepo / test-{id} / sync-{id} 等),所有按钮都按 !!busy 禁用,所以任意一个操作进行中都会锁住全页按钮。多按钮连点被结构性地禁止;某一请求挂起(30s 超时)期间页面不可操作,超时后恢复。

4.2 仓库列表与状态徽标

控件含义边界与细节
「▸ / ▾ 同步目标」链接按钮展开/收起该仓库的同步目标子区(title 提示「展开同步目标/收起同步目标」)首次展开去拉取该仓库同步目标;再次进入直接用缓存不再请求(见 4.7)
状态徽标仓库状态:active→绿、paused→灰、error→红,未知回退灰并显示 unknown徽标文案取 repo.status || 'unknown'
同步间隔列formatInterval(sync_interval_seconds):秒转中文小于 60 显示「N 秒」;小于 3600 显示「N 分钟」;否则「N.N 小时」;空或 ≤0 显示 -
最后同步列formatTime(repo.last_sync_at)T 换空格、去 Z、截秒空显示 -
URL 列等宽字体 + 超长省略(url-cell,max-width 260px),悬停看全url 是本地目录路径,填错是测试连接失败的主因

仓库行的操作列四按钮:测试连接 / 刷新 / 编辑 / 删除(见 4.4~4.6)。

仓库表各列取值:ID 纯数字;名称列无特殊样式、非等宽;URL(本地目录)列等宽字体加省略;默认分支列为 repo.default_branch || '-';状态徽标见上表;同步间隔由 formatInterval 换算;最后同步为 formatTime。仓库表不带分页与搜索,列表一次性渲染;repo.name 在删除确认与操作提示里会原样拼接进文案,特殊字符会影响可读性但不影响功能。

仓库列表本身没有「刷新列表」按钮——唯一的主动刷新手段是执行一次仓库级写操作(新建/编辑/删除/刷新),成功后自动重新拉取。若别人在后端/其它页面改了仓库,本页停留期间不会感知,需通过上述任一操作或整页重进触发重新加载。这一「无轮询、写后刷新」约定是全页一致的设计(见 4.14)。

4.3 新建 / 编辑仓库弹窗

弹窗标题区分模式:新建为「新建仓库(项目 #1)」,编辑为「编辑仓库 #{id}(项目 #1)」。提交创建走 POST /projects/1/git-repos(成功提示「仓库创建成功(id=N)」),编辑走 PUT /git-repos/{id}(成功提示「仓库 #{id} 更新成功」),成功后关闭弹窗并刷新仓库列表。

表单字段表

字段类型必填校验规则默认保存逻辑
名称(name)文本required + 前端非空校验,空则提示「名称(name)与 URL 必填」trim 后提交;项目内唯一(后端按项目判重)
URL(本地目录路径)文本同上trim 后提交;placeholder「如 C:/configs/app-config 或 ./repos/app-config」,输入框下方有灰字说明「TAD-02 自研轻量实现:url 指向本地磁盘目录,由后端直接读取。」
认证方式(auth_type)下拉三选项none选项:无需认证(none)/ SSH Key(ssh_key)/ 账号密码(basic)——仅记录,本地目录模拟不真正鉴权(见 8 章)
默认分支(default_branch)文本留空则保存时按 main 提交placeholder「如 main」
同步间隔(sync_interval_seconds)数字min=0;仅当填了正整数才进 payload留空placeholder「留空则不按周期自动同步」;>0 才带上,否则省略(后端 0=仅 webhook 语义)

编辑态预填当前值(数字字段转字符串回显);删除与取消照旧。注意一个不对称:新建/编辑都能改 url(即换目录),编辑仓库与编辑同步目标不同——仓库的归属不可变,但 url 可换。

提交细节(与 handleSaveRepo / buildRepoPayload 逐字对应):点保存先走前端校验,nameurl 任一为空立即红条「名称(name)与 URL 必填」并不发请求;通过后把按钮置 busy(saveRepo),先 trim 再组包。auth_type 恒带默认 nonedefault_branch 前端兜底补 mainrepoForm.default_branch || 'main')——因此即便用户留空,发给后端的请求体里 default_branch 也是 main,与后端 defaultString(req.DefaultBranch, "main") 双保险一致;sync_interval_seconds 只有 Number(...) > 0 才写进请求体,填 0 或不填都等价于「后端字段保持 0(0=不按周期)」。后端 name 判空/trim、url 判空/trim 有同名校验(name 不能为空/url 不能为空),与前端提示文案不同源但语义一致。名称冲突(同项目同 name,唯一索引 idx_git_repo_project_name)在创建时以 409/冲突语义报错并红条提示;创建响应体直接是仓库对象okStatus(201, repo)),前端用 data?.id || data?.repo?.id 双形态兜底取 id。

编辑仓库的局部更新语义:后端 PUT /git-repos/:id 用指针字段,只有请求体里出现的字段才会被更新handleUpdateGitRepo 逐字段判 != nil)。前端编辑态把 5 个表单字段全部回填进 repoForm,因此点击保存会把当前表单值整体提交——不存在「只改一项」的顾虑;但其它字段(如 status、webhook_secret)不会被本页表单改到。编辑仓库与编辑同步目标类似但更宽松:仓库编辑允许改 name/url/auth_type/default_branch/sync_interval_seconds,且不校验 name 是否与其它仓库撞名之外的约束(url 指向的目录暂不要求此刻可读)。

4.4 测试连接

控件触发后端调用成功判定细节
按钮「测试连接」POST /git-repos/{id}/test-connectionsuccess===true / ok===true / connected===true后端返回 {id, url, connected:true} 或直接报错(目录不存在/不可读);前端提示「仓库 "{名称}" 连接测试:{message}」,message 缺失按判定回退「连接成功/连接失败」

后端 TestConnection 调用 checkReadableDir 校验 url 指向的目录:目录不存在(404)、不是目录、不可读都会报错并在提示条展示。操作期间按钮禁用;成功后不会自动刷新列表(无数据变化)。

「测试连接」本质是只读探活,不做目录快照也不写任何历史;它在新建仓库前最有用——先在表单里把 url 填好保存,再用测试连接验证目标目录真实存在可读,避免把错误路径带进后续所有同步。该按钮不校验目录内是否有符合约定的声明文件,目录存在即可读即返回 connected:true——所以「连接成功」不代表「同步一定能成功」(同步还依赖文件命名约定与声明合法性,见 5.4)。

4.5 刷新(触发同步)

控件触发后端调用细节
按钮「刷新」POST /git-repos/{id}/refresh后端对仓库下全部 active 同步目标各触发一次手动同步(trigger=manual);响应为 {repo_id, triggered:[...], failed:[...]}。前端提示「仓库 "{名称}" 已触发刷新同步(...)」,消息优先取 data.message,其次 状态:{data.status},都没有显示「刷新成功」,随后刷新仓库列表

刷新不等同于把仓库状态刷新为同步完成,而是「按一下,把该仓库所有活跃目标都同步一遍」的批量入口;单个目标想精细操作应去子区用「同步」。若仓库没有 active 同步目标,triggered 为空数组,前端仍提示刷新成功——这是正常现象,不是失败。

后端逐目标语义(对照 handleRefreshGitRepo):先按仓库 id 解析全部同步目标(ResolveSyncTargetsForRepoListByRepo),然后逐个串行执行 Sync(trigger=manual),仅跳过 status 非 active 的目标。某个目标失败不会中断后面的目标:失败项进 failed:[{target_id,error}],成功项进 triggered:[{target_id,history_id,status}]。注意这与子区「同步」不同:刷新不管目标的 sync_policy,manual/auto/scheduled 一律触发,只认 status=active。每次刷新后端都会写一条审计事件(action=GIT_REPO_REFRESH,payload 含 triggered/failed 计数)。前端拿到响应后只取 data.messagedata.status 拼提示语,不会逐个展示 triggered/failed 明细——想确认每个目标本次结果应去对应目标的「历史」弹窗。

4.6 删除仓库

控件触发后端调用细节
按钮「删除」(危险)DELETE /git-repos/{id}二次确认:「确定删除仓库 "{名称}"(#{id})吗?关联的同步目标可能一并删除。」;成功后提示「仓库已删除」并收起展开区、刷新列表

后端删除是事务级级联:先删该仓库每个同步目标的全部同步历史,再删这些同步目标,最后删仓库,返回 {id, deleted:true}。所以确认框那句「关联的同步目标可能一并删除」不只是提醒——它实际删了目标及其全部历史,是整条 GitOps 链路的物理清理,不可恢复。删除后仓库名可复用。

4.7 同步目标子区(行展开)

点首列「▸ 同步目标」展开子区。加载该仓库的同步目标用的是项目级接口GET /projects/1/sync-targets 拉全部目标,前端按 git_repo_id 过滤(field(t,'git_repo_id') 做字符串比较),因此展开结果的正确性依赖接口返回完整列表。展开结果缓存在 syncMap[repo.id],首次展开拉取后,再次展开/收起不重新请求;但新建/同步/回滚/暂停/恢复/删除目标后都会以 force 语义强制重新拉取该仓库的目标列表(见 4.9/4.10),让缓存保持新鲜。

子区标题「同步目标(N)— 仓库 {name}」,右上角「新建同步目标」。列表列为 ID / Git 路径 / 配置类型 / 同步策略 / 漂移策略 / 目标环境 / 状态 / 操作。

取值规则
Git 路径t.git_path || '-'(等宽字体)
配置类型中文标签:K8s Manifest(k8s_manifest)/ Helm Chart(helm)/ Kustomize(kustomize)
同步策略manual→「手动」、auto→「自动」、scheduled→「定时({cron 或 -})」
漂移策略alert→「通知」、auto_fix→「自动修复」,未识别原样显示
目标环境优先在已加载环境下拉里找 id:找到显示「{name}(#{id})」,找不到显示「#{id}」(纯数字兜底)
状态t.status || 'unknown' 徽标:active→绿、syncing→蓝、paused→灰、error→红

目标行操作列七个按钮:同步 / 回滚 / 暂停(或恢复)/ 历史 / 漂移 / 编辑 / 删除。「暂停/恢复」按钮文案由当前状态决定:paused 显示「恢复」,否则显示「暂停」。

子区缓存与强制刷新约定:展开目标列表的请求 key 是 syncMap[repo.id](数组缓存)。页面初次进入时不会预拉任何目标列表——只有某仓库首次被展开才发它的第一个请求;之后该仓库的目标列表只在「新建/编辑/同步/回滚/暂停/恢复/删除」任一成功或失败触发 force 重拉时更新(见 4.14)。删除仓库成功后,对应展开区被强制收起、缓存清掉,避免悬空展示已删除仓库的目标。若某仓库从未展开过,页面关闭前不会产生该仓库目标的任何请求。

子区列宽与整行状态:目标行「状态」徽标取值 t.status || 'unknown',与仓库徽标用同一套样式但配色含义不同(目标多了 syncing 蓝);「Git 路径」列等宽字体 + 省略,与仓库 URL 列一致。目标行没有单独的空态占位——空列表时子区只显示标题行(同步目标(0)— 仓库 {name}),下方不渲染表格,也没有「暂无」提示文案。新建同步目标成功后新目标会出现在该仓库子区(按 force 重拉)。

4.8 新建 / 编辑同步目标弹窗

弹窗标题区分模式:「新建同步目标」/「编辑同步目标 #{id}」。创建提交 POST /projects/1/sync-targets(成功提示「同步目标创建成功(id=N)」),编辑提交 PUT /sync-targets/{id}(成功提示「同步目标 #{id} 更新成功」),成功后关闭弹窗并强制刷新所属仓库的目标列表。

表单字段表

字段类型必填默认校验规则保存逻辑
Git 仓库(git_repo_id)下拉新建时=所属仓库 id;编辑时=当前值编辑态禁用(不可迁移仓库);无值且 git_path 空会拦原样提交
Git 路径(git_path)文本前端非空校验,空则提示「Git 仓库与 git_path 必填」trim 后提交;placeholder「如 manifests/prod 或 charts/app」
配置类型(config_type)下拉k8s_manifest选项固定三个原样提交
同步策略(sync_policy)下拉manualmanual/auto/scheduled选 scheduled 才显示并提交 sync_cron
漂移策略(drift_policy)下拉alertalert / auto_fix;后端只认这两个值,旧值不识别空则按 alert 提交
目标环境(target_environment_id)*下拉或数字输入有环境下拉时取第一个环境 id空则提示「目标环境(target_environment_id)必填」Number() 强制转数字后提交
定时表达式(sync_cron)文本仅 scheduled选 scheduled 才出现;填了才提交placeholder「如 */30 * * * *(每 30 分钟)」

目标环境下拉的数据源是进入页面时并行拉取的 GET /projects/1/environments;该请求失败不阻塞页面(弹窗自动降级成数字输入框,placeholder「环境 ID(环境列表加载失败时手输)」)。下拉选项文案「{环境名}(#{id})」。此处的必填校验注意顺序:先查 git_repo_id/git_path,再查 target_environment_id,两条提示文案不同。

默认值语义(对照 openCreateTarget / openEditTarget):新建目标时 git_repo_id 默认填所属仓库的 id(因为新建入口在某个仓库的子区里,仓库是已知的);config_type 默认 k8s_manifestsync_policy 默认 manualdrift_policy 默认 alertsync_cron 空。target_environment_id 默认取环境列表第一项environments[0]?.id)——若进入页面时环境列表已加载成功,新建弹窗打开即有默认目标环境;若环境列表为空或加载失败,该默认值为 null,必须手选/手输,否则前端校验直接红条拦截。编辑目标时一律从行对象回填(git_path/config_type/sync_policy/sync_cron/drift_policy 都取当前值),目标环境用 field(t,'target_environment_id','target_environment') 双形态兜底取值。创建成功后前端提示取 data?.id || data?.sync_target?.id 双形态 id。

提交细节:保存按钮 busy 键为 saveTarget。前端校验顺序为 ①git_repo_idgit_path 任一空 → 红条「Git 仓库与 git_path 必填」;②target_environment_id 空(含 '')→ 红条「目标环境(target_environment_id)必填」。通过后组包:config_typesync_policydrift_policy 原样提交;target_environment_idNumber() 转数字;sync_cron 仅当 sync_policy=scheduled 且填写才带上(scheduled 未填 cron 会触发后端/调度告警,见 7 章第 5 条)。创建走 POST /projects/1/sync-targets,后端会校验 git_repo_id 属于当前项目(不属于则 400)与 git_path 非空,config_type/sync_policy/drift_policy 缺省分别为 k8s_manifest/auto/alert——注意后端 sync_policy 缺省是 auto,而前端新建默认是 manual,两者不一致时以后端为准的前提是前端每次都显式提交 manual(确实如此,所以实际生效的是 manual,除非后端直连调用未带该字段)。

4.9 同步与回滚

控件触发后端调用二次确认细节
按钮「同步」POST /sync-targets/{id}/sync结果提示「同步目标 #{id} 同步成功:commit {sha}{summary}」(失败则「同步失败」,红条)
按钮「回滚」POST /sync-targets/{id}/rollback有:「确定对同步目标 #{id} 执行回滚吗?将恢复上一次成功同步的配置。」提示「同步目标 #{id} 回滚成功:commit ......」

两者共用 parseSyncResult(data) 解析响应:识别 data.history/sync/record 嵌套或平铺记录,从记录里取 status / git_commit_sha / operation_summary / summary / message,再按 status 白名单(success/succeeded/synced/completed/ok)判定成功与否。但后端真实响应嵌套在 sync_history(回滚为 rollback_history)键下,不在前端识别列表内,导致 commit_sha 与 summary 恒取不到,提示永远显示「commit -」;状态判定读的是顶层 data.status(存在),所以成功/失败红绿提示仍是准的——这是本页最值得注意的前端缺陷(见第 8 章 d)。同步/回滚成功后都会强制刷新所属仓库目标列表。

点「同步」后端做了什么(对照 handleSyncTargetSync + SyncEngine.Sync):先校验同步目标存在且 status=active(非 active 返回 400「sync target %d 状态非 active」)→ 校验所属仓库存在且 active → 立即落一条 status=running 的同步历史 → 快照仓库目录 → 解析声明 → 逐条登记期望状态(draft)/幂等复用 → auto 策略走自动激活与门禁 → 回写历史 success/failed + commit + OperationSummary → 刷新仓库 last_sync_at同步是同步阻塞执行:页面按钮会一直处于忙碌直到后端完成整条链(一个请求内跑完),响应里的 sync_history 就是本次完整结果;历史弹窗里能看到 running 行只在极短窗口存在,正常情况打开时已是 success/failed。

失败时的双重留痕:无论哪一步失败,历史行都会被置为 failed(含错误信息)并记录 finished_at,同时后端返回错误给页面红条展示——所以一次失败同步会在「提示条」和「历史弹窗」两处都留下线索,历史弹窗的 failed 行信息更完整(含失败原因)。门禁 deny 类错误(403 AUTHZ_ERROR)会原样透传到前端(后端 fail 闭包保留 ierr 的错误码与 HTTP 状态,避免折叠成 500),因此红条里的文案能区分「策略拒绝」与「系统故障」。

4.10 暂停 / 恢复

控件触发后端调用细节
按钮「暂停」/「恢复」POST /sync-targets/{id}/pause / .../resume文案随 t.status === 'paused' 切换;响应 {id, status: 'paused'|'active'};提示「同步目标 #{id} 已暂停(状态:paused)」(或「已恢复(状态:active)」),随后强制刷新目标列表

暂停是同步引擎层面的闸门:暂停后目标 status 为 paused,手动 sync、auto/webhook 触发都不会再执行(引擎按状态校验),列表徽标变灰。

4.11 同步历史弹窗

行内「历史」打开,标题「同步历史:#{id}」,弹窗内「加载中...」/「暂无同步历史。」两态。接口 GET /sync-targets/{id}/history 直接返回数组(后端已按最新在前反转)。表格列:触发类型 / 状态 / Commit / 提交信息 / 操作摘要 / 时间,映射 h.trigger_type / h.status / h.git_commit_sha / h.git_commit_msg / h.operation_summary / formatTime(h.created_at),空值显示 -。状态徽标:success→绿、failed/error→红、running/pending→蓝。触发类型取值见 5.4(manual/webhook/scheduled/rollback 等)。只读弹窗,无任何写操作。

4.12 漂移事件弹窗

行内「漂移」打开,标题「漂移事件:#{id}」,空态提示「暂无漂移,目标环境与配置一致。」接口 GET /sync-targets/{id}/drift 返回 {sync_target_id, count, events},前端做双形态兼容(数组直用,{events:[...]} 取 events)。表格列:资源 / 类型 / 期望/实际 / 状态 / 时间,字段用别名兜底读取(resource_name/name/resourceresource_kind/kind/config_typedesired/expectedactual/currentstatus/drift_statusdetected_at/created_at),「期望/实际」两行灰字展示,都没有则回退展示 message/description/detail 或整个对象。状态徽标:drifted/detected→红、resolved/clean→绿、ignored→灰,未知回退红并显示 drifted。只读弹窗。

4.13 删除同步目标

控件触发后端调用细节
按钮「删除」(危险)DELETE /sync-targets/{id}二次确认:「确定删除同步目标 #{id} 吗?」;成功提示「同步目标已删除」,强制刷新所属仓库目标列表

后端删除目标事务内级联清理其全部同步历史后删目标(返回 {id,deleted:true}),同步历史随之消失。删除单个目标不影响仓库与其它目标。

4.14 操作结果提示与自动刷新约定

5. 后端关联

5.1 API 客户端

与其它 Apollo 页共用 action/web/src/api/apolloClient.jsbaseURL '/apollo-api/v1'、timeout 30000、请求拦截器附 apollo_token(回退 aip_token)Bearer 头;响应拦截器解包 {code:0,...} 信封,401 清 token 跳登录、403 统一 alert。后端成功信封形态:{code:0, message:"ok", data, request_id}

5.2 端点表

页面实际用到(前缀 /api/v1,经 Vite /apollo-api 到 18082;括号为 server.go 权限点):

方法路径请求体页面触发点
GET/projects/:pid/git-repos-加载仓库列表(PermGitRepoRead)
POST/projects/:pid/git-repos{name, url, auth_type?, default_branch?, sync_interval_seconds?, ...}新建仓库(PermGitRepoWrite,201)
PUT/git-repos/:id{name?, url?, auth_type?, default_branch?, sync_interval_seconds?, ...}编辑仓库(PermGitRepoWrite)
DELETE/git-repos/:id-删除仓库(PermGitRepoWrite)
POST/git-repos/:id/test-connection-测试连接(PermGitRepoExecute)
POST/git-repos/:id/refresh-刷新同步(PermGitRepoExecute)
GET/projects/:pid/sync-targets-展开子区拉目标列表(PermSyncTargetRead)
POST/projects/:pid/sync-targets{git_repo_id, git_path, config_type?, target_environment_id, sync_policy?, sync_cron?, drift_policy?, ...}新建同步目标(PermSyncTargetWrite,201)
PUT/sync-targets/:id{git_path?, config_type?, target_environment_id?, sync_policy?, sync_cron?, drift_policy?, ...}编辑同步目标(PermSyncTargetWrite)
DELETE/sync-targets/:id-删除同步目标(PermSyncTargetWrite)
POST/sync-targets/:id/sync-手动同步(PermSyncTargetExecute)
POST/sync-targets/:id/rollback-回滚(PermSyncTargetExecute)
POST/sync-targets/:id/pause/resume-暂停/恢复(PermSyncTargetExecute)
GET/sync-targets/:id/history-历史弹窗(PermSyncTargetRead)
GET/sync-targets/:id/drift-漂移弹窗(PermSyncTargetRead)
GET/projects/:pid/environments-同步目标弹窗的目标环境下拉(PermEnvRead)

后端另有 GET /sync-history/:id(单条历史详情,PermSyncTargetRead)与公开 Webhook 端点(见 5.4)本页未直接用。环境页的 resources/applications 同理不在本页。

5.3 响应结构示例

新建仓库成功(201,信封解包后的 data)

{
  "id": 3,
  "project_id": 1,
  "name": "app-config-repo",
  "url": "C:/configs/app-config",
  "auth_type": "none",
  "default_branch": "main",
  "sync_interval_seconds": 0,
  "status": "active",
  "webhook_secret": "",
  "last_sync_at": null,
  "created_at": "2026-09-07T09:00:00Z",
  "updated_at": "2026-09-07T09:00:00Z"
}

字段说明:url 是本地目录路径;auth_type 后端枚举为 none/ssh/https_token(与前端下拉值不完全一致,见 8 章);sync_interval_seconds=0 表示不按周期轮询(注释语义 0=仅 webhook);last_sync_at 每次成功同步后被刷新;webhook_secret 为可选的 Webhook 验签密钥。列表接口 GET /projects/1/git-repos 返回该对象的数组。

手动同步成功响应(注意嵌套键)

{
  "sync_target_id": 9,
  "status": "success",
  "sync_history": {
    "id": 15,
    "sync_target_id": 9,
    "trigger_type": "manual",
    "git_commit_sha": "3f1c2a9d4b07",
    "git_commit_msg": "gitops sync 2026-09-07T10:00:00+08:00",
    "status": "success",
    "operation_summary": "[{\"name\":\"web-app\",\"app\":\"web-app\",\"version\":\"v1.2.0\",\"desired_state_id\":41,\"status\":\"created\"}]",
    "started_at": "2026-09-07T02:00:00Z",
    "finished_at": "2026-09-07T02:00:01Z",
    "created_at": "2026-09-07T02:00:01Z"
  }
}

字段说明:status 为同步结果(success/failed);sync_history 为完整历史行,含本次 commit(16 位模拟 SHA)、提交信息与 operation_summary(本次同步逐声明的结果项数组,元素含 name/app/version/desired_state_id/status=created|reused)。回滚响应结构对称:{sync_target_id, status, rollback_history, based_on_history_id}前端 parseSyncResult 只识别 data.history/sync/record,不读 sync_history/rollback_history,因此 commit 与 summary 提示恒为 -(缺陷 d)。 而同步历史弹窗直接渲染 history 行(h.git_commit_sha 等),不受此缺陷影响。

测试连接成功 / 刷新同步响应

{ "id": 3, "url": "C:/configs/app-config", "connected": true }
{
  "repo_id": 3,
  "triggered": [{"target_id": 9, "history_id": 15, "status": "success"}],
  "failed": []
}

刷新同步响应中 triggered 列出被触发的目标与各自的 history_id/status,failed 列出失败项(含 error);没有 active 目标时两者都为空数组。漂移响应为 {sync_target_id, count, events},events 为 hub 漂移事件对象数组(含资源名/类型/期望/实际/状态/时间等,字段见 4.12 的别名兜底)。

5.4 关键机制

本地目录模拟与 commit 语义。仓库不含真实 git 逻辑:localrepocheckReadableDir 校验目录存在且可读;每次同步前对目录做 Snapshot(列出条目),生成的 commit 是 SimulateCommitSHA 按内容树与时间戳算出的 16 位十六进制串,提交信息固定形如 gitops sync <RFC3339>。目录另有 fsnotify Watch 变更监听 + PollChanges 轮询兜底。因此「测试连接」「同步」「commit」全部围绕磁盘目录展开,url 写错或目录权限不足是测试连接失败的根因。

同步引擎主链(SyncEngine.Sync)。一次手动/自动同步按序执行:① 校验同步目标与所属仓库状态(非 active 拒绝);② 写一条 status=running 的同步历史;③ 对仓库目录做 Snapshot,ParseManifests 在 git_path 前缀目录下找声明文件——文件命名约定为 desired-state.yaml/.ymldeclaration.yaml/.yml,或顶层 kind: DesiredState 的 yaml;④ 逐声明在 hub 创建期望状态(draft),同版本已存在时按幂等复用(409 冲突视为已存在而复用);⑤ 若同步策略为 auto 且本次新建了 draft 且内容已具备过审条件,执行安全门 securityGate.EvaluateDeployment——deny 会使整次同步失败(403 AUTHZ_ERROR),soft 的审批门 pending 则放行不阻塞;⑥ Activate 把 draft 置为 active 期望状态;⑦ 完成历史(status=success,写入 OperationSummary 与 commit),刷新仓库 last_sync_at。任一步失败会留下 status=failed 的历史行并终止。也就是说,页面上的一次「同步」可能创建出多个期望状态(每个声明一个),它们会出现在「部署与漂移」页。

回滚语义POST /sync-targets/:id/rollback 先取该目标最近一条同步历史(ListByTarget 按 id 升序,末条即最近一次),交给 SyncEngine.Rollback 基于该历史的 OperationSummary 构造「回到上一次声明版本」的新 draft 并激活,落一条 trigger=rollback 的新历史返回。回滚不是把状态字段改回去,而是「又做了一次新同步」,因此历史里能看到链条式的新行。无任何历史可回滚时后端返回 400 语义错误(页面会走 catch 分支红条提示)。

调度与触发方式。同步触发类型(trigger_type)包括 manual(手动/刷新按钮)、webhook(仓库 Webhook 回调)、scheduled(cron 定时)、rollback(回滚)、drift_fix(漂移自动修复)等,历史弹窗的「触发类型」列即展示该字段。scheduled 策略的目标由 StartScheduler 用 robfig/cron 注册定时任务(表达式来自 sync_cron,为空或非法会被跳过并告警);repo 级 sync_interval_seconds 注释语义为轮询间隔(0=仅 webhook)。Webhook 端点是公开端点(无需登录):POST /api/v1/webhooks/git/:repo_id 触发该仓库全部 active 且 auto/scheduled 策略的目标(trigger=webhook);当仓库配置了 webhook_secret 时强制要求请求头 X-LightApollo-Signature 携带 HMAC-SHA256(body, secret) 的 hex,缺失或不匹配返回 401——未配置 secret 的仓库不校验(显式降级)。

漂移与调和。漂移检测作用于「期望状态(hub)vs 实际状态」:GET /sync-targets/:id/drift 从该目标同步历史的 OperationSummary 里收集期望状态 id,逐 id 查询 hub 漂移事件仓储聚合返回。漂移策略 alert(通知)只告警不动作;auto_fix(自动修复)会触发漂移自动调和,把实际状态拉回期望。历史 OperationSummary 为空(从未成功同步)的目标不会有漂移事件来源。

6. 权限与安全

7. 常见问题与排错

  1. 测试连接提示失败:原因是仓库 url 指向的本地目录不存在、不是目录或后端进程不可读(checkReadableDir 失败,错误含 NOT_FOUND/非目录/不可读等语义)。处理:核对 url 填的是磁盘绝对或相对路径、目录确实存在且对后端进程可读;修改后在编辑弹窗保存再测。
  2. 点「同步」红条失败,历史里留下 failed 行:原因是 git_path 下没有匹配命名的声明文件(desired-state.yaml/.ymldeclaration.yaml/.ymlkind: DesiredState),或声明内容校验失败、或安全门 deny。处理:核对 git_path 前缀与文件命名约定;看同步历史弹窗对应行的「操作摘要/错误」;安全门 deny 需调整部署安全策略后再同步。
  3. 同步成功提示但 commit 显示 -:原因是后端把结果嵌套在 sync_history(回滚为 rollback_history)下,前端 parseSyncResult 未识别该键(缺陷 d)。处理:commit 与 summary 需到「历史」弹窗查看真实值;成功/失败红绿提示不受影响。
  4. 点「刷新」提示成功但什么都没发生:原因是仓库没有 active 的同步目标(triggeredfailed 都为空)或目标不是 active。处理:先展开子区确认存在 active 目标;逐个目标用行内「同步」验证。
  5. 选了 scheduled 策略但从不自动同步:原因是 sync_cron 留空或 cron 表达式非法,StartScheduler 会跳过并告警。处理:编辑同步目标,在出现的「定时表达式」输入框填写合法 cron(如 */30 * * * *,5 段式),保存后确认。
  6. 漂移弹窗提示「暂无漂移」,但实际环境有差异:原因是该目标没有成功同步过(OperationSummary 为空,无从收集期望状态 id),或 drift 事件的字段名与前端别名不匹配。处理:先执行一次成功同步,再重开「漂移」弹窗;仍空则核对仓库目录与目标环境实际状态。
  7. 历史弹窗全空/只有一条:原因是该目标从未同步过,或同步历史被级联删除(删目标/删仓库)。处理:先手动同步一次再看历史;确认不是刚删过目标。
  8. 目标环境列显示纯数字 #N:原因是页面上拉取的环境列表不包含该 id(环境被删或环境列表接口失败)。处理:到环境管理页确认该环境是否存在;此显示属兜底,不阻断其它操作。

8. 已知缺陷与边界

缺陷/边界说明位置
d. 同步/回滚提示 commit 恒为 -后端把结果放 sync_history/rollback_history,前端 parseSyncResult 只识别 history/sync/record,commit_sha 与 summary 取不到GitReposPage.vue 的 parseSyncResult;gitops_handlers handleSyncTargetSync/Rollback
Git 仓库为本地目录模拟url 指向磁盘目录,无真实 git 拉取/远端凭据;commit 为内容模拟 SHATAD-02 localrepo 自研实现
认证方式仅记录不鉴权前端下拉值 ssh_key/basic 与后端枚举 ssh/https_token 不一致;本地目录模拟下 auth 不参与连接GitReposPage AUTH_TYPES vs gitops models
漂移策略仅识 alert/auto_fix后端只认这两个值,reject/remediate 等旧值不被识别DRIFT_POLICIES 注释(对齐 gitops/models.go)
项目 id 写死为 1仓库/目标/环境下拉都固定请求 /projects/1/...GitReposPage.vue PROJECT_ID 常量
展开结果有前端缓存首次展开后复用缓存,外部改动需靠本页写操作触发强制刷新syncMap 缓存逻辑
同步目标列表全量拉取后前端过滤子区用项目级 /sync-targets 全量列表按 git_repo_id 过滤,仓库多时列表大loadSyncTargets
编辑同步目标不可迁移仓库git_repo_id 下拉在编辑态 disabledtargetModal template
无分页/搜索仓库与目标均全量一次性渲染fetchRepos/loadSyncTargets
历史/漂移无自动更新弹窗打开时拉取,后台变化需重开弹窗刷新openHistory/openDrift