1import{d as he,V as be,r as t,o as r,c as F,a as ke,b as a,ac as n,p as c,h as Q,t as d,g as p,e as v,F as N,J as z,g7 as G,j as J,a7 as xe,aa as we,dY as _e,a4 as ee,ea as ae,el as Ce,a5 as se,au as je}from"./index.a7219c61.js";import{B as S}from"./BaseInputText.8946c8ba.js";import{B as f}from"./BaseButton.070a6b6d.js";import{_ as le}from"./BaseInputCheckbox.vue_vue_type_script_setup_true_lang.06fb0f41.js";import{P as qe}from"./PageLayout.34b004d4.js";import{_ as Ie}from"./_plugin-vue_export-helper.cdc0426e.js";import"./Icon.vue_vue_type_script_setup_true_lang.bd97c4b4.js";/* empty css */import"./useFilesStore.730bdd9d.js";import"./useBoxes.18a69a0a.js";import"./files.8f5c9851.js";import"./index.1ffd022d.js";import"./usePlayerUtil.82a36452.js";import"./useFileInput.ab6184fb.js";import"./useLayout.49d39e61.js";import"./useSharedDragAndDrop.af757c69.js";import"./useBreakpoint.a9cd55ed.js";const Se={class:"d-flex flex-column gap-3"},Pe={class:"d-flex align-center gap-2"},Te={class:"migration-section d-flex flex-column gap-6"},Ve={class:"d-flex flex-column gap-2"},Re={class:"d-flex align-center gap-2"},$e={key:0,class:"s-caption s-text-secondary"},Ae={class:"d-flex flex-column gap-2"},Ne={class:"d-flex gap-2 align-center"},Be={key:0,class:"s-caption s-text-secondary"},Me={class:"d-flex flex-column gap-2"},De={class:"d-flex gap-2 align-center"},Ee={key:1,class:"s-caption s-text-secondary"},Le={class:"d-flex flex-column gap-2"},We={class:"d-flex gap-2 align-center"},Ue={key:0,class:"s-caption s-text-secondary"},Oe={class:"migration-section d-flex flex-column gap-6"},Fe={class:"d-flex flex-column gap-2"},Qe=["value"],ze={class:"d-flex gap-2 align-center"},Ge={key:1,class:"s-caption s-text-secondary"},Je={class:"d-flex flex-column gap-2"},He={class:"d-flex gap-2 align-center flex-wrap"},Ye=["value"],Ke={class:"d-flex gap-2 align-center"},Xe={key:0,class:"s-caption s-text-secondary"},Ze={class:"migration-section d-flex flex-column gap-6"},ea={class:"d-flex flex-column gap-2"},aa={class:"d-flex gap-2 align-center"},sa={key:0,class:"s-caption s-text-secondary"},la={class:"d-flex flex-column gap-2"},ta={class:"d-flex gap-2 align-center flex-wrap"},oa={class:"d-flex gap-2 align-center"},na={key:0,class:"s-caption s-text-secondary"},ra={key:0,class:"d-flex flex-column gap-2"},H=200,ia=4,da=he({__name:"CdnMigration",setup(ua){const{migrateProjectAudio:Y,migrateImages:te,startAudioMigration:oe,startImageMigration:ne,backfillPlayerShareIds:re,startShareIdBackfill:ie}=je(),u=be(),K=["projects","players","playlists","boxes","public","product_data"],o=t(!1),m=t(!1),x=t(""),w=t("projects"),_=t(""),C=t(!1),j=t(!1),P=t(!1),y=t(0),T=t(!1),V=t(!1),B=t(""),M=t(""),D=t(0),X=t(!1),h=t(!1),q=t(!1),R=t(""),i=t({total:0,done:0,signed:0,failed:0,lastProjectid:""}),b=t(0),$=t(!1),E=t(0),L=t(""),W=t(""),I=t(void 0),U=t([]);function k(s,e){console.log(`[cdn-migration] ${s}`,e),U.value.unshift(`${s} 2${JSON.stringify(e,null,2)}`)}async function de(){C.value=!0,y.value=0;try{let s;for(;;){const e=await Y(x.value.trim(),s,!o.value,m.value);if(y.value++,k(`audio ${x.value} page ${y.value}`,e),e.done||(s=e.after,!s))break}u.show(`Dry run complete \u2014 ${y.value} page(s)`)}catch(s){console.error("[cdn-migration] audio failed",s),u.show(s.message??"Dry run failed")}finally{C.value=!1}}async function Z(){j.value=!0;try{const s=await te(w.value,I.value,!o.value);k(`images ${w.value}`,s),I.value=s.done?void 0:s.after,s.done&&u.show(`${w.value}: reached the end`)}catch(s){console.error("[cdn-migration] images failed",s),u.show(s.message??"Dry run failed")}finally{j.value=!1}}async function ue(){P.value=!0,b.value=0;try{let s;for(;;){const e=await re(_.value.trim(),s,!o.value);if(b.value++,k(`shareids ${_.value} page ${b.value}`,e),e.done||(s=e.after,!s))break}u.show(`Complete \u2014 ${b.value} page(s)`)}catch(s){console.error("[cdn-migration] share ids failed",s),u.show(s.message??"Dry run failed")}finally{P.value=!1}}async function ce(){T.value=!0;try{const s=await oe(!o.value,R.value.trim()||void 0,m.value);X.value=!0,k("queued audio migration",s),u.show(`${s.dryRun?"Queued \u2014 dry run":"Queued \u2014 WRITING"}${s.force?" \u2014 FORCE":""}`)}catch(s){console.error("[cdn-migration] queueing failed",s),u.show(s.message??"Could not queue")}finally{T.value=!1}}async function pe(){$.value=!0;try{const s=await ie(!o.value,L.value||void 0,Number(W.value)||void 0);E.value=s.tasks,k("queued share id backfill",s),u.show(`${s.tasks} shard task(s) queued${s.dryRun?" \u2014 dry run":" \u2014 WRITING"}`)}catch(s){console.error("[cdn-migration] queueing share ids failed",s),u.show(s.message??"Could not queue")}finally{$.value=!1}}async function ve(){V.value=!0;try{const s=await ne(!o.value,B.value||void 0,Number(M.value)||void 0);D.value=s.tasks,k("queued image migration",s),u.show(`${s.tasks} shard task(s) queued${s.dryRun?" \u2014 dry run":" \u2014 WRITING"}`)}catch(s){console.error("[cdn-migration] queueing images failed",s),u.show(s.message??"Could not queue")}finally{V.value=!1}}async function ge(s){let e,l=0;for(;;){const g=await Y(s,e,!o.value,m.value);if(l+=Number(g.signed??0),g.done||(e=g.after,!e))break}return l}async function fe(){h.value=!0,q.value=!1,i.value={total:0,done:0,signed:0,failed:0,lastProjectid:""};try{const s=xe(we(),"projects");let e=R.value.trim()||void 0;for(;!q.value;){const l=await _e(e?ee(s,ae("__name__"),Ce(e),se(H)):ee(s,ae("__name__"),se(H)));if(l.empty)break;i.value.total+=l.size;const g=l.docs.map(A=>A.id);let me=0;if(await Promise.all(Array.from({length:Math.min(ia,g.length)},()=>(async()=>{for(;;){if(q.value)return;const A=me++;
2if(A>=g.length)return;const O=g[A];try{i.value.signed+=await ge(O)}catch(ye){i.value.failed++,console.error("[cdn-migration] project failed",O,ye)}i.value.done++,i.value.lastProjectid=O}})())),e=l.docs[l.docs.length-1],l.size<H)break}u.show(q.value?`Stopped after ${i.value.done} project(s)`:`Finished ${i.value.done} project(s)`)}catch(s){console.error("[cdn-migration] sweep failed",s),u.show(s.message??"Sweep failed")}finally{h.value=!1}}return(s,e)=>(r(),F(qe,{title:"CDN Migration"},{default:ke(()=>[a("div",{class:Q(["pa-4 d-flex flex-column gap-8",{"write-mode":o.value}]),style:{"max-width":"900px"}},[a("div",Se,[e[11]||(e[11]=a("div",{class:"s-caption s-text-secondary"},[n(" Reports what it "),a("em",null,"would"),n(" change unless Write is ticked. Details go to the browser console and to Cloud Logging under the function name. Access is enforced server-side by an operator allowlist. ")],-1)),a("div",Pe,[c(le,{value:o.value,onInput:e[0]||(e[0]=l=>o.value=l)},null,8,["value"]),a("span",{class:Q(o.value?"s-text-error":"s-text-secondary")},d(o.value?"WRITE \u2014 changes are permanent":"Dry run"),3)])]),a("section",Te,[e[18]||(e[18]=a("div",{class:"s-heading-sm"},"Audio \u2014 HLS manifests",-1)),a("div",Ve,[a("div",Re,[c(le,{value:m.value,onInput:e[1]||(e[1]=l=>m.value=l)},null,8,["value"]),a("span",{class:Q(m.value?"s-text-error":"s-text-secondary")},d(m.value?"FORCE \u2014 re-signs boxes whose token is still live":"Skips boxes already signed and not yet due"),3)]),m.value?(r(),p("div",$e," Needed after a key rotation: a box holding a live token signed by a retired key has to be re-signed before the old key leaves the keyset, and those are exactly the boxes the freshness check passes over. Note that re-signing restarts each box's refresh cycle, so a large forced run leaves those boxes falling due together afterwards. Applies to all three audio runs below, Cloud Tasks included; images and share ids ignore it. ")):v("",!0)]),a("div",Ae,[e[12]||(e[12]=a("div",{class:"s-body-semi-bold"},"One project",-1)),e[13]||(e[13]=a("div",{class:"s-caption s-text-secondary"}," Pages through a single project's boxes until it reaches the end. ",-1)),c(S,{modelValue:x.value,"onUpdate:modelValue":e[2]||(e[2]=l=>x.value=l),placeholder:"projectid",prependIcon:"mdi-folder-music"},null,8,["modelValue"]),a("div",Ne,[c(f,{label:o.value?"Migrate project":"Dry run project",loading:C.value,disabled:!x.value.trim()||C.value||h.value,onClick:de},null,8,["label","loading","disabled"]),y.value?(r(),p("span",Be,d(y.value)+" page(s) ",1)):v("",!0)])]),a("div",Me,[e[14]||(e[14]=a("div",{class:"s-body-semi-bold"},"Every project, from this tab",-1)),e[15]||(e[15]=a("div",{class:"s-caption s-text-secondary"}," Walks every project from this tab, a page at a time. Closing the tab stops it \u2014 Stop is safe, and the last project id is kept so Resume after picks up from there. Fine for staging; for the full corpus use Cloud Tasks below. ",-1)),c(S,{modelValue:R.value,"onUpdate:modelValue":e[3]||(e[3]=l=>R.value=l),placeholder:"Resume after projectid (optional)"},null,8,["modelValue"]),a("div",De,[c(f,{label:o.value?"Migrate ALL projects":"Dry run all projects",loading:h.value,disabled:C.value||h.value,onClick:fe},null,8,["label","loading","disabled"]),h.value?(r(),F(f,{key:0,label:"Stop",onClick:e[4]||(e[4]=l=>q.value=!0)})):v("",!0),i.value.total?(r(),p("span",Ee,[n(d(i.value.done)+"/"+d(i.value.total)+" projects \xB7 "+d(i.value.signed)+" signed \xB7 "+d(i.value.failed)+" failed ",1),i.value.lastProjectid?(r(),p(N,{key:0},[n(" \xB7 at "+d(i.value.lastProjectid),1)],64)):v("",!0)])):v("",!0)])]),a("div",Le,[e[16]||(e[16]=a("div",{class:"s-body-semi-bold"},"Every project, on Cloud Tasks",-1)),e[17]||(e[17]=a("div",{class:"s-caption s-text-secondary"},[n(" Runs server-side and survives closing this tab. It reports nothing here by design: progress is the depth of the "),a("code",null,"cdn-seedAudioMigration"),n(" and "),a("code",null,"cdn-migrateProjectAudioWorker"),n(" queues, and pausing either one stops the run. ")],-1)),a("div",We,[c(f,{label:o.value?"Queue ALL projects (Tasks)":"Queue dry run (Tasks)",loading:T.value,disabled:T.value,onClick:ce},null,8,["label","loading","disabled"]),X.value?(r(),p("span",Ue," queued \u2014 watch the queues and function logs ")):v("",!0)])])]),a("section",Oe,[e[24]||(e[24]=a("div",{class:"s-heading-sm"},"Images \u2014 stored URLs",-1)),a("div",Fe,[e[19]||(e[19]=a("div",{class:"s-body-semi-bold"},"One target, one page at a time",-1)),e[20]||(e[20]=a("div",{class:"s-caption s-text-secondary"},[n(" Large targets are paged deliberately \u2014 "),a("code",null,"boxes"),n(" and "),a("c
2ode",null,"product_data"),n(" are millions of documents, so a single call would run for the better part of an hour. ")],-1)),z(a("select",{"onUpdate:modelValue":e[5]||(e[5]=l=>w.value=l),class:"pa-2"},[(r(),p(N,null,J(K,l=>a("option",{key:l,value:l},d(l),9,Qe)),64))],512),[[G,w.value]]),a("div",ze,[c(f,{label:o.value?"Migrate one page":"Dry run one page",loading:j.value,disabled:j.value,onClick:Z},null,8,["label","loading","disabled"]),I.value?(r(),F(f,{key:0,label:"Next page",loading:j.value,onClick:Z},null,8,["loading"])):v("",!0),I.value?(r(),p("span",Ge," resuming after "+d(I.value),1)):v("",!0)])]),a("div",Je,[e[22]||(e[22]=a("div",{class:"s-body-semi-bold"},"All targets, on Cloud Tasks",-1)),e[23]||(e[23]=a("div",{class:"s-caption s-text-secondary"},[n(" Splits each target into ranges of its key space and runs them in parallel \u2014 a cursor walk alone is sequential, so sharding is what makes this finish in hours rather than most of a day. Progress is the depth of "),a("code",null,"cdn-migrateImageShardWorker"),n(". Shard defaults are sized for production \u2014 16 for the two largest targets, about an hour each; staging finishes instantly at any count. ")],-1)),a("div",He,[z(a("select",{"onUpdate:modelValue":e[6]||(e[6]=l=>B.value=l),class:"pa-2"},[e[21]||(e[21]=a("option",{value:""},"all targets",-1)),(r(),p(N,null,J(K,l=>a("option",{key:l,value:l},d(l)+" only ",9,Ye)),64))],512),[[G,B.value]]),c(S,{modelValue:M.value,"onUpdate:modelValue":e[7]||(e[7]=l=>M.value=l),type:"number",placeholder:"shards (blank = default)"},null,8,["modelValue"])]),a("div",Ke,[c(f,{label:o.value?"Queue images (Tasks, sharded)":"Queue image dry run (Tasks)",loading:V.value,disabled:V.value,onClick:ve},null,8,["label","loading","disabled"]),D.value?(r(),p("span",Xe,d(D.value)+" shard task(s) queued ",1)):v("",!0)])])]),a("section",Ze,[e[30]||(e[30]=a("div",{class:"s-heading-sm"},"Share ids \u2014 share link and history",-1)),a("div",ea,[e[25]||(e[25]=a("div",{class:"s-body-semi-bold"},"One player",-1)),e[26]||(e[26]=a("div",{class:"s-caption s-text-secondary"},[n(" A player that already has a share link keeps it \u2014 this only fills in the gaps. A dry run cannot mint, so it reports "),a("c
2ode",null,"share: absent"),n(" and no shareid for a player that would get a new one. ")],-1)),c(S,{modelValue:_.value,"onUpdate:modelValue":e[8]||(e[8]=l=>_.value=l),placeholder:"playerid",prependIcon:"mdi-link-variant"},null,8,["modelValue"]),a("div",aa,[c(f,{label:o.value?"Backfill player":"Dry run player",loading:P.value,disabled:!_.value.trim()||P.value,onClick:ue},null,8,["label","loading","disabled"]),b.value?(r(),p("span",sa,d(b.value)+" page(s) ",1)):v("",!0)])]),a("div",la,[e[28]||(e[28]=a("div",{class:"s-body-semi-bold"},"Every player, on Cloud Tasks",-1)),e[29]||(e[29]=a("div",{class:"s-caption s-text-secondary"},[n(" Two independent passes, sharded and run in parallel. "),a("strong",null,"Mint"),n(" gives every player a link \u2014 including one nobody holds, which the stamp pass would never reach. "),a("strong",null,"Stamp"),n(" walks the history collection group once and puts each player's link on every library entry holding it. Either order is fine; stamping mints on demand for anything it meets without a link. Progress is the depth of "),a("code",null,"migrate-mintShareIdShardWorker"),n(" and "),a("code",null,"migrate-stampHistoryShardWorker"),n(". ")],-1)),a("div",ta,[z(a("select",{"onUpdate:modelValue":e[9]||(e[9]=l=>L.value=l),class:"pa-2"},e[27]||(e[27]=[a("option",{value:""},"both passes",-1),a("option",{value:"mint"},"mint only",-1),a("option",{value:"stamp"},"stamp only",-1)]),512),[[G,L.value]]),c(S,{modelValue:W.value,"onUpdate:modelValue":e[10]||(e[10]=l=>W.value=l),type:"number",placeholder:"shards (blank = default)"},null,8,["modelValue"])]),a("div",oa,[c(f,{label:o.value?"Queue share ids (Tasks, sharded)":"Queue share id dry run (Tasks)",loading:$.value,disabled:$.value,onClick:pe},null,8,["label","loading","disabled"]),E.value?(r(),p("span",na,d(E.value)+" shard task(s) queued ",1)):v("",!0)])])]),U.value.length?(r(),p("section",ra,[e[31]||(e[31]=a("div",{class:"s-heading-sm"},"Results (newest first)",-1)),(r(!0),p(N,null,J(U.value,(l,g)=>(r(),p("pre",{key:g,class:"pa-2",style:{"overflow-x":"auto","font-size":"12px"}},d(l),1))),128))])):v("",!0)],2)]),_:1}))}});const Sa=Ie(da,[["__scopeId","data-v-327a425f"]]);export{Sa as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.