dcdc8d1ddd
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环): - 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口) - 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入 parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截 - 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过): * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id) * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配 * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数 * 回滚 job payload 支持 prior_execution_id(strict 解码) - 三通道发布+回滚演练实测:publish 4/4 verified,检索命中, rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false - 前端 A·Data-console 风格全站统一,120 用例通过 - 门禁:go vet / go test 全包通过,gofmt 干净
36 lines
5.5 KiB
TypeScript
36 lines
5.5 KiB
TypeScript
import { useContext,useEffect,useState } from 'react'
|
||
import { fetchCatalogSources } from './sourceCatalog'
|
||
import type { CatalogSource } from './sourceCatalog'
|
||
import { WorkspaceContextTabsSetter } from './workspaceContext'
|
||
import './sourceLinkManager.css'
|
||
const typeLabel={dingtalk:'钉钉知识库',pp:'PP资料下载中心',alist:'AList文件源'}
|
||
const authLabel={dingtalk:'专用浏览器扫码登录',pp:'公开访问,无需Key',alist:'只读Token'}
|
||
const actionLabel={dingtalk:'模拟登录并验证目录',pp:'模拟验证公开目录',alist:'模拟验证只读Token'}
|
||
const placeholder={dingtalk:'粘贴钉钉知识库网页链接(模拟)',pp:'粘贴PP资料下载中心链接(模拟)',alist:'例如:https://alist.example.com(模拟)'}
|
||
const linkLabel={dingtalk:'知识库链接',pp:'资料中心地址',alist:'AList站点地址'}
|
||
interface Props{sources:CatalogSource[];selected:CatalogSource|null;validated:boolean;loading:boolean;error:string|null;onSelect:(source:CatalogSource)=>void;onValidate:()=>void;onRefresh:()=>void}
|
||
export function SourceLinkManagerView({sources,selected,validated,loading,error,onSelect,onValidate,onRefresh}:Props){
|
||
const [link,setLink]=useState('')
|
||
return <main className="page-shell source-link-manager">
|
||
<header className="hero compact"><div><p className="eyebrow">连接配置</p><h1>来源链接</h1><p className="subtitle">配置钉钉、PP和AList的入口、认证方式及目录验证。</p></div><button onClick={onRefresh} disabled={loading}>{loading?'刷新中…':'刷新来源'}</button></header>
|
||
{error?<div className="error-banner" role="alert">{error}</div>:null}
|
||
<div className="simulation-banner"><strong>本地模拟配置</strong><span>使用真实来源名称和快照数量,但不连接登录服务、不保存真实Key。</span></div>
|
||
<div className="source-link-layout">
|
||
<aside className="source-link-list"><h2>内容来源</h2>{sources.map(source=><button className={selected?.connection_id===source.connection_id?'active':''} key={source.connection_id} onClick={()=>{setLink('');onSelect(source)}}><span><b>{source.name}</b><small>{typeLabel[source.source_type]}</small></span><strong>{source.file_count}份</strong></button>)}{!loading&&!error&&!sources.length?<div className="empty-state">尚无来源链接。</div>:null}</aside>
|
||
<section className="source-link-editor">{selected?<><div className="source-link-title"><div><h2>{selected.name}</h2><span>{typeLabel[selected.source_type]} · 本地只读快照</span></div><span className="connection-unverified">模拟验证待执行</span></div>
|
||
<div className="source-link-fields"><label>{linkLabel[selected.source_type]}<input value={link} onChange={event=>setLink(event.target.value)} placeholder={placeholder[selected.source_type]} /></label><label>认证方式<input value={authLabel[selected.source_type]} readOnly /></label><label>Key或Token<input value="" disabled placeholder="本地模拟阶段不接收真实Key" /></label><label>起始目录<input value={selected.source_type==='alist'?`/${selected.name}`:selected.name} readOnly /></label></div>
|
||
<div className="source-link-boundary"><b>当前快照</b><span>文件 {selected.file_count}</span><span>目录 {selected.folder_count}</span><span>不下载文件内容,只读取目录和元数据</span><span>不执行上传、移动、重命名或删除</span></div>
|
||
<button className="validate-source-link" onClick={onValidate}>{actionLabel[selected.source_type]}</button>
|
||
{validated?<div className="validation-results" aria-label="模拟验证结果"><div><span>网络连接</span><b>模拟通过</b></div><div><span>登录或Token</span><b>模拟通过</b></div><div><span>根目录</span><b>模拟通过</b></div><div><span>读取第一层目录</span><b>模拟通过</b></div><div><span>只读边界</span><b>模拟通过</b></div><small>以上只是本地交互模拟;迁移到测试服务器后才执行真实验证。</small></div>:null}
|
||
<p className="credential-note">真实模式下,新凭据先验证,成功后才替换旧凭据;网页永远不能读取已保存Key。</p>
|
||
</>:loading?<div className="empty-state">正在加载来源…</div>:null}</section>
|
||
</div>
|
||
</main>
|
||
}
|
||
export function SourceLinkManager(){
|
||
const [sources,setSources]=useState<CatalogSource[]>([]);const [selected,setSelected]=useState<CatalogSource|null>(null);const [validated,setValidated]=useState(false);const [loading,setLoading]=useState(true);const [error,setError]=useState<string|null>(null);const [revision,setRevision]=useState(0);const setContextTabs=useContext(WorkspaceContextTabsSetter)
|
||
useEffect(()=>{const controller=new AbortController();setLoading(true);void fetchCatalogSources(controller.signal).then(result=>{if(!controller.signal.aborted){setSources(result);setSelected(current=>current&&result.find(item=>item.connection_id===current.connection_id)||result[0]||null);setError(null)}}).catch(cause=>{if(!controller.signal.aborted)setError(cause instanceof Error?cause.message:'加载来源链接失败')}).finally(()=>{if(!controller.signal.aborted)setLoading(false)});return()=>controller.abort()},[revision])
|
||
useEffect(()=>{setContextTabs(selected?[{key:selected.connection_id,label:selected.name,onActivate:()=>undefined}]:[]);return()=>setContextTabs([])},[selected,setContextTabs])
|
||
return <SourceLinkManagerView sources={sources} selected={selected} validated={validated} loading={loading} error={error} onSelect={source=>{setSelected(source);setValidated(false)}} onValidate={()=>setValidated(true)} onRefresh={()=>{setValidated(false);setRevision(value=>value+1)}} />
|
||
}
|