You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

15 lines
43 KiB

import{g as me,j as Te,k as _e,o as a,c as o,a as e,d as v,l as k,F as M,r as J,n as O,t as i,m as de,p as ye,q as h,v as A,s as pe,x as ke,y as N,z as ee,A as Z,B as Me,_ as ge,C as he,D as Ne,E as Ie,G as De,H as Se,b as Ue,I as Ve}from"./6O01ZZML.js";import{u as be}from"./BgasD__e.js";import{u as ve}from"./xsgK-Nup.js";import{r as ce,n as fe,R as Oe}from"./7m3DvxFn.js";const Ae={class:"card"},Ee={class:"card-head"},Re={key:0,class:"py-14 text-center text-slate-400 text-sm"},je={class:"min-w-0"},Le={class:"dep-name"},ze={class:"truncate"},Be={key:0,class:"chip chip-warn"},Pe={key:1,class:"chip chip-ok"},qe={key:2,class:"chip"},Fe={class:"dep-sub"},He=["title"],Je={class:"flex items-center gap-1.5"},Ge={key:0,class:"text-[10.5px] text-amber-700",title:"latest 是可变 tag,升级后无法从列表看出实际版本"},Ye={class:"min-w-0"},Ke={class:"dep-acts"},Qe=["onClick"],We=["onClick"],Xe=["disabled","title","onClick"],Ze=["onClick"],et=["disabled","onClick"],tt=["disabled","onClick"],st=["onClick"],lt={class:"modal-box max-w-3xl"},nt={class:"font-bold text-lg"},at={key:0,class:"tabs tabs-boxed bg-slate-100 w-fit my-3"},ot={class:"grid grid-cols-2 gap-3"},it={class:"form-control"},dt=["value"],rt={class:"form-control"},ut=["placeholder"],pt={class:"grid grid-cols-2 gap-3"},ct={class:"form-control"},mt=["value"],gt={class:"form-control"},bt={class:"label py-1"},vt={class:"label-text text-sm font-medium"},xt={value:0},ft=["value"],_t={class:"grid grid-cols-3 gap-3"},yt={class:"form-control"},kt={class:"form-control"},ht=["placeholder"],$t={class:"form-control"},wt={class:"form-control"},Ct={class:"label py-1"},Tt={class:"label-text text-sm font-medium"},Mt={class:"form-control"},Nt={key:0,class:"text-sm space-y-0.5"},It={class:"form-control"},Dt={class:"flex items-center gap-2 cursor-pointer"},St={class:"modal-action"},Ut=["disabled"],Vt=["disabled"],Ot={key:1,class:"text-[11px] text-slate-400 text-right"},At={class:"modal-box max-w-2xl"},Et={class:"text-sm text-slate-500 mt-1"},Rt={class:"grid grid-cols-[76px_1fr] gap-x-3 gap-y-1 text-xs mt-4"},jt={class:"mt-3"},Lt={class:"flex items-center justify-between mb-1"},zt=["disabled"],Bt={key:0,class:"flex gap-2"},Pt=["disabled"],qt={value:""},Ft=["value"],Ht=["value"],Jt=["disabled"],Gt=["disabled"],Yt={key:2,class:"text-xs text-amber-600 mt-1"},Kt={key:3,class:"text-xs text-amber-600 mt-1"},Qt={key:0,class:"mt-3"},Wt={class:"flex items-center gap-2 text-xs mb-1"},Xt={key:0,class:"loading loading-spinner loading-xs text-amber-500"},Zt={key:1,class:"text-green-600 font-semibold"},es={key:2,class:"text-red-600 font-semibold"},ts={class:"text-slate-400"},ss={class:"modal-action"},ls=["disabled"],ns=["disabled"],as={method:"dialog",class:"modal-backdrop"},os=["disabled"],is={class:"modal-box"},ds={class:"text-sm text-slate-500 mt-1"},rs={class:"grid grid-cols-2 gap-3 mt-4"},us={key:0,class:"text-sm mt-3"},ps={key:0,class:"bg-slate-50 border border-slate-200 rounded-lg p-3"},cs={class:"flex justify-between items-center text-xs mb-1.5"},ms={class:"text-slate-600"},gs={class:"font-mono text-slate-400"},bs=["value"],vs={class:"mt-2 text-xs space-y-0.5"},xs={key:1,class:"text-red-600"},fs={key:2,class:"bg-green-50 border border-green-100 rounded-lg p-3"},_s={class:"grid grid-cols-2 gap-x-4 gap-y-1 text-xs text-slate-600 mb-2"},ys={class:"border-t border-green-100 pt-2 text-xs"},ks={class:"text-slate-500"},hs={class:"font-mono text-slate-700"},$s={class:"modal-action"},ws=["disabled"],Cs=["disabled"],Ts={method:"dialog",class:"modal-backdrop"},Ms=["disabled"],Ns=me({__name:"AppDeployTab",props:{appName:{},defaultImage:{},deployments:{},envs:{}},emits:["refresh","configure"],setup(P,{expose:E,emit:G}){const Y=P,B=G,{consoleApi:S}=be(),{success:$,error:g}=ve(),x=Te(),K=_e();function Q(d){const t=(d||"").match(/\/([^/?]+)\?/);return t?t[1]:d?"(自定义)":"未配置"}function I(d){const t=(d||"").trim();return!!t&&t!=="latest"}function f(d){return d?Y.envs.find(t=>t.id===d)?.name??"#"+d:""}function q(d){let t=(d.base_url||"").trim().replace(/\/+$/,"");if(!t){g("该部署未配置对外地址(base_url)");return}/^https?:\/\//i.test(t)||(t="https://"+t),window.open(t+"/swagger/index.html","_blank","noopener")}function R(d){window.confirm(`向部署 #${d} 发送一条配置 reload 事件?`)&&S("config/notify",{app_id:d,type:"all",action:"reload"}).then(t=>$("已发送 → "+(t&&t.subject))).catch(t=>g(t?.message??"发送失败"))}function V(d){window.confirm(`删除部署「${d.name||"#"+d.id}」?(不影响应用自身的库)`)&&S("apps/del",{id:d.id}).then(()=>{$("已删除"),B("refresh")}).catch(t=>g(t?.message??"删除失败"))}const W=N(null),j=N(!1),y=N("existing"),m=ee({id:0,name:"",region:"",env_id:0,base_url:"",service_dsn:"",deploy_dir:"",image_name:"",image_tag:"",remark:"",enabled:!0}),L=N(!1),_=N(!1),D=ee({show:!1,apiOk:!1,apiMsg:"",dbOk:!1,dbMsg:""}),ne=Z(()=>{const d=new Set(Y.deployments.filter(t=>t.id!==m.id).map(t=>fe(t.region)).filter(Boolean));return Oe.filter(t=>!d.has(t.value)||t.value===m.region)});function b(){L.value=!1,D.show=!1}function s(d){y.value=d,b()}function c(){j.value=!1,y.value="existing",Object.assign(m,{id:0,name:"",region:"",env_id:0,base_url:"",service_dsn:"",deploy_dir:"",image_name:Y.defaultImage,image_tag:"",remark:"",enabled:!0}),b(),W.value?.showModal()}function r(d){j.value=!0,y.value=d.status==="pending"?"deploy":"existing",Object.assign(m,{id:d.id,name:d.name||"",region:fe(d.region),env_id:d.env_id||0,base_url:d.base_url||"",service_dsn:d.service_dsn||"",deploy_dir:d.deploy_dir||"",image_name:d.image_name||"",image_tag:d.image_tag||"",remark:d.remark||"",enabled:!!d.enabled}),b(),W.value?.showModal()}async function u(){const d=m.base_url.trim(),t=m.service_dsn.trim();if(!d||!t){g("请先填应用地址和业务库 DSN");return}_.value=!0;try{const l=await S("apps/test",{base_url:d,service_dsn:t});D.apiOk=!!l.api_ok,D.apiMsg=l.api_msg,D.dbOk=!!l.db_ok,D.dbMsg=l.db_msg,D.show=!0,L.value=!!l.ok,L.value?$("检测通过,可以保存"):g("检测未通过")}catch(l){g(l?.message??"测试失败")}finally{_.value=!1}}async function te(){if(!m.name.trim()){g("请填部署名称");return}if(!j.value&&y.value==="existing"&&!L.value){g("请先「测试连接」通过再保存");return}if(y.value==="deploy"&&!m.env_id){g("请选择部署目标环境");return}const d={app_name:Y.appName,status:y.value==="deploy"?"pending":"existing",name:m.name,region:m.region,env_id:Number(m.env_id)||0,base_url:y.value==="deploy"?"":m.base_url,service_dsn:m.service_dsn,deploy_dir:m.deploy_dir,image_name:m.image_name,image_tag:m.image_tag,remark:m.remark,enabled:m.enabled};let t="apps/add";j.value&&m.id&&(d.id=Number(m.id),t="apps/update");try{await S(t,d),$("保存成功"),W.value?.close(),B("refresh")}catch(l){g(l?.message??"保存失败")}}const ae=N(null),X=N(null),n=ee({id:0,label:"",envName:"",deployDir:"",tag:"",tags:[],tagMode:"list",tagsLoading:!1,tagsError:"",log:"",show:!1,running:!1,ok:!1,canRun:!1,btnText:"开始升级"});function re(d){n.id=d.id,n.label="部署:"+(d.name||"部署#"+d.id)+(d.region?" · "+ce(d.region):""),n.envName=f(d.env_id),n.deployDir=d.deploy_dir||"",n.tag=d.image_tag||"",n.tags=[],n.tagMode="list",n.tagsError="",n.log="",n.show=!1,n.running=!1,n.ok=!1,n.canRun=!!(d.env_id&&(d.deploy_dir||"").trim()),n.btnText="开始升级",ae.value?.showModal(),n.canRun&&z()}async function z(){if(n.id){n.tagsLoading=!0,n.tagsError="";try{const d=await S("apps/tags",{id:n.id});n.tags=d.tags||[],n.tagMode="list",(!n.tag||!n.tags.includes(n.tag))&&(n.tag=n.tags.find(t=>t!=="latest")||n.tag||"")}catch(d){n.tagsError=d?.message??"拉取失败"}finally{n.tagsLoading=!1}}}async function F(){if(n.running)return;if(!n.canRun){const U=[];n.envName||U.push("绑定环境节点"),n.deployDir||U.push("填写部署目录"),window.alert(`无法升级:请先关闭本弹窗,点该部署的「编辑」完成 —— ${U.join("、")},保存后再来升级。`);return}const d=(n.tag||"").trim();if(!I(d)&&!window.confirm(`未选择明确版本号(当前 = ${d||"空 → 后端回退 latest"})。
latest 是可变 tag,升级后列表无法体现具体版本号,确定继续?`)||!window.confirm(`确认升级【${n.label}】?
目标版本:${d||"latest"}
将 SSH 连到「${n.envName}」,在 ${n.deployDir} 执行 docker compose 拉新镜像并重启容器。`))return;n.running=!0,n.show=!0,n.ok=!1,n.log="",n.btnText="升级中…";let t=null,l=!1;function w(U){n.log+=(n.log?`
`:"")+U,Me(()=>{X.value&&(X.value.scrollTop=X.value.scrollHeight)})}function oe(U){if(U=(U||"").trim(),!U)return;let C;try{C=JSON.parse(U)}catch{return}if(C.code===18){l=!0;return}if(C.code!==void 0&&C.code!==0&&C.type===void 0){t=C.msg||"请求被拒绝";return}C.type==="log"?w(C.line||""):C.type==="error"&&(t=C.msg,w("✗ "+(C.msg||"")))}try{const C=(await fetch("/console/api/apps/upgrade",{method:"POST",headers:{"Content-Type":"application/json",Authorization:x.token},body:JSON.stringify({id:n.id,tag:d})})).body.getReader(),T=new TextDecoder;let le="";for(;;){const se=await C.read();if(se.done)break;le+=T.decode(se.value,{stream:!0});const ie=le.split(`
`);le=ie.pop();for(const ue of ie)oe(ue)}oe(le)}catch(U){t="网络异常: "+U.message,w("✗ "+t)}if(l){x.logout(),K.push("/");return}n.running=!1,n.btnText="重新升级",n.ok=!t,t?g(t):($("升级完成"),B("refresh"))}const H=N(null),p=ee({appId:0,label:"",start:"",end:"",mode:"",steps:[],curIndex:0,curTotal:0,curName:"",pct:0,errMsg:"",report:null,running:!1,btnText:"开始分析"});function xe(d){return parseInt((d||"").replace(/-/g,""))||0}function $e(d){Object.assign(p,{appId:d.id,label:"部署:"+(d.name||"部署#"+d.id)+" (#"+d.id+")",start:"",end:"",mode:"",steps:[],curIndex:0,curTotal:0,curName:"",pct:0,errMsg:"",report:null,running:!1,btnText:"开始分析"}),H.value?.showModal()}async function we(){const d=p.start||"最早",t=p.end||"至今";if(!window.confirm(`将先删除【${p.label}】在 ${d} ~ ${t} 区间的历史统计数据,然后重新分析写入。
确认继续?`))return;Object.assign(p,{running:!0,btnText:"分析中...",mode:"progress",steps:[],curIndex:0,curTotal:0,curName:"",pct:0,report:null,errMsg:""});let l=null,w=null,oe=!1;function U(C){if(C=(C||"").trim(),!C)return;let T;try{T=JSON.parse(C)}catch{return}if(T.code===18){oe=!0;return}if(T.code!==void 0&&T.code!==0){w=T.msg||"请求被拒绝";return}T.type==="progress"?(p.steps[T.index-1]=T.name,p.pct=Math.round(T.index/(T.total+1)*100),p.curIndex=T.index,p.curTotal=T.total,p.curName=T.name):T.type==="done"?l=T.report:T.type==="error"&&(w=T.msg)}try{const T=(await fetch("/console/api/apps/analyze",{method:"POST",headers:{"Content-Type":"application/json",Authorization:x.token},body:JSON.stringify({app_id:p.appId,start_day:xe(p.start),end_day:xe(p.end)})})).body.getReader(),le=new TextDecoder;let se="";for(;;){const ie=await T.read();if(ie.done)break;se+=le.decode(ie.value,{stream:!0});const ue=se.split(`
`);se=ue.pop();for(const Ce of ue)U(Ce)}U(se)}catch(C){w="网络异常: "+C.message}if(oe){x.logout(),K.push("/");return}if(p.running=!1,p.btnText="重新分析",w){p.mode="error",p.errMsg=w;return}if(!l){p.mode="error",p.errMsg="分析中断,未收到完成事件";return}p.mode="done",p.report=l,$("分析完成"),B("refresh")}return E({openNew:c}),(d,t)=>(a(),o("div",null,[e("div",Ae,[e("div",Ee,[t[21]||(t[21]=e("div",null,[e("div",{class:"card-title"},"区域部署"),e("div",{class:"card-desc"},[v(" 每个区域一份部署,各自连自己的业务库。「升级」会 SSH 到绑定的环境节点,在部署目录执行 "),e("span",{class:"font-mono"},"docker compose pull & up -d"),v("。 ")])],-1)),e("button",{class:"btn btn-primary btn-xs text-white shrink-0",onClick:t[0]||(t[0]=l=>c())},"+ 部署新环境")]),P.deployments.length?k("",!0):(a(),o("div",Re," 该应用还没有部署,点右上「+ 部署新环境」添加。 ")),(a(!0),o(M,null,J(P.deployments,l=>(a(),o("div",{key:l.id,class:O(["dep",{pending:l.status==="pending"}])},[e("div",je,[e("div",Le,[t[22]||(t[22]=e("span",{class:"chip chip-dep"},"部署",-1)),e("span",ze,i(l.name||"部署#"+l.id),1),l.status==="pending"?(a(),o("span",Be,"待部署")):(a(),o("span",Pe,"已接入")),l.enabled?k("",!0):(a(),o("span",qe,"停用"))]),e("div",Fe,"#"+i(l.id)+" · "+i(de(ce)(l.region))+" · 业务库 "+i(Q(l.service_dsn)),1)]),e("div",null,[t[23]||(t[23]=e("div",{class:"field-l"},"环境节点",-1)),e("div",{class:O(["text-xs truncate",l.env_id?"text-slate-600":"text-red-500"]),title:f(l.env_id)||"未绑定"},i(f(l.env_id)||"未绑定"),11,He)]),e("div",null,[t[24]||(t[24]=e("div",{class:"field-l"},"当前版本",-1)),e("div",Je,[e("span",{class:O(["chip chip-mono",I(l.image_tag)?"chip-ok":"chip-warn"])},i(l.image_tag||"latest"),3),I(l.image_tag)?k("",!0):(a(),o("span",Ge,"未固定"))])]),e("div",Ye,[t[25]||(t[25]=e("div",{class:"field-l"},"部署目录",-1)),e("div",{class:O(["font-mono text-[11px] truncate",l.deploy_dir?"text-slate-500":"text-red-500"])},i(l.deploy_dir||"未填"),3)]),e("div",Ke,[e("button",{class:"btn btn-xs btn-warning",onClick:w=>re(l)},"升级",8,Qe),e("button",{class:"btn btn-xs",onClick:w=>B("configure",l.id)},"配置",8,We),e("button",{class:"btn btn-xs btn-ghost text-teal-600",disabled:!l.base_url,title:l.base_url?"打开该部署的 Swagger 文档":"该部署未配置对外地址(base_url)",onClick:w=>q(l)},"API 测试",8,Xe),e("button",{class:"btn btn-xs btn-ghost",onClick:w=>r(l)},"编辑",8,Ze),e("button",{class:"btn btn-xs btn-ghost text-emerald-600",disabled:l.status==="pending",onClick:w=>$e(l)},"分析",8,et),e("button",{class:"btn btn-xs btn-ghost text-indigo-500",disabled:l.status==="pending",onClick:w=>R(l.id)},"发事件",8,tt),e("button",{class:"btn btn-xs btn-ghost text-red-500",onClick:w=>V(l)},"删除",8,st)])],2))),128))]),e("dialog",{ref_key:"formDlg",ref:W,class:"modal"},[e("div",lt,[e("h3",nt,i(j.value?"编辑部署":"部署新环境"),1),j.value?k("",!0):(a(),o("div",at,[e("a",{class:O(["tab",y.value==="existing"?"tab-active":""]),onClick:t[1]||(t[1]=l=>s("existing"))},"添加现有",2),e("a",{class:O(["tab",y.value==="deploy"?"tab-active":""]),onClick:t[2]||(t[2]=l=>s("deploy"))},"部署到目标环境",2)])),e("p",{class:O(["text-xs text-slate-400 mb-4",j.value?"mt-1":""])},[j.value?(a(),o(M,{key:0},[v("改好后回列表点「升级」执行远程部署(拉新镜像并重启)。")],64)):y.value==="existing"?(a(),o(M,{key:1},[v("把「已经部署好的应用区域」加进来:填地址与业务库 DSN,测试通过后保存。")],64)):(a(),o(M,{key:2},[t[26]||(t[26]=v("选一个目标环境节点,先建关联并标记",-1)),t[27]||(t[27]=e("b",null,"待部署",-1)),t[28]||(t[28]=v(";保存后回列表点「升级」执行首次部署。",-1))],64))],2),e("form",{class:"space-y-3",onSubmit:ye(te,["prevent"])},[e("div",ot,[e("div",it,[t[29]||(t[29]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"所属应用")],-1)),e("input",{value:P.appName,type:"text",class:"input input-bordered input-sm bg-slate-100",readonly:""},null,8,dt)]),e("div",rt,[t[30]||(t[30]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"部署名称 *")],-1)),h(e("input",{"onUpdate:modelValue":t[3]||(t[3]=l=>m.name=l),type:"text",class:"input input-bordered input-sm",required:"",placeholder:`如 ${P.appName}(国内)`},null,8,ut),[[A,m.name]])])]),e("div",pt,[e("div",ct,[t[32]||(t[32]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"区域")],-1)),h(e("select",{"onUpdate:modelValue":t[4]||(t[4]=l=>m.region=l),class:"select select-bordered select-sm"},[t[31]||(t[31]=e("option",{value:""},"未指定",-1)),(a(!0),o(M,null,J(ne.value,l=>(a(),o("option",{key:l.value,value:l.value},i(l.label),9,mt))),128))],512),[[pe,m.region]]),t[33]||(t[33]=e("span",{class:"text-[11px] text-slate-400 mt-1"},"一个应用国内/海外各一份部署,已用的区域不再出现。",-1))]),e("div",gt,[e("label",bt,[e("span",vt,"部署环境"+i(y.value==="deploy"?" *":""),1)]),h(e("select",{"onUpdate:modelValue":t[5]||(t[5]=l=>m.env_id=l),class:"select select-bordered select-sm"},[e("option",xt,i(y.value==="deploy"?"请选择目标环境":"未关联"),1),(a(!0),o(M,null,J(P.envs,l=>(a(),o("option",{key:l.id,value:l.id},i(l.name)+"("+i(l.code||"-")+")",9,ft))),128))],512),[[pe,m.env_id,void 0,{number:!0}]])])]),e("div",_t,[e("div",yt,[t[34]||(t[34]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"部署目录")],-1)),h(e("input",{"onUpdate:modelValue":t[6]||(t[6]=l=>m.deploy_dir=l),type:"text",class:"input input-bordered input-sm font-mono text-xs",placeholder:"/home/work/app"},null,512),[[A,m.deploy_dir]])]),e("div",kt,[t[35]||(t[35]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"镜像名")],-1)),h(e("input",{"onUpdate:modelValue":t[7]||(t[7]=l=>m.image_name=l),type:"text",class:"input input-bordered input-sm font-mono text-xs",placeholder:P.defaultImage||"默认取应用"},null,8,ht),[[A,m.image_name]])]),e("div",$t,[t[36]||(t[36]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"镜像 Tag")],-1)),h(e("input",{"onUpdate:modelValue":t[8]||(t[8]=l=>m.image_tag=l),type:"text",class:"input input-bordered input-sm font-mono text-xs",placeholder:"0.1.1 / latest"},null,512),[[A,m.image_tag]])])]),e("div",wt,[e("label",Ct,[e("span",Tt,"业务库 DSN"+i(y.value==="existing"?" *":""),1)]),h(e("input",{"onUpdate:modelValue":t[9]||(t[9]=l=>m.service_dsn=l),type:"text",class:"input input-bordered input-sm font-mono text-xs",placeholder:"root:pwd@tcp(host:3306)/earphone?charset=utf8mb4&parseTime=True&loc=Local",onInput:b},null,544),[[A,m.service_dsn]]),t[37]||(t[37]=e("span",{class:"text-[11px] text-slate-400 mt-1"},"业务功能配置、会议记录服务配置都写在这个库里。",-1))]),y.value==="existing"?(a(),o(M,{key:0},[e("div",Mt,[t[38]||(t[38]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"应用地址 *")],-1)),h(e("input",{"onUpdate:modelValue":t[10]||(t[10]=l=>m.base_url=l),type:"text",class:"input input-bordered input-sm",placeholder:"https://api.xxx.com/",onInput:b},null,544),[[A,m.base_url]]),t[39]||(t[39]=e("span",{class:"text-[11px] text-slate-400 mt-1"},"用于通知应用业务服务事件(如配置更新)、打开 Swagger。",-1))]),D.show?(a(),o("div",Nt,[e("div",{class:O(D.apiOk?"text-green-700":"text-red-600")},i(D.apiOk?"✓":"✗")+" 应用地址:"+i(D.apiMsg),3),e("div",{class:O(D.dbOk?"text-green-700":"text-red-600")},i(D.dbOk?"✓":"✗")+" 业务库:"+i(D.dbMsg),3)])):k("",!0)],64)):k("",!0),e("div",It,[t[40]||(t[40]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"备注")],-1)),h(e("input",{"onUpdate:modelValue":t[11]||(t[11]=l=>m.remark=l),type:"text",class:"input input-bordered input-sm"},null,512),[[A,m.remark]])]),e("label",Dt,[h(e("input",{"onUpdate:modelValue":t[12]||(t[12]=l=>m.enabled=l),type:"checkbox",class:"toggle toggle-primary toggle-sm"},null,512),[[ke,m.enabled]]),t[41]||(t[41]=e("span",{class:"text-sm"},"启用",-1))]),e("div",St,[e("button",{type:"button",class:"btn btn-ghost btn-sm",onClick:t[13]||(t[13]=l=>W.value?.close())},"取消"),y.value==="existing"?(a(),o("button",{key:0,type:"button",class:"btn btn-outline btn-sm",disabled:_.value,onClick:u},i(_.value?"测试中…":"测试连接"),9,Ut)):k("",!0),e("button",{type:"submit",class:"btn btn-primary btn-sm text-white",disabled:!j.value&&y.value==="existing"&&!L.value},"保存",8,Vt)]),!j.value&&y.value==="existing"?(a(),o("p",Ot," 需先「测试连接」通过(应用地址可达 + 业务库可连)才能保存。 ")):k("",!0)],32)]),t[42]||(t[42]=e("form",{method:"dialog",class:"modal-backdrop"},[e("button",null,"close")],-1))],512),e("dialog",{ref_key:"upgradeDlg",ref:ae,class:"modal"},[e("div",At,[t[46]||(t[46]=e("h3",{class:"font-bold text-lg"},"升级部署",-1)),e("p",Et,i(n.label),1),t[47]||(t[47]=e("div",{class:"text-xs text-amber-700 mt-3 bg-amber-50 border border-amber-100 rounded-lg p-3 leading-relaxed"},[v(" 将 "),e("b",null,"SSH 连到该部署绑定的环境节点"),v(",进入部署目录执行 "),e("span",{class:"font-mono"},"docker login → compose pull → up -d → 清理旧镜像"),v("。"),e("br"),v(" · 需服务器上已维护好 "),e("span",{class:"font-mono"},"docker-compose.yml / .env / confs"),v("(本操作不上传真实配置)。"),e("br"),v(" · 会"),e("b",null,"重启该部署的容器"),v(",请确认无误后再执行。 ")],-1)),e("dl",Rt,[t[43]||(t[43]=e("dt",{class:"text-slate-400"},"环境节点",-1)),e("dd",{class:O(n.envName?"font-medium text-slate-700":"font-medium text-red-500")},i(n.envName||"未绑定 —— 请先在「编辑」里选择环境节点"),3),t[44]||(t[44]=e("dt",{class:"text-slate-400"},"部署目录",-1)),e("dd",{class:O(n.deployDir?"font-mono text-slate-700":"font-medium text-red-500")},i(n.deployDir||"未填 —— 请先在「编辑」里填写远端部署目录"),3)]),e("div",jt,[e("div",Lt,[t[45]||(t[45]=e("label",{class:"text-xs text-slate-500"},"镜像版本 Tag",-1)),e("button",{type:"button",class:"text-xs text-indigo-500 hover:underline",disabled:n.running,onClick:t[14]||(t[14]=l=>n.tagMode=n.tagMode==="list"?"custom":"list")},i(n.tagMode==="list"?"手动输入…":"从仓库选择"),9,zt)]),n.tagMode==="list"?(a(),o("div",Bt,[h(e("select",{"onUpdate:modelValue":t[15]||(t[15]=l=>n.tag=l),class:"select select-bordered select-sm flex-1 font-mono",disabled:n.running||n.tagsLoading},[e("option",qt,i(n.tagsLoading?"加载可用版本中…":"请选择版本"),1),n.tag&&!n.tags.includes(n.tag)?(a(),o("option",{key:0,value:n.tag},i(n.tag)+"(当前记录)",9,Ft)):k("",!0),(a(!0),o(M,null,J(n.tags,l=>(a(),o("option",{key:l,value:l},i(l)+i(l==="latest"?" · 不固定,不推荐":""),9,Ht))),128))],8,Pt),[[pe,n.tag]]),e("button",{type:"button",class:"btn btn-ghost btn-sm",disabled:n.running||n.tagsLoading,onClick:z},"刷新",8,Jt)])):h((a(),o("input",{key:1,"onUpdate:modelValue":t[16]||(t[16]=l=>n.tag=l),type:"text",class:"input input-bordered input-sm w-full font-mono",placeholder:"0.1.1",disabled:n.running},null,8,Gt)),[[A,n.tag]]),n.tagsError?(a(),o("p",Yt," 拉取仓库版本失败:"+i(n.tagsError)+"(可点上方「手动输入」直接填版本号,或点「刷新」重试) ",1)):I(n.tag)?k("",!0):(a(),o("p",Kt," ⚠ 未固定版本,升级后列表无法体现具体版本号,建议选明确版本(如 0.1.1)。 "))]),n.show?(a(),o("div",Qt,[e("div",Wt,[n.running?(a(),o("span",Xt)):n.ok?(a(),o("span",Zt,"✓ 部署完成")):(a(),o("span",es,"✗ 部署失败")),e("span",ts,i(n.running?"执行中…":""),1)]),e("pre",{ref_key:"upLogBox",ref:X,class:"bg-slate-900 text-slate-100 text-[11px] leading-relaxed rounded-lg p-3 h-64 overflow-auto font-mono whitespace-pre-wrap"},i(n.log||"(等待输出…)"),513)])):k("",!0),e("div",ss,[e("button",{class:"btn btn-ghost btn-sm",disabled:n.running,onClick:t[17]||(t[17]=l=>ae.value?.close())},"关闭",8,ls),e("button",{class:"btn btn-warning btn-sm",disabled:n.running,onClick:F},i(n.btnText),9,ns)])]),e("form",as,[e("button",{disabled:n.running},"close",8,os)])],512),e("dialog",{ref_key:"analyzeDlg",ref:H,class:"modal"},[e("div",is,[t[55]||(t[55]=e("h3",{class:"font-bold text-lg"},"分析历史数据",-1)),e("p",ds,i(p.label),1),t[56]||(t[56]=e("div",{class:"text-xs text-amber-700 mt-3 bg-amber-50 border border-amber-100 rounded-lg p-3 leading-relaxed"},[v(" 扫描该应用业务库,按 "),e("b",null,"天 + 产品 + 区域"),v(" 生成历史统计(新增用户 / 订单 / 资源发放·使用 / 激活绑定设备),汇入 console 主库。"),e("br"),v(" · "),e("b",null,"分析前会先清理该应用+区域在所选区间内的旧统计行"),v(",避免数据重叠。"),e("br"),v(" · 登录、活跃用户、AI对话等无每日原始记录,历史无法还原(由实时管道往后采集)。"),e("br"),v(" · 仅对实时管道上线前的区间运行。留空 = 全部历史。大库可能耗时。 ")],-1)),e("div",rs,[e("div",null,[t[48]||(t[48]=e("label",{class:"text-xs text-slate-500"},"起始日(含)",-1)),h(e("input",{"onUpdate:modelValue":t[18]||(t[18]=l=>p.start=l),type:"date",class:"input input-bordered input-sm w-full"},null,512),[[A,p.start]])]),e("div",null,[t[49]||(t[49]=e("label",{class:"text-xs text-slate-500"},"结束日(含)",-1)),h(e("input",{"onUpdate:modelValue":t[19]||(t[19]=l=>p.end=l),type:"date",class:"input input-bordered input-sm w-full"},null,512),[[A,p.end]])])]),p.mode?(a(),o("div",us,[p.mode==="progress"?(a(),o("div",ps,[e("div",cs,[e("span",ms,[p.curName?(a(),o(M,{key:0},[t[50]||(t[50]=e("span",{class:"loading loading-spinner loading-xs"},null,-1)),v(" 正在:"+i(p.curName)+" ("+i(p.curIndex)+"/"+i(p.curTotal)+")",1)],64)):(a(),o(M,{key:1},[v("准备中...")],64))]),e("span",gs,i(p.pct)+"%",1)]),e("progress",{class:"progress progress-primary w-full h-2",value:p.pct,max:"100"},null,8,bs),e("div",vs,[(a(!0),o(M,null,J(p.steps,(l,w)=>(a(),o("div",{key:w,class:O(w===p.curIndex-1?"text-slate-600":"text-green-600")},[w===p.curIndex-1?(a(),o(M,{key:0},[t[51]||(t[51]=e("span",{class:"loading loading-spinner loading-xs"},null,-1)),v(" "+i(l),1)],64)):(a(),o(M,{key:1},[v("✓ "+i(l),1)],64))],2))),128))])])):p.mode==="error"?(a(),o("div",xs,"✗ "+i(p.errMsg),1)):p.mode==="done"&&p.report?(a(),o("div",fs,[t[54]||(t[54]=e("div",{class:"font-semibold text-green-700 mb-2"},"✓ 分析完成",-1)),e("div",_s,[e("div",null,[t[52]||(t[52]=v("应用:",-1)),e("b",null,i(p.report.app_name||""),1)]),e("div",null,"区域:"+i(de(ce)(p.report.region)),1),e("div",null,"区间:"+i((p.report.start_day||"不限")+" ~ "+(p.report.end_day||"不限")),1),e("div",null,"清理旧行:"+i(p.report.cleared_rows||0),1),e("div",null,[t[53]||(t[53]=v("写入天数:",-1)),e("b",null,i(p.report.days_written||0),1)]),e("div",null,"写入行数:"+i(p.report.rows_written||0),1)]),e("div",ys,[(a(!0),o(M,null,J(p.report.steps||[],(l,w)=>(a(),o("div",{key:w,class:"flex justify-between py-0.5"},[e("span",ks,i(l.name),1),e("span",hs,i((l.groups||0)+" 组"),1)]))),128))])])):k("",!0)])):k("",!0),e("div",$s,[e("button",{class:"btn btn-ghost btn-sm",disabled:p.running,onClick:t[20]||(t[20]=l=>H.value?.close())},"关闭",8,ws),e("button",{class:"btn btn-primary btn-sm text-white",disabled:p.running,onClick:we},i(p.btnText),9,Cs)])]),e("form",Ts,[e("button",{disabled:p.running},"close",8,Ms)])],512)]))}}),Is=Object.assign(ge(Ns,[["__scopeId","data-v-c511dcb4"]]),{__name:"AppDeployTab"}),Ds={class:"card"},Ss={class:"card-head"},Us={class:"card-desc"},Vs=["disabled"],Os={key:0,class:"m-4 alert alert-warning py-2 text-sm"},As={key:1,class:"py-14 text-center"},Es={key:2,class:"cfg"},Rs={class:"cfg-nav"},js={class:"cfg-group"},Ls=["aria-current","onClick"],zs={key:0,class:"dot",title:"库中已有配置"},Bs={class:"cfg-form"},Ps={class:"flex items-start justify-between gap-3 mb-4 flex-wrap"},qs={class:"text-sm font-semibold text-slate-800"},Fs={key:0,class:"text-[11px] text-slate-400 mt-0.5"},Hs={class:"flex items-center gap-2"},Js=["disabled"],Gs=["disabled"],Ys=["disabled"],Ks={class:"grid grid-cols-1 gap-3 max-w-2xl"},Qs={class:"label py-0.5"},Ws={class:"label-text text-sm"},Xs={key:0,class:"text-rose-400"},Zs={key:1,class:"lock-tag ml-1"},el={class:"text-[10px] font-normal text-slate-300 ml-1 font-mono"},tl={key:0,class:"cursor-pointer flex items-center gap-2"},sl=["checked","onChange"],ll={class:"text-xs text-slate-500"},nl=["onUpdate:modelValue"],al=["value"],ol=["onUpdate:modelValue","placeholder"],il={key:3,class:"relative"},dl=["onUpdate:modelValue","type"],rl=["onClick"],ul=["onUpdate:modelValue","placeholder"],pl={key:5,class:"text-[11px] text-slate-400 mt-0.5"},cl={key:1,class:"py-10 text-center text-slate-400 text-sm"},ml=me({__name:"AppModuleTab",props:{appId:{},depName:{}},setup(P){const E=P,{consoleApi:G}=be(),{success:Y,info:B,error:S}=ve(),$=N([]),g=N(""),x=ee({}),K=ee(new Set),Q=ee({}),I=N(!1),f=N(!1),q=N(""),R=Z(()=>$.value.find(s=>s.module===g.value)||null),V=Z(()=>x[g.value]||{}),W=Z(()=>{const s=[],c=new Map;for(const r of $.value)c.has(r.group)||(c.set(r.group,[]),s.push(r.group)),c.get(r.group).push(r);return s.map(r=>({name:r,modules:c.get(r)}))});function j(s,c){const r=x[g.value]||(x[g.value]={});r[s]=c.target.checked?"true":"false"}async function y(){try{const s=await G("modulecfg/catalog",{});$.value=s?.modules??[];for(const c of $.value)x[c.module]||(x[c.module]={});$.value.length&&!g.value&&(g.value=$.value[0].module)}catch(s){S(s?.message??"获取配置目录失败")}}async function m(){if(E.appId){I.value=!0,q.value="";try{const s=await G("modulecfg/list",{app_id:E.appId});for(const c of $.value)x[c.module]={};K.clear();for(const c of s?.items??[])x[c.module]||(x[c.module]={}),x[c.module][c.key]=c.value,K.add(c.module)}catch(s){q.value=s?.message??"连接失败"}finally{I.value=!1}}}function L(s,c,r="配置已保存"){const u=s??[];if(!u.length){B(`${r},但未收到任何服务的生效回执 —— 目标应用可能未运行、未填对外地址(base_url)、或两端 FIELD_ENCRYPT_KEY 不一致,需重启后生效`);return}const te=z=>{const F=c?z.results?.filter(H=>H.module===c):z.results;if(!(!F||!F.length)){if(F.some(H=>H.status==="failed"))return"failed";if(F.some(H=>H.status==="restart_required"))return"restart_required";if(F.some(H=>H.status==="applied"))return"applied"}},ae=z=>(c?z.results?.find(F=>F.module===c):z.results?.find(F=>F.status==="failed"))?.err??"",X=u.filter(z=>te(z)==="failed"),n=u.filter(z=>te(z)==="restart_required"),re=u.filter(z=>te(z)==="applied");X.length?S(`${r},但 ${X.length}/${u.length} 个服务重载失败(仍在使用旧配置):${X[0].instance}: ${ae(X[0])}`):n.length?B(`${r}。${n.length}/${u.length} 个服务需重启才生效(含支付等暂不支持热更新的模块)`):re.length?Y(`${r},${re.length}/${u.length} 个服务已实时生效`):B(`${r},收到 ${u.length} 个服务的回执`)}async function _(){if(R.value){f.value=!0;try{const s=R.value.fields.map(r=>({key:r.key,value:x[g.value]?.[r.key]??""})),c=await G("modulecfg/save",{app_id:E.appId,module:g.value,items:s});L(c?.acks,g.value),await m()}catch(s){S(s?.message??"保存失败")}finally{f.value=!1}}}async function D(){if(R.value&&confirm(`确认把「${R.value.name}」还原为业务服务配置文件(yaml)里的初始值?
库中该模块现有的配置将被全部覆盖,且无法撤销。`)){f.value=!0;try{const s=await G("modulecfg/reset",{app_id:E.appId,module:g.value});L(s?.acks,g.value,"已还原为配置文件初始值"),await m()}catch(s){S(s?.message??"重置失败")}finally{f.value=!1}}}async function ne(){const s=prompt(`⚠️ 危险操作:把「${E.depName}」业务库里【所有】业务模块的配置,整体还原为业务服务配置文件(yaml)的初始值。
这会覆盖你在后台手工保存过的全部配置(例如邮件的 Resend 设置会被打回 yaml 默认值),且无法撤销。
确认请输入:RESET`);if(s!=="RESET"){s!==null&&B("已取消(未输入 RESET)");return}f.value=!0;try{const c=await G("modulecfg/reset",{app_id:E.appId,module:"*"});L(c?.acks,void 0,"整库已还原为配置文件初始值"),await m()}catch(c){S(c?.message??"全部重置失败")}finally{f.value=!1}}async function b(){if(g.value&&confirm(`确认清空「${R.value?.name}」的全部配置?`)){f.value=!0;try{await G("modulecfg/del",{app_id:E.appId,module:g.value}),Y("已清空"),await m()}catch(s){S(s?.message??"清空失败")}finally{f.value=!1}}}return he(async()=>{await y(),await m()}),(s,c)=>(a(),o("div",Ds,[e("div",Ss,[e("div",null,[c[2]||(c[2]=e("div",{class:"card-title"},"业务功能配置",-1)),e("div",Us,[c[0]||(c[0]=v(" 登录 / 短信 / 邮件 / 支付等应用独立配置,保存在",-1)),e("b",null,"「"+i(P.depName)+"」这个部署的业务库",1),c[1]||(c[1]=v("(不进公共库)。密钥字段加密落库,显示时脱敏。 ",-1))])]),e("button",{class:"btn btn-outline btn-error btn-xs shrink-0",disabled:f.value||!!q.value,title:"把该部署业务库里所有业务模块的配置,整体还原为业务服务配置文件(yaml)的初始值。用于升级/迁移把库写脏后的整体恢复。",onClick:ne},"全部重置为配置文件",8,Vs)]),q.value?(a(),o("div",Os,[e("span",null,[v(" 无法连接应用业务库:"+i(q.value),1),c[3]||(c[3]=e("br",null,null,-1)),c[4]||(c[4]=e("span",{class:"text-xs opacity-70"},"待部署的应用先完成部署、或检查业务库 DSN 连通后再配置。",-1))])])):k("",!0),I.value?(a(),o("div",As,[...c[5]||(c[5]=[e("span",{class:"loading loading-spinner loading-sm"},null,-1)])])):(a(),o("div",Es,[e("aside",Rs,[(a(!0),o(M,null,J(W.value,r=>(a(),o("div",{key:r.name},[e("div",js,i(r.name),1),(a(!0),o(M,null,J(r.modules,u=>(a(),o("button",{key:u.module,class:"cfg-item","aria-current":g.value===u.module,onClick:te=>g.value=u.module},[e("span",null,i(u.name),1),K.has(u.module)?(a(),o("span",zs)):k("",!0)],8,Ls))),128))]))),128))]),e("section",Bs,[R.value?(a(),o(M,{key:0},[e("div",Ps,[e("div",null,[e("div",qs,i(R.value.name),1),R.value.desc?(a(),o("div",Fs,i(R.value.desc),1)):k("",!0)]),e("div",Hs,[e("button",{class:"btn btn-ghost btn-xs text-amber-600",disabled:f.value,title:"丢弃库中该模块的全部配置,改用业务服务配置文件(yaml)里的初始值重新写入。",onClick:D},"重置",8,Js),K.has(g.value)?(a(),o("button",{key:0,class:"btn btn-ghost btn-xs text-rose-500",disabled:f.value,onClick:b},"清空",8,Gs)):k("",!0),e("button",{class:"btn btn-primary btn-sm text-white",disabled:f.value||!!q.value,onClick:_},i(f.value?"保存中…":"保存"),9,Ys)])]),e("div",Ks,[(a(!0),o(M,null,J(R.value.fields,r=>(a(),o("div",{key:r.key,class:"form-control"},[e("label",Qs,[e("span",Ws,[v(i(r.label)+" ",1),r.required?(a(),o("span",Xs,"*")):k("",!0),r.secret?(a(),o("span",Zs,"🔒")):k("",!0),e("span",el,i(r.key),1)])]),r.type==="bool"?(a(),o("label",tl,[e("input",{type:"checkbox",class:"toggle toggle-sm toggle-primary",checked:V.value[r.key]==="true",onChange:u=>j(r.key,u)},null,40,sl),e("span",ll,i(V.value[r.key]==="true"?"开启":"关闭"),1)])):r.type==="select"?h((a(),o("select",{key:1,"onUpdate:modelValue":u=>V.value[r.key]=u,class:"select select-bordered select-sm w-full"},[c[6]||(c[6]=e("option",{value:""},"请选择",-1)),(a(!0),o(M,null,J(r.options,u=>(a(),o("option",{key:u.value,value:u.value},i(u.label),9,al))),128))],8,nl)),[[pe,V.value[r.key]]]):r.type==="textarea"?h((a(),o("textarea",{key:2,"onUpdate:modelValue":u=>V.value[r.key]=u,class:"textarea textarea-bordered w-full font-mono text-xs",rows:"3",placeholder:r.secret?"留空/保持 [encrypted] 则不变":r.placeholder||""},null,8,ol)),[[A,V.value[r.key]]]):r.type==="password"?(a(),o("div",il,[h(e("input",{"onUpdate:modelValue":u=>V.value[r.key]=u,type:Q[r.key]?"text":"password",class:"input input-bordered input-sm w-full font-mono text-xs pr-14",placeholder:"留空/保持 [encrypted] 则不变"},null,8,dl),[[Ne,V.value[r.key]]]),e("button",{type:"button",class:"absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400 hover:text-slate-700",onClick:u=>Q[r.key]=!Q[r.key]},i(Q[r.key]?"隐藏":"显示"),9,rl)])):h((a(),o("input",{key:4,"onUpdate:modelValue":u=>V.value[r.key]=u,type:"text",class:"input input-bordered input-sm w-full font-mono text-xs",placeholder:r.placeholder||""},null,8,ul)),[[A,V.value[r.key]]]),r.desc?(a(),o("span",pl,i(r.desc),1)):k("",!0)]))),128))])],64)):(a(),o("div",cl,"暂无可配置的业务模块。"))])]))]))}}),gl=Object.assign(ge(ml,[["__scopeId","data-v-ff0e4b71"]]),{__name:"AppModuleTab"}),bl={class:"flex items-center gap-2 mb-4 text-sm"},vl={class:"font-semibold text-slate-700"},xl={key:0,class:"bg-white rounded-xl border border-slate-100 py-16 text-center"},fl={key:1,class:"bg-white rounded-xl border border-slate-100 py-16 text-center"},_l={class:"text-slate-500"},yl={class:"hero"},kl={class:"hero-logo"},hl={class:"flex-1 min-w-[240px]"},$l={class:"hero-name"},wl={class:"chip chip-mono"},Cl={class:"text-xs text-slate-500 mt-1.5"},Tl={class:"flex gap-2"},Ml={class:"kpis"},Nl={class:"kpi accent"},Il={class:"kpi-v"},Dl={class:"kpi ok"},Sl={class:"kpi-v"},Ul={class:"kpi-v"},Vl={class:"kpi"},Ol={class:"kpi-v",style:{"font-size":"14px","padding-top":"5px"}},Al={class:"tablist",role:"tablist"},El=["aria-selected"],Rl={class:"tab-n"},jl=["aria-selected"],Ll={key:0,class:"depbar"},zl={class:"flex gap-1.5 flex-wrap"},Bl=["aria-pressed","title","onClick"],Pl={class:"text-[10.5px] text-slate-400 font-mono"},ql={key:0,class:"text-xs text-slate-400 py-1"},Fl={key:2,class:"bg-white rounded-xl border border-slate-100 py-16 text-center text-slate-400"},Hl={class:"modal-box max-w-lg"},Jl={class:"form-control"},Gl={class:"form-control"},Yl={class:"form-control"},Kl={class:"flex items-center gap-2 cursor-pointer pt-1"},Ql={class:"modal-action"},Wl=me({__name:"[name]",setup(P){const{consoleApi:E}=be(),{success:G,error:Y}=ve(),B=Ie(),S=_e(),$=Z(()=>String(B.params.name||"")),g=N(null),x=N([]),K=N([]),Q=N(!1),I=N(B.query.tab==="modules"?"modules":"deps"),f=N(0),q=N(null),R=Z(()=>x.value.filter(b=>b.status!=="pending").length),V=Z(()=>x.value.filter(b=>b.status==="pending").length),W=Z(()=>{const b=x.value.find(s=>s.id===f.value);return b?b.name||"部署#"+b.id:""}),j=Z(()=>{const b=[...new Set(x.value.filter(s=>s.status!=="pending").map(s=>s.image_tag||"latest"))];return b.length?b.join(" · "):"—"});async function y(){Q.value=!0;try{const[b,s,c]=await Promise.all([E("products/list",{}),E("apps/list",{}),E("envs/list",{})]);if(g.value=(b??[]).find(r=>r.name===$.value)??null,x.value=(s??[]).filter(r=>r.app_name===$.value),K.value=c??[],!x.value.some(r=>r.id===f.value)){const r=x.value.find(u=>u.status!=="pending")??x.value[0];f.value=r?.id??0}}catch(b){Y(b?.message??"加载应用详情失败")}finally{Q.value=!1}}function m(b){f.value=b,I.value="modules"}const L=N(null),_=ee({id:0,name:"",image_name:"",remark:"",enabled:!0});function D(){const b=g.value;b&&(_.id=b.id,_.name=b.name,_.image_name=b.image_name??"",_.remark=b.remark??"",_.enabled=!!b.enabled,L.value?.showModal())}async function ne(){try{const b=_.name!==$.value;await E("products/update",{id:_.id,name:_.name,image_name:_.image_name,remark:_.remark,enabled:_.enabled}),G("保存成功"),L.value?.close(),b?S.replace(`/apps/${encodeURIComponent(_.name)}`):await y()}catch(b){Y(b?.message??"保存失败")}}return De($,y),he(y),(b,s)=>{const c=Is,r=gl;return a(),o("div",null,[e("div",bl,[e("button",{class:"btn btn-ghost btn-xs",onClick:s[0]||(s[0]=u=>de(S).push("/apps"))},"← 应用中心"),s[11]||(s[11]=e("span",{class:"text-slate-300"},"/",-1)),e("span",vl,i($.value),1)]),Q.value?(a(),o("div",xl,[...s[12]||(s[12]=[e("span",{class:"loading loading-spinner loading-sm"},null,-1)])])):g.value?(a(),o(M,{key:2},[e("div",yl,[e("div",kl,i((g.value.name||"?").charAt(0).toUpperCase()),1),e("div",hl,[e("div",$l,[v(i(g.value.name)+" ",1),e("span",{class:O(["chip",g.value.enabled?"chip-ok":""])},i(g.value.enabled?"启用":"停用"),3),e("span",wl,"🐳 "+i(g.value.image_name||"未设置镜像"),1)]),e("div",Cl,i(g.value.remark||"—"),1)]),e("div",Tl,[e("button",{class:"btn btn-sm",onClick:s[2]||(s[2]=u=>D())},"编辑应用"),e("button",{class:"btn btn-primary btn-sm text-white",onClick:s[3]||(s[3]=u=>q.value?.openNew())},"+ 部署新环境")])]),e("div",Ml,[e("div",Nl,[s[14]||(s[14]=e("div",{class:"kpi-l"},"部署",-1)),e("div",Il,[v(i(x.value.length),1),s[13]||(s[13]=e("small",null,"个区域",-1))])]),e("div",Dl,[s[15]||(s[15]=e("div",{class:"kpi-l"},"已接入",-1)),e("div",Sl,i(R.value),1)]),e("div",{class:O(["kpi",V.value?"warn":""])},[s[16]||(s[16]=e("div",{class:"kpi-l"},"待部署",-1)),e("div",Ul,i(V.value),1)],2),e("div",Vl,[s[17]||(s[17]=e("div",{class:"kpi-l"},"版本分布",-1)),e("div",Ol,i(j.value),1)])]),e("div",Al,[e("button",{class:"tab",role:"tab","aria-selected":I.value==="deps",onClick:s[4]||(s[4]=u=>I.value="deps")},[s[18]||(s[18]=v(" 部署环境 ",-1)),e("span",Rl,i(x.value.length),1)],8,El),e("button",{class:"tab",role:"tab","aria-selected":I.value==="modules",onClick:s[5]||(s[5]=u=>I.value="modules")},"业务功能配置",8,jl)]),I.value==="modules"?(a(),o("div",Ll,[s[19]||(s[19]=e("span",{class:"text-xs text-slate-500 font-semibold shrink-0"},"配置目标",-1)),e("div",zl,[(a(!0),o(M,null,J(x.value,u=>(a(),o("button",{key:u.id,class:"depchip","aria-pressed":f.value===u.id,title:u.status==="pending"?"待部署:业务库可能还未就绪":"",onClick:te=>f.value=u.id},[e("span",{class:O(["dot",{pending:u.status==="pending"}])},null,2),v(" "+i(u.region?de(ce)(u.region):u.name||"部署#"+u.id)+" ",1),e("span",Pl,"#"+i(u.id),1)],8,Bl))),128)),x.value.length?k("",!0):(a(),o("span",ql,"该应用还没有部署,无处存放配置"))]),s[20]||(s[20]=e("span",{class:"text-[11px] text-slate-400 ml-auto"},"配置存在该部署自己的业务库里,各区域互不影响",-1))])):k("",!0),h(Ue(c,{ref_key:"deployTab",ref:q,"app-name":$.value,"default-image":g.value.image_name||"",deployments:x.value,envs:K.value,onRefresh:y,onConfigure:m},null,8,["app-name","default-image","deployments","envs"]),[[Se,I.value==="deps"]]),I.value==="modules"&&f.value?(a(),Ve(r,{key:"m"+f.value,"app-id":f.value,"dep-name":W.value},null,8,["app-id","dep-name"])):k("",!0),I.value==="modules"&&!f.value?(a(),o("div",Fl," 先在「部署环境」里创建一个部署,才能配置。 ")):k("",!0)],64)):(a(),o("div",fl,[e("p",_l,"找不到应用「"+i($.value)+"」",1),e("button",{class:"btn btn-sm mt-4",onClick:s[1]||(s[1]=u=>de(S).push("/apps"))},"返回应用中心")])),e("dialog",{ref_key:"appDlg",ref:L,class:"modal"},[e("div",Hl,[s[26]||(s[26]=e("h3",{class:"font-bold text-lg"},"编辑应用",-1)),s[27]||(s[27]=e("p",{class:"text-xs text-slate-400 mt-1"}," 应用名是关联键:部署、账号作用域、第三方服务配置都按它挂载,改名会断开已有关联。 ",-1)),e("form",{class:"mt-4 space-y-3",onSubmit:ye(ne,["prevent"])},[e("div",Jl,[s[21]||(s[21]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"应用名 *")],-1)),h(e("input",{"onUpdate:modelValue":s[6]||(s[6]=u=>_.name=u),type:"text",class:"input input-bordered input-sm w-full",required:""},null,512),[[A,_.name]])]),e("div",Gl,[s[22]||(s[22]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"默认镜像名")],-1)),h(e("input",{"onUpdate:modelValue":s[7]||(s[7]=u=>_.image_name=u),type:"text",class:"input input-bordered input-sm w-full font-mono text-xs"},null,512),[[A,_.image_name]])]),e("div",Yl,[s[23]||(s[23]=e("label",{class:"label py-1"},[e("span",{class:"label-text text-sm font-medium"},"备注")],-1)),h(e("input",{"onUpdate:modelValue":s[8]||(s[8]=u=>_.remark=u),type:"text",class:"input input-bordered input-sm w-full"},null,512),[[A,_.remark]])]),e("label",Kl,[h(e("input",{"onUpdate:modelValue":s[9]||(s[9]=u=>_.enabled=u),type:"checkbox",class:"toggle toggle-primary toggle-sm"},null,512),[[ke,_.enabled]]),s[24]||(s[24]=e("span",{class:"text-sm"},"启用",-1))]),e("div",Ql,[e("button",{type:"button",class:"btn btn-ghost btn-sm",onClick:s[10]||(s[10]=u=>L.value?.close())},"取消"),s[25]||(s[25]=e("button",{type:"submit",class:"btn btn-primary btn-sm text-white"},"保存",-1))])],32)]),s[28]||(s[28]=e("form",{method:"dialog",class:"modal-backdrop"},[e("button",null,"close")],-1))],512)])}}}),sn=ge(Wl,[["__scopeId","data-v-5215c85d"]]);export{sn as default};