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