fix: use uvx for bench migrate in containers, add migrate progress bar

- Fix bench migrate command to use `su frappe -c 'uvx --from frappe-bench bench migrate'`
  instead of bare `bench migrate` which fails with exit code 127
- Apply fix to both run_bench_migrate and rebuild_container functions
- Increase migrate timeout from 300s to 600s
- Add animated progress bar with elapsed time counter to migrate modal
- Separate migrate progress UI from rebuild progress UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
js 2026-02-13 17:32:17 +00:00
parent 69b889c07a
commit 430a8c521f
46 changed files with 127 additions and 66 deletions

View File

@ -652,8 +652,8 @@ def run_bench_migrate(container_name, target_server=None):
logs.append("Executing bench migrate...") logs.append("Executing bench migrate...")
result = _exec_in_container( result = _exec_in_container(
target_server, container_name, target_server, container_name,
"cd /home/frappe/frappe-bench && bench migrate 2>&1", "su frappe -c 'cd /home/frappe/frappe-bench && uvx --from frappe-bench bench migrate' 2>&1",
timeout=300, timeout=600,
) )
if result["output"]: if result["output"]:
@ -738,8 +738,8 @@ def rebuild_container(container_name, image, target_server=None, run_migrate=Fal
time.sleep(5) # Wait for services to start time.sleep(5) # Wait for services to start
migrate_result = _exec_in_container( migrate_result = _exec_in_container(
target_server, container_name, target_server, container_name,
"cd /home/frappe/frappe-bench && bench migrate 2>&1", "su frappe -c 'cd /home/frappe/frappe-bench && uvx --from frappe-bench bench migrate' 2>&1",
timeout=300, timeout=600,
) )
if migrate_result["output"]: if migrate_result["output"]:
for line in migrate_result["output"].split("\n"): for line in migrate_result["output"].split("\n"):

View File

@ -1,2 +1,2 @@
import{o as a,c as r,b as e,t as i,M as s}from"./index-9e4551d9.js";const n={class:"rounded-lg border p-4 shadow-sm"},c={class:"flex items-center justify-between mb-4"},d={class:"text-base font-medium text-ink-gray-9"},l={class:"h-[200px]"},p={__name:"ChartCard",props:{title:{type:String,required:!0}},setup(o){return(t,_)=>(a(),r("div",n,[e("div",c,[e("h3",d,i(o.title),1),s(t.$slots,"actions")]),e("div",l,[s(t.$slots,"default")])]))}};export{p as _}; import{o as a,c as r,b as e,t as i,N as s}from"./index-840692da.js";const n={class:"rounded-lg border p-4 shadow-sm"},c={class:"flex items-center justify-between mb-4"},d={class:"text-base font-medium text-ink-gray-9"},l={class:"h-[200px]"},p={__name:"ChartCard",props:{title:{type:String,required:!0}},setup(o){return(t,_)=>(a(),r("div",n,[e("div",c,[e("h3",d,i(o.title),1),s(t.$slots,"actions")]),e("div",l,[s(t.$slots,"default")])]))}};export{p as _};
//# sourceMappingURL=ChartCard-ce1d8c61.js.map //# sourceMappingURL=ChartCard-bf2862cd.js.map

View File

@ -1 +1 @@
{"version":3,"file":"ChartCard-ce1d8c61.js","sources":["../../../../frontend/src/components/Common/ChartCard.vue"],"sourcesContent":["<template>\n <div class=\"rounded-lg border p-4 shadow-sm\">\n <div class=\"flex items-center justify-between mb-4\">\n <h3 class=\"text-base font-medium text-ink-gray-9\">\n {{ title }}\n </h3>\n <slot name=\"actions\" />\n </div>\n <div class=\"h-[200px]\">\n <slot />\n </div>\n </div>\n</template>\n\n<script setup>\ndefineProps({\n title: {\n type: String,\n required: true,\n },\n})\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_toDisplayString","__props","_renderSlot","_ctx","_hoisted_4"],"mappings":"sUACEA,EAAA,EAAAC,EAUM,MAVNC,EAUM,CATJC,EAKM,MALNC,EAKM,CAJJD,EAEK,KAFLE,EAEKC,EADAC,EAAK,KAAA,EAAA,CAAA,EAEVC,EAAuBC,EAAA,OAAA,SAAA,IAEzBN,EAEM,MAFNO,EAEM,CADJF,EAAQC,EAAA,OAAA,SAAA"} {"version":3,"file":"ChartCard-bf2862cd.js","sources":["../../../../frontend/src/components/Common/ChartCard.vue"],"sourcesContent":["<template>\n <div class=\"rounded-lg border p-4 shadow-sm\">\n <div class=\"flex items-center justify-between mb-4\">\n <h3 class=\"text-base font-medium text-ink-gray-9\">\n {{ title }}\n </h3>\n <slot name=\"actions\" />\n </div>\n <div class=\"h-[200px]\">\n <slot />\n </div>\n </div>\n</template>\n\n<script setup>\ndefineProps({\n title: {\n type: String,\n required: true,\n },\n})\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_toDisplayString","__props","_renderSlot","_ctx","_hoisted_4"],"mappings":"sUACEA,EAAA,EAAAC,EAUM,MAVNC,EAUM,CATJC,EAKM,MALNC,EAKM,CAJJD,EAEK,KAFLE,EAEKC,EADAC,EAAK,KAAA,EAAA,CAAA,EAEVC,EAAuBC,EAAA,OAAA,SAAA,IAEzBN,EAEM,MAFNO,EAEM,CADJF,EAAQC,EAAA,OAAA,SAAA"}

View File

@ -1,2 +1,2 @@
import{_ as g}from"./LayoutHeader-2dd693c9.js";import{c as v}from"./payments-4b7e79ca.js";import{n as k,o as c,c as m,d as n,g as r,b as t,t as s,p as w,q as $,e as i,_ as C,F as d,k as p,j as b,G as V,r as x}from"./index-9e4551d9.js";const F={class:"flex flex-col h-full"},B={class:"flex-1 overflow-auto p-5"},L={class:"rounded-lg border overflow-hidden"},N={class:"min-w-full divide-y"},U={class:"bg-surface-gray-2"},j={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},q={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},A={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},D={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},E={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},I={class:"px-6 py-3 text-right text-xs font-medium text-ink-gray-5 uppercase"},R={class:"divide-y"},S={class:"px-6 py-4"},z={class:"flex items-center gap-3"},G={class:"w-8 h-8 rounded-full bg-surface-gray-2 flex items-center justify-center"},Q={class:"text-sm font-medium text-ink-gray-5"},T={class:"text-sm font-medium text-ink-gray-9"},H={class:"px-6 py-4 text-sm text-ink-gray-7"},J={class:"px-6 py-4 text-sm text-ink-gray-7"},K={class:"px-6 py-4 text-sm font-medium text-ink-gray-9"},M={class:"px-6 py-4"},O={class:"px-6 py-4 text-sm text-ink-gray-7"},P={class:"px-6 py-4 text-right"},et={__name:"Customers",setup(W){const l=x(""),u=x(v),_=k(()=>{if(!l.value)return u.value;const e=l.value.toLowerCase();return u.value.filter(o=>o.name.toLowerCase().includes(e)||o.email.toLowerCase().includes(e))});function f(e){return new Intl.NumberFormat("ru-RU",{style:"currency",currency:"RUB",minimumFractionDigits:0}).format(e)}function y(){console.log("Exporting customers...")}function h(e){console.log("Viewing customer:",e.name)}return(e,o)=>(c(),m("div",F,[n(g,{title:e.$t("payments.customers")},{right:r(()=>[n(i(C),{modelValue:l.value,"onUpdate:modelValue":o[0]||(o[0]=a=>l.value=a),type:"text",placeholder:e.$t("common.search"),class:"w-64"},{prefix:r(()=>[n(i(d),{name:"search",class:"h-4 w-4 text-ink-gray-4"})]),_:1},8,["modelValue","placeholder"]),n(i(p),{variant:"subtle",onClick:y},{prefix:r(()=>[n(i(d),{name:"download",class:"h-4 w-4"})]),default:r(()=>[b(" "+s(e.$t("common.export")),1)]),_:1})]),_:1},8,["title"]),t("div",B,[t("div",L,[t("table",N,[t("thead",U,[t("tr",null,[t("th",j,s(e.$t("payments.customer.name")),1),t("th",q,s(e.$t("payments.customer.email")),1),t("th",A,s(e.$t("payments.connections")),1),t("th",D,s(e.$t("payments.customer.amount")),1),t("th",E,s(e.$t("payments.customer.status")),1),o[1]||(o[1]=t("th",{class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},"Since",-1)),t("th",I,s(e.$t("common.actions")),1)])]),t("tbody",R,[(c(!0),m(w,null,$(_.value,a=>(c(),m("tr",{key:a.id},[t("td",S,[t("div",z,[t("div",G,[t("span",Q,s(a.name.charAt(0)),1)]),t("span",T,s(a.name),1)])]),t("td",H,s(a.email),1),t("td",J,s(a.connections),1),t("td",K,s(f(a.monthlyAmount))+"/mo ",1),t("td",M,[n(i(V),{variant:a.status==="active"?"success":"info",label:a.status},null,8,["variant","label"])]),t("td",O,s(a.since),1),t("td",P,[n(i(p),{variant:"ghost",size:"sm",onClick:X=>h(a)},{default:r(()=>[n(i(d),{name:"eye",class:"h-4 w-4"})]),_:1},8,["onClick"])])]))),128))])])])])]))}};export{et as default}; import{_ as g}from"./LayoutHeader-aeb3d2f2.js";import{c as v}from"./payments-4b7e79ca.js";import{n as k,o as c,c as m,d as n,g as r,b as t,t as s,p as w,q as $,e as i,_ as C,F as d,k as p,j as b,G as V,r as x}from"./index-840692da.js";const F={class:"flex flex-col h-full"},B={class:"flex-1 overflow-auto p-5"},L={class:"rounded-lg border overflow-hidden"},N={class:"min-w-full divide-y"},U={class:"bg-surface-gray-2"},j={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},q={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},A={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},D={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},E={class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},I={class:"px-6 py-3 text-right text-xs font-medium text-ink-gray-5 uppercase"},R={class:"divide-y"},S={class:"px-6 py-4"},z={class:"flex items-center gap-3"},G={class:"w-8 h-8 rounded-full bg-surface-gray-2 flex items-center justify-center"},Q={class:"text-sm font-medium text-ink-gray-5"},T={class:"text-sm font-medium text-ink-gray-9"},H={class:"px-6 py-4 text-sm text-ink-gray-7"},J={class:"px-6 py-4 text-sm text-ink-gray-7"},K={class:"px-6 py-4 text-sm font-medium text-ink-gray-9"},M={class:"px-6 py-4"},O={class:"px-6 py-4 text-sm text-ink-gray-7"},P={class:"px-6 py-4 text-right"},et={__name:"Customers",setup(W){const l=x(""),u=x(v),_=k(()=>{if(!l.value)return u.value;const e=l.value.toLowerCase();return u.value.filter(o=>o.name.toLowerCase().includes(e)||o.email.toLowerCase().includes(e))});function f(e){return new Intl.NumberFormat("ru-RU",{style:"currency",currency:"RUB",minimumFractionDigits:0}).format(e)}function y(){console.log("Exporting customers...")}function h(e){console.log("Viewing customer:",e.name)}return(e,o)=>(c(),m("div",F,[n(g,{title:e.$t("payments.customers")},{right:r(()=>[n(i(C),{modelValue:l.value,"onUpdate:modelValue":o[0]||(o[0]=a=>l.value=a),type:"text",placeholder:e.$t("common.search"),class:"w-64"},{prefix:r(()=>[n(i(d),{name:"search",class:"h-4 w-4 text-ink-gray-4"})]),_:1},8,["modelValue","placeholder"]),n(i(p),{variant:"subtle",onClick:y},{prefix:r(()=>[n(i(d),{name:"download",class:"h-4 w-4"})]),default:r(()=>[b(" "+s(e.$t("common.export")),1)]),_:1})]),_:1},8,["title"]),t("div",B,[t("div",L,[t("table",N,[t("thead",U,[t("tr",null,[t("th",j,s(e.$t("payments.customer.name")),1),t("th",q,s(e.$t("payments.customer.email")),1),t("th",A,s(e.$t("payments.connections")),1),t("th",D,s(e.$t("payments.customer.amount")),1),t("th",E,s(e.$t("payments.customer.status")),1),o[1]||(o[1]=t("th",{class:"px-6 py-3 text-left text-xs font-medium text-ink-gray-5 uppercase"},"Since",-1)),t("th",I,s(e.$t("common.actions")),1)])]),t("tbody",R,[(c(!0),m(w,null,$(_.value,a=>(c(),m("tr",{key:a.id},[t("td",S,[t("div",z,[t("div",G,[t("span",Q,s(a.name.charAt(0)),1)]),t("span",T,s(a.name),1)])]),t("td",H,s(a.email),1),t("td",J,s(a.connections),1),t("td",K,s(f(a.monthlyAmount))+"/mo ",1),t("td",M,[n(i(V),{variant:a.status==="active"?"success":"info",label:a.status},null,8,["variant","label"])]),t("td",O,s(a.since),1),t("td",P,[n(i(p),{variant:"ghost",size:"sm",onClick:X=>h(a)},{default:r(()=>[n(i(d),{name:"eye",class:"h-4 w-4"})]),_:1},8,["onClick"])])]))),128))])])])])]))}};export{et as default};
//# sourceMappingURL=Customers-657eb23a.js.map //# sourceMappingURL=Customers-4e585bd9.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
import{o as t,c as s,b as n,d as r,e as c,F as o,t as a,i as l,M as d}from"./index-9e4551d9.js";const m={class:"flex flex-col items-center justify-center py-12 text-center"},u={class:"mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-surface-gray-2"},f={class:"mb-2 text-lg font-medium text-ink-gray-9"},y={key:0,class:"mb-4 max-w-sm text-sm text-ink-gray-5"},p={__name:"EmptyState",props:{icon:{type:String,default:"inbox"},title:{type:String,required:!0},description:{type:String,default:""}},setup(e){return(i,x)=>(t(),s("div",m,[n("div",u,[r(c(o),{name:e.icon,class:"h-8 w-8 text-ink-gray-4"},null,8,["name"])]),n("h3",f,a(e.title),1),e.description?(t(),s("p",y,a(e.description),1)):l("",!0),d(i.$slots,"action")]))}};export{p as _}; import{o as t,c as s,b as n,d as r,e as c,F as o,t as a,i as l,N as d}from"./index-840692da.js";const m={class:"flex flex-col items-center justify-center py-12 text-center"},u={class:"mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-surface-gray-2"},f={class:"mb-2 text-lg font-medium text-ink-gray-9"},y={key:0,class:"mb-4 max-w-sm text-sm text-ink-gray-5"},p={__name:"EmptyState",props:{icon:{type:String,default:"inbox"},title:{type:String,required:!0},description:{type:String,default:""}},setup(e){return(i,x)=>(t(),s("div",m,[n("div",u,[r(c(o),{name:e.icon,class:"h-8 w-8 text-ink-gray-4"},null,8,["name"])]),n("h3",f,a(e.title),1),e.description?(t(),s("p",y,a(e.description),1)):l("",!0),d(i.$slots,"action")]))}};export{p as _};
//# sourceMappingURL=EmptyState-1b153c1f.js.map //# sourceMappingURL=EmptyState-c9a94953.js.map

View File

@ -1 +1 @@
{"version":3,"file":"EmptyState-1b153c1f.js","sources":["../../../../frontend/src/components/Common/EmptyState.vue"],"sourcesContent":["<template>\n <div class=\"flex flex-col items-center justify-center py-12 text-center\">\n <div\n class=\"mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-surface-gray-2\"\n >\n <FeatherIcon :name=\"icon\" class=\"h-8 w-8 text-ink-gray-4\" />\n </div>\n <h3 class=\"mb-2 text-lg font-medium text-ink-gray-9\">\n {{ title }}\n </h3>\n <p v-if=\"description\" class=\"mb-4 max-w-sm text-sm text-ink-gray-5\">\n {{ description }}\n </p>\n <slot name=\"action\" />\n </div>\n</template>\n\n<script setup>\nimport { FeatherIcon } from 'frappe-ui'\n\ndefineProps({\n icon: {\n type: String,\n default: 'inbox',\n },\n title: {\n type: String,\n required: true,\n },\n description: {\n type: String,\n default: '',\n },\n})\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","_unref","FeatherIcon","__props","_hoisted_3","_toDisplayString","_hoisted_4","_renderSlot","_ctx"],"mappings":"ohBACEA,EAAA,EAAAC,EAaM,MAbNC,EAaM,CAZJC,EAIM,MAJNC,EAIM,CADJC,EAA4DC,EAAAC,CAAA,EAAA,CAA9C,KAAMC,EAAI,KAAE,MAAM,8CAElCL,EAEK,KAFLM,EAEKC,EADAF,EAAK,KAAA,EAAA,CAAA,EAEDA,EAAW,iBAApBP,EAEI,IAFJU,EAEID,EADCF,EAAW,WAAA,EAAA,CAAA,YAEhBI,EAAsBC,EAAA,OAAA,QAAA"} {"version":3,"file":"EmptyState-c9a94953.js","sources":["../../../../frontend/src/components/Common/EmptyState.vue"],"sourcesContent":["<template>\n <div class=\"flex flex-col items-center justify-center py-12 text-center\">\n <div\n class=\"mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-surface-gray-2\"\n >\n <FeatherIcon :name=\"icon\" class=\"h-8 w-8 text-ink-gray-4\" />\n </div>\n <h3 class=\"mb-2 text-lg font-medium text-ink-gray-9\">\n {{ title }}\n </h3>\n <p v-if=\"description\" class=\"mb-4 max-w-sm text-sm text-ink-gray-5\">\n {{ description }}\n </p>\n <slot name=\"action\" />\n </div>\n</template>\n\n<script setup>\nimport { FeatherIcon } from 'frappe-ui'\n\ndefineProps({\n icon: {\n type: String,\n default: 'inbox',\n },\n title: {\n type: String,\n required: true,\n },\n description: {\n type: String,\n default: '',\n },\n})\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","_unref","FeatherIcon","__props","_hoisted_3","_toDisplayString","_hoisted_4","_renderSlot","_ctx"],"mappings":"ohBACEA,EAAA,EAAAC,EAaM,MAbNC,EAaM,CAZJC,EAIM,MAJNC,EAIM,CADJC,EAA4DC,EAAAC,CAAA,EAAA,CAA9C,KAAMC,EAAI,KAAE,MAAM,8CAElCL,EAEK,KAFLM,EAEKC,EADAF,EAAK,KAAA,EAAA,CAAA,EAEDA,EAAW,iBAApBP,EAEI,IAFJU,EAEID,EADCF,EAAW,WAAA,EAAA,CAAA,YAEhBI,EAAsBC,EAAA,OAAA,QAAA"}

View File

@ -1,2 +1,2 @@
import{o as a,c as r,b as e,M as s,t as i}from"./index-9e4551d9.js";const c={class:"flex items-center justify-between border-b bg-surface-white px-5 py-2.5"},l={class:"flex items-center gap-2"},n={class:"text-xl font-semibold text-ink-gray-9"},d={class:"flex items-center gap-2"},f={__name:"LayoutHeader",props:{title:{type:String,default:""}},setup(o){return(t,_)=>(a(),r("header",c,[e("div",l,[s(t.$slots,"left",{},()=>[e("h1",n,i(o.title),1)])]),e("div",d,[s(t.$slots,"right")])]))}};export{f as _}; import{o as a,c as r,b as e,N as s,t as i}from"./index-840692da.js";const c={class:"flex items-center justify-between border-b bg-surface-white px-5 py-2.5"},l={class:"flex items-center gap-2"},n={class:"text-xl font-semibold text-ink-gray-9"},d={class:"flex items-center gap-2"},f={__name:"LayoutHeader",props:{title:{type:String,default:""}},setup(o){return(t,_)=>(a(),r("header",c,[e("div",l,[s(t.$slots,"left",{},()=>[e("h1",n,i(o.title),1)])]),e("div",d,[s(t.$slots,"right")])]))}};export{f as _};
//# sourceMappingURL=LayoutHeader-2dd693c9.js.map //# sourceMappingURL=LayoutHeader-aeb3d2f2.js.map

View File

@ -1 +1 @@
{"version":3,"file":"LayoutHeader-2dd693c9.js","sources":["../../../../frontend/src/components/Common/LayoutHeader.vue"],"sourcesContent":["<template>\n <header class=\"flex items-center justify-between border-b bg-surface-white px-5 py-2.5\">\n <div class=\"flex items-center gap-2\">\n <slot name=\"left\">\n <h1 class=\"text-xl font-semibold text-ink-gray-9\">\n {{ title }}\n </h1>\n </slot>\n </div>\n <div class=\"flex items-center gap-2\">\n <slot name=\"right\" />\n </div>\n </header>\n</template>\n\n<script setup>\ndefineProps({\n title: {\n type: String,\n default: '',\n },\n})\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_renderSlot","_ctx","_hoisted_3","_toDisplayString","__props","_hoisted_4"],"mappings":"+WACEA,EAAA,EAAAC,EAWS,SAXTC,EAWS,CAVPC,EAMM,MANNC,EAMM,CALJC,EAIOC,mBAJP,IAIO,CAHLH,EAEK,KAFLI,EAEKC,EADAC,EAAK,KAAA,EAAA,CAAA,MAIdN,EAEM,MAFNO,EAEM,CADJL,EAAqBC,EAAA,OAAA,OAAA"} {"version":3,"file":"LayoutHeader-aeb3d2f2.js","sources":["../../../../frontend/src/components/Common/LayoutHeader.vue"],"sourcesContent":["<template>\n <header class=\"flex items-center justify-between border-b bg-surface-white px-5 py-2.5\">\n <div class=\"flex items-center gap-2\">\n <slot name=\"left\">\n <h1 class=\"text-xl font-semibold text-ink-gray-9\">\n {{ title }}\n </h1>\n </slot>\n </div>\n <div class=\"flex items-center gap-2\">\n <slot name=\"right\" />\n </div>\n </header>\n</template>\n\n<script setup>\ndefineProps({\n title: {\n type: String,\n default: '',\n },\n})\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_renderSlot","_ctx","_hoisted_3","_toDisplayString","__props","_hoisted_4"],"mappings":"+WACEA,EAAA,EAAAC,EAWS,SAXTC,EAWS,CAVPC,EAMM,MANNC,EAMM,CALJC,EAIOC,mBAJP,IAIO,CAHLH,EAEK,KAFLI,EAEKC,EADAC,EAAK,KAAA,EAAA,CAAA,MAIdN,EAEM,MAFNO,EAEM,CADJL,EAAqBC,EAAA,OAAA,OAAA"}

View File

@ -1,2 +1,2 @@
import{u as b,a as _,o as f,c as y,b as e,d as r,t as a,w as x,e as n,f as w,v as k,g as V,h as $,i as B,r as o,A as L,_ as p,j as M,k as N,l as j,m as q,s as A}from"./index-9e4551d9.js";const C={class:"min-h-screen flex items-center justify-center bg-surface-gray-2 py-12 px-4 sm:px-6 lg:px-8"},U={class:"max-w-md w-full space-y-8"},D={class:"text-center"},P={class:"mt-2 text-sm text-ink-gray-5"},R={class:"space-y-4"},S={for:"email",class:"block text-sm font-medium text-ink-gray-7"},E={for:"password",class:"block text-sm font-medium text-ink-gray-7"},T={class:"flex items-center justify-between"},z={class:"flex items-center"},F={for:"remember-me",class:"ml-2 block text-sm text-ink-gray-7"},G={class:"text-sm"},H={href:"#",class:"font-medium text-blue-600 hover:text-blue-500"},K={__name:"Login",setup(I){const h=b(),v=_(),d=o(""),u=o(""),c=o(!1),m=o(!1),i=o("");async function g(){m.value=!0,i.value="";try{await q("login",{usr:d.value,pwd:u.value}),await A.init();const s=v.query.redirect||"/dashboard";h.push(s)}catch(s){i.value=s.message||"Login failed"}finally{m.value=!1}}return(s,t)=>(f(),y("div",C,[e("div",U,[e("div",D,[r(L,{class:"mx-auto h-16 w-16"}),t[3]||(t[3]=e("h2",{class:"mt-6 text-3xl font-bold text-ink-gray-9"}," Admin Panel ",-1)),e("p",P,a(s.$t("auth.login")),1)]),e("form",{class:"mt-8 space-y-6",onSubmit:x(g,["prevent"])},[e("div",R,[e("div",null,[e("label",S,a(s.$t("auth.email")),1),r(n(p),{id:"email",modelValue:d.value,"onUpdate:modelValue":t[0]||(t[0]=l=>d.value=l),type:"email",required:"",class:"mt-1",placeholder:s.$t("auth.email")},null,8,["modelValue","placeholder"])]),e("div",null,[e("label",E,a(s.$t("auth.password")),1),r(n(p),{id:"password",modelValue:u.value,"onUpdate:modelValue":t[1]||(t[1]=l=>u.value=l),type:"password",required:"",class:"mt-1",placeholder:s.$t("auth.password")},null,8,["modelValue","placeholder"])])]),e("div",T,[e("div",z,[w(e("input",{id:"remember-me","onUpdate:modelValue":t[2]||(t[2]=l=>c.value=l),type:"checkbox",class:"h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"},null,512),[[k,c.value]]),e("label",F,a(s.$t("auth.rememberMe")),1)]),e("div",G,[e("a",H,a(s.$t("auth.forgotPassword")),1)])]),e("div",null,[r(n(N),{type:"submit",variant:"solid",class:"w-full",loading:m.value},{default:V(()=>[M(a(s.$t("auth.loginButton")),1)]),_:1},8,["loading"])]),i.value?(f(),$(n(j),{key:0,message:i.value},null,8,["message"])):B("",!0)],32)])]))}};export{K as default}; import{u as b,a as _,o as f,c as y,b as e,d as r,t as a,w as x,e as n,f as w,v as k,g as V,h as $,i as B,r as o,A as L,_ as p,j as M,k as N,l as j,m as q,s as A}from"./index-840692da.js";const C={class:"min-h-screen flex items-center justify-center bg-surface-gray-2 py-12 px-4 sm:px-6 lg:px-8"},U={class:"max-w-md w-full space-y-8"},D={class:"text-center"},P={class:"mt-2 text-sm text-ink-gray-5"},R={class:"space-y-4"},S={for:"email",class:"block text-sm font-medium text-ink-gray-7"},E={for:"password",class:"block text-sm font-medium text-ink-gray-7"},T={class:"flex items-center justify-between"},z={class:"flex items-center"},F={for:"remember-me",class:"ml-2 block text-sm text-ink-gray-7"},G={class:"text-sm"},H={href:"#",class:"font-medium text-blue-600 hover:text-blue-500"},K={__name:"Login",setup(I){const h=b(),v=_(),d=o(""),u=o(""),c=o(!1),m=o(!1),i=o("");async function g(){m.value=!0,i.value="";try{await q("login",{usr:d.value,pwd:u.value}),await A.init();const s=v.query.redirect||"/dashboard";h.push(s)}catch(s){i.value=s.message||"Login failed"}finally{m.value=!1}}return(s,t)=>(f(),y("div",C,[e("div",U,[e("div",D,[r(L,{class:"mx-auto h-16 w-16"}),t[3]||(t[3]=e("h2",{class:"mt-6 text-3xl font-bold text-ink-gray-9"}," Admin Panel ",-1)),e("p",P,a(s.$t("auth.login")),1)]),e("form",{class:"mt-8 space-y-6",onSubmit:x(g,["prevent"])},[e("div",R,[e("div",null,[e("label",S,a(s.$t("auth.email")),1),r(n(p),{id:"email",modelValue:d.value,"onUpdate:modelValue":t[0]||(t[0]=l=>d.value=l),type:"email",required:"",class:"mt-1",placeholder:s.$t("auth.email")},null,8,["modelValue","placeholder"])]),e("div",null,[e("label",E,a(s.$t("auth.password")),1),r(n(p),{id:"password",modelValue:u.value,"onUpdate:modelValue":t[1]||(t[1]=l=>u.value=l),type:"password",required:"",class:"mt-1",placeholder:s.$t("auth.password")},null,8,["modelValue","placeholder"])])]),e("div",T,[e("div",z,[w(e("input",{id:"remember-me","onUpdate:modelValue":t[2]||(t[2]=l=>c.value=l),type:"checkbox",class:"h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"},null,512),[[k,c.value]]),e("label",F,a(s.$t("auth.rememberMe")),1)]),e("div",G,[e("a",H,a(s.$t("auth.forgotPassword")),1)])]),e("div",null,[r(n(N),{type:"submit",variant:"solid",class:"w-full",loading:m.value},{default:V(()=>[M(a(s.$t("auth.loginButton")),1)]),_:1},8,["loading"])]),i.value?(f(),$(n(j),{key:0,message:i.value},null,8,["message"])):B("",!0)],32)])]))}};export{K as default};
//# sourceMappingURL=Login-d5ee5279.js.map //# sourceMappingURL=Login-5943e925.js.map

View File

@ -1,2 +1,2 @@
import{o,c as a,b as e,d as r,g as n,e as i,j as d,k as l}from"./index-9e4551d9.js";const x={class:"min-h-screen flex items-center justify-center bg-surface-gray-2 py-12 px-4"},u={class:"text-center"},g={__name:"NotFound",setup(f){return(s,t)=>(o(),a("div",x,[e("div",u,[t[2]||(t[2]=e("h1",{class:"text-9xl font-bold text-ink-gray-2"},"404",-1)),t[3]||(t[3]=e("h2",{class:"mt-4 text-2xl font-semibold text-ink-gray-9"}," Page Not Found ",-1)),t[4]||(t[4]=e("p",{class:"mt-2 text-ink-gray-5"}," The page you're looking for doesn't exist. ",-1)),r(i(l),{variant:"solid",class:"mt-6",onClick:t[0]||(t[0]=m=>s.$router.push("/dashboard"))},{default:n(()=>[...t[1]||(t[1]=[d(" Go to Dashboard ",-1)])]),_:1})])]))}};export{g as default}; import{o,c as a,b as e,d as r,g as n,e as i,j as d,k as l}from"./index-840692da.js";const x={class:"min-h-screen flex items-center justify-center bg-surface-gray-2 py-12 px-4"},u={class:"text-center"},g={__name:"NotFound",setup(f){return(s,t)=>(o(),a("div",x,[e("div",u,[t[2]||(t[2]=e("h1",{class:"text-9xl font-bold text-ink-gray-2"},"404",-1)),t[3]||(t[3]=e("h2",{class:"mt-4 text-2xl font-semibold text-ink-gray-9"}," Page Not Found ",-1)),t[4]||(t[4]=e("p",{class:"mt-2 text-ink-gray-5"}," The page you're looking for doesn't exist. ",-1)),r(i(l),{variant:"solid",class:"mt-6",onClick:t[0]||(t[0]=m=>s.$router.push("/dashboard"))},{default:n(()=>[...t[1]||(t[1]=[d(" Go to Dashboard ",-1)])]),_:1})])]))}};export{g as default};
//# sourceMappingURL=NotFound-4f692438.js.map //# sourceMappingURL=NotFound-c369cbc5.js.map

View File

@ -1 +1 @@
{"version":3,"file":"NotFound-4f692438.js","sources":["../../../../frontend/src/pages/admin/NotFound.vue"],"sourcesContent":["<template>\n <div class=\"min-h-screen flex items-center justify-center bg-surface-gray-2 py-12 px-4\">\n <div class=\"text-center\">\n <h1 class=\"text-9xl font-bold text-ink-gray-2\">404</h1>\n <h2 class=\"mt-4 text-2xl font-semibold text-ink-gray-9\">\n Page Not Found\n </h2>\n <p class=\"mt-2 text-ink-gray-5\">\n The page you're looking for doesn't exist.\n </p>\n <Button\n variant=\"solid\"\n class=\"mt-6\"\n @click=\"$router.push('/dashboard')\"\n >\n Go to Dashboard\n </Button>\n </div>\n </div>\n</template>\n\n<script setup>\nimport { Button } from 'frappe-ui'\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_createVNode","_unref","Button","$event","$router"],"mappings":"qPACEA,EAAA,EAAAC,EAiBM,MAjBNC,EAiBM,CAhBJC,EAeM,MAfNC,EAeM,CAdJC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAAuD,KAAnD,CAAA,MAAM,oCAAoC,EAAC,MAAG,EAAA,GAClDE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAEK,KAFD,CAAA,MAAM,6CAA6C,EAAC,mBAExD,EAAA,GACAE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAEI,IAFD,CAAA,MAAM,sBAAsB,EAAC,+CAEhC,EAAA,GACAG,EAMSC,EAAAC,CAAA,EAAA,CALP,QAAQ,QACR,MAAM,OACL,QAAKH,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAI,GAAEC,EAAO,QAAC,KAAI,YAAA,eACrB,IAED,CAAA,GAAAL,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAFC,oBAED,EAAA"} {"version":3,"file":"NotFound-c369cbc5.js","sources":["../../../../frontend/src/pages/admin/NotFound.vue"],"sourcesContent":["<template>\n <div class=\"min-h-screen flex items-center justify-center bg-surface-gray-2 py-12 px-4\">\n <div class=\"text-center\">\n <h1 class=\"text-9xl font-bold text-ink-gray-2\">404</h1>\n <h2 class=\"mt-4 text-2xl font-semibold text-ink-gray-9\">\n Page Not Found\n </h2>\n <p class=\"mt-2 text-ink-gray-5\">\n The page you're looking for doesn't exist.\n </p>\n <Button\n variant=\"solid\"\n class=\"mt-6\"\n @click=\"$router.push('/dashboard')\"\n >\n Go to Dashboard\n </Button>\n </div>\n </div>\n</template>\n\n<script setup>\nimport { Button } from 'frappe-ui'\n</script>\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_createVNode","_unref","Button","$event","$router"],"mappings":"qPACEA,EAAA,EAAAC,EAiBM,MAjBNC,EAiBM,CAhBJC,EAeM,MAfNC,EAeM,CAdJC,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAAuD,KAAnD,CAAA,MAAM,oCAAoC,EAAC,MAAG,EAAA,GAClDE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAEK,KAFD,CAAA,MAAM,6CAA6C,EAAC,mBAExD,EAAA,GACAE,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAF,EAEI,IAFD,CAAA,MAAM,sBAAsB,EAAC,+CAEhC,EAAA,GACAG,EAMSC,EAAAC,CAAA,EAAA,CALP,QAAQ,QACR,MAAM,OACL,QAAKH,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAI,GAAEC,EAAO,QAAC,KAAI,YAAA,eACrB,IAED,CAAA,GAAAL,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAA,GAFC,oBAED,EAAA"}

View File

@ -0,0 +1 @@
.migrate-progress[data-v-9073c711]{animation:migrate-bar-9073c711 3s ease-in-out infinite}@keyframes migrate-bar-9073c711{0%{width:5%}50%{width:70%}to{width:95%}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
import{n as s,o as c,c as u,b as r,E as i,d as o,e as d,F as g,t as l,i as m}from"./index-9e4551d9.js";const p={class:"rounded-lg border p-4 shadow-sm"},v={class:"flex items-center justify-between"},h={class:"flex items-center gap-3"},w={class:"text-sm text-ink-gray-5"},N={class:"text-2xl font-semibold text-ink-gray-9"},k={class:"text-sm font-medium"},S={key:0,class:"mt-2 text-xs text-ink-gray-5"},F={__name:"StatCard",props:{label:{type:String,required:!0},value:{type:[Number,String],required:!0},iconName:{type:String,default:"activity"},color:{type:String,default:"blue",validator:e=>["blue","green","red","yellow","purple","gray"].includes(e)},trend:{type:Number,default:void 0},subtitle:{type:String,default:""},format:{type:String,default:"number",validator:e=>["number","currency","percent"].includes(e)}},setup(e){const n=e,a={blue:{bg:"bg-blue-100",icon:"text-blue-600"},green:{bg:"bg-green-100",icon:"text-green-600"},red:{bg:"bg-red-100",icon:"text-red-600"},yellow:{bg:"bg-orange-100",icon:"text-orange-600"},purple:{bg:"bg-purple-100",icon:"text-purple-600"},gray:{bg:"bg-surface-gray-2",icon:"text-ink-gray-5"}},b=s(()=>{var t;return((t=a[n.color])==null?void 0:t.bg)||a.blue.bg}),f=s(()=>{var t;return((t=a[n.color])==null?void 0:t.icon)||a.blue.icon}),y=s(()=>n.trend>0?"text-green-600":n.trend<0?"text-red-600":"text-ink-gray-5"),x=s(()=>{const t=typeof n.value=="number"?n.value:parseFloat(n.value);if(isNaN(t))return n.value;switch(n.format){case"currency":return new Intl.NumberFormat("ru-RU",{style:"currency",currency:"RUB",minimumFractionDigits:0}).format(t);case"percent":return`${t}%`;default:return new Intl.NumberFormat("ru-RU").format(t)}});return(t,C)=>(c(),u("div",p,[r("div",v,[r("div",h,[r("div",{class:i(["flex h-10 w-10 items-center justify-center rounded-lg",b.value])},[o(d(g),{name:e.iconName,class:i(["h-5 w-5",f.value])},null,8,["name","class"])],2),r("div",null,[r("p",w,l(e.label),1),r("p",N,l(x.value),1)])]),e.trend!==void 0?(c(),u("div",{key:0,class:i(["flex items-center gap-1",y.value])},[o(d(g),{name:e.trend>0?"trending-up":"trending-down",class:"h-4 w-4"},null,8,["name"]),r("span",k,l(Math.abs(e.trend))+"%",1)],2)):m("",!0)]),e.subtitle?(c(),u("div",S,l(e.subtitle),1)):m("",!0)]))}};export{F as _}; import{n as s,o as c,c as u,b as r,E as i,d as o,e as d,F as g,t as l,i as m}from"./index-840692da.js";const p={class:"rounded-lg border p-4 shadow-sm"},v={class:"flex items-center justify-between"},h={class:"flex items-center gap-3"},w={class:"text-sm text-ink-gray-5"},N={class:"text-2xl font-semibold text-ink-gray-9"},k={class:"text-sm font-medium"},S={key:0,class:"mt-2 text-xs text-ink-gray-5"},F={__name:"StatCard",props:{label:{type:String,required:!0},value:{type:[Number,String],required:!0},iconName:{type:String,default:"activity"},color:{type:String,default:"blue",validator:e=>["blue","green","red","yellow","purple","gray"].includes(e)},trend:{type:Number,default:void 0},subtitle:{type:String,default:""},format:{type:String,default:"number",validator:e=>["number","currency","percent"].includes(e)}},setup(e){const n=e,a={blue:{bg:"bg-blue-100",icon:"text-blue-600"},green:{bg:"bg-green-100",icon:"text-green-600"},red:{bg:"bg-red-100",icon:"text-red-600"},yellow:{bg:"bg-orange-100",icon:"text-orange-600"},purple:{bg:"bg-purple-100",icon:"text-purple-600"},gray:{bg:"bg-surface-gray-2",icon:"text-ink-gray-5"}},b=s(()=>{var t;return((t=a[n.color])==null?void 0:t.bg)||a.blue.bg}),f=s(()=>{var t;return((t=a[n.color])==null?void 0:t.icon)||a.blue.icon}),y=s(()=>n.trend>0?"text-green-600":n.trend<0?"text-red-600":"text-ink-gray-5"),x=s(()=>{const t=typeof n.value=="number"?n.value:parseFloat(n.value);if(isNaN(t))return n.value;switch(n.format){case"currency":return new Intl.NumberFormat("ru-RU",{style:"currency",currency:"RUB",minimumFractionDigits:0}).format(t);case"percent":return`${t}%`;default:return new Intl.NumberFormat("ru-RU").format(t)}});return(t,C)=>(c(),u("div",p,[r("div",v,[r("div",h,[r("div",{class:i(["flex h-10 w-10 items-center justify-center rounded-lg",b.value])},[o(d(g),{name:e.iconName,class:i(["h-5 w-5",f.value])},null,8,["name","class"])],2),r("div",null,[r("p",w,l(e.label),1),r("p",N,l(x.value),1)])]),e.trend!==void 0?(c(),u("div",{key:0,class:i(["flex items-center gap-1",y.value])},[o(d(g),{name:e.trend>0?"trending-up":"trending-down",class:"h-4 w-4"},null,8,["name"]),r("span",k,l(Math.abs(e.trend))+"%",1)],2)):m("",!0)]),e.subtitle?(c(),u("div",S,l(e.subtitle),1)):m("",!0)]))}};export{F as _};
//# sourceMappingURL=StatCard-81039846.js.map //# sourceMappingURL=StatCard-39bd0409.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,8 +15,8 @@
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
})(); })();
</script> </script>
<script type="module" crossorigin src="/assets/admin_panel/frontend/assets/index-9e4551d9.js"></script> <script type="module" crossorigin src="/assets/admin_panel/frontend/assets/index-840692da.js"></script>
<link rel="stylesheet" href="/assets/admin_panel/frontend/assets/index-43b47371.css"> <link rel="stylesheet" href="/assets/admin_panel/frontend/assets/index-4119d0df.css">
</head> </head>
<body class="h-full"> <body class="h-full">
<div id="app" class="h-full"></div> <div id="app" class="h-full"></div>

