- Implemented a new API endpoint for retrieving and managing user favorites in /api/users. - Added functionality for admins to edit the shared "main" user's favorites. - Created a one-shot DB smoke test script for verifying multi-user kiosk migrations. - Introduced a RoomClock class for synchronizing server time across clients using WebSocket.
2 lines
20 KiB
JavaScript
2 lines
20 KiB
JavaScript
import"./modulepreload-polyfill-B5Qt9EMX.js";import{a as m,P as ne,m as se,c as J,e as n}from"./debug-DBzSAgZo.js";import{R as re,c as oe,a as ie,s as le}from"./playGate-C1e0nYli.js";let E=null,y=null,U=null,x=null,N=null;function ue(t){const a=window.devicePixelRatio||1,s=t.clientWidth||t.width||1,r=t.clientHeight||t.height||1,i=Math.max(1,Math.round(s*a)),o=Math.max(1,Math.round(r*a));t.width!==i&&(t.width=i),t.height!==o&&(t.height=o)}function ce(t){if(!y&&N&&(y=N.getAnalyser(),y&&(U=new Uint8Array(y.frequencyBinCount))),!y||!U)return;ue(t);const a=t.getContext("2d");if(!a)return;y.getByteFrequencyData(U);const s=t.width,r=t.height;a.clearRect(0,0,s,r);const i=64,o=U.length,f=Math.max(1,Math.floor(s/i/6)),d=(s-f*(i-1))/i,v=a.createLinearGradient(0,r,0,0);v.addColorStop(0,"rgba(80, 220, 255, 0.85)"),v.addColorStop(.6,"rgba(140, 120, 255, 0.85)"),v.addColorStop(1,"rgba(255, 100, 200, 0.95)"),a.fillStyle=v;for(let u=0;u<i;u++){const g=u/i,C=(u+1)/i,b=Math.floor(Math.pow(g,2)*o),q=Math.max(b+1,Math.floor(Math.pow(C,2)*o));let z=0;for(let $=b;$<q&&$<o;$++)z+=U[$];const ee=z/(q-b),te=Math.pow(ee/255,.7),W=Math.max(2,Math.round(te*r)),ae=Math.round(u*(d+f));a.fillRect(ae,r-W,Math.max(1,Math.round(d)),W)}}function X(){if(!x){E=null;return}ce(x),E=requestAnimationFrame(X)}function de(t,a){if(!(!t||!(a!=null&&a.audio))){if(N=a,typeof a._ensureAudioGraph=="function")try{a._ensureAudioGraph()}catch{}y=a.getAnalyser(),y&&(U=new Uint8Array(y.frequencyBinCount)),x=t,E==null&&(E=requestAnimationFrame(X))}}const S=document.getElementById("app"),e={user:null,users:[],mainUser:null,device:{trusted:!1,users:[]},rooms:[],roomSlug:null,room:null,peers:[],devices:{list:[],current:"default"},np:{stationId:null,station:null,playing:!1,loading:!1,volume:.7,error:null},voteStats:null,favorites:[],tabUser:null,tabFavorites:[],tabLoading:!1,favGenre:"",showOutputs:!1,showAvatars:!1,session:null},c=window.oradioNative||null;let h=null,l=null,I=!1;function D(){I=!0}async function pe(t,a){if(I)return!0;if(ie())return I=!0,!0;try{return await le({stationName:t||"Radio",subtitle:a||"Tap Start to enable audio.",onStart:()=>{I=!0}}),I}catch{return!1}}let k=null,F=0;const w=new re;async function Z(){var s,r;try{e.user=await m.get("/api/auth/me")}catch{return Oe()}const a=new URLSearchParams(location.search).get("room");try{e.rooms=await m.get("/api/rooms")}catch{e.rooms=[]}if(e.roomSlug=a||e.rooms[0]&&e.rooms[0].slug||`u-${e.user.id}`,c!=null&&c.isElectron&&c.listOutputs){try{e.devices.list=await c.listOutputs(),e.devices.current=await c.getCurrent()||((s=e.devices.list[0])==null?void 0:s.id)||"default"}catch(i){console.warn("[master] listOutputs failed",i),e.devices.list=[],e.devices.current="default"}(r=c.onCurrentChanged)==null||r.call(c,i=>{e.devices.current=i,R(),p()})}else e.devices.list=[],e.devices.current=null;l=new ne({onState:i=>{const o={...e.np};Object.assign(e.np,i);const f=e.np.stationId,d=!!e.np.playing,v=e.np.volume,u=f!==o.stationId||d!==!!o.playing,g=Math.abs((v??0)-(o.volume??0))>.001,C=i.loading===!0,b=i.playing===!1&&i.stationId==null&&k!=null;!C&&!b&&(u?(M&&(clearTimeout(M),M=null),T()):g&&ge()),Y()}}),l.onPlayingOnce=i=>{if(!h||!i)return;const o=w.now?w.now():Date.now();try{h.send({type:"state",stationId:i.id,playing:!0,volume:e.np.volume,started_at:o})}catch{}l.sync.enabled?l.updateSyncTarget(o):A(o)};try{e.favorites=await m.get("/api/me/favorites")}catch{e.favorites=[]}try{e.users=await m.get("/api/auth/users/public")}catch{e.users=[]}try{e.mainUser=await m.get("/api/auth/main")}catch{e.mainUser=null}try{e.device=await m.get("/api/auth/devices/me")}catch{e.device={trusted:!1,users:[]}}e.tabUser=e.user.username,e.tabFavorites=e.favorites,B(),fe(),se({player:l,clock:w,getWs:()=>h,role:"master"}),p()}typeof window<"u"&&(window.addEventListener("pagehide",()=>_({beacon:!0})),window.addEventListener("beforeunload",()=>_({beacon:!0})));function B(){if(h)try{h.close()}catch{}w.detach(),h=oe(me,{room:e.roomSlug,kind:"display",onOpen:()=>R()}),w.attachWs(h)}let L=null;function fe(){L||(L=setInterval(()=>{var a,s,r,i;if(!h||!l||l.audio.paused||l.audio.readyState<2)return;const t=e.np.stationId;if(Number.isFinite(t))try{h.send({type:"sync-pos",stationId:t,masterCT:l.audio.currentTime,atServerNow:w.now?w.now():Date.now(),pdtMs:((r=(s=(a=l.hls)==null?void 0:a.playingDate)==null?void 0:s.getTime)==null?void 0:r.call(s))??null,bufferMs:((i=l.sync)==null?void 0:i.bufferMs)??null})}catch{}},2e3))}function ve(){L&&(clearInterval(L),L=null)}typeof window<"u"&&window.addEventListener("pagehide",ve);function A(t){l&&(t?l.enableSync({clock:w,startedAt:t}):l.disableSync())}function me(t){var a,s;if(!(!t||!t.type)){if(t.type==="clock-pong"){w.handlePong(t);return}switch(t.type){case"hello":{e.room=t.room,e.peers=t.peers||[],(a=t.you)!=null&&a.kind&&t.you.kind!=="display"&&(e.np.error=`This room already has a display (${ye(t.peers)} active). You were joined as ${t.you.kind}.`);const r=t.state;r!=null&&r.station_id&&r.station&&r.station_id!==e.np.stationId&&G(r.station,{silent:!0}),typeof(r==null?void 0:r.volume)=="number"&&l.setVolume(r.volume),A((r==null?void 0:r.started_at)||null),p();return}case"state":{t.started_at?(s=l==null?void 0:l.sync)!=null&&s.enabled?l.updateSyncTarget(t.started_at):A(t.started_at):A(null);return}case"presence":e.peers=t.peers||[],p();return;case"command":he(t);return;case"vote":case"plays":t.stationId===e.np.stationId&&(e.voteStats={...e.voteStats||{},...t.stats||{}},t.type==="plays"&&(e.voteStats.plays=t.plays),p());return;default:return}}}function he(t){switch(t.action){case"play":{const a=Number(t.stationId);if(!Number.isFinite(a)||a===e.np.stationId||a===k)return;k=a;const s=++F;m.get(`/api/stations/${a}`).then(r=>{s===F&&(k=null,G(r))}).catch(()=>{s===F&&(k=null)});return}case"pause":e.np.playing&&l.togglePause();return;case"stop":if(!e.np.stationId)return;l.stop(),_(),e.np.playing=!1,e.np.stationId=null,T(),p();return;case"volume":typeof t.value=="number"&&Math.abs(t.value-e.np.volume)>.001&&l.setVolume(t.value);return;case"setSink":K(String(t.deviceId||""));return;case"setSyncBuffer":Number.isFinite(t.value)&&l&&l.setSyncBufferMs(t.value);return;default:return}}async function G(t,{silent:a}={}){if(!(!t||(a||D(),F++,k=null,_(),e.np.station=t,e.np.stationId=t.id,e.voteStats={up:t.up||0,down:t.down||0,plays:t.plays||0,score:t.score||0},p(),!await pe(t.name,a?"Tap Start to resume the group audio.":"Tap Start to play.")))&&(await l.play(t),l.audio.paused&&l.audio.play().catch(()=>{}),!a))try{const r=await m.post(`/api/stations/${t.id}/play`);e.np.stationId===t.id?(e.session={id:r.sessionId,stationId:t.id,startedAt:Date.now()},e.voteStats={...e.voteStats,...r}):r.sessionId&&m.post(`/api/stations/${t.id}/play/end`,{sessionId:r.sessionId,duration_ms:0}).catch(()=>{})}catch{}}function _({beacon:t=!1}={}){const a=e.session;if(!a||!a.id)return;e.session=null;const s={sessionId:a.id,duration_ms:Math.max(0,Date.now()-a.startedAt)},r=`/api/stations/${a.stationId}/play/end`;if(t&&typeof navigator<"u"&&navigator.sendBeacon)try{navigator.sendBeacon(r,new Blob([JSON.stringify(s)],{type:"application/json"}));return}catch{}m.post(r,s).catch(()=>{})}function T(){h==null||h.send({type:"state",stationId:e.np.stationId,playing:!!e.np.playing,volume:e.np.volume})}let M=null;function ge(){M||(M=setTimeout(()=>{M=null,T()},80))}let O=!1;function Y(){O||(O=!0,requestAnimationFrame(()=>{O=!1,p()}))}let P=0;function V(t){return t instanceof HTMLInputElement&&t.type==="range"}if(typeof window<"u"){document.addEventListener("pointerdown",a=>{V(a.target)&&P++},!0);const t=a=>{V(a.target)&&P>0&&(P--,Y())};document.addEventListener("pointerup",t,!0),document.addEventListener("pointercancel",t,!0)}function R(){h==null||h.send({type:"devices",list:e.devices.list,current:e.devices.current})}async function K(t){if(t){if(c!=null&&c.setOutput)try{await c.setOutput(t)}catch(a){console.warn("[master] setOutput failed",a);return}if(e.devices.current=t,c!=null&&c.isElectron)try{await l.setSinkId(t)}catch(a){console.warn("[master] setSinkId failed",a)}R(),e.showOutputs=!1,p()}}function ye(t){return(t||[]).filter(a=>a.kind==="display").length}function Q(t){return!!t&&e.favorites.some(a=>a.id===t)}async function we(t){if(!t)return;const a=Q(t);try{a?await m.del(`/api/me/favorites/${t}`):await m.put(`/api/me/favorites/${t}`,{position:e.favorites.length}),e.favorites=await m.get("/api/me/favorites"),e.tabUser===e.user.username&&(e.tabFavorites=e.favorites),p()}catch(s){console.warn("[master] toggleFavorite failed",s)}}function be(){const t=new Map;for(const a of e.tabFavorites)for(const s of a.genres||[])t.set(s,(t.get(s)||0)+1);return[...t.entries()].sort((a,s)=>s[1]-a[1]||a[0].localeCompare(s[0])).map(([a,s])=>({genre:a,count:s}))}function Se(){return e.favGenre?e.tabFavorites.filter(t=>(t.genres||[]).includes(e.favGenre)):e.tabFavorites}function Ce(){const t=[],a=new Set;e.mainUser&&(t.push({...e.mainUser,main:!0}),a.add(e.mainUser.username)),e.user&&!a.has(e.user.username)&&(t.push({...e.user,self:!0}),a.add(e.user.username));for(const s of e.users)a.has(s.username)||(t.push(s),a.add(s.username));for(const s of t)s.username===e.user.username&&(s.self=!0);return t}async function Ue(t){if(!(!t||t===e.tabUser)){if(e.tabUser=t,e.favGenre="",t===e.user.username){e.tabFavorites=e.favorites,p();return}e.tabLoading=!0,p();try{e.tabFavorites=await m.get(`/api/users/${encodeURIComponent(t)}/favorites`)}catch(a){console.warn("[master] failed to load favorites for",t,a),e.tabFavorites=[]}finally{e.tabLoading=!1,p()}}}function ke(){return e.tabUser?e.tabUser===e.user.username?!0:e.user.role==="admin"&&e.mainUser&&e.tabUser===e.mainUser.username:!1}async function Me(t){if(!t||t===e.user.username){e.showAvatars=!1,p();return}try{await m.post("/api/auth/switch",{username:t})}catch(a){console.warn("[master] switch failed",a),alert(a.message||"Could not switch user");return}location.reload()}async function Ie(){if(!e.mainUser)return;const t=`u-${e.mainUser.id}`;e.roomSlug=t,history.replaceState(null,"",`?room=${encodeURIComponent(t)}`);try{e.rooms=await m.get("/api/rooms")}catch{}B(),p()}function p(){var v;if(P>0)return;const t=((v=S.querySelector(".favs-grid"))==null?void 0:v.scrollLeft)??0;J(S);const a=e.np,s=a.station,r=(s==null?void 0:s.image_display_url)||(s==null?void 0:s.image_url)||null,i=Q(s==null?void 0:s.id),o=n("div",{class:"master"},n("header",{class:"topbar"},n("h1",{},"◉ MASTER"),n("div",{class:"pill"},n("span",{},"Room:"),n("select",{onChange:u=>{e.roomSlug=u.target.value,history.replaceState(null,"",`?room=${encodeURIComponent(e.roomSlug)}`),B()}},...e.rooms.map(u=>n("option",{value:u.slug,selected:u.slug===e.roomSlug},u.name)))),n("div",{class:"pill peers"},`${e.peers.length} peer${e.peers.length===1?"":"s"}`),n("div",{class:"pill role-pill",title:"This master is the authoritative audio source for the room."},"◉ Broadcasting"),a.error?n("div",{class:"err-banner"},a.error):null,n("div",{class:"grow"}),c!=null&&c.isElectron?n("button",{class:"pill out-btn"+(e.showOutputs?" active":""),title:"Audio output",onClick:()=>{e.showOutputs=!e.showOutputs,p()}},"🔊 ",Ae()):null,Ee(),Pe()),n("section",{class:"stage"},n("div",{class:"np"},n("div",{class:"art"+(r?"":" empty")},r?n("img",{class:"art-img",src:r,alt:"",referrerpolicy:"no-referrer",onError:u=>{const g=u.target.parentNode;u.target.remove(),g&&g.classList.add("empty")}}):null),n("div",{class:"meta"},n("div",{class:"tiny"},a.loading?"Loading…":a.playing?"Now playing":s?"Paused":"Idle"),n("div",{class:"title-row"},n("h2",{},(s==null?void 0:s.name)||"—"),s?n("button",{class:"fav-toggle"+(i?" on":""),title:i?"Remove favorite":"Add favorite",onClick:()=>we(s.id)},i?"★":"☆"):null),n("div",{class:"genres"},...((s==null?void 0:s.genres)||[]).slice(0,6).map(u=>n("span",{class:"tag"},u))),c!=null&&c.isElectron?n("canvas",{class:"np-spectrum","data-spectrum":"1"}):null,e.voteStats?n("div",{class:"stats"},n("span",{},"▲ ",n("b",{},String(e.voteStats.up||0))),n("span",{},"▼ ",n("b",{},String(e.voteStats.down||0))),n("span",{},"▶ ",n("b",{},String(e.voteStats.plays||0)))):null,s!=null&&s.country?n("div",{class:"stats"},n("span",{},s.country)):null,n("div",{class:"transport"},n("button",{class:"ctrl primary",title:"Play / pause",disabled:!s,onClick:()=>{D(),l.togglePause()}},a.playing?"❚❚":"▶"),n("button",{class:"ctrl",title:"Stop",disabled:!s,onClick:()=>{D(),l.stop(),_(),e.np.playing=!1,T(),p()}},"■"),n("div",{class:"vol"},n("span",{class:"vol-icon"},"🔊"),n("input",{type:"range",min:0,max:1,step:.01,value:a.volume,onInput:u=>{var b;const g=Number(u.target.value);l.setVolume(g);const C=(b=u.target.parentNode)==null?void 0:b.querySelector(".val");C&&(C.textContent=Math.round(g*100)+"%")}}),n("span",{class:"val"},Math.round(a.volume*100)+"%"))),n("div",{class:"peer-line"},n("span",{class:"peer-line-label"},"In room:"),...e.peers.length?e.peers.map(u=>{var g;return n("span",{class:"peer role-"+u.kind},n("span",{class:"role-tag"},u.kind),n("span",{},((g=u.user)==null?void 0:g.username)||"?"))}):[n("span",{class:"peer"},"Just you.")]),_e()))),n("section",{class:"stations-bar"},$e()),c!=null&&c.isElectron&&e.showOutputs?Fe():null,e.showAvatars?Te():null);S.appendChild(o);const f=S.querySelector(".favs-grid");f&&(t&&(f.scrollLeft=t),Le(f));const d=S.querySelector("canvas.np-spectrum");d&&l&&de(d,l)}function Le(t){if(t.dataset.dragBound==="1")return;t.dataset.dragBound="1";let a=!1,s=!1,r=0,i=0,o=-1;t.addEventListener("pointerdown",d=>{d.pointerType==="mouse"&&d.button!==0||(a=!0,s=!1,r=d.clientX,i=t.scrollLeft,o=d.pointerId)}),t.addEventListener("pointermove",d=>{if(!a)return;const v=d.clientX-r;if(!s&&Math.abs(v)>5){s=!0;try{t.setPointerCapture(o)}catch{}t.classList.add("dragging")}s&&(t.scrollLeft=i-v,d.preventDefault())});const f=()=>{if(a=!1,s){const d=v=>{v.stopPropagation(),v.preventDefault()};t.addEventListener("click",d,{capture:!0,once:!0}),setTimeout(()=>t.removeEventListener("click",d,!0),0)}s=!1,t.classList.remove("dragging");try{t.releasePointerCapture(o)}catch{}};t.addEventListener("pointerup",f),t.addEventListener("pointercancel",f),t.addEventListener("pointerleave",()=>{a&&!s&&(a=!1)})}function j(t){const a=S.querySelector(".favs-grid");if(!a)return;const s=Math.max(160,Math.round(a.clientWidth*.8));a.scrollBy({left:t*s,behavior:"smooth"})}const H=new Map;function _e(){if(!h)return null;const t=(e.peers||[]).filter(a=>a.kind!=="display");return t.length?n("div",{class:"zones-panel",title:"Per-zone (local) volume for each connected client."},n("div",{class:"zones-label"},"Zones"),...t.map(a=>{var o,f;const s=(o=a.user)==null?void 0:o.id,r=((f=a.user)==null?void 0:f.username)||"?",i=H.get(s)??.7;return n("div",{class:"zone-row"},n("span",{class:"zone-name",title:`${r} (${a.kind})`},r),n("input",{type:"range",min:0,max:1,step:.05,value:i,"aria-label":`Volume for ${r}`,onInput:d=>{var g;const v=Number(d.target.value);H.set(s,v);try{h.send({type:"command",action:"peerVolume",userId:s,value:v})}catch{}const u=(g=d.target.parentNode)==null?void 0:g.querySelector(".zone-val");u&&(u.textContent=Math.round(v*100)+"%")}}),n("span",{class:"zone-val"},Math.round(i*100)+"%"))})):null}function $e(){const t=Ce(),a=ke(),s=be(),r=Se(),i=e.tabUser===e.user.username?"My favorites":e.mainUser&&e.tabUser===e.mainUser.username?`${e.tabUser} (main)`:e.tabUser;return n("div",{class:"card favs-card"},n("div",{class:"fav-tabs"},...t.map(o=>n("button",{class:"fav-tab"+(o.username===e.tabUser?" active":"")+(o.main?" main":"")+(o.self?" self":""),title:o.username+(o.main?" (main / shared)":"")+(o.self?" (you)":""),onClick:()=>Ue(o.username)},n("span",{class:"fav-tab-glyph"},o.main?"★":o.avatar_emoji||"●"),n("span",{class:"fav-tab-name"},o.username)))),n("div",{class:"favs-header"},n("h3",{},i," ",n("span",{class:"fav-count"},`(${r.length}${e.favGenre?`/${e.tabFavorites.length}`:""})`),a?null:n("span",{class:"fav-readonly",title:"Read-only — switch to your own tab to edit"}," · read-only")),s.length?n("select",{class:"genre-filter",title:"Filter by genre",onChange:o=>{e.favGenre=o.target.value,p()}},n("option",{value:""},"All genres"),...s.map(({genre:o,count:f})=>n("option",{value:o,selected:e.favGenre===o},`${o} (${f})`))):null,n("button",{class:"favs-nav",title:"Scroll left",onClick:()=>j(-1)},"‹"),n("button",{class:"favs-nav",title:"Scroll right",onClick:()=>j(1)},"›")),n("div",{class:"favs-grid"},...e.tabLoading?[n("div",{class:"favs-empty"},"Loading…")]:r.length?r.map(o=>{const f=o.image_display_url||o.image_url,d=e.np.stationId===o.id;return n("button",{class:"fav-tile"+(d?" active":""),title:o.name,onClick:()=>G(o)},n("div",{class:"fav-art"+(f?"":" empty"),style:f?{backgroundImage:`url("${f}")`}:{}}),n("div",{class:"fav-name"},o.name))}):[n("div",{class:"favs-empty"},e.favGenre?"No favorites in this genre.":a?"No favorites yet. Star a station to add it.":`${e.tabUser} has no favorites yet.`)]))}function Fe(){return n("div",{class:"out-popover-wrap",onClick:t=>{t.target===t.currentTarget&&(e.showOutputs=!1,p())}},n("div",{class:"out-popover card"},n("div",{class:"out-popover-head"},n("h3",{},"Audio output"),n("button",{class:"close",title:"Close",onClick:()=>{e.showOutputs=!1,p()}},"×")),n("div",{class:"device-list"},...e.devices.list.map(t=>n("button",{class:"device"+(t.id===e.devices.current?" active":""),onClick:()=>{K(t.id)}},n("span",{class:"dot"}),n("span",{class:"name"},t.label),n("span",{class:"kind"},t.kind))))))}function Ae(){const t=e.devices.list.find(a=>a.id===e.devices.current);return t?t.label:"—"}function Pe(){const t=!!e.device.trusted,a=(e.device.users||[]).filter(r=>r.username!==e.user.username),s=t&&a.length>0;return n("button",{class:"pill user-pill"+(e.showAvatars?" active":""),title:s?"Switch user":t?"No other users on this device":"This device is not trusted for fast switching",onClick:()=>{s&&(e.showAvatars=!e.showAvatars,p())}},n("span",{class:"avatar",style:e.user.avatar_color?{background:e.user.avatar_color}:{}},e.user.avatar_emoji||e.user.username.slice(0,1).toUpperCase()),n("span",{},e.user.username),s?n("span",{class:"caret"},"▾"):null)}function Ee(){if(!e.mainUser)return null;const t=`u-${e.mainUser.id}`,a=e.roomSlug===t;return e.user.id===e.mainUser.id&&a?null:n("button",{class:"pill follow-pill"+(a?" active":""),title:a?`Following ${e.mainUser.username}'s group`:`Join ${e.mainUser.username}'s group (the house default)`,onClick:()=>{Ie()}},a?`◉ Following ${e.mainUser.username}`:`↗ Follow ${e.mainUser.username}`)}function Te(){const t=e.device.users||[];return n("div",{class:"avatar-popover-wrap",onClick:a=>{a.target===a.currentTarget&&(e.showAvatars=!1,p())}},n("div",{class:"avatar-popover card"},n("div",{class:"avatar-popover-head"},n("h3",{},"Switch user"),n("button",{class:"close",title:"Close",onClick:()=>{e.showAvatars=!1,p()}},"×")),n("div",{class:"avatar-list"},...t.map(a=>n("button",{class:"avatar-row"+(a.username===e.user.username?" active":""),onClick:()=>Me(a.username)},n("span",{class:"avatar lg",style:a.avatar_color?{background:a.avatar_color}:{}},a.avatar_emoji||a.username.slice(0,1).toUpperCase()),n("span",{class:"avatar-name"},a.username,a.is_main?n("span",{class:"avatar-tag"}," ★ main"):null,a.username===e.user.username?n("span",{class:"avatar-tag dim"}," (signed in)"):null)))),n("div",{class:"avatar-hint"},"Add users via the admin panel → Trust device.")))}function Oe(){J(S),S.appendChild(n("div",{class:"login"},n("form",{onSubmit:async t=>{t.preventDefault();const a=new FormData(t.target);try{e.user=await m.post("/api/auth/login",{username:a.get("username"),password:a.get("password")}),await Z()}catch(s){t.target.querySelector(".err").textContent=s.message}}},n("h1",{},"Master sign in"),n("input",{name:"username",placeholder:"Username",required:!0}),n("input",{name:"password",type:"password",placeholder:"Password",required:!0}),n("div",{class:"err"}),n("button",{type:"submit"},"Sign in"))))}Z();
|