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.
 
 
 
 
 
 

7 lines
30 KiB

import{g as De,C as Ae,z as ze,j as Le,c as d,k as b,v as V,a as e,d as m,F as _,r as I,t as i,D as ne,m as w,q as pe,n as k,l as N,p as le,s as h,A as me,x as z,E as Re,o as r,_ as Fe}from"./CTAruH_8.js";import{u as je}from"./9B2ftqmN.js";import{u as Pe}from"./lLLegusN.js";import"./D9HxoMhB.js";import"./yUUyh6cG.js";const Ee={class:"flex items-center justify-between mb-5"},Be={key:0,class:"bg-white rounded-xl border border-slate-100 py-14 text-center"},qe={key:1,class:"bg-white rounded-xl border border-slate-100 py-14 text-center text-slate-400"},He={key:2,class:"space-y-4"},Je=["onClick"],Ye={class:"chev"},Ke={class:"min-w-0 flex-1"},Ge={class:"flex items-center gap-2 flex-wrap"},Qe={class:"app-name"},We={key:1,class:"chip-mono"},Xe={class:"chip-count"},Ze={class:"text-xs text-slate-400 mt-0.5 truncate"},et=["onClick"],tt=["onClick"],st=["onClick"],nt={class:"app-body"},lt={key:0,class:"text-center text-slate-300 py-5 text-xs"},ot={key:1,class:"overflow-x-auto"},at={class:"table table-sm text-sm"},it={class:"font-medium text-slate-700 ml-1"},dt={class:"text-xs text-slate-400 ml-1 font-mono"},rt=["onUpdate:modelValue","disabled","onChange"],ut=["value"],pt={key:0,class:"badge badge-warning badge-sm text-amber-900"},mt={key:1,class:"badge badge-success badge-sm text-white"},ct={class:"font-mono text-xs"},bt={key:0,class:"text-green-600"},gt={key:1,class:"text-slate-400"},xt={class:"text-right"},vt={class:"flex gap-1 justify-end"},ft=["onClick"],_t=["onClick"],yt=["onClick"],kt=["onClick"],ht=["onClick"],wt=["onClick"],Ct=["onClick"],Tt={class:"flex items-center gap-3 mb-5"},Mt={class:"text-lg font-semibold text-slate-800"},$t={class:"bg-white rounded-xl shadow-sm border border-slate-100 p-6 max-w-xl"},Nt={class:"form-control"},St={class:"form-control mt-4"},Ut={class:"form-control mt-4"},Vt={class:"flex items-center gap-2 mt-4"},It={class:"flex items-center gap-2 cursor-pointer"},Ot={class:"flex gap-3 mt-6"},Dt={class:"flex items-center gap-3 mb-5"},At={class:"text-lg font-semibold text-slate-800"},zt={class:"bg-white rounded-xl shadow-sm border border-slate-100 p-6 max-w-3xl"},Lt={key:0,class:"tabs tabs-boxed bg-slate-100 w-fit mb-5"},Rt={class:"grid grid-cols-2 gap-4"},Ft={class:"form-control"},jt=["value"],Pt={class:"form-control"},Et={class:"grid grid-cols-2 gap-4 mt-4"},Bt={class:"form-control"},qt=["value"],Ht={class:"form-control"},Jt={class:"label"},Yt={class:"label-text font-medium"},Kt={value:0},Gt=["value"],Qt={class:"grid grid-cols-3 gap-4 mt-4"},Wt={class:"form-control"},Xt={class:"form-control"},Zt={class:"form-control"},es={class:"form-control mt-4"},ts={class:"label"},ss={class:"label-text font-medium"},ns={class:"form-control mt-4"},ls={class:"mt-4 text-sm"},os={class:"form-control mt-4"},as={class:"flex items-center gap-2 mt-4"},is={class:"flex items-center gap-2 cursor-pointer"},ds={class:"flex gap-3 mt-6"},rs=["disabled"],us=["disabled"],ps={key:2,class:"text-xs text-slate-400 mt-2"},ms={class:"modal-box"},cs={class:"text-sm text-slate-500 mt-1"},bs={class:"grid grid-cols-2 gap-3 mt-4"},gs={key:0,class:"bg-slate-50 border border-slate-200 rounded-lg p-3"},xs={class:"flex justify-between items-center text-xs mb-1.5"},vs={class:"text-slate-600"},fs={class:"font-mono text-slate-400"},_s=["value"],ys={class:"mt-2 text-xs space-y-0.5"},ks={key:1,class:"text-red-600"},hs={key:2,class:"bg-green-50 border border-green-100 rounded-lg p-3"},ws={class:"grid grid-cols-2 gap-x-4 gap-y-1 text-xs text-slate-600 mb-2"},Cs={class:"border-t border-green-100 pt-2 text-xs"},Ts={class:"text-slate-500"},Ms={class:"font-mono text-slate-700"},$s={class:"modal-action"},Ns=["disabled"],Ss={class:"modal-box max-w-2xl"},Us={class:"text-sm text-slate-500 mt-1"},Vs={class:"grid grid-cols-1 gap-2 mt-4 text-xs"},Is={class:"flex"},Os={class:"flex"},Ds={class:"mt-3"},As=["disabled"],zs={class:"mt-3"},Ls={class:"flex items-center gap-2 text-xs mb-1"},Rs={key:0,class:"loading loading-spinner loading-xs text-amber-500"},Fs={key:1,class:"text-green-600 font-semibold"},js={key:2,class:"text-red-600 font-semibold"},Ps={class:"text-slate-400"},Es={class:"modal-action"},Bs=["disabled"],qs=["disabled"],Hs={method:"dialog",class:"modal-backdrop"},Js=["disabled"],Ys="__orphan__",Ks=De({__name:"apps",setup(Gs){const{consoleApi:C}=je(),{success:M,error:y}=Pe(),F=Ae(),Y=ze(),O=h("list"),j=h([]),K=h([]),P=h([]),G=h(!1),Q=h(0),W=z({}),ce=["中国","美国","东亚","东南亚","南亚","中东","欧洲","俄罗斯","非洲","南美","北美","大洋洲","巴西","印度"],oe=me(()=>{const n=new Map;for(const p of K.value){const c=p.app_name||"";n.has(c)||n.set(c,[]),n.get(c).push(p)}const t=new Set(j.value.map(p=>p.name)),s=j.value.map(p=>({key:p.name,product:p,deployments:n.get(p.name)||[]})),u=[];for(const[p,c]of n)(p===""||!t.has(p))&&u.push(...c);return u.length&&s.push({key:Ys,product:null,deployments:u}),s});function ae(n){return W[n]!==!1}function be(n){W[n]=W[n]===!1}function ge(n){if(!n)return"-";const t=P.value.find(s=>s.id===n);return t?t.name:"#"+n}function xe(n){const t=(n||"").match(/\/([^/?]+)\?/);return t?t[1]:"-"}async function S(){G.value=!0;try{const[n,t,s]=await Promise.all([C("products/list",{}),C("apps/list",{}),C("envs/list",{})]);j.value=n??[],K.value=t??[],P.value=s??[]}catch(n){y(n?.message??"获取应用列表失败")}finally{G.value=!1}}function D(){O.value="list",S()}const E=h(!1),g=z({id:0,name:"",image_name:"",remark:"",enabled:!0});function ie(n){n?(E.value=!0,g.id=n.id,g.name=n.name||"",g.image_name=n.image_name||"",g.remark=n.remark||"",g.enabled=!!n.enabled):(E.value=!1,g.id=0,g.name="",g.image_name="",g.remark="",g.enabled=!0),O.value="productForm"}async function ve(){const n={name:g.name,image_name:g.image_name,remark:g.remark,enabled:g.enabled};let t="products/add";E.value&&g.id&&(n.id=Number(g.id),t="products/update");try{await C(t,n),M("保存成功"),D()}catch(s){y(s?.message??"失败")}}function fe(n){window.confirm(`删除应用「${n.name}」?(其下若有区域部署将被拒绝)`)&&C("products/del",{id:n.id}).then(()=>{M("已删除"),S()}).catch(t=>y(t?.message??"失败"))}const $=h(!1),x=h("existing"),o=z({id:0,app_name:"",name:"",region:"",env_id:0,base_url:"",service_dsn:"",deploy_dir:"",image_name:"",image_tag:"",remark:"",enabled:!0});function _e(n){return n&&j.value.find(s=>s.name===n)?.image_name||""}const ye=me(()=>{const n=new Set(K.value.filter(t=>t.app_name===o.app_name&&t.id!==o.id).map(t=>t.region).filter(Boolean));return ce.filter(t=>!n.has(t))}),L=h(!1),B=h(!1),v=z({show:!1,apiOk:!1,apiMsg:"",dbOk:!1,dbMsg:""});function q(){L.value=!1,v.show=!1,v.apiMsg="",v.dbMsg=""}function de(n){x.value=n,q()}function X(n,t){t?($.value=!0,x.value=t.status==="pending"?"deploy":"existing",o.id=t.id,o.app_name=t.app_name||n||"",o.name=t.name||"",o.region=t.region||"",o.env_id=t.env_id||0,o.base_url=t.base_url||"",o.service_dsn=t.service_dsn||"",o.deploy_dir=t.deploy_dir||"",o.image_name=t.image_name||"",o.image_tag=t.image_tag||"",o.remark=t.remark||"",o.enabled=!!t.enabled):($.value=!1,x.value="existing",o.id=0,o.app_name=n||"",o.name="",o.region="",o.env_id=0,o.base_url="",o.service_dsn="",o.deploy_dir="",o.image_name=_e(n),o.image_tag="",o.remark="",o.enabled=!0),q(),O.value="deployForm"}async function ke(){const n=o.base_url.trim(),t=o.service_dsn.trim();if(!n||!t){y("请先填应用地址和业务库 DSN");return}B.value=!0;try{const s=await C("apps/test",{base_url:n,service_dsn:t});v.apiOk=!!s.api_ok,v.apiMsg=s.api_msg,v.dbOk=!!s.db_ok,v.dbMsg=s.db_msg,v.show=!0,L.value=!!s.ok,L.value?M("检测通过,可以保存"):y("检测未通过")}catch(s){y(s?.message??"测试失败")}finally{B.value=!1}}async function he(){if(!o.app_name){y("请选择所属应用");return}if(!o.name.trim()){y("请填部署名称");return}if(x.value==="existing"&&!L.value){y("请先「测试连接」通过再保存");return}if(x.value==="deploy"&&!o.env_id){y("请选择部署目标环境");return}const n={app_name:o.app_name,status:x.value==="deploy"?"pending":"existing",name:o.name,region:o.region,env_id:Number(o.env_id)||0,base_url:x.value==="deploy"?"":o.base_url,service_dsn:o.service_dsn,deploy_dir:o.deploy_dir,image_name:o.image_name,image_tag:o.image_tag,remark:o.remark,enabled:o.enabled};let t="apps/add";$.value&&o.id&&(n.id=Number(o.id),t="apps/update");try{await C(t,n),M("保存成功"),D()}catch(s){y(s?.message??"失败")}}function we(n){Y.push({path:"/appconfig",query:{id:n.id,name:n.name||"部署#"+n.id,region:n.region||"",app_name:n.app_name||""}})}async function Ce(n){Q.value=n.id;try{await C("apps/bindenv",{id:n.id,env_id:Number(n.env_id)||0}),M("部署环境已更新")}catch(t){y(t?.message??"切换环境失败"),S()}finally{Q.value=0}}function Te(n){window.confirm(`删除部署 #${n} ?(不影响应用自身的库)`)&&C("apps/del",{id:n}).then(()=>{M("已删除"),S()}).catch(t=>y(t?.message??"失败"))}const Z=h(null),l=z({appId:0,appLabel:"",start:"",end:"",resultShow:!1,resultClass:"",mode:"",steps:[],curIndex:0,curTotal:0,curName:"",pct:0,errMsg:"",report:null,running:!1,btnText:"开始分析"});function re(n){return parseInt((n||"").replace(/-/g,""))||0}function Me(n,t){l.appId=n,l.appLabel="部署:"+t+" (#"+n+")",l.start="",l.end="",l.resultShow=!1,l.mode="",l.report=null,l.errMsg="",l.steps=[],l.curIndex=0,l.curTotal=0,l.curName="",l.pct=0,l.running=!1,l.btnText="开始分析",Z.value?.showModal()}function $e(){Z.value?.close()}function Ne(n){l.steps[n.index-1]=n.name,l.pct=Math.round(n.index/(n.total+1)*100),l.curIndex=n.index,l.curTotal=n.total,l.curName=n.name}async function Se(){const n=l.appLabel,t=l.start||"最早",s=l.end||"至今";if(!window.confirm(`将先删除【${n}】在 ${t} ~ ${s} 区间的历史统计数据,然后重新分析写入。
确认继续?`))return;l.running=!0,l.btnText="分析中...",l.resultShow=!0,l.resultClass="",l.mode="progress",l.steps=[],l.curIndex=0,l.curTotal=0,l.curName="",l.pct=0,l.report=null,l.errMsg="";const u={app_id:l.appId,start_day:re(l.start),end_day:re(l.end)};let p=null,c=null,R=!1;function U(T){if(T=(T||"").trim(),!T)return;let f;try{f=JSON.parse(T)}catch{return}if(f.code===18){R=!0;return}if(f.code!==void 0&&f.code!==0){c=f.msg||"请求被拒绝";return}f.type==="progress"?Ne(f):f.type==="done"?p=f.report:f.type==="error"&&(c=f.msg)}try{const f=(await fetch("/console/api/apps/analyze",{method:"POST",headers:{"Content-Type":"application/json",Authorization:F.token},body:JSON.stringify(u)})).body.getReader(),A=new TextDecoder;let J="";for(;;){const ue=await f.read();if(ue.done)break;J+=A.decode(ue.value,{stream:!0});const te=J.split(`
`);J=te.pop();for(let se=0;se<te.length;se++)U(te[se])}U(J)}catch(T){c="网络异常: "+T.message}if(R){F.logout(),Y.push("/");return}if(l.running=!1,l.btnText="重新分析",c){l.resultClass="text-red-600",l.mode="error",l.errMsg=c;return}if(!p){l.resultClass="text-red-600",l.mode="error",l.errMsg="分析中断,未收到完成事件";return}l.resultClass="",l.mode="done",l.report=p,M("分析完成"),S()}function Ue(n){window.confirm(`向部署 #${n} 发送一条配置 reload 事件?`)&&C("config/notify",{app_id:n,type:"all",action:"reload"}).then(t=>{M("已发送 → "+(t&&t.subject))}).catch(t=>y(t?.message??"失败"))}const ee=h(null),H=h(null),a=z({id:0,label:"",envName:"",deployDir:"",tag:"",log:"",show:!1,running:!1,ok:!1,canRun:!1,btnText:"开始升级"});function Ve(n){a.id=n.id,a.label="部署:"+(n.name||"部署#"+n.id)+" (#"+n.id+")"+(n.region?" · "+n.region:""),a.envName=n.env_id?ge(n.env_id):"",a.deployDir=n.deploy_dir||"",a.tag=n.image_tag||"",a.log="",a.show=!1,a.running=!1,a.ok=!1,a.canRun=!!(n.env_id&&(n.deploy_dir||"").trim()),a.btnText="开始升级",ee.value?.showModal()}function Ie(){a.running||ee.value?.close()}async function Oe(){if(a.running)return;if(!a.canRun){const p=[];a.envName||p.push("绑定环境节点"),a.deployDir||p.push("填写部署目录"),window.alert(`无法升级:请先关闭本弹窗,点该部署的「编辑」完成 —— ${p.join("、")},保存后再来升级。`);return}if(!window.confirm(`确认升级【${a.label}】?
将 SSH 连到「${a.envName}」,在 ${a.deployDir} 执行 docker compose 拉新镜像并重启容器。`))return;a.running=!0,a.show=!0,a.ok=!1,a.log="",a.btnText="升级中…";let n=null,t=!1;function s(p){a.log+=(a.log?`
`:"")+p,Re(()=>{H.value&&(H.value.scrollTop=H.value.scrollHeight)})}function u(p){if(p=(p||"").trim(),!p)return;let c;try{c=JSON.parse(p)}catch{return}if(c.code===18){t=!0;return}if(c.code!==void 0&&c.code!==0&&c.type===void 0){n=c.msg||"请求被拒绝";return}c.type==="log"?s(c.line||""):c.type==="error"&&(n=c.msg,s("✗ "+(c.msg||"")))}try{const c=(await fetch("/console/api/apps/upgrade",{method:"POST",headers:{"Content-Type":"application/json",Authorization:F.token},body:JSON.stringify({id:a.id,tag:(a.tag||"").trim()})})).body.getReader(),R=new TextDecoder;let U="";for(;;){const T=await c.read();if(T.done)break;U+=R.decode(T.value,{stream:!0});const f=U.split(`
`);U=f.pop();for(let A=0;A<f.length;A++)u(f[A])}u(U)}catch(p){n="网络异常: "+p.message,s("✗ "+n)}if(t){F.logout(),Y.push("/");return}a.running=!1,a.btnText="重新升级",a.ok=!n,n?y(n):(M("升级完成"),S())}return Le(S),(n,t)=>(r(),d("div",null,[b(e("div",null,[e("div",Ee,[t[25]||(t[25]=e("div",null,[e("h2",{class:"text-lg font-semibold text-slate-800"},"应用管理"),e("p",{class:"text-sm text-slate-400 mt-1"},[e("span",{class:"chip-app"},"应用"),m(" 是一个产品(如 Voitrans);其下挂多条 "),e("span",{class:"chip-dep"},"部署"),m("(各区域一份)。 卡片 = 应用;卡片内表格 = 该应用的区域部署。 ")])],-1)),e("button",{class:"btn btn-primary btn-sm text-white",onClick:t[0]||(t[0]=s=>ie())},"+ 添加应用")]),G.value?(r(),d("div",Be,[...t[26]||(t[26]=[e("span",{class:"loading loading-spinner loading-sm"},null,-1)])])):oe.value.length?(r(),d("div",He,[(r(!0),d(_,null,I(oe.value,s=>(r(),d("div",{key:s.key,class:k(["app-card",{orphan:!s.product}])},[e("div",{class:"app-head",onClick:u=>be(s.key)},[e("button",Ye,i(ae(s.key)?"▾":"▸"),1),e("div",{class:k(["app-logo",{"orphan-logo":!s.product}])},i(s.product?(s.product.name||"?").charAt(0).toUpperCase():"?"),3),e("div",Ke,[e("div",Ge,[t[27]||(t[27]=e("span",{class:"chip-app"},"应用",-1)),e("span",Qe,i(s.product?s.product.name:"未分组部署"),1),s.product?(r(),d("span",{key:0,class:k(["badge badge-xs",s.product.enabled?"badge-success":"badge-ghost"])},i(s.product.enabled?"启用":"停用"),3)):N("",!0),s.product&&s.product.image_name?(r(),d("span",We,"🐳 "+i(s.product.image_name),1)):N("",!0),e("span",Xe,i(s.deployments.length)+" 个部署",1)]),e("p",Ze,i(s.product?s.product.remark||"—":"未填所属应用名、或应用已删除的历史部署;编辑部署可归位到某个应用"),1)]),s.product?(r(),d("div",{key:0,class:"flex gap-1 shrink-0",onClick:t[1]||(t[1]=ne(()=>{},["stop"]))},[e("button",{class:"btn btn-primary btn-xs text-white",onClick:u=>X(s.product.name)},"+ 部署",8,et),e("button",{class:"btn btn-ghost btn-xs",onClick:u=>ie(s.product)},"编辑应用",8,tt),e("button",{class:"btn btn-ghost btn-xs text-red-500",onClick:u=>fe(s.product)},"删除",8,st)])):N("",!0)],8,Je),b(e("div",nt,[s.deployments.length?(r(),d("div",ot,[e("table",at,[t[31]||(t[31]=e("thead",null,[e("tr",{class:"text-slate-400"},[e("th",null,"部署"),e("th",null,"区域"),e("th",null,"环境"),e("th",null,"状态"),e("th",null,"业务库"),e("th",null,"启用"),e("th",{class:"text-right"},"操作")])],-1)),e("tbody",null,[(r(!0),d(_,null,I(s.deployments,u=>(r(),d("tr",{key:u.id,class:"dep-row hover"},[e("td",null,[t[29]||(t[29]=e("span",{class:"chip-dep"},"部署",-1)),e("span",it,i(u.name||"部署#"+u.id),1),e("span",dt,"#"+i(u.id),1)]),e("td",null,i(u.region||"—"),1),e("td",null,[b(e("select",{"onUpdate:modelValue":p=>u.env_id=p,class:"select select-bordered select-xs w-36 text-xs",disabled:Q.value===u.id,onChange:p=>Ce(u)},[t[30]||(t[30]=e("option",{value:0},"未绑定",-1)),(r(!0),d(_,null,I(P.value,p=>(r(),d("option",{key:p.id,value:p.id},i(p.name),9,ut))),128))],40,rt),[[le,u.env_id,void 0,{number:!0}]])]),e("td",null,[u.status==="pending"?(r(),d("span",pt,"待部署")):(r(),d("span",mt,"已接入"))]),e("td",ct,i(xe(u.service_dsn)),1),e("td",null,[u.enabled?(r(),d("span",bt,"是")):(r(),d("span",gt,"否"))]),e("td",xt,[e("div",vt,[u.status!=="pending"?(r(),d(_,{key:0},[e("button",{class:"btn btn-ghost btn-xs text-indigo-600",onClick:p=>we(u)},"服务配置",8,ft),e("button",{class:"btn btn-ghost btn-xs text-slate-500",onClick:p=>X(u.app_name,u)},"编辑",8,_t),e("button",{class:"btn btn-ghost btn-xs text-amber-600",onClick:p=>Ve(u)},"升级",8,yt),e("button",{class:"btn btn-ghost btn-xs text-emerald-600",onClick:p=>Me(u.id,u.name||"部署#"+u.id)},"分析",8,kt),e("button",{class:"btn btn-ghost btn-xs text-indigo-500",onClick:p=>Ue(u.id)},"发事件",8,ht)],64)):(r(),d("button",{key:1,class:"btn btn-ghost btn-xs text-amber-600",onClick:p=>X(u.app_name,u)},"编辑",8,wt)),e("button",{class:"btn btn-ghost btn-xs text-red-500",onClick:p=>Te(u.id)},"删除",8,Ct)])])]))),128))])])])):(r(),d("div",lt,[t[28]||(t[28]=m(" 该应用暂无部署",-1)),s.product?(r(),d(_,{key:0},[m(",点右上「+ 部署」添加")],64)):N("",!0)]))],512),[[V,ae(s.key)]])],2))),128))])):(r(),d("div",qe,"暂无应用,先「添加应用」"))],512),[[V,O.value==="list"]]),b(e("div",null,[e("div",Tt,[e("button",{class:"btn btn-ghost btn-sm",onClick:t[2]||(t[2]=s=>D())},"← 返回"),e("h2",Mt,i(E.value?"编辑应用":"添加应用"),1)]),e("div",$t,[e("form",{onSubmit:ne(ve,["prevent"])},[e("div",Nt,[t[32]||(t[32]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"应用名 *")],-1)),b(e("input",{"onUpdate:modelValue":t[3]||(t[3]=s=>g.name=s),type:"text",class:"input input-bordered w-full",required:"",placeholder:"如 Voitrans"},null,512),[[w,g.name]]),t[33]||(t[33]=e("span",{class:"text-xs text-slate-400 mt-1"},"账号作用域按应用名绑定,尽量与统计/账号里一致。",-1))]),e("div",St,[t[34]||(t[34]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"默认镜像名")],-1)),b(e("input",{"onUpdate:modelValue":t[4]||(t[4]=s=>g.image_name=s),type:"text",class:"input input-bordered w-full",placeholder:"voitrans-a11(部署时用,可留空)"},null,512),[[w,g.image_name]])]),e("div",Ut,[t[35]||(t[35]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"备注")],-1)),b(e("input",{"onUpdate:modelValue":t[5]||(t[5]=s=>g.remark=s),type:"text",class:"input input-bordered w-full"},null,512),[[w,g.remark]])]),e("div",Vt,[e("label",It,[b(e("input",{"onUpdate:modelValue":t[6]||(t[6]=s=>g.enabled=s),type:"checkbox",class:"toggle toggle-primary toggle-sm"},null,512),[[pe,g.enabled]]),t[36]||(t[36]=e("span",{class:"text-sm"},"启用",-1))])]),e("div",Ot,[e("button",{type:"button",class:"btn btn-ghost",onClick:t[7]||(t[7]=s=>D())},"取消"),t[37]||(t[37]=e("button",{type:"submit",class:"btn btn-primary text-white"},"保存",-1))])],32)])],512),[[V,O.value==="productForm"]]),b(e("div",null,[e("div",Dt,[e("button",{class:"btn btn-ghost btn-sm",onClick:t[8]||(t[8]=s=>D())},"← 返回"),e("h2",At,i($.value?"编辑部署":"添加部署"),1)]),e("div",zt,[e("form",{onSubmit:ne(he,["prevent"])},[$.value?N("",!0):(r(),d("div",Lt,[e("a",{class:k(["tab",x.value==="existing"?"tab-active":""]),onClick:t[9]||(t[9]=s=>de("existing"))},"添加现有",2),e("a",{class:k(["tab",x.value==="deploy"?"tab-active":""]),onClick:t[10]||(t[10]=s=>de("deploy"))},"部署到目标环境",2)])),e("p",{class:k(["text-xs text-slate-400 mb-4",$.value?"":"-mt-3"])},[$.value?(r(),d(_,{key:0},[m("编辑该区域部署的配置(镜像 Tag / 部署目录 / 环境节点 等);改好后回列表点「升级」执行远程部署(拉新镜像并重启)。")],64)):x.value==="existing"?(r(),d(_,{key:1},[m("把「已经部署好的应用区域」加进来:填地址与业务库 DSN,测试通过后保存。")],64)):(r(),d(_,{key:2},[t[38]||(t[38]=m("选一个「部署目标环境」,先建关联并标记",-1)),t[39]||(t[39]=e("b",null,"待部署",-1)),t[40]||(t[40]=m(";保存后回列表点「升级」执行远程部署。",-1))],64))],2),e("div",Rt,[e("div",Ft,[t[41]||(t[41]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"所属应用")],-1)),e("input",{value:o.app_name,type:"text",class:"input input-bordered w-full bg-slate-100",readonly:""},null,8,jt)]),e("div",Pt,[t[42]||(t[42]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"部署名称 *")],-1)),b(e("input",{"onUpdate:modelValue":t[11]||(t[11]=s=>o.name=s),type:"text",class:"input input-bordered w-full",required:"",placeholder:"如 Voitrans(国内)"},null,512),[[w,o.name]])])]),e("div",Et,[e("div",Bt,[t[44]||(t[44]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"区域")],-1)),b(e("select",{"onUpdate:modelValue":t[12]||(t[12]=s=>o.region=s),class:"select select-bordered w-full"},[t[43]||(t[43]=e("option",{value:""},"未指定",-1)),(r(!0),d(_,null,I(ye.value,s=>(r(),d("option",{key:s,value:s},i(s),9,qt))),128))],512),[[le,o.region]]),t[45]||(t[45]=e("span",{class:"text-xs text-slate-400 mt-1"},"已部署的区域不再出现(一个应用每区域一份部署)。",-1))]),e("div",Ht,[e("label",Jt,[e("span",Yt,"部署环境"+i(x.value==="deploy"?" *":""),1)]),b(e("select",{"onUpdate:modelValue":t[13]||(t[13]=s=>o.env_id=s),class:"select select-bordered w-full"},[e("option",Kt,i(x.value==="deploy"?"请选择目标环境":"未关联"),1),(r(!0),d(_,null,I(P.value,s=>(r(),d("option",{key:s.id,value:s.id},i(s.name)+"("+i(s.code||"-")+")",9,Gt))),128))],512),[[le,o.env_id,void 0,{number:!0}]])])]),e("div",Qt,[e("div",Wt,[t[46]||(t[46]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"部署目录")],-1)),b(e("input",{"onUpdate:modelValue":t[14]||(t[14]=s=>o.deploy_dir=s),type:"text",class:"input input-bordered w-full font-mono text-xs",placeholder:"/home/work/voitrans-a11"},null,512),[[w,o.deploy_dir]])]),e("div",Xt,[t[47]||(t[47]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"镜像名")],-1)),b(e("input",{"onUpdate:modelValue":t[15]||(t[15]=s=>o.image_name=s),type:"text",class:"input input-bordered w-full",placeholder:"voitrans-a11(默认取应用)"},null,512),[[w,o.image_name]])]),e("div",Zt,[t[48]||(t[48]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"镜像 Tag")],-1)),b(e("input",{"onUpdate:modelValue":t[16]||(t[16]=s=>o.image_tag=s),type:"text",class:"input input-bordered w-full",placeholder:"0.1.1 / latest"},null,512),[[w,o.image_tag]])])]),e("div",es,[e("label",ts,[e("span",ss,"业务库 DSN"+i(x.value==="existing"?" *":""),1)]),b(e("input",{"onUpdate:modelValue":t[17]||(t[17]=s=>o.service_dsn=s),type:"text",class:"input input-bordered w-full font-mono text-xs",placeholder:"root:pwd@tcp(host:3306)/earphone?charset=utf8mb4&parseTime=True&loc=Local",onInput:q},null,544),[[w,o.service_dsn]]),t[49]||(t[49]=e("span",{class:"text-xs text-slate-400 mt-1"},"用于读取业务数据(重置设备码时清 userdevice/product_stat)",-1))]),x.value==="existing"?(r(),d(_,{key:1},[e("div",ns,[t[50]||(t[50]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"应用地址 *")],-1)),b(e("input",{"onUpdate:modelValue":t[18]||(t[18]=s=>o.base_url=s),type:"text",class:"input input-bordered w-full",placeholder:"https://api.xxx.com/",onInput:q},null,544),[[w,o.base_url]]),t[51]||(t[51]=e("span",{class:"text-xs text-slate-400 mt-1"},"用于通知应用业务服务事件(如配置更新)",-1))]),b(e("div",ls,[e("div",{class:k(v.apiOk?"text-green-700":"text-red-600")},[e("span",{class:k(v.apiOk?"text-green-600":"text-red-500")},i(v.apiOk?"✓":"✗"),3),m(" 应用地址:"+i(v.apiMsg),1)],2),e("div",{class:k(v.dbOk?"text-green-700":"text-red-600")},[e("span",{class:k(v.dbOk?"text-green-600":"text-red-500")},i(v.dbOk?"✓":"✗"),3),m(" 业务库:"+i(v.dbMsg),1)],2)],512),[[V,v.show]])],64)):N("",!0),e("div",os,[t[52]||(t[52]=e("label",{class:"label"},[e("span",{class:"label-text font-medium"},"备注")],-1)),b(e("input",{"onUpdate:modelValue":t[19]||(t[19]=s=>o.remark=s),type:"text",class:"input input-bordered w-full"},null,512),[[w,o.remark]])]),e("div",as,[e("label",is,[b(e("input",{"onUpdate:modelValue":t[20]||(t[20]=s=>o.enabled=s),type:"checkbox",class:"toggle toggle-primary toggle-sm"},null,512),[[pe,o.enabled]]),t[53]||(t[53]=e("span",{class:"text-sm"},"启用",-1))])]),e("div",ds,[e("button",{type:"button",class:"btn btn-ghost",onClick:t[21]||(t[21]=s=>D())},"取消"),x.value==="existing"?(r(),d("button",{key:0,type:"button",class:"btn btn-outline",disabled:B.value,onClick:ke},i(B.value?"测试中...":"测试连接"),9,rs)):N("",!0),e("button",{type:"submit",class:"btn btn-primary text-white",disabled:!$.value&&x.value==="existing"&&!L.value},"保存",8,us)]),!$.value&&x.value==="existing"?(r(),d("p",ps,"需先「测试连接」通过(应用地址可达 + 业务库可连)才能保存。")):N("",!0)],32)])],512),[[V,O.value==="deployForm"]]),e("dialog",{ref_key:"analyzeModal",ref:Z,class:"modal"},[e("div",ms,[t[61]||(t[61]=e("h3",{class:"font-bold text-lg"},"分析历史数据",-1)),e("p",cs,i(l.appLabel),1),t[62]||(t[62]=e("div",{class:"text-xs text-amber-700 mt-3 bg-amber-50 border border-amber-100 rounded-lg p-3 leading-relaxed"},[m(" 扫描该应用业务库,按 "),e("b",null,"天 + 产品 + 区域"),m(" 生成历史统计(新增用户 / 订单 / 资源发放·使用 / 激活绑定设备),汇入 console 主库。"),e("br"),m(" · "),e("b",null,"分析前会先清理该应用+区域在所选区间内的旧统计行"),m(",避免数据重叠。"),e("br"),m(" · 登录、活跃用户、AI对话等无每日原始记录,历史无法还原(由实时管道往后采集)。"),e("br"),m(" · 仅对实时管道上线前的区间运行。留空 = 全部历史。大库可能耗时。 ")],-1)),e("div",bs,[e("div",null,[t[54]||(t[54]=e("label",{class:"text-xs text-slate-500"},"起始日(含)",-1)),b(e("input",{"onUpdate:modelValue":t[22]||(t[22]=s=>l.start=s),type:"date",class:"input input-bordered input-sm w-full"},null,512),[[w,l.start]])]),e("div",null,[t[55]||(t[55]=e("label",{class:"text-xs text-slate-500"},"结束日(含)",-1)),b(e("input",{"onUpdate:modelValue":t[23]||(t[23]=s=>l.end=s),type:"date",class:"input input-bordered input-sm w-full"},null,512),[[w,l.end]])])]),b(e("div",{class:k(["text-sm mt-3",l.resultClass])},[l.mode==="progress"?(r(),d("div",gs,[e("div",xs,[e("span",vs,[l.curName?(r(),d(_,{key:0},[t[56]||(t[56]=e("span",{class:"loading loading-spinner loading-xs"},null,-1)),m(" 正在:"+i(l.curName)+" ("+i(l.curIndex)+"/"+i(l.curTotal)+") ",1)],64)):(r(),d(_,{key:1},[m("准备中...")],64))]),e("span",fs,i(l.pct)+"%",1)]),e("progress",{class:"progress progress-primary w-full h-2",value:l.pct,max:"100"},null,8,_s),e("div",ys,[(r(!0),d(_,null,I(l.steps,(s,u)=>(r(),d("div",{key:u,class:k(u===l.curIndex-1?"text-slate-600":"text-green-600")},[u===l.curIndex-1?(r(),d(_,{key:0},[t[57]||(t[57]=e("span",{class:"loading loading-spinner loading-xs"},null,-1)),m(" "+i(s),1)],64)):(r(),d(_,{key:1},[m("✓ "+i(s),1)],64))],2))),128))])])):l.mode==="error"?(r(),d("div",ks,"✗ "+i(l.errMsg),1)):l.mode==="done"&&l.report?(r(),d("div",hs,[t[60]||(t[60]=e("div",{class:"font-semibold text-green-700 mb-2"},"✓ 分析完成",-1)),e("div",ws,[e("div",null,[t[58]||(t[58]=m("应用:",-1)),e("b",null,i(l.report.app_name||""),1)]),e("div",null,"区域:"+i(l.report.region||"未指定"),1),e("div",null,"区间:"+i((l.report.start_day||"不限")+" ~ "+(l.report.end_day||"不限")),1),e("div",null,"清理旧行:"+i(l.report.cleared_rows||0),1),e("div",null,[t[59]||(t[59]=m("写入天数:",-1)),e("b",null,i(l.report.days_written||0),1)]),e("div",null,"写入行数:"+i(l.report.rows_written||0),1)]),e("div",Cs,[(r(!0),d(_,null,I(l.report.steps||[],(s,u)=>(r(),d("div",{key:u,class:"flex justify-between py-0.5"},[e("span",Ts,i(s.name),1),e("span",Ms,i((s.groups||0)+" 组"),1)]))),128))])])):N("",!0)],2),[[V,l.resultShow]]),e("div",$s,[e("button",{class:"btn btn-ghost btn-sm",onClick:$e},"关闭"),e("button",{class:"btn btn-primary btn-sm text-white",disabled:l.running,onClick:Se},i(l.btnText),9,Ns)])]),t[63]||(t[63]=e("form",{method:"dialog",class:"modal-backdrop"},[e("button",null,"close")],-1))],512),e("dialog",{ref_key:"upgradeModal",ref:ee,class:"modal"},[e("div",Ss,[t[67]||(t[67]=e("h3",{class:"font-bold text-lg"},"升级部署",-1)),e("p",Us,i(a.label),1),t[68]||(t[68]=e("div",{class:"text-xs text-amber-700 mt-3 bg-amber-50 border border-amber-100 rounded-lg p-3 leading-relaxed"},[m(" 将 "),e("b",null,"SSH 连接到该部署绑定的环境节点"),m(",进入部署目录执行:"),e("span",{class:"font-mono"},"docker login → compose pull → up -d 重启 → 清理旧镜像"),m("。"),e("br"),m(" · 需服务器上已维护好 "),e("span",{class:"font-mono"},"docker-compose.yml / .env / confs"),m("(本操作不上传真实配置)。"),e("br"),m(" · 会"),e("b",null,"重启该部署的容器"),m(",请确认无误后再执行。 ")],-1)),e("div",Vs,[e("div",Is,[t[64]||(t[64]=e("span",{class:"w-20 text-slate-400 shrink-0"},"环境节点",-1)),e("span",{class:k(a.envName?"font-medium text-slate-700":"font-medium text-red-500")},i(a.envName||"未绑定 —— 请先在「编辑」里选择环境节点"),3)]),e("div",Os,[t[65]||(t[65]=e("span",{class:"w-20 text-slate-400 shrink-0"},"部署目录",-1)),e("span",{class:k(a.deployDir?"font-mono text-slate-700":"font-medium text-red-500")},i(a.deployDir||"未填 —— 请先在「编辑」里填写远端部署目录"),3)])]),e("div",Ds,[t[66]||(t[66]=e("label",{class:"text-xs text-slate-500"},"镜像 Tag(留空 = latest)",-1)),b(e("input",{"onUpdate:modelValue":t[24]||(t[24]=s=>a.tag=s),type:"text",class:"input input-bordered input-sm w-full font-mono",placeholder:"latest",disabled:a.running},null,8,As),[[w,a.tag]])]),b(e("div",zs,[e("div",Ls,[a.running?(r(),d("span",Rs)):a.ok?(r(),d("span",Fs,"✓ 部署完成")):(r(),d("span",js,"✗ 部署失败")),e("span",Ps,i(a.running?"执行中…":""),1)]),e("pre",{ref_key:"upLogBox",ref:H,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(a.log||"(等待输出…)"),513)],512),[[V,a.show]]),e("div",Es,[e("button",{class:"btn btn-ghost btn-sm",disabled:a.running,onClick:Ie},"关闭",8,Bs),e("button",{class:"btn btn-warning btn-sm",disabled:a.running,onClick:Oe},i(a.btnText),9,qs)])]),e("form",Hs,[e("button",{disabled:a.running},"close",8,Js)])],512)]))}}),tn=Fe(Ks,[["__scopeId","data-v-f8779360"]]);export{tn as default};