View File

@ -15,8 +15,8 @@
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
})(); })();
</script> </script>
<script type="module" crossorigin src="/assets/admin_panel/frontend/assets/index-9e4551d9.js"></script> <script type="module" crossorigin src="/assets/admin_panel/frontend/assets/index-840692da.js"></script>
<link rel="stylesheet" href="/assets/admin_panel/frontend/assets/index-43b47371.css"> <link rel="stylesheet" href="/assets/admin_panel/frontend/assets/index-4119d0df.css">
</head> </head>
<body class="h-full"> <body class="h-full">
<div id="app" class="h-full"></div> <div id="app" class="h-full"></div>

View File

@ -516,7 +516,7 @@
</Dialog> </Dialog>
<!-- Rebuild Progress/Result Modal --> <!-- Rebuild Progress/Result Modal -->
<Dialog v-model="showRebuildProgress" :options="{ title: rebuilding ? 'Updating...' : (actionLoading === 'migrate' ? 'Running migrate...' : 'Result'), size: 'lg' }"> <Dialog v-model="showRebuildProgress" :options="{ title: rebuilding ? 'Updating...' : (migrateRunning ? 'Running migrate...' : 'Result'), size: 'lg' }">
<template #body-content> <template #body-content>
<div class="space-y-4"> <div class="space-y-4">
<!-- Progress indicator during rebuild --> <!-- Progress indicator during rebuild -->
@ -531,7 +531,24 @@
</div> </div>
</div> </div>
<!-- Result after rebuild completes --> <!-- Progress indicator during migrate -->
<div v-else-if="migrateRunning" class="flex flex-col gap-4 py-4">
<div class="flex items-center gap-3">
<Spinner class="h-6 w-6 text-blue-500 flex-shrink-0" />
<div>
<p class="text-sm font-medium text-ink-gray-8">Running bench migrate...</p>
<p class="text-xs text-ink-gray-5 mt-0.5">
Executing: <code class="bg-surface-gray-2 px-1 rounded">uvx --from frappe-bench bench migrate</code>
</p>
</div>
</div>
<div class="w-full bg-surface-gray-2 rounded-full h-2 overflow-hidden">
<div class="h-full bg-blue-500 rounded-full transition-all duration-1000 ease-out migrate-progress"></div>
</div>
<p class="text-xs text-ink-gray-4 text-center">{{ migrateElapsed }}</p>
</div>
<!-- Result after rebuild/migrate completes -->
<template v-else> <template v-else>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<FeatherIcon <FeatherIcon
@ -655,6 +672,32 @@ const showRebuildProgress = ref(false)
const rebuildResult = ref({ success: false, message: '', logs: [] }) const rebuildResult = ref({ success: false, message: '', logs: [] })
const runMigrateAfterRebuild = ref(true) const runMigrateAfterRebuild = ref(true)
// Migrate progress state
const migrateRunning = ref(false)
const migrateStartTime = ref(null)
const migrateElapsed = ref('Elapsed: 0s')
let migrateTimer = null
function startMigrateTimer() {
migrateStartTime.value = Date.now()
migrateElapsed.value = 'Elapsed: 0s'
migrateTimer = setInterval(() => {
const seconds = Math.floor((Date.now() - migrateStartTime.value) / 1000)
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
migrateElapsed.value = mins > 0
? `Elapsed: ${mins}m ${secs}s`
: `Elapsed: ${secs}s`
}, 1000)
}
function stopMigrateTimer() {
if (migrateTimer) {
clearInterval(migrateTimer)
migrateTimer = null
}
}
const tabs = [ const tabs = [
{ id: 'overview', label: 'sites.detail.overview' }, { id: 'overview', label: 'sites.detail.overview' },
{ id: 'activity', label: 'sites.detail.activity' }, { id: 'activity', label: 'sites.detail.activity' },
@ -860,8 +903,10 @@ async function runBenchMigrate() {
actionLoading.value = 'migrate' actionLoading.value = 'migrate'
actionError.value = '' actionError.value = ''
rebuilding.value = false rebuilding.value = false
migrateRunning.value = true
rebuildResult.value = { success: false, message: '', logs: [] } rebuildResult.value = { success: false, message: '', logs: [] }
showRebuildProgress.value = true showRebuildProgress.value = true
startMigrateTimer()
try { try {
const resp = await call('admin_panel.api.sites.run_bench_migrate', { const resp = await call('admin_panel.api.sites.run_bench_migrate', {
@ -869,6 +914,8 @@ async function runBenchMigrate() {
target_server: targetServer.value, target_server: targetServer.value,
}) })
migrateRunning.value = false
stopMigrateTimer()
rebuildResult.value = { rebuildResult.value = {
success: resp.success, success: resp.success,
message: resp.message || (resp.success ? 'bench migrate completed' : 'bench migrate failed'), message: resp.message || (resp.success ? 'bench migrate completed' : 'bench migrate failed'),
@ -876,6 +923,8 @@ async function runBenchMigrate() {
} }
} catch (e) { } catch (e) {
console.error('Failed to run bench migrate:', e) console.error('Failed to run bench migrate:', e)
migrateRunning.value = false
stopMigrateTimer()
rebuildResult.value = { rebuildResult.value = {
success: false, success: false,
message: e.messages?.[0] || e.message || 'bench migrate failed', message: e.messages?.[0] || e.message || 'bench migrate failed',
@ -1013,3 +1062,14 @@ async function rebuildContainer() {
} }
} }
</script> </script>
<style scoped>
.migrate-progress {
animation: migrate-bar 3s ease-in-out infinite;
}
@keyframes migrate-bar {
0% { width: 5%; }
50% { width: 70%; }
100% { width: 95%; }
}
</style>