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