web-mojo 2.2.65 → 2.2.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/ChatView-Cfe0ZGvr.js +2 -0
- package/dist/chunks/ChatView-Cfe0ZGvr.js.map +1 -0
- package/dist/chunks/ChatView-DuQVFrCY.js +2 -0
- package/dist/chunks/ChatView-DuQVFrCY.js.map +1 -0
- package/dist/chunks/{Collection-1sPoIFvQ.js → Collection-BWKmydl5.js} +2 -2
- package/dist/chunks/{Collection-1sPoIFvQ.js.map → Collection-BWKmydl5.js.map} +1 -1
- package/dist/chunks/{Collection-DSBRXpwK.js → Collection-CmjTsmrP.js} +2 -2
- package/dist/chunks/{Collection-DSBRXpwK.js.map → Collection-CmjTsmrP.js.map} +1 -1
- package/dist/chunks/ContextMenu-8vTiZZQV.js +2 -0
- package/dist/chunks/ContextMenu-8vTiZZQV.js.map +1 -0
- package/dist/chunks/ContextMenu-DBw0WMTO.js +2 -0
- package/dist/chunks/ContextMenu-DBw0WMTO.js.map +1 -0
- package/dist/chunks/{DataView--nUWtq6r.js → DataView-BEovBggn.js} +2 -2
- package/dist/chunks/{DataView--nUWtq6r.js.map → DataView-BEovBggn.js.map} +1 -1
- package/dist/chunks/{DataView-CK3Z0TJH.js → DataView-DyJKgOn3.js} +2 -2
- package/dist/chunks/{DataView-CK3Z0TJH.js.map → DataView-DyJKgOn3.js.map} +1 -1
- package/dist/chunks/{Dialog-VoLlToMl.js → Dialog-DW7PHzUc.js} +2 -2
- package/dist/chunks/{Dialog-wBhTkeWg.js.map → Dialog-DW7PHzUc.js.map} +1 -1
- package/dist/chunks/{Dialog-wBhTkeWg.js → Dialog-jfBsXy5X.js} +2 -2
- package/dist/chunks/{Dialog-VoLlToMl.js.map → Dialog-jfBsXy5X.js.map} +1 -1
- package/dist/chunks/Files-C-ChBvr5.js +2 -0
- package/dist/chunks/Files-C-ChBvr5.js.map +1 -0
- package/dist/chunks/Files-DNbHDy43.js +2 -0
- package/dist/chunks/Files-DNbHDy43.js.map +1 -0
- package/dist/chunks/FormView-EoB_ZdIB.js +3 -0
- package/dist/chunks/FormView-EoB_ZdIB.js.map +1 -0
- package/dist/chunks/FormView-Q_lFA0nr.js +3 -0
- package/dist/chunks/FormView-Q_lFA0nr.js.map +1 -0
- package/dist/chunks/{ListView-6JQ6tRXs.js → ListView-BLFFK_Ir.js} +2 -2
- package/dist/chunks/{ListView-6JQ6tRXs.js.map → ListView-BLFFK_Ir.js.map} +1 -1
- package/dist/chunks/{ListView-DVStKiMi.js → ListView-CMZpwyyC.js} +2 -2
- package/dist/chunks/{ListView-DVStKiMi.js.map → ListView-CMZpwyyC.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-CnAEbUw_.js → MetricsCountryMapView-B0kWK-Js.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-CnAEbUw_.js.map → MetricsCountryMapView-B0kWK-Js.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-J067qrrt.js → MetricsCountryMapView-DuBKO7gz.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-J067qrrt.js.map → MetricsCountryMapView-DuBKO7gz.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-DtWq2_YJ.js → MetricsMiniChartWidget-BkMjI-gz.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-DtWq2_YJ.js.map → MetricsMiniChartWidget-BkMjI-gz.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-CtCN1rtt.js → MetricsMiniChartWidget-ChC5GGm6.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-CtCN1rtt.js.map → MetricsMiniChartWidget-ChC5GGm6.js.map} +1 -1
- package/dist/chunks/{PDFViewer-BNCyDElg.js → PDFViewer-iOqYpg-6.js} +2 -2
- package/dist/chunks/{PDFViewer-BNCyDElg.js.map → PDFViewer-iOqYpg-6.js.map} +1 -1
- package/dist/chunks/{PDFViewer-Di4OHAkv.js → PDFViewer-sFoyopz3.js} +2 -2
- package/dist/chunks/{PDFViewer-Di4OHAkv.js.map → PDFViewer-sFoyopz3.js.map} +1 -1
- package/dist/chunks/{Rest-Ds9e8tN8.js → Rest-B1eUyLX5.js} +2 -2
- package/dist/chunks/{Rest-Ds9e8tN8.js.map → Rest-B1eUyLX5.js.map} +1 -1
- package/dist/chunks/{Rest-DHbszkuP.js → Rest-BJ3Mvx1L.js} +2 -2
- package/dist/chunks/{Rest-DHbszkuP.js.map → Rest-BJ3Mvx1L.js.map} +1 -1
- package/dist/chunks/TokenManager-BYMKH_aW.js +2 -0
- package/dist/chunks/{TokenManager-D-9tqubS.js.map → TokenManager-BYMKH_aW.js.map} +1 -1
- package/dist/chunks/TokenManager-DhDUKmaw.js +2 -0
- package/dist/chunks/{TokenManager-8JM2qj_1.js.map → TokenManager-DhDUKmaw.js.map} +1 -1
- package/dist/chunks/User-BnlvMG5J.js +3 -0
- package/dist/chunks/User-BnlvMG5J.js.map +1 -0
- package/dist/chunks/User-DSqcOwPL.js +3 -0
- package/dist/chunks/User-DSqcOwPL.js.map +1 -0
- package/dist/chunks/{WebApp-JJAK0eNM.js → WebApp-B0m6JCjO.js} +2 -2
- package/dist/chunks/{WebApp-JJAK0eNM.js.map → WebApp-B0m6JCjO.js.map} +1 -1
- package/dist/chunks/{WebApp-BdJA4Uup.js → WebApp-Bsic6FPo.js} +2 -2
- package/dist/chunks/{WebApp-BdJA4Uup.js.map → WebApp-Bsic6FPo.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-BoF7TV7i.js → WebSocketClient-Bh0Mmtje.js} +2 -2
- package/dist/chunks/{WebSocketClient-BoF7TV7i.js.map → WebSocketClient-Bh0Mmtje.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-BxL2M7p0.js → WebSocketClient-CLgYPxWX.js} +2 -2
- package/dist/chunks/{WebSocketClient-BxL2M7p0.js.map → WebSocketClient-CLgYPxWX.js.map} +1 -1
- package/dist/chunks/{version-kTtMlfsq.js → version-BY7AsEkb.js} +2 -2
- package/dist/chunks/{version-kTtMlfsq.js.map → version-BY7AsEkb.js.map} +1 -1
- package/dist/chunks/{version-Cn26llLs.js → version-BdfRyQDm.js} +2 -2
- package/dist/chunks/{version-Cn26llLs.js.map → version-BdfRyQDm.js.map} +1 -1
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.cjs.js.map +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/docit.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.cjs.js.map +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/lightbox.es.js.map +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/web-mojo.lite.iife.js +1230 -2
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +95 -74
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +8 -7
- package/dist/chunks/ChatView-D1-ev2qA.js +0 -2
- package/dist/chunks/ChatView-D1-ev2qA.js.map +0 -1
- package/dist/chunks/ChatView-DesGp57e.js +0 -2
- package/dist/chunks/ChatView-DesGp57e.js.map +0 -1
- package/dist/chunks/ContextMenu-CsQGpSrv.js +0 -3
- package/dist/chunks/ContextMenu-CsQGpSrv.js.map +0 -1
- package/dist/chunks/ContextMenu-dqFxitXY.js +0 -3
- package/dist/chunks/ContextMenu-dqFxitXY.js.map +0 -1
- package/dist/chunks/FormView-BQnvMheR.js +0 -3
- package/dist/chunks/FormView-BQnvMheR.js.map +0 -1
- package/dist/chunks/FormView-C7FnbmEI.js +0 -3
- package/dist/chunks/FormView-C7FnbmEI.js.map +0 -1
- package/dist/chunks/TokenManager-8JM2qj_1.js +0 -2
- package/dist/chunks/TokenManager-D-9tqubS.js +0 -2
package/dist/index.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{B as e,V as t,a as s,b as i,c as a,d as n}from"./chunks/version-kTtMlfsq.js";import{V as r,d as o,M as l}from"./chunks/Rest-DHbszkuP.js";import{D as c,E as d,a as h,r as u}from"./chunks/Rest-DHbszkuP.js";import{G as p,a as g,P as m,T as b,U as v}from"./chunks/ContextMenu-CsQGpSrv.js";import{C as f,b as w,c as y,d as S,e as A,f as P,g as M,h as C,i as L}from"./chunks/ContextMenu-CsQGpSrv.js";import{W as T}from"./chunks/WebApp-BdJA4Uup.js";import{E as I,R as k}from"./chunks/WebApp-BdJA4Uup.js";import{C as D,M as x}from"./chunks/Collection-1sPoIFvQ.js";import{M as E}from"./chunks/ChatView-DesGp57e.js";import{B as _,C as G,a as N,b as F,c as R,d as V,e as H,D as O,E as U,f as B,g as K,h as j,i as z,j as $,F as q,k as J,l as W,m as Y,n as Q,o as X,p as Z,q as ee,G as te,r as se,I as ie,s as ae,t as ne,u as re,v as oe,w as le,x as ce,y as de,z as he,A as ue,H as pe,J as ge,K as me,L as be,N as ve,O as fe,P as we,Q as ye,R as Se,S as Ae,T as Pe,U as Me,V as Ce,W as Le,X as Te,Y as Ie,Z as ke,_ as De,$ as xe,a0 as Ee,a1 as _e,a2 as Ge,a3 as Ne,a4 as Fe,a5 as Re,a6 as Ve,a7 as He,a8 as Oe,a9 as Ue,aa as Be,ab as Ke,ac as je,ad as ze,ae as $e,af as qe,ag as Je,ah as We,ai as Ye,aj as Qe,ak as Xe,al as Ze,am as et,an as tt,ao as st,ap as it,aq as at,ar as nt,as as rt,at as ot,au as lt,av as ct,aw as dt,ax as ht,ay as ut,az as pt,aA as gt,aB as mt,aC as bt,aD as vt,aE as ft,aF as wt,aG as yt}from"./chunks/ChatView-DesGp57e.js";import{S as St,T as At,a as Pt}from"./chunks/TokenManager-D-9tqubS.js";import Mt from"./chunks/Dialog-wBhTkeWg.js";import{L as Ct,a as Lt}from"./chunks/ListView-6JQ6tRXs.js";import{default as Tt}from"./chunks/DataView--nUWtq6r.js";import{F as It}from"./chunks/FormView-C7FnbmEI.js";import{a as kt}from"./chunks/FormView-C7FnbmEI.js";import{W as Dt}from"./chunks/WebSocketClient-BoF7TV7i.js";const xt={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1},Et=Object.freeze({silent:0,error:1,warn:2,info:3,log:3,debug:4,trace:5,all:5}),_t=(()=>{try{if(void 0!==import.meta&&import.meta&&xt)return!1}catch{}if("undefined"!=typeof globalThis&&void 0!==globalThis.__DEV__)try{return!!globalThis.__DEV__}catch{}return!("undefined"==typeof process||!process||"object"!=typeof process.env||"string"!=typeof process.env.NODE_ENV)&&"production"!==process.env.NODE_ENV})(),Gt="undefined"!=typeof window&&"undefined"!=typeof document,Nt="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:global,Ft=Nt.console||{},Rt={};let Vt=!1,Ht=null;function Ot(e){if("number"==typeof e){const t=Et.silent,s=Et.trace;return Math.min(Math.max(e,t),s)}if("string"==typeof e){const t=e.toLowerCase();if(Object.prototype.hasOwnProperty.call(Et,t))return Et[t]}return null}function Ut(e,t){const s=Rt[e]||Ft[e]||(()=>{});return function(...e){if(Ht>=t)return s.apply(Ft,e)}}const Bt={install(e={}){if(Vt)return e&&void 0!==e.level&&this.setLevel(e.level,{persist:!!e.persist}),this;if(!Nt||!Ft)return Vt=!0,this;Ht=function(e){const t=Ot(e);if(null!==t)return t;const s=function(){if(!Gt||"undefined"==typeof location||!location.search)return null;try{const e=new URLSearchParams(location.search),t=["logLevel","loglevel","mojoLog"];for(const s of t){const t=e.get(s);if(null!=t){const e=Ot(t);if(null!==e)return e}}}catch{}return null}();if(null!==s)return s;const i=function(){if(!Gt||!("localStorage"in Nt))return null;try{const e=Nt.localStorage.getItem("MOJO_LOG_LEVEL");if(null!=e){const t=Ot(e);if(null!==t)return t}}catch{}return null}();return null!==i?i:Ot(_t?"debug":"warn")}(e.level);const t=function(){const e={...Ft},t={error:Et.error,warn:Et.warn,info:Et.info,log:Et.info,dir:Et.info,table:Et.info,debug:Et.debug,group:Et.debug,groupCollapsed:Et.debug,groupEnd:Et.debug,time:Et.debug,timeEnd:Et.debug,timeLog:Et.debug,trace:Et.trace};for(const s of Object.keys(t))Rt[s]=Ft[s]||(()=>{}),e[s]=Ut(s,t[s]);return Rt.assert=Ft.assert||(()=>{}),e.assert=function(){const e=Rt.assert||Ft.assert||(()=>{});return function(t,...s){if(!t)return Ht>=Et.error?e.apply(Ft,[t,...s]):void 0}}(),e}();return Nt.console=t,Vt=!0,Nt.MOJOConsoleSilencer=this,this},uninstall(){if(!Vt)return this;try{Nt.console=Ft}catch{}return Vt=!1,this},setLevel(e,{persist:t=!1}={}){const s=Ot(e);return null===s||(Ht=s,t&&function(e){if(Gt&&"localStorage"in Nt)try{const t="string"==typeof e?e:null===e?null:Object.entries(Et).find(([,t])=>t===e)?.[0]??null;t?Nt.localStorage.setItem("MOJO_LOG_LEVEL",t):Nt.localStorage.removeItem("MOJO_LOG_LEVEL")}catch{}}(e)),this},getLevel:()=>Ht,getLevelName(){const e=Object.entries(Et).find(([,e])=>e===Ht);return e?e[0]:null},criticalOnly({persist:e=!1}={}){return this.setLevel("warn",{persist:e})},errorsOnly({persist:e=!1}={}){return this.setLevel("error",{persist:e})},silent({persist:e=!1}={}){return this.setLevel("silent",{persist:e})},verbose({persist:e=!1}={}){return this.setLevel(_t?"debug":"info",{persist:e})},allowAll({persist:e=!1}={}){return this.setLevel("trace",{persist:e})},withTemporaryLevel(e,t){const s=Ht,i=Ot(e);if(null===i||"function"!=typeof t)return t?.();Ht=i;try{return t()}finally{Ht=s}},LEVELS:Et},Kt=e=>Bt.install(e);class GroupSearchView extends St{constructor(e={}){super({...e,className:`group-search-view ${e.className||""}`.trim()}),this.showKind=void 0===e.showKind||e.showKind,this.parentField=e.parentField||"parent",this.kindField=e.kindField||"kind",this.treeData=[],this.flattenedItems=[],this.showLines=void 0===e.showLines||e.showLines}buildTreeHierarchy(e){if(!e||0===e.length)return[];const t=/* @__PURE__ */new Map;e.forEach(e=>{t.has(e.id)||t.set(e.id,{...e,children:[],level:0,hasChildren:!1});const s=e[this.parentField];s&&s.id&&!t.has(s.id)&&t.set(s.id,{...s,children:[],level:0,hasChildren:!1})});const s=[];t.forEach((i,a)=>{const n=e.find(e=>e.id===a)||i,r=n[this.parentField]?.id;if(r&&t.has(r)){const e=t.get(r);e.children.push(i),e.hasChildren=!0}else s.push(i)});const i=(e,t=0)=>{e.forEach(e=>{e.level=t,e.children.length>0&&(e.children.sort((e,t)=>(e.name||"").localeCompare(t.name||"")),i(e.children,t+1))})};return s.sort((e,t)=>(e.name||"").localeCompare(t.name||"")),i(s),s}flattenTree(e,t=[],s=[]){return e.forEach((i,a)=>{i._isLastChild=a===e.length-1,i._ancestorLastFlags=[...s];const n=s.every(e=>e);if(i._isLastDescendant=n&&i._isLastChild&&(!i.children||0===i.children.length),t.push(i),i.children&&i.children.length>0){const e=[...s,i._isLastChild];this.flattenTree(i.children,t,e)}}),t}computeVerticalLines(e){for(let t=0;t<e.length;t++){const s=e[t];s._continueVertical=[];for(let i=0;i<s.level;i++){let a=!1;for(let s=t+1;s<e.length;s++){const t=e[s];if(t.level===i+1){a=!0;break}if(t.level<=i)break}s._continueVertical[i]=a}}}updateFilteredItems(){if(!this.collection)return this.filteredItems=[],this.treeData=[],void(this.flattenedItems=[]);const e=this.collection.toJSON();this.treeData=this.buildTreeHierarchy(e),this.flattenedItems=this.flattenTree(this.treeData),this.computeVerticalLines(this.flattenedItems),this.filteredItems=this.flattenedItems,this.updateResultsView()}getDefaultItemTemplate(){return'\n <div class="tree-item-content">\n <div class="tree-item-name">{{name}}</div>\n {{#showKind}}\n <div class="tree-item-kind">{{kind}}</div>\n {{/showKind}}\n </div>\n '}processItemTemplate(e){let t=this.itemTemplate;t=t.replace(/\{\{(\w+)\}\}/g,(t,s)=>"showKind"===s?this.showKind?"true":"":this.getNestedValue(e,s)||""),t=this.showKind?t.replace(/\{\{#showKind\}\}(.*?)\{\{\/showKind\}\}/gs,"$1"):t.replace(/\{\{#showKind\}\}.*?\{\{\/showKind\}\}/gs,"");let s="";if(this.showLines&&e.level>0)for(let i=0;i<e.level;i++)i===e.level-1?s+=`<span class="${e._isLastChild?"tree-seg tree-seg-last":"tree-seg tree-seg-mid"}"></span>`:s+=e._continueVertical&&e._continueVertical[i]?'<span class="tree-seg tree-seg-vert"></span>':'<span class="tree-seg"></span>';return`\n <div class="tree-item-wrapper${e.hasChildren?" has-children":""}${e._isLastChild?" is-last-child":""}" data-tree-level="${e.level}">\n <div class="tree-lines">\n ${s}\n </div>\n <div class="tree-item-body flex-grow-1">\n ${t}\n </div>\n </div>\n `}getRootItems(){return this.treeData}getNodeChildren(e){const t=(e,s)=>{for(const i of e){if(i.id===s)return i.children;if(i.children.length>0){const e=t(i.children,s);if(e)return e}}return null};return t(this.treeData,e)||[]}}class Sidebar extends r{constructor(e={}){super({tagName:"nav",className:"sidebar",id:"sidebar",...e}),this.menus=/* @__PURE__ */new Map,this.activeMenuName=null,this.currentRoute=null,this.showToggle=e.showToggle,this.isCollapsed=!1,this.sidebarTheme=e.theme||"sidebar-light",this.customView=null,this.options.groupHeader&&(this.groupHeader=this.options.groupHeader),this.groupSelectorMode=e.groupSelectorMode||"inline",this.groupSelectorDialog=null,this.sidebarTheme&&this.addClass(this.sidebarTheme),this.initializeMenus(e),this.setupRouteListeners(),!1!==e.autoCollapseMobile&&this.setupResponsiveBehavior()}groupHeader='\n {{#group.parent}}\n <div class="sidebar-parent-bar" data-action="select-group-parent">\n <div class="parent-info">\n <span class="parent-label">{{group.parent.kind}}:</span>\n <span class="parent-name collapsed-hidden">{{group.parent.name}}</span>\n </div>\n <i class="bi bi-chevron-down parent-expand collapsed-hidden"></i>\n </div>\n {{/group.parent}}\n <div class="sidebar-selected-group-row" data-action="show-group-search">\n <div class="selected-group-info">\n <div class=\'selected-group-name collapsed-hidden\'>{{group.name}}</div>\n <div class=\'selected-group-meta collapsed-hidden\'>\n <span class="selected-group-kind">{{group.kind}}</span>\n </div>\n </div>\n <i class="bi bi-chevron-down selected-group-chevron collapsed-hidden"></i>\n </div>\n ';async onInit(){await super.onInit();const e=this.getApp(),t=e?.router;if(t){const e=t.getCurrentPath();e&&this.autoSwitchToMenuForRoute(e)}this.initializeTooltips(),this.searchView=new GroupSearchView({noAppend:!0,showExitButton:!0,headerText:"Select Group",containerId:"sidebar-search-container",Collection:p,itemTemplate:'\n <div class="p-3 border-bottom">\n <div class="fw-semibold text-dark">{{name}}</div>\n <small class="text-muted">#{{id}} {{kind}}</small>\n </div>\n '}),this.addChild(this.searchView),this.searchView.on("item:selected",e=>{this.getApp().setActiveGroup(e.model)}),this.searchView.on("exit",e=>{this.hideGroupSearch()})}showGroupSearch(){"dialog"===this.groupSelectorMode?this.showGroupSearchDialog():(this.setClass("sidebar"),this.showSearch=!0,this.render())}hideGroupSearch(){"dialog"===this.groupSelectorMode?this.groupSelectorDialog&&this.groupSelectorDialog.hide():(this.setClass("sidebar"),this.showSearch=!1,this.render())}onActionShowGroupSearch(){this.showGroupSearch()}async onActionSelectGroupParent(){const e=this.getApp().activeGroup;if(await Mt.confirm(`Are you sure you want to navigate to the '${e.get("parent.name")}'?`)){this.getApp().showLoading();let t=new g({id:e.get("parent.id")});await t.fetch(),this.getApp().setActiveGroup(t),this.getApp().hideLoading()}}async showGroupSearchDialog(){const e=new p,t=new GroupSearchView({Collection:p,collection:e,searchFields:["name"],headerText:null,searchPlaceholder:"Search groups...",headerIcon:null,maxHeight:Math.min(600,window.innerHeight-200),showExitButton:!1,showKind:!0,parentField:"parent",kindField:"kind",autoExpandRoot:!0,autoExpandAll:!1,indentSize:20,showLines:!0});this.groupSelectorDialog=new Mt({body:t,size:"md",header:null,noBodyPadding:!0,scrollable:!1,buttons:[],closeButton:!0}),t.on("item:selected",e=>{this.getApp().setActiveGroup(e.model),this.groupSelectorDialog&&this.groupSelectorDialog.hide()}),this.groupSelectorDialog.on("hidden",()=>{this.groupSelectorDialog.destroy(),this.groupSelectorDialog=null}),await this.groupSelectorDialog.render(!0,document.body),this.groupSelectorDialog.show()}autoSwitchToMenuForRoute(e){for(const[t,s]of this.menus)if((!s.groupKind||this.getApp().activeGroup)&&this.menuContainsRoute(s,e))return this._setActiveMenu(t),this.currentRoute=e,this.clearAllActiveStates(),this.setActiveItemByRoute(e),this.render(),this.emit("menu-auto-switched",{menuName:t,route:e,config:s,sidebar:this}),!0;return!1}clearAllActiveStates(){for(const[e,t]of this.menus)for(const s of t.items||[])if(s.active=!1,s.children)for(const e of s.children)e.active=!1}setActiveItemByRoute(e){const t=e=>{if(!e)return"/";const t=decodeURIComponent(e);return t.startsWith("/")?t:`/${t}`},s=t(e);for(const[i,a]of this.menus)if(!a.groupKind||this.getApp().activeGroup)for(const e of a.items||[]){if(e.route){const i=t(e.route);if(this.routesMatch(s,i))return e.active=!0,this.activeMenuItem=e,!0}if(e.children)for(const i of e.children)if(i.route){const a=t(i.route);if(this.routesMatch(s,a))return i.active=!0,e.active=!0,!0}}return!1}menuContainsRoute(e,t){const s=e=>{if(!e)return"/";const t=decodeURIComponent(e);return t.startsWith("/")?t:`/${t}`},i=s(t);for(const a of e.items||[]){if(a.route){const e=s(a.route);if(this.routesMatch(i,e))return!0}if(a.children)for(const e of a.children)if(e.route){const t=s(e.route);if(this.routesMatch(i,t))return!0}}return!1}routesMatch(e,t){return this.getApp().router.doRoutesMatch(e,t)}getTemplate(){return this.customView?'<div class="sidebar-container" id="sidebar-custom-view-container"></div>':this.showSearch?this.getSearchTemplate():this.getMenuTemplate()}getSearchTemplate(){return'\n <div class="sidebar-container" id="sidebar-search-container">\n </div>\n '}getMenuTemplate(){return'\n <div class="sidebar-container">\n {{#data.currentMenu}}\n \x3c!-- Header --\x3e\n {{#header}}\n <div class="sidebar-header">\n {{{header}}}\n {{#showToggle}}\n <button class="sidebar-toggle" data-action="toggle-sidebar"\n aria-label="Toggle Sidebar">\n <i class="bi bi-chevron-left toggle-icon"></i>\n <i class="bi bi-chevron-right toggle-icon"></i>\n </button>\n {{/showToggle}}\n </div>\n {{/header}}\n\n \x3c!-- Navigation Items --\x3e\n <div class="sidebar-body">\n <ul class="nav nav-pills flex-column sidebar-nav" id="sidebar-nav-menu">\n {{#items}}\n {{>nav-item}}\n {{/items}}\n </ul>\n </div>\n\n \x3c!-- Footer --\x3e\n {{#footer}}\n <div class="sidebar-footer">\n {{{footer}}}\n </div>\n {{/footer}}\n {{/data.currentMenu}}\n\n {{^data.currentMenu}}\n <div class="sidebar-empty">\n <p class="text-danger text-center">No menu configured</p>\n </div>\n {{/data.currentMenu}}\n </div>\n '}getPartials(){return{"nav-item":'\n {{#isDivider}}\n {{>nav-divider}}\n {{/isDivider}}\n {{#isSpacer}}\n {{>nav-spacer}}\n {{/isSpacer}}\n {{#isLabel}}\n {{>nav-label}}\n {{/isLabel}}\n\n {{^isDivider}}\n {{^isSpacer}}\n {{^isLabel}}\n <li class="nav-item">\n {{#hasChildren}}\n \x3c!-- Item with submenu --\x3e\n <a class="nav-link {{#active}}active{{/active}} has-children collapsed"\n data-bs-toggle="collapse"\n href="#collapse-{{id}}"\n role="button"\n aria-expanded="{{#active}}true{{/active}}{{^active}}false{{/active}}"\n data-action="toggle-submenu">\n {{#icon}}<i class="{{icon}} me-2"></i>{{/icon}}\n <span class="nav-text">{{text}}</span>\n {{#badge}}\n <span class="{{badge.class}} ms-auto">{{badge.text}}</span>\n {{/badge}}\n <i class="bi bi-chevron-down nav-arrow ms-auto"></i>\n </a>\n <div class="collapse {{#active}}show{{/active}}" id="collapse-{{id}}" data-bs-parent="#sidebar-nav-menu">\n <ul class="nav flex-column nav-submenu">\n {{#children}}\n <li class="nav-item">\n <a class="nav-link {{#active}}active{{/active}}"\n {{#action}}data-action="{{action}}"{{/action}}\n {{#href}}href="{{href}}"{{/href}}>\n {{#icon}}<i class="{{icon}} me-2"></i>{{/icon}}\n <span class="nav-text">{{text}}</span>\n {{#badge}}\n <span class="{{badge.class}} ms-auto">{{badge.text}}</span>\n {{/badge}}\n </a>\n </li>\n {{/children}}\n </ul>\n </div>\n {{/hasChildren}}\n {{^hasChildren}}\n \x3c!-- Simple item --\x3e\n <a class="nav-link {{#active}}active{{/active}} {{#disabled}}disabled{{/disabled}}"\n {{#action}}{{^disabled}}data-action="{{action}}"{{/disabled}}{{/action}}\n {{#href}}{{^disabled}}href="{{href}}"{{/disabled}}{{/href}}>\n {{#icon}}<i class="{{icon}} me-2"></i>{{/icon}}\n <span class="nav-text">{{text}}</span>\n {{#badge}}\n <span class="{{badge.class}} ms-auto">{{badge.text}}</span>\n {{/badge}}\n </a>\n {{/hasChildren}}\n </li>\n {{/isLabel}}\n {{/isSpacer}}\n {{/isDivider}}\n ',"nav-divider":'\n <li class="nav-divider-item">\n <hr class="nav-divider-line">\n </li>\n ',"nav-spacer":'\n <li class="nav-spacer-item"></li>\n ',"nav-label":'\n <li class="nav-item {{className}}">\n <div class="nav-text px-3">{{text}}</div>\n </li>\n '}}getGroupHeader(){return this.groupHeader}addMenu(e,t){return t.groupKind&&!t.header&&(t.header=this.getGroupHeader()),this.menus.set(e,{name:e,groupKind:t.groupKind||null,header:t.header||null,footer:t.footer||null,items:t.items||[],data:t.data||{},className:t.className||"sidebar sidebar-dark"}),this.activeMenuName||this._setActiveMenu(e),this}_setActiveMenu(e){this.showSearch=!1,this.activeMenuName=e;const t=this.getCurrentMenuConfig();t.className?this.setClass(t.className):this.setClass("sidebar")}async setActiveMenu(e){if(!this.menus.has(e))return console.warn(`Menu '${e}' not found`),this;const t=this.menus.get(e);if(!t.groupKind||(this.lastGroupMenu=t,this.getApp().activeGroup))return this._setActiveMenu(e),await this.render(),this.emit("menu-changed",{menuName:e,config:t,sidebar:this}),this;this.showGroupSearch()}getGroupMenu(e){if(!e)return console.warn("No group provided"),null;let t=this.lastGroupMenu,s=null;if(e._.kind)for(const[i,a]of this.menus){if(this._groupKindMatches(a.groupKind,e._.kind)){t=a;break}"any"===a.groupKind&&(s=a)}return t||s}_groupKindMatches(e,t){return!(!e||!t)&&(Array.isArray(e)?e.includes(t):e===t)}showMenuForGroup(e){if(!e)return void console.warn("No group provided");let t=this.getGroupMenu(e);if(t)return this._setActiveMenu(t.name),this.render(),this.emit("menu-changed",{menuName:t.name,config:t,sidebar:this}),this;console.warn(`No menu found for group kind: ${e.kind}`)}getMenuConfig(e){return this.menus.get(e)||null}getCurrentMenuConfig(){return this.activeMenuName?this.menus.get(this.activeMenuName):null}updateMenu(e,t){const s=this.menus.get(e);return s?(Object.assign(s,t),this.activeMenuName===e&&this.render(),this):(console.warn(`Menu '${e}' not found`),this)}removeMenu(e){if(this.menus.delete(e),this.activeMenuName===e){const e=Array.from(this.menus.keys());this.activeMenuName=e.length>0?e[0]:null,this.render()}return this}async onBeforeRender(){const e=this.getCurrentMenuConfig();if(!e)return{currentMenu:null};let t={version:this.getApp().version||null,group:this.getApp().activeGroup||null,user:this.getApp.activeUser||null};this.data={currentMenu:{header:this.renderTemplateString(e.header||"",t),footer:this.renderTemplateString(e.footer||"",t),items:this.processNavItems(e.items,e.groupKind),data:e.data,showToggle:this.showToggle}}}async onAfterRender(){this.isCollapsedState()?setTimeout(()=>this.initializeTooltips(),50):this.destroyTooltips()}setCustomView(e){return this.customView&&this.removeChild(this.customView.id),this.customView=e,e&&(e.containerId="sidebar-custom-view-container",this.addChild(e)),this.render(),this}clearCustomView(){return this.customView&&(this.removeChild(this.customView.id),this.customView=null),this.render(),this}processNavItems(e,t){const s=this.getApp(),i=s?.activeUser,a=s?.activeGroup,n=e=>{let s=e;if(e.startsWith("/")&&!e.includes("?")&&(s=`?page=${e.substring(1)||"home"}`),t&&a&&a.id){const e=s.includes("?")?"&":"?";return`${s}${e}group=${a.id}`}return s};return e.map((e,t)=>{if(""===e||"object"==typeof e&&e.divider)return{isDivider:!0,id:`divider-${t}`};if("object"==typeof e&&e.spacer)return{isSpacer:!0,id:`spacer-${t}`};const s={...e};if(s.permissions&&(!i||!i.hasPermission(s.permissions)))return null;if(s.requiresGroupKind){const e=a?._.kind||a?.kind;if(!e||!this._groupKindMatches(s.requiresGroupKind,e))return null}if("label"===s.kind)return s.isLabel=!0,s.id||(s.id=`nav-label-${t}`),s;if(s.id||(s.id=`nav-${t}`),s.route)s.href=n(s.route);else if(s.page){const e=s.page.startsWith("/")?s.page:`/${s.page}`;s.href=n(e),s.route=s.href}return s.children?(s.children=s.children.map(e=>{const t={...e};if(t.permissions&&i&&!i.hasPermission(t.permissions))return null;if(t.requiresGroupKind){const e=a?._.kind||a?.kind;if(!e||!this._groupKindMatches(t.requiresGroupKind,e))return null}if(t.route)t.href=n(t.route);else if(t.page){const e=t.page.startsWith("/")?t.page:`/${t.page}`;t.href=n(e),t.route=t.href}return t}).filter(e=>null!==e),s.hasChildren=!!(s.children&&s.children.length>0)):s.hasChildren=!1,s}).filter(e=>null!==e)}isItemActive(e){if(!e.route||!this.currentRoute)return!1;const t=e=>{if(!e)return"/";const t=decodeURIComponent(e);return t.startsWith("/")?t:`/${t}`},s=t(e.route),i=t(this.currentRoute);return"/"===s&&"/"===i||"/"!==s&&"/"!==i&&(i.startsWith(s)||i===s)}async updateActiveItem(e){return this.currentRoute=e,this.clearAllActiveStates(),this.setActiveItemByRoute(e),await this.render(),this}async handleActionToggleSubmenu(e,t){const s=t.querySelector(".nav-arrow");s&&s.classList.toggle("rotated")}async handleActionToggleSidebar(e,t){this.toggleSidebar()}onActionShowGroupMenu(e,t,s){return this.setActiveMenu("group_default"),!1}async onActionDefault(e,t,s){const i=this.getCurrentMenuConfig();if(!i)return;const a=i=>{for(const n of i){if(n.action==e&&n.handler)return n.handler(e,t,s,this.getApp()),!0;if(n.children&&n.children.length>0&&a(n.children))return!0}return!1};return a(i.items)}getMenuNames(){return Array.from(this.menus.keys())}hasMenu(e){return this.menus.has(e)}clearMenus(){return this.menus.clear(),this.activeMenuName=null,this.render(),this}setMenuData(e){const t=this.getCurrentMenuConfig();return t&&(t.data={...t.data,...e},this.render()),this}getMenuData(){const e=this.getCurrentMenuConfig();return e?e.data:{}}setupRouteListeners(){const e=this.getApp();e&&e.events&&(e.events.on(["page:showing"],e=>{this.onRouteChanged(e)}),e.events.on("group:changed",e=>{this.showMenuForGroup(e.group)}),e.events.on("portal:user-changed",e=>{this.render()}))}onRouteChanged(e){if(e.page&&e.page.route){const t=e.page.route;if(this.activeMenuItem&&this.routesMatch(t,this.activeMenuItem.route))return;if(this.autoSwitchToMenuForRoute(t))return;const s=e.page.sidebarMenu||e.page.options?.sidebarMenu||null;if(s&&this.menus.has(s))return this._setActiveMenu(s),this.clearAllActiveStates(),void this.render();let i=null;for(const[e,a]of this.menus)if(!a.groupKind){i=e;break}i&&this.activeMenuName!==i&&this._setActiveMenu(i),this.clearAllActiveStates(),this.updateActiveItem(t),this.render()}}toggleSidebar(){const e=document.querySelector(".portal-container");if(!e)return;this.hideAllTooltips();const t=e.classList.contains("collapse-sidebar");return e.classList.contains("hide-sidebar")?(e.classList.remove("hide-sidebar"),this.isCollapsed=!1,this.destroyTooltips()):t?(e.classList.remove("collapse-sidebar"),this.isCollapsed=!1,this.destroyTooltips()):(e.classList.add("collapse-sidebar"),this.isCollapsed=!0,setTimeout(()=>this.initializeTooltips(),150)),this}setSidebarState(e){const t=document.querySelector(".portal-container");if(!t)return this;switch(t.classList.remove("collapse-sidebar","hide-sidebar"),e){case"collapsed":t.classList.add("collapse-sidebar"),this.isCollapsed=!0;break;case"hidden":t.classList.add("hide-sidebar"),this.isCollapsed=!1;break;default:this.isCollapsed=!1}return this.isCollapsed?(this.hideAllTooltips(),setTimeout(()=>this.initializeTooltips(),100)):this.destroyTooltips(),this}initializeTooltips(){return this.destroyTooltips(),this.isCollapsedState()?(this.element.querySelectorAll(".sidebar-nav .nav-link").forEach(e=>{const t=e.querySelector(".nav-text");if(t&&t.textContent.trim()){const s=t.textContent.trim();if(e.setAttribute("data-bs-toggle","tooltip"),e.setAttribute("data-bs-placement","right"),e.setAttribute("data-bs-title",s),e.setAttribute("data-bs-container","body"),window.bootstrap&&window.bootstrap.Tooltip){const t=e.getAttribute("data-tooltip-theme"),s=e.getAttribute("data-tooltip-size");let i="";t&&(i+=`tooltip-${t} `),s&&(i+=`tooltip-${s}`);const a={placement:"right",container:"body",trigger:"hover",delay:{show:500,hide:100},fallbackPlacements:["top","bottom","left"]},n=i.trim();n&&(a.customClass=n);const r=new window.bootstrap.Tooltip(e,a);e._tooltipInstance=r,e.addEventListener("click",()=>{r.hide()}),e.addEventListener("blur",()=>{r.hide()})}}}),this.addTooltipHideListeners(),this):this}destroyTooltips(){return this.removeTooltipHideListeners(),this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle="tooltip"]').forEach(e=>{const t=e._tooltipInstance||window.bootstrap?.Tooltip?.getInstance(e);t&&(t.hide(),t.dispose()),delete e._tooltipInstance,e.removeAttribute("data-bs-toggle"),e.removeAttribute("data-bs-placement"),e.removeAttribute("data-bs-title"),e.removeAttribute("data-bs-container")}),this}getSidebarState(){const e=document.querySelector(".portal-container");return e?e.classList.contains("hide-sidebar")?"hidden":e.classList.contains("collapse-sidebar")?"collapsed":"normal":"normal"}isCollapsedState(){return"collapsed"===this.getSidebarState()}setToggleEnabled(e){return this.showToggle=e,this.render(),this}initializeMenus(e){if(e.menus)for(const t of e.menus)this.addMenu(t.name,t);else e.menu&&(e.menu.name=e.menu.name||"default",this.addMenu(e.menu.name,e.menu))}addTooltipHideListeners(){this._tooltipScrollHandler=()=>this.hideAllTooltips(),this.element.addEventListener("scroll",this._tooltipScrollHandler,{passive:!0}),this._tooltipRouteHandler=()=>this.hideAllTooltips(),this.getApp(),this._tooltipBlurHandler=()=>this.hideAllTooltips(),window.addEventListener("blur",this._tooltipBlurHandler),this._tooltipEscapeHandler=e=>{"Escape"===e.key&&this.hideAllTooltips()},document.addEventListener("keydown",this._tooltipEscapeHandler)}removeTooltipHideListeners(){this._tooltipScrollHandler&&(this.element.removeEventListener("scroll",this._tooltipScrollHandler),delete this._tooltipScrollHandler),this._tooltipBlurHandler&&(window.removeEventListener("blur",this._tooltipBlurHandler),delete this._tooltipBlurHandler),this._tooltipEscapeHandler&&(document.removeEventListener("keydown",this._tooltipEscapeHandler),delete this._tooltipEscapeHandler)}hideAllTooltips(){this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle="tooltip"]').forEach(e=>{const t=e._tooltipInstance||window.bootstrap?.Tooltip?.getInstance(e);t&&t.hide()}),document.querySelectorAll(".tooltip.show").forEach(e=>{e.remove()})}async onBeforeDestroy(){this.destroyTooltips(),await super.onBeforeDestroy()}setupResponsiveBehavior(){const e=()=>{const e=window.innerWidth<=768,t=document.querySelector(".portal-container");t&&(e?t.classList.add("sidebar-mobile"):t.classList.remove("sidebar-mobile","sidebar-open"))};e(),window.addEventListener("resize",e)}static createDefault(e={}){return new Sidebar({theme:"sidebar-clean",showToggle:!0,autoCollapseMobile:!0,...e})}static createMinimal(e={}){return new Sidebar({theme:"sidebar-clean",showToggle:!1,autoCollapseMobile:!1,...e})}setSidebarTheme(e){return this.removeClass("sidebar-light sidebar-dark sidebar-clean"),this.sidebarTheme=e,this.addClass(e),this}show(){return this.setSidebarState("normal")}hide(){return this.setSidebarState("hidden")}collapse(){return this.setSidebarState("collapsed")}expand(){return this.setSidebarState("normal")}pulseToggle(){const e=this.element.querySelector(".sidebar-toggle");if(e){e.classList.add("pulse");const t=()=>{e.classList.remove("pulse"),e.removeEventListener("click",t)};e.addEventListener("click",t,{once:!0}),setTimeout(t,3e3)}return this}addSimpleMenuItem(e,t,s,i="bi-circle"){const a=this.menus.get(e);return a&&(a.items=a.items||[],a.items.push({text:t,route:s,icon:i}),this.activeMenuName===e&&this.render()),this}setSimpleMenu(e,t,s){const i={name:e,header:t,items:s};return this.addMenu(e,i),this.setActiveMenu(e),this}}class PageHeader extends r{constructor(e={}){super({tagName:"div",className:"page-header",...e}),this.style=e.style||"default",this.size=e.size||"md",this.showIcon=!1!==e.showIcon,this.showDescription=!1!==e.showDescription,this.showBreadcrumbs=e.showBreadcrumbs||!1,this.currentPage=null}async getTemplate(){return"minimal"===this.style?this.getMinimalTemplate():"breadcrumb"===this.style?this.getBreadcrumbTemplate():this.getDefaultTemplate()}getDefaultTemplate(){return'\n {{#data.hasPage}}\n <div class="page-header-content page-header-{{data.size}}">\n <div class="page-header-main">\n <div class="page-header-info">\n {{#data.showIcon}}\n {{#data.pageIcon}}\n <div class="page-icon">\n <i class="{{data.pageIcon}}"></i>\n </div>\n {{/data.pageIcon}}\n {{/data.showIcon}}\n \n <div class="page-title-group">\n <h1 class="page-title">{{data.pageTitle}}</h1>\n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class="page-description text-muted">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n </div>\n\n {{#data.hasActions}}\n <div class="page-actions">\n {{#data.actions}}\n <button class="btn {{buttonClass}}" \n data-action="{{action}}"\n type="button">\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n </div>\n {{/data.hasPage}}\n '}getMinimalTemplate(){return'\n {{#data.hasPage}}\n <div class="page-header-content page-header-minimal">\n <h1 class="page-title">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class="{{data.pageIcon}} me-2"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n </div>\n {{/data.hasPage}}\n '}getBreadcrumbTemplate(){return'\n {{#data.hasPage}}\n <div class="page-header-content page-header-breadcrumb">\n {{#data.showBreadcrumbs}}\n <nav aria-label="breadcrumb">\n <ol class="breadcrumb mb-2">\n {{#data.breadcrumbs}}\n <li class="breadcrumb-item {{#active}}active{{/active}}">\n {{#href}}<a href="{{href}}">{{label}}</a>{{/href}}\n {{^href}}{{label}}{{/href}}\n </li>\n {{/data.breadcrumbs}}\n </ol>\n </nav>\n {{/data.showBreadcrumbs}}\n \n <div class="d-flex justify-content-between align-items-start">\n <h1 class="page-title">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class="{{data.pageIcon}} me-2"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n \n {{#data.hasActions}}\n <div class="page-actions">\n {{#data.actions}}\n <button class="btn {{buttonClass}}" \n data-action="{{action}}"\n type="button">\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n \n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class="page-description text-muted mt-2">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n {{/data.hasPage}}\n '}async onBeforeRender(){await super.onBeforeRender();const e=this.currentPage,t=!!e;e&&(e.title,e.displayName,e.name,e.pageName,e.icon,e.pageIcon,e.pageDescription,e.description);const s=e?.options?.headerActions||e?.headerActions||e?.constructor?.prototype?.headerActions||[];this.data={hasPage:t,pageTitle:e?.title||e?.displayName||e?.name||e?.pageName||"",pageIcon:e?.icon||e?.pageIcon||"",pageDescription:e?.pageDescription||e?.description||"",showIcon:this.showIcon,showDescription:this.showDescription,showBreadcrumbs:this.showBreadcrumbs,breadcrumbs:e?.options?.breadcrumbs||e?.breadcrumbs||[],actions:s,hasActions:s.length>0,size:this.size},this.data}async setPage(e){this.currentPage=e,e&&await this.render()}getPage(){return this.currentPage}async onActionDefault(e,t,s){return this.currentPage&&"function"==typeof this.currentPage.onHeaderAction?(await this.currentPage.onHeaderAction(e,t,s),!0):(this.emit("action",{action:e,event:t,element:s,page:this.currentPage}),!1)}}class DeniedPage extends m{constructor(e={}){super({pageName:"Access Denied",route:"/denied",title:"Access Denied",pageIcon:"bi bi-shield-x",template:'\n <div class="container mt-5">\n <div class="row justify-content-center">\n <div class="col-md-8 col-lg-6">\n <div class="text-center mb-4">\n <i class="bi bi-shield-x text-muted" style="font-size: 3rem;"></i>\n <h2 class="mt-3 mb-2">Access Denied</h2>\n <p class="text-muted">You don\'t have permission to access this page.</p>\n </div>\n\n {{#deniedPage}}\n <div class="card border-0 shadow-sm mb-4">\n <div class="card-body">\n <h6 class="card-subtitle mb-2 text-muted">Requested Page</h6>\n <h5 class="card-title">\n <i class="{{pageIcon}} me-2"></i>\n {{displayName}}\n </h5>\n {{#route}}\n <p class="card-text text-muted small">{{route}}</p>\n {{/route}}\n {{#description}}\n <p class="card-text">{{description}}</p>\n {{/description}}\n\n {{#requiredPermissions}}\n <div class="mt-3">\n <h6 class="mb-2">Required Permissions:</h6>\n {{#permissions}}\n <span class="badge bg-light text-dark me-1 mb-1">{{.}}</span>\n {{/permissions}}\n {{^permissions}}\n <span class="text-muted small">Authentication required</span>\n {{/permissions}}\n </div>\n {{/requiredPermissions}}\n </div>\n </div>\n {{/deniedPage}}\n\n <div class="d-grid gap-2 d-md-flex justify-content-md-center">\n <button type="button" class="btn btn-primary" data-action="go-back">\n <i class="bi bi-arrow-left me-1"></i>\n Go Back\n </button>\n <button type="button" class="btn btn-outline-secondary" data-action="go-home">\n <i class="bi bi-house me-1"></i>\n Home\n </button>\n {{#showLogin}}\n <button type="button" class="btn btn-outline-primary" data-action="login">\n <i class="bi bi-box-arrow-in-right me-1"></i>\n Login\n </button>\n {{/showLogin}}\n </div>\n\n {{#currentUser}}\n <div class="text-center mt-4">\n <small class="text-muted">\n Logged in as <strong>{{username}}</strong>\n </small>\n </div>\n {{/currentUser}}\n </div>\n </div>\n </div>\n ',...e}),this.deniedPage=null,this.deniedPageOptions=null}async onParams(e={},t={}){await super.onParams(e,t),e.page?(this.deniedPage=e.page,this.deniedPageOptions=e.page.options||e.page.pageOptions||{}):t.page&&(this.deniedPageName=t.page)}setDeniedPage(e){return this.deniedPage=e,this.deniedPageOptions=e?.options||e?.pageOptions||{},this}async getViewData(){const e=this.getApp(),t=e?.activeUser||e?.getCurrentUser?.()||null;let s=null;if(this.deniedPage){const e=this.deniedPageOptions?.permissions||this.deniedPage.options?.permissions||this.deniedPage.pageOptions?.permissions;s={displayName:this.deniedPage.displayName||this.deniedPage.pageName||this.deniedPage.title||"Unknown Page",pageName:this.deniedPage.pageName,route:this.deniedPage.route,description:this.deniedPage.pageDescription||this.deniedPage.description,pageIcon:this.deniedPage.pageIcon||"bi bi-file-text",requiredPermissions:e?{permissions:Array.isArray(e)?e:[e]}:null}}else this.deniedPageName&&(s={displayName:this.deniedPageName,pageName:this.deniedPageName,pageIcon:"bi bi-file-text"});return{deniedPage:s,currentUser:t?{username:t.username||t.name||t.email||"Unknown User",name:t.name,email:t.email}:null,showLogin:!t}}async handleActionGoBack(e,t){e.preventDefault(),window.history.length>1?window.history.back():await this.handleActionGoHome(e,t)}async handleActionGoHome(e,t){e.preventDefault();const s=this.getApp();s?await s.navigateToDefault():window.location.href="/"}async handleActionLogin(e,t){e.preventDefault();const s=this.getApp();if(s)try{await s.showPage("login")}catch(i){try{await s.navigate("/login")}catch(a){this.emit("login-required",{returnUrl:this.deniedPage?.route||window.location.pathname}),setTimeout(()=>{s?.showInfo?.("Please contact your administrator for access.")},100)}}}async onEnter(){await super.onEnter();const e=this.deniedPage?.pageName||this.deniedPageName;e&&this.setMeta({title:`Access Denied - ${e}`}),console.warn("Access denied to page:",{page:this.deniedPage?.pageName||this.deniedPageName,route:this.deniedPage?.route,permissions:this.deniedPageOptions?.permissions,timestamp:/* @__PURE__ */(new Date).toISOString()})}static showForPage(e,t){const s=new DeniedPage;return s.setDeniedPage(t),e.showPage(s)}}class NotFoundPage extends m{constructor(e={}){super({pageName:"404",route:"/404",title:"404 - Page Not Found",pageIcon:"bi bi-search",template:'\n <div class="container mt-5">\n <div class="row justify-content-center">\n <div class="col-md-8 col-lg-6">\n <div class="text-center mb-4">\n <i class="bi bi-search text-muted" style="font-size: 3rem;"></i>\n <h2 class="mt-3 mb-2">Page Not Found</h2>\n <p class="text-muted">The page you\'re looking for doesn\'t exist.</p>\n </div>\n\n {{#path}}\n <div class="card border-0 shadow-sm mb-4">\n <div class="card-body text-center">\n <h6 class="card-subtitle mb-2 text-muted">Requested Path</h6>\n <code class="text-primary">{{path}}</code>\n </div>\n </div>\n {{/path}}\n\n <div class="d-grid gap-2 d-md-flex justify-content-md-center">\n <button type="button" class="btn btn-primary" data-action="go-back">\n <i class="bi bi-arrow-left me-1"></i>\n Go Back\n </button>\n <button type="button" class="btn btn-outline-secondary" data-action="go-home">\n <i class="bi bi-house me-1"></i>\n Home\n </button>\n </div>\n </div>\n </div>\n </div>\n ',...e}),this.path=null}async onParams(e={},t={}){await super.onParams(e,t),e.path&&(this.path=e.path),t.path&&(this.path=t.path)}setInfo(e){return this.path=e||null,this}async handleActionGoBack(e,t){e.preventDefault(),window.history.length>1?window.history.back():await this.handleActionGoHome(e,t)}async handleActionGoHome(e,t){e.preventDefault();const s=this.getApp();s?await s.navigateToDefault():window.location.href="/"}async onEnter(){await super.onEnter(),this.path&&this.setMeta({title:`404 - ${this.path} Not Found`}),console.warn("404 Not Found:",{path:this.path,timestamp:/* @__PURE__ */(new Date).toISOString()})}static showForPath(e,t){const s=new NotFoundPage;return s.setInfo(t),s.render()}}class PortalApp extends T{constructor(e={}){super(e),this.sidebarConfig=e.sidebar,this.topbarConfig=e.topbar||{},e.topnav&&!e.topbar&&(this.topbarConfig=e.topnav),this.showPageHeader=e.showPageHeader||!1,this.pageHeaderConfig=e.pageHeader||{},this.sidebar=null,this.topbar=null,this.topnav=null,this.pageHeader=null,this.tokenManager=new At,this.activeGroup=null,this.isMobile()?this.sidebarCollapsed=this.sidebarConfig.defaultCollapsed||!1:this.sidebarCollapsed=this.loadSidebarState()??(this.sidebarConfig.defaultCollapsed||!1),this.setupPageContainer(),this.toast=new b,this.Dialog=Mt,this.registerPage("denied",DeniedPage),this.registerPage("404",NotFoundPage)}async start(){await this.checkAuthStatus(),this.events.on("auth:unauthorized",()=>{this.tokenManager.clearTokens(),this.rest.clearAuth(),this.setActiveUser(null)}),this.events.on("auth:logout",()=>{this.tokenManager.clearTokens(),this.rest.clearAuth(),this.setActiveUser(null)}),this.events.on("browser:focus",()=>{this.activeUser&&this.tokenManager.checkAndRefreshTokens(this)}),this.events.on("portal:action",this.onPortalAction.bind(this)),this.activeUser&&await this.checkActiveGroup(),await this.setupRouter(),this.isStarted=!0,this.events.emit("app:ready",{app:this})}async checkAuthStatus(){const e=this.tokenManager.checkTokenStatus();if("logout"===e.action)return this.events.emit("auth:unauthorized",{app:this}),!1;if("refresh"===e.action&&!(await this.tokenManager.checkAndRefreshTokens(this)))return!1;const t=this.tokenManager.getTokenInstance();if(this.activeUser)return this.tokenManager.startAutoRefresh(this),!0;this.rest.setAuthToken(t.token);const s=new v({id:t.getUserId()}),i=await s.fetch();return i.success?(this.setActiveUser(s),this.tokenManager.startAutoRefresh(this),!0):(this.tokenManager.clearTokens(),this.events.emit("auth:unauthorized",{app:this,error:i.error}),!1)}async checkActiveGroup(){const e=new URLSearchParams(window.location.search).get("group"),t=e||this.loadActiveGroupId();if(t)try{const s=new g({id:t}),i=await s.fetch();if(!i.success||!i.data.status)return this.clearActiveGroup(),void console.warn("Failed to load active group:",i.statusText);this.activeGroup=s,e&&this.saveActiveGroupId(t),this.activeUser&&(this.activeUser.member=new E,await this.activeUser.member.fetchForGroup(s.id)),this.events.emit("group:loaded",{group:this.activeGroup})}catch(s){if(console.warn("Failed to load active group:",s),e&&!this.loadActiveGroupId())this.clearActiveGroupId();else if(e){const t=this.loadActiveGroupId();if(t&&t!==e)try{const e=new g({id:t});await e.fetch(),this.activeGroup=e,this.events.emit("group:loaded",{group:this.activeGroup})}catch(i){console.warn("Fallback to stored group also failed:",i),this.clearActiveGroupId()}}}}async setActiveGroup(e){const t=this.activeGroup;this.activeGroup=e,e&&e.get("id")?this.saveActiveGroupId(e.get("id")):this.clearActiveGroupId(),this.activeUser&&(this.activeUser.member=new E,await this.activeUser.member.fetchForGroup(e.id)),this.events.emit("group:changed",{group:e,previousGroup:t,app:this});const s=this.getCurrentPage();return s&&s.onGroupChange(e),this.router.updateUrl({group:e.id},{replace:!0}),this}getActiveGroup(){return this.activeGroup}async clearActiveGroup(){const e=this.activeGroup;return this.activeGroup=null,this.clearActiveGroupId(),this.events.emit("group:cleared",{previousGroup:e,app:this}),this}saveActiveGroupId(e){try{const t=this.getActiveGroupStorageKey();localStorage.setItem(t,e.toString())}catch(t){console.warn("Failed to save active group ID:",t)}}loadActiveGroupId(){try{const e=this.getActiveGroupStorageKey();return localStorage.getItem(e)}catch(e){return console.warn("Failed to load active group ID:",e),null}}clearActiveGroupId(){try{const e=this.getActiveGroupStorageKey();localStorage.removeItem(e)}catch(e){console.warn("Failed to clear active group ID:",e)}}getActiveGroupStorageKey(){return"active_group_id"}setPortalProfile(e){try{localStorage.setItem("portal_profile",e)}catch(t){console.warn("Failed to save portal profile:",t)}}needsGroupSelection(){return!this.activeGroup}setupPageContainer(){const e="string"==typeof this.container?document.querySelector(this.container):this.container;if(!e)throw new Error(`Portal container not found: ${this.container}`);const t=this.sidebarConfig&&Object.keys(this.sidebarConfig).length>0,s=this.topbarConfig&&Object.keys(this.topbarConfig).length>0,i=this.showPageHeader?'\n <div class="portal-content" id="portal-content">\n <div id="page-header"></div>\n <div id="page-container">\n \x3c!-- Pages render here --\x3e\n </div>\n </div>\n ':'\n <div class="portal-content" id="page-container">\n \x3c!-- Pages render here --\x3e\n </div>\n ';e.innerHTML=`\n <div class="portal-layout hide-sidebar">\n ${t?'<div id="portal-sidebar"></div>':""}\n <div class="portal-body">\n ${s?'<div id="portal-topnav"></div>':""}\n ${i}\n </div>\n </div>\n `,this.pageContainer="#page-container",e.classList.add("portal-container"),this.setupPortalComponents(),this.applySidebarState(e)}async setupPortalComponents(){await this.setupSidebar(),await this.setupTopbar(),await this.setupPageHeader(),this.setupPortalEvents()}async setupSidebar(){this.sidebarConfig&&0!==Object.keys(this.sidebarConfig).length&&(this.sidebar=new Sidebar({containerId:"portal-sidebar",...this.sidebarConfig}),await this.sidebar.render())}async setupTopbar(){this.topbarConfig&&0!==Object.keys(this.topbarConfig).length&&(this.topbar=new Pt({containerId:"portal-topnav",brandText:this.topbarConfig.brand||this.brand||this.title,brandRoute:this.topbarConfig.brandRoute||"/",brandIcon:this.topbarConfig.brandIcon||this.brandIcon,navItems:this.topbarConfig.leftItems||[],rightItems:this.topbarConfig.rightItems||[],displayMode:this.topbarConfig.displayMode||"both",showSidebarToggle:this.topbarConfig.showSidebarToggle||!1,...this.topbarConfig}),await this.topbar.render(),this.topnav=this.topbar)}async setupPageHeader(){if(!this.showPageHeader)return;this.pageHeader=new PageHeader({containerId:"page-header",style:this.pageHeaderConfig.style||"default",showIcon:!1!==this.pageHeaderConfig.showIcon,showDescription:!1!==this.pageHeaderConfig.showDescription,showBreadcrumbs:this.pageHeaderConfig.showBreadcrumbs||!1,...this.pageHeaderConfig});const e=document.getElementById("page-header");e&&await this.pageHeader.render(!0,e)}setupPortalEvents(){if(document.addEventListener("click",e=>{e.target.closest('[data-action="toggle-sidebar"]')&&(e.preventDefault(),this.toggleSidebar())}),window.ResizeObserver){const e=new ResizeObserver(()=>{this.handleResponsive()});e.observe(document.body),this._resizeObserver=e}else this._resizeHandler=()=>this.handleResponsive(),window.addEventListener("resize",this._resizeHandler);this.handleResponsive()}toggleSidebar(){if(!this.sidebar)return;const e=document.querySelector(".portal-container"),t=this.isMobile();t?e.classList.toggle("hide-sidebar"):(e.classList.toggle("collapse-sidebar"),this.sidebarCollapsed=!this.sidebarCollapsed,this.saveSidebarState(this.sidebarCollapsed)),this.events.emit("sidebar:toggled",{collapsed:this.sidebarCollapsed,mobile:t})}handleResponsive(){const e=document.querySelector(".portal-container");if(!e)return;const t=this.isMobile();t?(e.classList.add("mobile-layout"),e.classList.contains("hide-sidebar")||e.classList.add("hide-sidebar")):e.classList.remove("mobile-layout","hide-sidebar"),this.events.emit("responsive:changed",{mobile:t})}getPortalContainer(){return document.querySelector(".portal-container")}isMobile(){return window.innerWidth<768}hasMobileLayout(){return this.getPortalContainer().classList.contains("mobile-layout")}async showPage(e,t={},s={},i={}){const a=await super.showPage(e,t,s,i);return this.hasMobileLayout()&&this.getPortalContainer().classList.add("hide-sidebar"),this.currentPage&&this.updateNavigation(this.currentPage),a}updateNavigation(e){this.sidebar&&this.sidebar.setActivePage&&this.sidebar.setActivePage(e.route),this.topbar&&this.topbar.setActivePage&&this.topbar.setActivePage(e.route),this.pageHeader&&this.pageHeader.setPage(e),this.events.emit("portal:page-changed",{page:e})}setActiveUser(e){this.activeUser=e,this.topbar&&this.topbar.setUser(e),this.events.emit("portal:user-changed",{user:e})}getActiveUser(){return this.activeUser}saveSidebarState(e){try{const t=this.getSidebarStorageKey();localStorage.setItem(t,JSON.stringify(e))}catch(t){console.warn("Failed to save sidebar state:",t)}}loadSidebarState(){try{const e=this.getSidebarStorageKey(),t=localStorage.getItem(e);return null!==t?JSON.parse(t):null}catch(e){return console.warn("Failed to load sidebar state:",e),null}}getSidebarStorageKey(){return`${this.title?this.title.replace(/\s+/g,"_").toLowerCase():"portal_app"}_sidebar_collapsed`}applySidebarState(e=null){e||(e=document.querySelector(".portal-container")),e&&(this.sidebarCollapsed?e.classList.add("collapse-sidebar"):e.classList.remove("collapse-sidebar"))}clearSidebarState(){try{const e=this.getSidebarStorageKey();localStorage.removeItem(e)}catch(e){console.warn("Failed to clear sidebar state:",e)}}async changePassword(){const e=await this.showForm({title:"Change Password",fields:[{name:"current_password",type:"password",label:"Current Password",required:!0,showToggle:!0,strengthMeter:!0,capsLockWarning:!0},{name:"new_password",type:"password",label:"New Password",required:!0,showToggle:!0,passwordUsage:"new",strengthMeter:!0,capsLockWarning:!0,attributes:{autocomplete:"new-password"}},{name:"confirm_password",type:"password",label:"Confirm Password",required:!0,showToggle:!0,passwordUsage:"new",strengthMeter:!0,capsLockWarning:!0,attributes:{}}],submitLabel:"Change Password"});e&&(e.new_password===e.confirm_password?200===(await this.activeUser.save(e)).status?this.toast.success("Password changed successfully"):this.toast.error("Failed to change password"):this.toast.error("Passwords do not match"))}onPortalAction(e){switch(e.action){case"logout":this.tokenManager.clearTokens(),this.rest.clearAuth(),this.setActiveUser(null);break;case"profile":this.showProfile();break;case"change-password":this.changePassword();break;default:console.warn(`Unknown portal action: ${e}`)}}async showProfile(){if(this.activeUser)try{const e=await Mt.showModelForm({title:"Edit Profile",size:"lg",fileHandling:"base64",model:this.activeUser,fields:[{type:"header",text:"Profile Information",level:4,class:"text-primary mb-3"},{type:"group",columns:{xs:12,md:4},title:"Avatar",fields:[{type:"image",name:"avatar",size:"lg",imageSize:{width:200,height:200},placeholder:"Upload your avatar",help:"Square images work best"}]},{type:"group",columns:{xs:12,md:8},title:"Details",fields:[{type:"text",name:"display_name",label:"Display Name",required:!0,columns:12,placeholder:"Enter first name"},{type:"email",name:"email",label:"Email Address",required:!0,columns:8,placeholder:"your.email@example.com"},{type:"tel",name:"phone_number",label:"Phone Number",columns:4,placeholder:"(555) 123-4567"}]},{type:"group",columns:12,title:"Account Settings",class:"pt-3",fields:[{type:"select",name:"timezone",label:"Timezone",columns:6,options:[{value:"America/New_York",text:"Eastern Time"},{value:"America/Chicago",text:"Central Time"},{value:"America/Denver",text:"Mountain Time"},{value:"America/Los_Angeles",text:"Pacific Time"},{value:"UTC",text:"UTC"}]},{type:"select",name:"language",label:"Language",columns:6,options:[{value:"en",text:"English"},{value:"es",text:"Spanish"},{value:"fr",text:"French"},{value:"de",text:"German"}]},{type:"switch",name:"email_notifications",label:"Email Notifications",columns:4},{type:"switch",name:"two_factor_enabled",label:"Two-Factor Authentication",columns:4},{type:"switch",name:"profile_public",label:"Public Profile",columns:4}]}],submitText:"Save Profile",cancelText:"Cancel"});e&&e.success?this.showSuccess("Profile updated successfully!"):e&&e.success}catch(e){console.error("Error showing profile form:",e),this.showError("Failed to load profile form")}else this.showError("No user is currently logged in")}async destroy(){this.activeGroup=null,this._resizeObserver&&this._resizeObserver.disconnect(),this._resizeHandler&&window.removeEventListener("resize",this._resizeHandler),this.topbar&&(await this.topbar.destroy(),this.topbar=null,this.topnav=null),this.sidebar&&(await this.sidebar.destroy(),this.sidebar=null),await super.destroy()}static create(e={}){return new PortalApp(e)}}class FormPage extends m{constructor(e={}){super({title:"Form Page",description:"A page for submitting forms",icon:"form",fields:[],template:'<div data-container="form-view-container"></div>',className:"form-page container-sm",...e})}async onInit(){await super.onInit(),await this.recreateFormView()}async onEnter(){await super.onEnter(),this.formView&&await this.recreateFormView()}async onGroupChange(e){this.formView&&await this.recreateFormView()}async getModel(){return this.model?this.model:this.getApp().activeGroup?this.getApp().activeGroup:null}async recreateFormView(){this.formView&&(await this.formView.destroy(),this.removeChild(this.formView)),this.formView=new It({containerId:"form-view-container",fields:this.options.fields,autosaveModelField:!0}),this.addChild(this.formView);const e=await this.getModel();e&&this.formView.setModel(e)}}const jt=new class{constructor(){this.formatter=o,this.compiledTemplates=/* @__PURE__ */new Map}render(e,t,s={}){return l.render(e,t,s)}compile(e){const t=l.parse(e);return this.compiledTemplates.set(e,t),t}renderCompiled(e,t,s={}){return l.render(e,t,s)}clearCache(){this.compiledTemplates.clear(),l.clearCache()}cache(e,t){return{key:e,template:t,compiled:this.compile(t)}}getCached(e){for(const[t,s]of this.compiledTemplates)if(t===e||s===e)return{key:e,template:t,compiled:s};return null}registerFormatter(e,t){return this.formatter.register(e,t),this}hasPipes(e){return/\{\{[{]?[^}|]+\|[^}]+\}[}]?\}/.test(e)}processData(e,t){const s={...e};for(const[i,a]of Object.entries(t))if(e&&"function"==typeof e.get)s[i]=e.get(`${i}|${a}`);else{const t=this.getValueFromPath(e,i);s[i]=this.formatter.pipe(t,a)}return s}getValueFromPath(e,t){if(!e||!t)return;if(e&&"function"==typeof e.get)return e.get(t);const s=t.split(".");let i=e;for(const a of s){if(null==i)return;i=!isNaN(a)&&Array.isArray(i)?i[parseInt(a)]:i[a]}return i}processTemplate(e,t){return{template:e,data:t}}};Bt.install({level:"warn"});const zt="MOJO",$t="web-mojo",qt={FRAMEWORK_NAME:zt,PACKAGE_NAME:$t};export{e as BUILD_TIME,_ as BundleByOptions,G as ChatInputView,N as ChatMessageView,F as ChatView,D as Collection,R as CommonEventFields,V as CommonScopeOptions,H as ComparatorOptions,Bt as ConsoleSilencer,f as ContextMenu,Tt as DataView,c as DataWrapper,Mt as Dialog,O as DjangoLookups,U as EmailDomain,B as EmailDomainForms,K as EmailDomainList,j as EmailTemplate,z as EmailTemplateForms,$ as EmailTemplateList,I as EventBus,d as EventDelegate,zt as FRAMEWORK_NAME,q as File,J as FileForms,W as FileList,Y as FileManager,Q as FileManagerForms,X as FileManagerList,Z as FilePreviewView,ee as FileUpload,FormPage,It as FormView,te as GeoLocatedIP,se as GeoLocatedIPList,g as Group,w as GroupForms,p as GroupList,ie as Incident,ae as IncidentEvent,ne as IncidentEventForms,re as IncidentEventList,oe as IncidentForms,le as IncidentHistory,ce as IncidentHistoryList,de as IncidentList,he as IncidentRule,ue as IncidentRuleList,pe as IncidentRuleSet,ge as IncidentRuleSetList,me as IncidentStats,be as Job,ve as JobEvent,fe as JobEventList,we as JobForms,ye as JobList,Se as JobLog,Ae as JobLogList,Pe as JobRunner,Me as JobRunnerForms,Ce as JobRunnerList,Le as JobsEngineStats,Te as LOOKUPS,Ct as ListView,Lt as ListViewItem,Ie as Log,ke as LogList,h as MOJOUtils,De as Mailbox,xe as MailboxForms,Ee as MailboxList,_e as MatchByOptions,E as Member,Ge as MemberForms,Ne as MemberList,Fe as MetricsForms,Re as MetricsPermission,Ve as MetricsPermissionList,x as Model,jt as MustacheFormatter,$t as PACKAGE_NAME,m as Page,PortalApp,He as ProgressView,Oe as PushConfig,Ue as PushConfigForms,Be as PushConfigList,Ke as PushDelivery,je as PushDeliveryList,ze as PushDevice,$e as PushDeviceList,qe as PushTemplate,Je as PushTemplateForms,We as PushTemplateList,u as Rest,k as Router,Ye as Rule,Qe as RuleForms,Xe as RuleList,Ze as RuleSet,et as RuleSetForms,tt as RuleSetList,st as S3Bucket,it as S3BucketForms,at as S3BucketList,nt as SentMessage,rt as SentMessageForms,ot as SentMessageList,Sidebar,St as SimpleSearchView,lt as TabView,ct as TablePage,dt as TableRow,ht as TableView,ut as Ticket,pt as TicketCategories,gt as TicketForms,mt as TicketList,bt as TicketNote,vt as TicketNoteList,b as ToastService,At as TokenManager,Pt as TopNav,v as User,y as UserDataView,S as UserDevice,A as UserDeviceList,P as UserDeviceLocation,M as UserDeviceLocationList,C as UserForms,L as UserList,t as VERSION,s as VERSION_INFO,i as VERSION_MAJOR,a as VERSION_MINOR,n as VERSION_REVISION,ft as ValueTypeOptions,r as View,T as WebApp,Dt as WebSocketClient,kt as applyFileDropMixin,o as dataFormatter,qt as default,wt as formatFilterDisplay,Kt as installConsoleSilencer,yt as parseFilterKey};
|
|
1
|
+
import{B as e,V as t,a as s,b as i,c as a,d as n}from"./chunks/version-BY7AsEkb.js";import{V as o,d as r,a as l,r as d}from"./chunks/Rest-BJ3Mvx1L.js";import{D as c,b as p,M as u}from"./chunks/Rest-BJ3Mvx1L.js";import{P as h}from"./chunks/ContextMenu-DBw0WMTO.js";import{C as m}from"./chunks/ContextMenu-DBw0WMTO.js";import{W as g}from"./chunks/WebApp-Bsic6FPo.js";import{E as f,R as b}from"./chunks/WebApp-Bsic6FPo.js";import{M as v,C as y}from"./chunks/Collection-BWKmydl5.js";import{c as w,T as k,a8 as x,M as A}from"./chunks/ChatView-DuQVFrCY.js";import{B as P,a9 as S,aa as _,C,ab as T,ac as I,ad as M,ae as N,x as E,E as L,w as z,D,F,H as O,af as R,G as V,e as G,k as U,m as B,n as H,I as q,l as j,j as K,i as $,g as J,ag as W,ah as Y,ai as Q,aj as Z,h as X,V as ee,ak as te,W as se,Y as ie,_ as ae,al as ne,X as oe,a1 as re,a0 as le,$ as de,Z as ce,am as pe,a2 as ue,L as he,A as me,y as ge,z as fe,t as be,d as ve,a4 as ye,a3 as we,a5 as ke,an as xe,N as Ae,O as Pe,ao as Se,K as _e,ap as Ce,P as Te,aq as Ie,Q as Me,U as Ne,ar as Ee,as as Le,u as ze,R as De,at as Fe,v as Oe,au as Re,a6 as Ve,a7 as Ge,S as Ue,av as Be,J as He,a as qe,b as je,q as Ke,s as $e,r as Je,f as We,p as Ye,o as Qe,aw as Ze,ax as Xe,ay as et}from"./chunks/ChatView-DuQVFrCY.js";import{F as tt,c as st,d as it,e as at,a as nt,b as ot,f as rt,P as lt}from"./chunks/Files-C-ChBvr5.js";import{G as dt,f as ct,T as pt,a as ut,d as ht,c as mt}from"./chunks/User-DSqcOwPL.js";import{g as gt,b as ft,h as bt,i as vt,e as yt,U as wt}from"./chunks/User-DSqcOwPL.js";import{S as kt,T as xt,a as At}from"./chunks/TokenManager-BYMKH_aW.js";import Pt from"./chunks/Dialog-jfBsXy5X.js";import{a as St,L as _t}from"./chunks/ListView-BLFFK_Ir.js";import{default as Ct}from"./chunks/DataView-BEovBggn.js";import{F as Tt}from"./chunks/FormView-Q_lFA0nr.js";import{a as It}from"./chunks/FormView-Q_lFA0nr.js";import{W as Mt}from"./chunks/WebSocketClient-Bh0Mmtje.js";const Nt={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1},Et=Object.freeze({silent:0,error:1,warn:2,info:3,log:3,debug:4,trace:5,all:5}),Lt=(()=>{try{if(void 0!==import.meta&&import.meta&&Nt)return!1}catch{}if("undefined"!=typeof globalThis&&void 0!==globalThis.__DEV__)try{return!!globalThis.__DEV__}catch{}return!("undefined"==typeof process||!process||"object"!=typeof process.env||"string"!=typeof process.env.NODE_ENV)&&"production"!==process.env.NODE_ENV})(),zt="undefined"!=typeof window&&"undefined"!=typeof document,Dt="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:global,Ft=Dt.console||{},Ot={};let Rt=!1,Vt=null;function Gt(e){if("number"==typeof e){const t=Et.silent,s=Et.trace;return Math.min(Math.max(e,t),s)}if("string"==typeof e){const t=e.toLowerCase();if(Object.prototype.hasOwnProperty.call(Et,t))return Et[t]}return null}function Ut(e,t){const s=Ot[e]||Ft[e]||(()=>{});return function(...e){if(Vt>=t)return s.apply(Ft,e)}}const Bt={install(e={}){if(Rt)return e&&void 0!==e.level&&this.setLevel(e.level,{persist:!!e.persist}),this;if(!Dt||!Ft)return Rt=!0,this;Vt=function(e){const t=Gt(e);if(null!==t)return t;const s=function(){if(!zt||"undefined"==typeof location||!location.search)return null;try{const e=new URLSearchParams(location.search),t=["logLevel","loglevel","mojoLog"];for(const s of t){const t=e.get(s);if(null!=t){const e=Gt(t);if(null!==e)return e}}}catch{}return null}();if(null!==s)return s;const i=function(){if(!zt||!("localStorage"in Dt))return null;try{const e=Dt.localStorage.getItem("MOJO_LOG_LEVEL");if(null!=e){const t=Gt(e);if(null!==t)return t}}catch{}return null}();return null!==i?i:Gt(Lt?"debug":"warn")}(e.level);const t=function(){const e={...Ft},t={error:Et.error,warn:Et.warn,info:Et.info,log:Et.info,dir:Et.info,table:Et.info,debug:Et.debug,group:Et.debug,groupCollapsed:Et.debug,groupEnd:Et.debug,time:Et.debug,timeEnd:Et.debug,timeLog:Et.debug,trace:Et.trace};for(const s of Object.keys(t))Ot[s]=Ft[s]||(()=>{}),e[s]=Ut(s,t[s]);return Ot.assert=Ft.assert||(()=>{}),e.assert=function(){const e=Ot.assert||Ft.assert||(()=>{});return function(t,...s){if(!t)return Vt>=Et.error?e.apply(Ft,[t,...s]):void 0}}(),e}();return Dt.console=t,Rt=!0,Dt.MOJOConsoleSilencer=this,this},uninstall(){if(!Rt)return this;try{Dt.console=Ft}catch{}return Rt=!1,this},setLevel(e,{persist:t=!1}={}){const s=Gt(e);return null===s||(Vt=s,t&&function(e){if(zt&&"localStorage"in Dt)try{const t="string"==typeof e?e:null===e?null:Object.entries(Et).find(([,t])=>t===e)?.[0]??null;t?Dt.localStorage.setItem("MOJO_LOG_LEVEL",t):Dt.localStorage.removeItem("MOJO_LOG_LEVEL")}catch{}}(e)),this},getLevel:()=>Vt,getLevelName(){const e=Object.entries(Et).find(([,e])=>e===Vt);return e?e[0]:null},criticalOnly({persist:e=!1}={}){return this.setLevel("warn",{persist:e})},errorsOnly({persist:e=!1}={}){return this.setLevel("error",{persist:e})},silent({persist:e=!1}={}){return this.setLevel("silent",{persist:e})},verbose({persist:e=!1}={}){return this.setLevel(Lt?"debug":"info",{persist:e})},allowAll({persist:e=!1}={}){return this.setLevel("trace",{persist:e})},withTemporaryLevel(e,t){const s=Vt,i=Gt(e);if(null===i||"function"!=typeof t)return t?.();Vt=i;try{return t()}finally{Vt=s}},LEVELS:Et},Ht=e=>Bt.install(e);class GroupSearchView extends kt{constructor(e={}){super({...e,className:`group-search-view ${e.className||""}`.trim()}),this.showKind=void 0===e.showKind||e.showKind,this.parentField=e.parentField||"parent",this.kindField=e.kindField||"kind",this.treeData=[],this.flattenedItems=[],this.showLines=void 0===e.showLines||e.showLines}buildTreeHierarchy(e){if(!e||0===e.length)return[];const t=/* @__PURE__ */new Map;e.forEach(e=>{t.has(e.id)||t.set(e.id,{...e,children:[],level:0,hasChildren:!1});const s=e[this.parentField];s&&s.id&&!t.has(s.id)&&t.set(s.id,{...s,children:[],level:0,hasChildren:!1})});const s=[];t.forEach((i,a)=>{const n=e.find(e=>e.id===a)||i,o=n[this.parentField]?.id;if(o&&t.has(o)){const e=t.get(o);e.children.push(i),e.hasChildren=!0}else s.push(i)});const i=(e,t=0)=>{e.forEach(e=>{e.level=t,e.children.length>0&&(e.children.sort((e,t)=>(e.name||"").localeCompare(t.name||"")),i(e.children,t+1))})};return s.sort((e,t)=>(e.name||"").localeCompare(t.name||"")),i(s),s}flattenTree(e,t=[],s=[]){return e.forEach((i,a)=>{i._isLastChild=a===e.length-1,i._ancestorLastFlags=[...s];const n=s.every(e=>e);if(i._isLastDescendant=n&&i._isLastChild&&(!i.children||0===i.children.length),t.push(i),i.children&&i.children.length>0){const e=[...s,i._isLastChild];this.flattenTree(i.children,t,e)}}),t}computeVerticalLines(e){for(let t=0;t<e.length;t++){const s=e[t];s._continueVertical=[];for(let i=0;i<s.level;i++){let a=!1;for(let s=t+1;s<e.length;s++){const t=e[s];if(t.level===i+1){a=!0;break}if(t.level<=i)break}s._continueVertical[i]=a}}}updateFilteredItems(){if(!this.collection)return this.filteredItems=[],this.treeData=[],void(this.flattenedItems=[]);const e=this.collection.toJSON();this.treeData=this.buildTreeHierarchy(e),this.flattenedItems=this.flattenTree(this.treeData),this.computeVerticalLines(this.flattenedItems),this.filteredItems=this.flattenedItems,this.updateResultsView()}getDefaultItemTemplate(){return'\n <div class="tree-item-content">\n <div class="tree-item-name">{{name}}</div>\n {{#showKind}}\n <div class="tree-item-kind">{{kind}}</div>\n {{/showKind}}\n </div>\n '}processItemTemplate(e){let t=this.itemTemplate;t=t.replace(/\{\{(\w+)\}\}/g,(t,s)=>"showKind"===s?this.showKind?"true":"":this.getNestedValue(e,s)||""),t=this.showKind?t.replace(/\{\{#showKind\}\}(.*?)\{\{\/showKind\}\}/gs,"$1"):t.replace(/\{\{#showKind\}\}.*?\{\{\/showKind\}\}/gs,"");let s="";if(this.showLines&&e.level>0)for(let i=0;i<e.level;i++)i===e.level-1?s+=`<span class="${e._isLastChild?"tree-seg tree-seg-last":"tree-seg tree-seg-mid"}"></span>`:s+=e._continueVertical&&e._continueVertical[i]?'<span class="tree-seg tree-seg-vert"></span>':'<span class="tree-seg"></span>';return`\n <div class="tree-item-wrapper${e.hasChildren?" has-children":""}${e._isLastChild?" is-last-child":""}" data-tree-level="${e.level}">\n <div class="tree-lines">\n ${s}\n </div>\n <div class="tree-item-body flex-grow-1">\n ${t}\n </div>\n </div>\n `}getRootItems(){return this.treeData}getNodeChildren(e){const t=(e,s)=>{for(const i of e){if(i.id===s)return i.children;if(i.children.length>0){const e=t(i.children,s);if(e)return e}}return null};return t(this.treeData,e)||[]}}class Sidebar extends o{constructor(e={}){super({tagName:"nav",className:"sidebar",id:"sidebar",...e}),this.menus=/* @__PURE__ */new Map,this.activeMenuName=null,this.currentRoute=null,this.showToggle=e.showToggle,this.isCollapsed=!1,this.sidebarTheme=e.theme||"sidebar-light",this.customView=null,this.options.groupHeader&&(this.groupHeader=this.options.groupHeader),this.groupSelectorMode=e.groupSelectorMode||"inline",this.groupSelectorDialog=null,this.sidebarTheme&&this.addClass(this.sidebarTheme),this.initializeMenus(e),this.setupRouteListeners(),!1!==e.autoCollapseMobile&&this.setupResponsiveBehavior()}groupHeader='\n {{#group.parent}}\n <div class="sidebar-parent-bar" data-action="select-group-parent">\n <div class="parent-info">\n <span class="parent-label">{{group.parent.kind}}:</span>\n <span class="parent-name collapsed-hidden">{{group.parent.name}}</span>\n </div>\n <i class="bi bi-chevron-down parent-expand collapsed-hidden"></i>\n </div>\n {{/group.parent}}\n <div class="sidebar-selected-group-row" data-action="show-group-search">\n <div class="selected-group-info">\n <div class=\'selected-group-name collapsed-hidden\'>{{group.name}}</div>\n <div class=\'selected-group-meta collapsed-hidden\'>\n <span class="selected-group-kind">{{group.kind}}</span>\n </div>\n </div>\n <i class="bi bi-chevron-down selected-group-chevron collapsed-hidden"></i>\n </div>\n ';async onInit(){await super.onInit();const e=this.getApp(),t=e?.router;if(t){const e=t.getCurrentPath();e&&this.autoSwitchToMenuForRoute(e)}this.initializeTooltips(),this.searchView=new GroupSearchView({noAppend:!0,showExitButton:!0,headerText:"Select Group",containerId:"sidebar-search-container",Collection:dt,itemTemplate:'\n <div class="p-3 border-bottom">\n <div class="fw-semibold text-dark">{{name}}</div>\n <small class="text-muted">#{{id}} {{kind}}</small>\n </div>\n '}),this.addChild(this.searchView),this.searchView.on("item:selected",e=>{this.getApp().setActiveGroup(e.model)}),this.searchView.on("exit",e=>{this.hideGroupSearch()})}showGroupSearch(){"dialog"===this.groupSelectorMode?this.showGroupSearchDialog():(this.setClass("sidebar"),this.showSearch=!0,this.render())}hideGroupSearch(){"dialog"===this.groupSelectorMode?this.groupSelectorDialog&&this.groupSelectorDialog.hide():(this.setClass("sidebar"),this.showSearch=!1,this.render())}onActionShowGroupSearch(){this.showGroupSearch()}async onActionSelectGroupParent(){const e=this.getApp().activeGroup;if(await Pt.confirm(`Are you sure you want to navigate to the '${e.get("parent.name")}'?`)){this.getApp().showLoading();let t=new ct({id:e.get("parent.id")});await t.fetch(),this.getApp().setActiveGroup(t),this.getApp().hideLoading()}}async showGroupSearchDialog(){const e=new dt,t=new GroupSearchView({Collection:dt,collection:e,searchFields:["name"],headerText:null,searchPlaceholder:"Search groups...",headerIcon:null,maxHeight:Math.min(600,window.innerHeight-200),showExitButton:!1,showKind:!0,parentField:"parent",kindField:"kind",autoExpandRoot:!0,autoExpandAll:!1,indentSize:20,showLines:!0});this.groupSelectorDialog=new Pt({body:t,size:"md",header:null,noBodyPadding:!0,scrollable:!1,buttons:[],closeButton:!0}),t.on("item:selected",e=>{this.getApp().setActiveGroup(e.model),this.groupSelectorDialog&&this.groupSelectorDialog.hide()}),this.groupSelectorDialog.on("hidden",()=>{this.groupSelectorDialog.destroy(),this.groupSelectorDialog=null}),await this.groupSelectorDialog.render(!0,document.body),this.groupSelectorDialog.show()}autoSwitchToMenuForRoute(e){for(const[t,s]of this.menus)if((!s.groupKind||this.getApp().activeGroup)&&this.menuContainsRoute(s,e))return this._setActiveMenu(t),this.currentRoute=e,this.clearAllActiveStates(),this.setActiveItemByRoute(e),this.render(),this.emit("menu-auto-switched",{menuName:t,route:e,config:s,sidebar:this}),!0;return!1}clearAllActiveStates(){for(const[e,t]of this.menus)for(const s of t.items||[])if(s.active=!1,s.children)for(const e of s.children)e.active=!1}setActiveItemByRoute(e){const t=e=>{if(!e)return"/";const t=decodeURIComponent(e);return t.startsWith("/")?t:`/${t}`},s=t(e);for(const[i,a]of this.menus)if(!a.groupKind||this.getApp().activeGroup)for(const e of a.items||[]){if(e.route){const i=t(e.route);if(this.routesMatch(s,i))return e.active=!0,this.activeMenuItem=e,!0}if(e.children)for(const i of e.children)if(i.route){const a=t(i.route);if(this.routesMatch(s,a))return i.active=!0,e.active=!0,!0}}return!1}menuContainsRoute(e,t){const s=e=>{if(!e)return"/";const t=decodeURIComponent(e);return t.startsWith("/")?t:`/${t}`},i=s(t);for(const a of e.items||[]){if(a.route){const e=s(a.route);if(this.routesMatch(i,e))return!0}if(a.children)for(const e of a.children)if(e.route){const t=s(e.route);if(this.routesMatch(i,t))return!0}}return!1}routesMatch(e,t){return this.getApp().router.doRoutesMatch(e,t)}getTemplate(){return this.customView?'<div class="sidebar-container" id="sidebar-custom-view-container"></div>':this.showSearch?this.getSearchTemplate():this.getMenuTemplate()}getSearchTemplate(){return'\n <div class="sidebar-container" id="sidebar-search-container">\n </div>\n '}getMenuTemplate(){return'\n <div class="sidebar-container">\n {{#data.currentMenu}}\n \x3c!-- Header --\x3e\n {{#header}}\n <div class="sidebar-header">\n {{{header}}}\n {{#showToggle}}\n <button class="sidebar-toggle" data-action="toggle-sidebar"\n aria-label="Toggle Sidebar">\n <i class="bi bi-chevron-left toggle-icon"></i>\n <i class="bi bi-chevron-right toggle-icon"></i>\n </button>\n {{/showToggle}}\n </div>\n {{/header}}\n\n \x3c!-- Navigation Items --\x3e\n <div class="sidebar-body">\n <ul class="nav nav-pills flex-column sidebar-nav" id="sidebar-nav-menu">\n {{#items}}\n {{>nav-item}}\n {{/items}}\n </ul>\n </div>\n\n \x3c!-- Footer --\x3e\n {{#footer}}\n <div class="sidebar-footer">\n {{{footer}}}\n </div>\n {{/footer}}\n {{/data.currentMenu}}\n\n {{^data.currentMenu}}\n <div class="sidebar-empty">\n <p class="text-danger text-center">No menu configured</p>\n </div>\n {{/data.currentMenu}}\n </div>\n '}getPartials(){return{"nav-item":'\n {{#isDivider}}\n {{>nav-divider}}\n {{/isDivider}}\n {{#isSpacer}}\n {{>nav-spacer}}\n {{/isSpacer}}\n {{#isLabel}}\n {{>nav-label}}\n {{/isLabel}}\n\n {{^isDivider}}\n {{^isSpacer}}\n {{^isLabel}}\n <li class="nav-item">\n {{#hasChildren}}\n \x3c!-- Item with submenu --\x3e\n <a class="nav-link {{#active}}active{{/active}} has-children collapsed"\n data-bs-toggle="collapse"\n href="#collapse-{{id}}"\n role="button"\n aria-expanded="{{#active}}true{{/active}}{{^active}}false{{/active}}"\n data-action="toggle-submenu">\n {{#icon}}<i class="{{icon}} me-2"></i>{{/icon}}\n <span class="nav-text">{{text}}</span>\n {{#badge}}\n <span class="{{badge.class}} ms-auto">{{badge.text}}</span>\n {{/badge}}\n <i class="bi bi-chevron-down nav-arrow ms-auto"></i>\n </a>\n <div class="collapse {{#active}}show{{/active}}" id="collapse-{{id}}" data-bs-parent="#sidebar-nav-menu">\n <ul class="nav flex-column nav-submenu">\n {{#children}}\n <li class="nav-item">\n <a class="nav-link {{#active}}active{{/active}}"\n {{#action}}data-action="{{action}}"{{/action}}\n {{#href}}href="{{href}}"{{/href}}>\n {{#icon}}<i class="{{icon}} me-2"></i>{{/icon}}\n <span class="nav-text">{{text}}</span>\n {{#badge}}\n <span class="{{badge.class}} ms-auto">{{badge.text}}</span>\n {{/badge}}\n </a>\n </li>\n {{/children}}\n </ul>\n </div>\n {{/hasChildren}}\n {{^hasChildren}}\n \x3c!-- Simple item --\x3e\n <a class="nav-link {{#active}}active{{/active}} {{#disabled}}disabled{{/disabled}}"\n {{#action}}{{^disabled}}data-action="{{action}}"{{/disabled}}{{/action}}\n {{#href}}{{^disabled}}href="{{href}}"{{/disabled}}{{/href}}>\n {{#icon}}<i class="{{icon}} me-2"></i>{{/icon}}\n <span class="nav-text">{{text}}</span>\n {{#badge}}\n <span class="{{badge.class}} ms-auto">{{badge.text}}</span>\n {{/badge}}\n </a>\n {{/hasChildren}}\n </li>\n {{/isLabel}}\n {{/isSpacer}}\n {{/isDivider}}\n ',"nav-divider":'\n <li class="nav-divider-item">\n <hr class="nav-divider-line">\n </li>\n ',"nav-spacer":'\n <li class="nav-spacer-item"></li>\n ',"nav-label":'\n <li class="nav-item {{className}}">\n <div class="nav-text px-3">{{text}}</div>\n </li>\n '}}getGroupHeader(){return this.groupHeader}addMenu(e,t){return t.groupKind&&!t.header&&(t.header=this.getGroupHeader()),this.menus.set(e,{name:e,groupKind:t.groupKind||null,header:t.header||null,footer:t.footer||null,items:t.items||[],data:t.data||{},className:t.className||"sidebar sidebar-dark"}),this.activeMenuName||this._setActiveMenu(e),this}_setActiveMenu(e){this.showSearch=!1,this.activeMenuName=e;const t=this.getCurrentMenuConfig();t.className?this.setClass(t.className):this.setClass("sidebar")}async setActiveMenu(e){if(!this.menus.has(e))return console.warn(`Menu '${e}' not found`),this;const t=this.menus.get(e);if(!t.groupKind||(this.lastGroupMenu=t,this.getApp().activeGroup))return this._setActiveMenu(e),await this.render(),this.emit("menu-changed",{menuName:e,config:t,sidebar:this}),this;this.showGroupSearch()}getGroupMenu(e){if(!e)return console.warn("No group provided"),null;let t=this.lastGroupMenu,s=null;if(e._.kind)for(const[i,a]of this.menus){if(this._groupKindMatches(a.groupKind,e._.kind)){t=a;break}"any"===a.groupKind&&(s=a)}return t||s}_groupKindMatches(e,t){return!(!e||!t)&&(Array.isArray(e)?e.includes(t):e===t)}showMenuForGroup(e){if(!e)return void console.warn("No group provided");let t=this.getGroupMenu(e);if(t)return this._setActiveMenu(t.name),this.render(),this.emit("menu-changed",{menuName:t.name,config:t,sidebar:this}),this;console.warn(`No menu found for group kind: ${e.kind}`)}getMenuConfig(e){return this.menus.get(e)||null}getCurrentMenuConfig(){return this.activeMenuName?this.menus.get(this.activeMenuName):null}updateMenu(e,t){const s=this.menus.get(e);return s?(Object.assign(s,t),this.activeMenuName===e&&this.render(),this):(console.warn(`Menu '${e}' not found`),this)}removeMenu(e){if(this.menus.delete(e),this.activeMenuName===e){const e=Array.from(this.menus.keys());this.activeMenuName=e.length>0?e[0]:null,this.render()}return this}async onBeforeRender(){const e=this.getCurrentMenuConfig();if(!e)return{currentMenu:null};let t={version:this.getApp().version||null,group:this.getApp().activeGroup||null,user:this.getApp.activeUser||null};this.data={currentMenu:{header:this.renderTemplateString(e.header||"",t),footer:this.renderTemplateString(e.footer||"",t),items:this.processNavItems(e.items,e.groupKind),data:e.data,showToggle:this.showToggle}}}async onAfterRender(){this.isCollapsedState()?setTimeout(()=>this.initializeTooltips(),50):this.destroyTooltips()}setCustomView(e){return this.customView&&this.removeChild(this.customView.id),this.customView=e,e&&(e.containerId="sidebar-custom-view-container",this.addChild(e)),this.render(),this}clearCustomView(){return this.customView&&(this.removeChild(this.customView.id),this.customView=null),this.render(),this}processNavItems(e,t){const s=this.getApp(),i=s?.activeUser,a=s?.activeGroup,n=e=>{let s=e;if(e.startsWith("/")&&!e.includes("?")&&(s=`?page=${e.substring(1)||"home"}`),t&&a&&a.id){const e=s.includes("?")?"&":"?";return`${s}${e}group=${a.id}`}return s};return e.map((e,t)=>{if(""===e||"object"==typeof e&&e.divider)return{isDivider:!0,id:`divider-${t}`};if("object"==typeof e&&e.spacer)return{isSpacer:!0,id:`spacer-${t}`};const s={...e};if(s.permissions&&(!i||!i.hasPermission(s.permissions)))return null;if(s.requiresGroupKind){const e=a?._.kind||a?.kind;if(!e||!this._groupKindMatches(s.requiresGroupKind,e))return null}if("label"===s.kind)return s.isLabel=!0,s.id||(s.id=`nav-label-${t}`),s;if(s.id||(s.id=`nav-${t}`),s.route)s.href=n(s.route);else if(s.page){const e=s.page.startsWith("/")?s.page:`/${s.page}`;s.href=n(e),s.route=s.href}return s.children?(s.children=s.children.map(e=>{const t={...e};if(t.permissions&&i&&!i.hasPermission(t.permissions))return null;if(t.requiresGroupKind){const e=a?._.kind||a?.kind;if(!e||!this._groupKindMatches(t.requiresGroupKind,e))return null}if(t.route)t.href=n(t.route);else if(t.page){const e=t.page.startsWith("/")?t.page:`/${t.page}`;t.href=n(e),t.route=t.href}return t}).filter(e=>null!==e),s.hasChildren=!!(s.children&&s.children.length>0)):s.hasChildren=!1,s}).filter(e=>null!==e)}isItemActive(e){if(!e.route||!this.currentRoute)return!1;const t=e=>{if(!e)return"/";const t=decodeURIComponent(e);return t.startsWith("/")?t:`/${t}`},s=t(e.route),i=t(this.currentRoute);return"/"===s&&"/"===i||"/"!==s&&"/"!==i&&(i.startsWith(s)||i===s)}async updateActiveItem(e){return this.currentRoute=e,this.clearAllActiveStates(),this.setActiveItemByRoute(e),await this.render(),this}async handleActionToggleSubmenu(e,t){const s=t.querySelector(".nav-arrow");s&&s.classList.toggle("rotated")}async handleActionToggleSidebar(e,t){this.toggleSidebar()}onActionShowGroupMenu(e,t,s){return this.setActiveMenu("group_default"),!1}async onActionDefault(e,t,s){const i=this.getCurrentMenuConfig();if(!i)return;const a=i=>{for(const n of i){if(n.action==e&&n.handler)return n.handler(e,t,s,this.getApp()),!0;if(n.children&&n.children.length>0&&a(n.children))return!0}return!1};return a(i.items)}getMenuNames(){return Array.from(this.menus.keys())}hasMenu(e){return this.menus.has(e)}clearMenus(){return this.menus.clear(),this.activeMenuName=null,this.render(),this}setMenuData(e){const t=this.getCurrentMenuConfig();return t&&(t.data={...t.data,...e},this.render()),this}getMenuData(){const e=this.getCurrentMenuConfig();return e?e.data:{}}setupRouteListeners(){const e=this.getApp();e&&e.events&&(e.events.on(["page:showing"],e=>{this.onRouteChanged(e)}),e.events.on("group:changed",e=>{this.showMenuForGroup(e.group)}),e.events.on("portal:user-changed",e=>{this.render()}))}onRouteChanged(e){if(e.page&&e.page.route){const t=e.page.route;if(this.activeMenuItem&&this.routesMatch(t,this.activeMenuItem.route))return;if(this.autoSwitchToMenuForRoute(t))return;const s=e.page.sidebarMenu||e.page.options?.sidebarMenu||null;if(s&&this.menus.has(s))return this._setActiveMenu(s),this.clearAllActiveStates(),void this.render();let i=null;for(const[e,a]of this.menus)if(!a.groupKind){i=e;break}i&&this.activeMenuName!==i&&this._setActiveMenu(i),this.clearAllActiveStates(),this.updateActiveItem(t),this.render()}}toggleSidebar(){const e=document.querySelector(".portal-container");if(!e)return;this.hideAllTooltips();const t=e.classList.contains("collapse-sidebar");return e.classList.contains("hide-sidebar")?(e.classList.remove("hide-sidebar"),this.isCollapsed=!1,this.destroyTooltips()):t?(e.classList.remove("collapse-sidebar"),this.isCollapsed=!1,this.destroyTooltips()):(e.classList.add("collapse-sidebar"),this.isCollapsed=!0,setTimeout(()=>this.initializeTooltips(),150)),this}setSidebarState(e){const t=document.querySelector(".portal-container");if(!t)return this;switch(t.classList.remove("collapse-sidebar","hide-sidebar"),e){case"collapsed":t.classList.add("collapse-sidebar"),this.isCollapsed=!0;break;case"hidden":t.classList.add("hide-sidebar"),this.isCollapsed=!1;break;default:this.isCollapsed=!1}return this.isCollapsed?(this.hideAllTooltips(),setTimeout(()=>this.initializeTooltips(),100)):this.destroyTooltips(),this}initializeTooltips(){return this.destroyTooltips(),this.isCollapsedState()?(this.element.querySelectorAll(".sidebar-nav .nav-link").forEach(e=>{const t=e.querySelector(".nav-text");if(t&&t.textContent.trim()){const s=t.textContent.trim();if(e.setAttribute("data-bs-toggle","tooltip"),e.setAttribute("data-bs-placement","right"),e.setAttribute("data-bs-title",s),e.setAttribute("data-bs-container","body"),window.bootstrap&&window.bootstrap.Tooltip){const t=e.getAttribute("data-tooltip-theme"),s=e.getAttribute("data-tooltip-size");let i="";t&&(i+=`tooltip-${t} `),s&&(i+=`tooltip-${s}`);const a={placement:"right",container:"body",trigger:"hover",delay:{show:500,hide:100},fallbackPlacements:["top","bottom","left"]},n=i.trim();n&&(a.customClass=n);const o=new window.bootstrap.Tooltip(e,a);e._tooltipInstance=o,e.addEventListener("click",()=>{o.hide()}),e.addEventListener("blur",()=>{o.hide()})}}}),this.addTooltipHideListeners(),this):this}destroyTooltips(){return this.removeTooltipHideListeners(),this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle="tooltip"]').forEach(e=>{const t=e._tooltipInstance||window.bootstrap?.Tooltip?.getInstance(e);t&&(t.hide(),t.dispose()),delete e._tooltipInstance,e.removeAttribute("data-bs-toggle"),e.removeAttribute("data-bs-placement"),e.removeAttribute("data-bs-title"),e.removeAttribute("data-bs-container")}),this}getSidebarState(){const e=document.querySelector(".portal-container");return e?e.classList.contains("hide-sidebar")?"hidden":e.classList.contains("collapse-sidebar")?"collapsed":"normal":"normal"}isCollapsedState(){return"collapsed"===this.getSidebarState()}setToggleEnabled(e){return this.showToggle=e,this.render(),this}initializeMenus(e){if(e.menus)for(const t of e.menus)this.addMenu(t.name,t);else e.menu&&(e.menu.name=e.menu.name||"default",this.addMenu(e.menu.name,e.menu))}addTooltipHideListeners(){this._tooltipScrollHandler=()=>this.hideAllTooltips(),this.element.addEventListener("scroll",this._tooltipScrollHandler,{passive:!0}),this._tooltipRouteHandler=()=>this.hideAllTooltips(),this.getApp(),this._tooltipBlurHandler=()=>this.hideAllTooltips(),window.addEventListener("blur",this._tooltipBlurHandler),this._tooltipEscapeHandler=e=>{"Escape"===e.key&&this.hideAllTooltips()},document.addEventListener("keydown",this._tooltipEscapeHandler)}removeTooltipHideListeners(){this._tooltipScrollHandler&&(this.element.removeEventListener("scroll",this._tooltipScrollHandler),delete this._tooltipScrollHandler),this._tooltipBlurHandler&&(window.removeEventListener("blur",this._tooltipBlurHandler),delete this._tooltipBlurHandler),this._tooltipEscapeHandler&&(document.removeEventListener("keydown",this._tooltipEscapeHandler),delete this._tooltipEscapeHandler)}hideAllTooltips(){this.element.querySelectorAll('.sidebar-nav .nav-link[data-bs-toggle="tooltip"]').forEach(e=>{const t=e._tooltipInstance||window.bootstrap?.Tooltip?.getInstance(e);t&&t.hide()}),document.querySelectorAll(".tooltip.show").forEach(e=>{e.remove()})}async onBeforeDestroy(){this.destroyTooltips(),await super.onBeforeDestroy()}setupResponsiveBehavior(){const e=()=>{const e=window.innerWidth<=768,t=document.querySelector(".portal-container");t&&(e?t.classList.add("sidebar-mobile"):t.classList.remove("sidebar-mobile","sidebar-open"))};e(),window.addEventListener("resize",e)}static createDefault(e={}){return new Sidebar({theme:"sidebar-clean",showToggle:!0,autoCollapseMobile:!0,...e})}static createMinimal(e={}){return new Sidebar({theme:"sidebar-clean",showToggle:!1,autoCollapseMobile:!1,...e})}setSidebarTheme(e){return this.removeClass("sidebar-light sidebar-dark sidebar-clean"),this.sidebarTheme=e,this.addClass(e),this}show(){return this.setSidebarState("normal")}hide(){return this.setSidebarState("hidden")}collapse(){return this.setSidebarState("collapsed")}expand(){return this.setSidebarState("normal")}pulseToggle(){const e=this.element.querySelector(".sidebar-toggle");if(e){e.classList.add("pulse");const t=()=>{e.classList.remove("pulse"),e.removeEventListener("click",t)};e.addEventListener("click",t,{once:!0}),setTimeout(t,3e3)}return this}addSimpleMenuItem(e,t,s,i="bi-circle"){const a=this.menus.get(e);return a&&(a.items=a.items||[],a.items.push({text:t,route:s,icon:i}),this.activeMenuName===e&&this.render()),this}setSimpleMenu(e,t,s){const i={name:e,header:t,items:s};return this.addMenu(e,i),this.setActiveMenu(e),this}}class PageHeader extends o{constructor(e={}){super({tagName:"div",className:"page-header",...e}),this.style=e.style||"default",this.size=e.size||"md",this.showIcon=!1!==e.showIcon,this.showDescription=!1!==e.showDescription,this.showBreadcrumbs=e.showBreadcrumbs||!1,this.currentPage=null}async getTemplate(){return"minimal"===this.style?this.getMinimalTemplate():"breadcrumb"===this.style?this.getBreadcrumbTemplate():this.getDefaultTemplate()}getDefaultTemplate(){return'\n {{#data.hasPage}}\n <div class="page-header-content page-header-{{data.size}}">\n <div class="page-header-main">\n <div class="page-header-info">\n {{#data.showIcon}}\n {{#data.pageIcon}}\n <div class="page-icon">\n <i class="{{data.pageIcon}}"></i>\n </div>\n {{/data.pageIcon}}\n {{/data.showIcon}}\n \n <div class="page-title-group">\n <h1 class="page-title">{{data.pageTitle}}</h1>\n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class="page-description text-muted">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n </div>\n\n {{#data.hasActions}}\n <div class="page-actions">\n {{#data.actions}}\n <button class="btn {{buttonClass}}" \n data-action="{{action}}"\n type="button">\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n </div>\n {{/data.hasPage}}\n '}getMinimalTemplate(){return'\n {{#data.hasPage}}\n <div class="page-header-content page-header-minimal">\n <h1 class="page-title">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class="{{data.pageIcon}} me-2"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n </div>\n {{/data.hasPage}}\n '}getBreadcrumbTemplate(){return'\n {{#data.hasPage}}\n <div class="page-header-content page-header-breadcrumb">\n {{#data.showBreadcrumbs}}\n <nav aria-label="breadcrumb">\n <ol class="breadcrumb mb-2">\n {{#data.breadcrumbs}}\n <li class="breadcrumb-item {{#active}}active{{/active}}">\n {{#href}}<a href="{{href}}">{{label}}</a>{{/href}}\n {{^href}}{{label}}{{/href}}\n </li>\n {{/data.breadcrumbs}}\n </ol>\n </nav>\n {{/data.showBreadcrumbs}}\n \n <div class="d-flex justify-content-between align-items-start">\n <h1 class="page-title">\n {{#data.showIcon}}\n {{#data.pageIcon}}<i class="{{data.pageIcon}} me-2"></i>{{/data.pageIcon}}\n {{/data.showIcon}}\n {{data.pageTitle}}\n </h1>\n \n {{#data.hasActions}}\n <div class="page-actions">\n {{#data.actions}}\n <button class="btn {{buttonClass}}" \n data-action="{{action}}"\n type="button">\n {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}\n {{label}}\n </button>\n {{/data.actions}}\n </div>\n {{/data.hasActions}}\n </div>\n \n {{#data.showDescription}}\n {{#data.pageDescription}}\n <p class="page-description text-muted mt-2">{{data.pageDescription}}</p>\n {{/data.pageDescription}}\n {{/data.showDescription}}\n </div>\n {{/data.hasPage}}\n '}async onBeforeRender(){await super.onBeforeRender();const e=this.currentPage,t=!!e;e&&(e.title,e.displayName,e.name,e.pageName,e.icon,e.pageIcon,e.pageDescription,e.description);const s=e?.options?.headerActions||e?.headerActions||e?.constructor?.prototype?.headerActions||[];this.data={hasPage:t,pageTitle:e?.title||e?.displayName||e?.name||e?.pageName||"",pageIcon:e?.icon||e?.pageIcon||"",pageDescription:e?.pageDescription||e?.description||"",showIcon:this.showIcon,showDescription:this.showDescription,showBreadcrumbs:this.showBreadcrumbs,breadcrumbs:e?.options?.breadcrumbs||e?.breadcrumbs||[],actions:s,hasActions:s.length>0,size:this.size},this.data}async setPage(e){this.currentPage=e,e&&await this.render()}getPage(){return this.currentPage}async onActionDefault(e,t,s){return this.currentPage&&"function"==typeof this.currentPage.onHeaderAction?(await this.currentPage.onHeaderAction(e,t,s),!0):(this.emit("action",{action:e,event:t,element:s,page:this.currentPage}),!1)}}class DeniedPage extends h{constructor(e={}){super({pageName:"Access Denied",route:"/denied",title:"Access Denied",pageIcon:"bi bi-shield-x",template:'\n <div class="container mt-5">\n <div class="row justify-content-center">\n <div class="col-md-8 col-lg-6">\n <div class="text-center mb-4">\n <i class="bi bi-shield-x text-muted" style="font-size: 3rem;"></i>\n <h2 class="mt-3 mb-2">Access Denied</h2>\n <p class="text-muted">You don\'t have permission to access this page.</p>\n </div>\n\n {{#deniedPage}}\n <div class="card border-0 shadow-sm mb-4">\n <div class="card-body">\n <h6 class="card-subtitle mb-2 text-muted">Requested Page</h6>\n <h5 class="card-title">\n <i class="{{pageIcon}} me-2"></i>\n {{displayName}}\n </h5>\n {{#route}}\n <p class="card-text text-muted small">{{route}}</p>\n {{/route}}\n {{#description}}\n <p class="card-text">{{description}}</p>\n {{/description}}\n\n {{#requiredPermissions}}\n <div class="mt-3">\n <h6 class="mb-2">Required Permissions:</h6>\n {{#permissions}}\n <span class="badge bg-light text-dark me-1 mb-1">{{.}}</span>\n {{/permissions}}\n {{^permissions}}\n <span class="text-muted small">Authentication required</span>\n {{/permissions}}\n </div>\n {{/requiredPermissions}}\n </div>\n </div>\n {{/deniedPage}}\n\n <div class="d-grid gap-2 d-md-flex justify-content-md-center">\n <button type="button" class="btn btn-primary" data-action="go-back">\n <i class="bi bi-arrow-left me-1"></i>\n Go Back\n </button>\n <button type="button" class="btn btn-outline-secondary" data-action="go-home">\n <i class="bi bi-house me-1"></i>\n Home\n </button>\n {{#showLogin}}\n <button type="button" class="btn btn-outline-primary" data-action="login">\n <i class="bi bi-box-arrow-in-right me-1"></i>\n Login\n </button>\n {{/showLogin}}\n </div>\n\n {{#currentUser}}\n <div class="text-center mt-4">\n <small class="text-muted">\n Logged in as <strong>{{username}}</strong>\n </small>\n </div>\n {{/currentUser}}\n </div>\n </div>\n </div>\n ',...e}),this.deniedPage=null,this.deniedPageOptions=null}async onParams(e={},t={}){await super.onParams(e,t),e.page?(this.deniedPage=e.page,this.deniedPageOptions=e.page.options||e.page.pageOptions||{}):t.page&&(this.deniedPageName=t.page)}setDeniedPage(e){return this.deniedPage=e,this.deniedPageOptions=e?.options||e?.pageOptions||{},this}async getViewData(){const e=this.getApp(),t=e?.activeUser||e?.getCurrentUser?.()||null;let s=null;if(this.deniedPage){const e=this.deniedPageOptions?.permissions||this.deniedPage.options?.permissions||this.deniedPage.pageOptions?.permissions;s={displayName:this.deniedPage.displayName||this.deniedPage.pageName||this.deniedPage.title||"Unknown Page",pageName:this.deniedPage.pageName,route:this.deniedPage.route,description:this.deniedPage.pageDescription||this.deniedPage.description,pageIcon:this.deniedPage.pageIcon||"bi bi-file-text",requiredPermissions:e?{permissions:Array.isArray(e)?e:[e]}:null}}else this.deniedPageName&&(s={displayName:this.deniedPageName,pageName:this.deniedPageName,pageIcon:"bi bi-file-text"});return{deniedPage:s,currentUser:t?{username:t.username||t.name||t.email||"Unknown User",name:t.name,email:t.email}:null,showLogin:!t}}async handleActionGoBack(e,t){e.preventDefault(),window.history.length>1?window.history.back():await this.handleActionGoHome(e,t)}async handleActionGoHome(e,t){e.preventDefault();const s=this.getApp();s?await s.navigateToDefault():window.location.href="/"}async handleActionLogin(e,t){e.preventDefault();const s=this.getApp();if(s)try{await s.showPage("login")}catch(i){try{await s.navigate("/login")}catch(a){this.emit("login-required",{returnUrl:this.deniedPage?.route||window.location.pathname}),setTimeout(()=>{s?.showInfo?.("Please contact your administrator for access.")},100)}}}async onEnter(){await super.onEnter();const e=this.deniedPage?.pageName||this.deniedPageName;e&&this.setMeta({title:`Access Denied - ${e}`}),console.warn("Access denied to page:",{page:this.deniedPage?.pageName||this.deniedPageName,route:this.deniedPage?.route,permissions:this.deniedPageOptions?.permissions,timestamp:/* @__PURE__ */(new Date).toISOString()})}static showForPage(e,t){const s=new DeniedPage;return s.setDeniedPage(t),e.showPage(s)}}class NotFoundPage extends h{constructor(e={}){super({pageName:"404",route:"/404",title:"404 - Page Not Found",pageIcon:"bi bi-search",template:'\n <div class="container mt-5">\n <div class="row justify-content-center">\n <div class="col-md-8 col-lg-6">\n <div class="text-center mb-4">\n <i class="bi bi-search text-muted" style="font-size: 3rem;"></i>\n <h2 class="mt-3 mb-2">Page Not Found</h2>\n <p class="text-muted">The page you\'re looking for doesn\'t exist.</p>\n </div>\n\n {{#path}}\n <div class="card border-0 shadow-sm mb-4">\n <div class="card-body text-center">\n <h6 class="card-subtitle mb-2 text-muted">Requested Path</h6>\n <code class="text-primary">{{path}}</code>\n </div>\n </div>\n {{/path}}\n\n <div class="d-grid gap-2 d-md-flex justify-content-md-center">\n <button type="button" class="btn btn-primary" data-action="go-back">\n <i class="bi bi-arrow-left me-1"></i>\n Go Back\n </button>\n <button type="button" class="btn btn-outline-secondary" data-action="go-home">\n <i class="bi bi-house me-1"></i>\n Home\n </button>\n </div>\n </div>\n </div>\n </div>\n ',...e}),this.path=null}async onParams(e={},t={}){await super.onParams(e,t),e.path&&(this.path=e.path),t.path&&(this.path=t.path)}setInfo(e){return this.path=e||null,this}async handleActionGoBack(e,t){e.preventDefault(),window.history.length>1?window.history.back():await this.handleActionGoHome(e,t)}async handleActionGoHome(e,t){e.preventDefault();const s=this.getApp();s?await s.navigateToDefault():window.location.href="/"}async onEnter(){await super.onEnter(),this.path&&this.setMeta({title:`404 - ${this.path} Not Found`}),console.warn("404 Not Found:",{path:this.path,timestamp:/* @__PURE__ */(new Date).toISOString()})}static showForPath(e,t){const s=new NotFoundPage;return s.setInfo(t),s.render()}}class PortalApp extends g{constructor(e={}){super(e),this.sidebarConfig=e.sidebar,this.topbarConfig=e.topbar||{},e.topnav&&!e.topbar&&(this.topbarConfig=e.topnav),this.showPageHeader=e.showPageHeader||!1,this.pageHeaderConfig=e.pageHeader||{},this.sidebar=null,this.topbar=null,this.topnav=null,this.pageHeader=null,this.tokenManager=new xt,this.activeGroup=null,this.isMobile()?this.sidebarCollapsed=this.sidebarConfig.defaultCollapsed||!1:this.sidebarCollapsed=this.loadSidebarState()??(this.sidebarConfig.defaultCollapsed||!1),this.setupPageContainer(),this.toast=new pt,this.Dialog=Pt,this.registerPage("denied",DeniedPage),this.registerPage("404",NotFoundPage)}async start(){await this.checkAuthStatus(),this.events.on("auth:unauthorized",()=>{this.tokenManager.clearTokens(),this.rest.clearAuth(),this.setActiveUser(null)}),this.events.on("auth:logout",()=>{this.tokenManager.clearTokens(),this.rest.clearAuth(),this.setActiveUser(null)}),this.events.on("browser:focus",()=>{this.activeUser&&this.tokenManager.checkAndRefreshTokens(this)}),this.events.on("portal:action",this.onPortalAction.bind(this)),this.activeUser&&await this.checkActiveGroup(),await this.setupRouter(),this.isStarted=!0,this.events.emit("app:ready",{app:this}),this.activeUser&&!this.activeUser.get("has_passkey")&&this.showPasskeySetup()}async checkAuthStatus(){const e=this.tokenManager.checkTokenStatus();if("logout"===e.action)return this.events.emit("auth:unauthorized",{app:this}),!1;if("refresh"===e.action&&!(await this.tokenManager.checkAndRefreshTokens(this)))return!1;const t=this.tokenManager.getTokenInstance();if(this.activeUser)return this.tokenManager.startAutoRefresh(this),!0;this.rest.setAuthToken(t.token);const s=new ut({id:t.getUserId()}),i=await s.fetch();return i.success?(this.setActiveUser(s),this.tokenManager.startAutoRefresh(this),!0):(this.tokenManager.clearTokens(),this.events.emit("auth:unauthorized",{app:this,error:i.error}),!1)}async checkActiveGroup(){const e=new URLSearchParams(window.location.search).get("group"),t=e||this.loadActiveGroupId();if(t)try{const s=new ct({id:t}),i=await s.fetch();if(!i.success||!i.data.status)return this.clearActiveGroup(),void console.warn("Failed to load active group:",i.statusText);this.activeGroup=s,e&&this.saveActiveGroupId(t),this.activeUser&&(this.activeUser.member=new w,await this.activeUser.member.fetchForGroup(s.id)),this.events.emit("group:loaded",{group:this.activeGroup})}catch(s){if(console.warn("Failed to load active group:",s),e&&!this.loadActiveGroupId())this.clearActiveGroupId();else if(e){const t=this.loadActiveGroupId();if(t&&t!==e)try{const e=new ct({id:t});await e.fetch(),this.activeGroup=e,this.events.emit("group:loaded",{group:this.activeGroup})}catch(i){console.warn("Fallback to stored group also failed:",i),this.clearActiveGroupId()}}}}async setActiveGroup(e){const t=this.activeGroup;this.activeGroup=e,e&&e.get("id")?this.saveActiveGroupId(e.get("id")):this.clearActiveGroupId(),this.activeUser&&(this.activeUser.member=new w,await this.activeUser.member.fetchForGroup(e.id)),this.events.emit("group:changed",{group:e,previousGroup:t,app:this});const s=this.getCurrentPage();return s&&s.onGroupChange(e),this.router.updateUrl({group:e.id},{replace:!0}),this}getActiveGroup(){return this.activeGroup}async clearActiveGroup(){const e=this.activeGroup;return this.activeGroup=null,this.clearActiveGroupId(),this.events.emit("group:cleared",{previousGroup:e,app:this}),this}saveActiveGroupId(e){try{const t=this.getActiveGroupStorageKey();localStorage.setItem(t,e.toString())}catch(t){console.warn("Failed to save active group ID:",t)}}loadActiveGroupId(){try{const e=this.getActiveGroupStorageKey();return localStorage.getItem(e)}catch(e){return console.warn("Failed to load active group ID:",e),null}}clearActiveGroupId(){try{const e=this.getActiveGroupStorageKey();localStorage.removeItem(e)}catch(e){console.warn("Failed to clear active group ID:",e)}}getActiveGroupStorageKey(){return"active_group_id"}setPortalProfile(e){try{localStorage.setItem("portal_profile",e)}catch(t){console.warn("Failed to save portal profile:",t)}}needsGroupSelection(){return!this.activeGroup}setupPageContainer(){const e="string"==typeof this.container?document.querySelector(this.container):this.container;if(!e)throw new Error(`Portal container not found: ${this.container}`);const t=this.sidebarConfig&&Object.keys(this.sidebarConfig).length>0,s=this.topbarConfig&&Object.keys(this.topbarConfig).length>0,i=this.showPageHeader?'\n <div class="portal-content" id="portal-content">\n <div id="page-header"></div>\n <div id="page-container">\n \x3c!-- Pages render here --\x3e\n </div>\n </div>\n ':'\n <div class="portal-content" id="page-container">\n \x3c!-- Pages render here --\x3e\n </div>\n ';e.innerHTML=`\n <div class="portal-layout hide-sidebar">\n ${t?'<div id="portal-sidebar"></div>':""}\n <div class="portal-body">\n ${s?'<div id="portal-topnav"></div>':""}\n ${i}\n </div>\n </div>\n `,this.pageContainer="#page-container",e.classList.add("portal-container"),this.setupPortalComponents(),this.applySidebarState(e)}async setupPortalComponents(){await this.setupSidebar(),await this.setupTopbar(),await this.setupPageHeader(),this.setupPortalEvents()}async setupSidebar(){this.sidebarConfig&&0!==Object.keys(this.sidebarConfig).length&&(this.sidebar=new Sidebar({containerId:"portal-sidebar",...this.sidebarConfig}),await this.sidebar.render())}async setupTopbar(){this.topbarConfig&&0!==Object.keys(this.topbarConfig).length&&(this.topbar=new At({containerId:"portal-topnav",brandText:this.topbarConfig.brand||this.brand||this.title,brandRoute:this.topbarConfig.brandRoute||"/",brandIcon:this.topbarConfig.brandIcon||this.brandIcon,navItems:this.topbarConfig.leftItems||[],rightItems:this.topbarConfig.rightItems||[],displayMode:this.topbarConfig.displayMode||"both",showSidebarToggle:this.topbarConfig.showSidebarToggle||!1,...this.topbarConfig}),await this.topbar.render(),this.topnav=this.topbar)}async setupPageHeader(){if(!this.showPageHeader)return;this.pageHeader=new PageHeader({containerId:"page-header",style:this.pageHeaderConfig.style||"default",showIcon:!1!==this.pageHeaderConfig.showIcon,showDescription:!1!==this.pageHeaderConfig.showDescription,showBreadcrumbs:this.pageHeaderConfig.showBreadcrumbs||!1,...this.pageHeaderConfig});const e=document.getElementById("page-header");e&&await this.pageHeader.render(!0,e)}setupPortalEvents(){if(document.addEventListener("click",e=>{e.target.closest('[data-action="toggle-sidebar"]')&&(e.preventDefault(),this.toggleSidebar())}),window.ResizeObserver){const e=new ResizeObserver(()=>{this.handleResponsive()});e.observe(document.body),this._resizeObserver=e}else this._resizeHandler=()=>this.handleResponsive(),window.addEventListener("resize",this._resizeHandler);this.handleResponsive()}toggleSidebar(){if(!this.sidebar)return;const e=document.querySelector(".portal-container"),t=this.isMobile();t?e.classList.toggle("hide-sidebar"):(e.classList.toggle("collapse-sidebar"),this.sidebarCollapsed=!this.sidebarCollapsed,this.saveSidebarState(this.sidebarCollapsed)),this.events.emit("sidebar:toggled",{collapsed:this.sidebarCollapsed,mobile:t})}handleResponsive(){const e=document.querySelector(".portal-container");if(!e)return;const t=this.isMobile();t?(e.classList.add("mobile-layout"),e.classList.contains("hide-sidebar")||e.classList.add("hide-sidebar")):e.classList.remove("mobile-layout","hide-sidebar"),this.events.emit("responsive:changed",{mobile:t})}getPortalContainer(){return document.querySelector(".portal-container")}isMobile(){return window.innerWidth<768}hasMobileLayout(){return this.getPortalContainer().classList.contains("mobile-layout")}async showPage(e,t={},s={},i={}){const a=await super.showPage(e,t,s,i);return this.hasMobileLayout()&&this.getPortalContainer().classList.add("hide-sidebar"),this.currentPage&&this.updateNavigation(this.currentPage),a}updateNavigation(e){this.sidebar&&this.sidebar.setActivePage&&this.sidebar.setActivePage(e.route),this.topbar&&this.topbar.setActivePage&&this.topbar.setActivePage(e.route),this.pageHeader&&this.pageHeader.setPage(e),this.events.emit("portal:page-changed",{page:e})}setActiveUser(e){this.activeUser=e,this.topbar&&this.topbar.setUser(e),this.events.emit("portal:user-changed",{user:e})}getActiveUser(){return this.activeUser}saveSidebarState(e){try{const t=this.getSidebarStorageKey();localStorage.setItem(t,JSON.stringify(e))}catch(t){console.warn("Failed to save sidebar state:",t)}}loadSidebarState(){try{const e=this.getSidebarStorageKey(),t=localStorage.getItem(e);return null!==t?JSON.parse(t):null}catch(e){return console.warn("Failed to load sidebar state:",e),null}}getSidebarStorageKey(){return`${this.title?this.title.replace(/\s+/g,"_").toLowerCase():"portal_app"}_sidebar_collapsed`}applySidebarState(e=null){e||(e=document.querySelector(".portal-container")),e&&(this.sidebarCollapsed?e.classList.add("collapse-sidebar"):e.classList.remove("collapse-sidebar"))}clearSidebarState(){try{const e=this.getSidebarStorageKey();localStorage.removeItem(e)}catch(e){console.warn("Failed to clear sidebar state:",e)}}async changePassword(){const e=await this.showForm({title:"Change Password",fields:[{name:"current_password",type:"password",label:"Current Password",required:!0,showToggle:!0,strengthMeter:!0,capsLockWarning:!0},{name:"new_password",type:"password",label:"New Password",required:!0,showToggle:!0,passwordUsage:"new",strengthMeter:!0,capsLockWarning:!0,attributes:{autocomplete:"new-password"}},{name:"confirm_password",type:"password",label:"Confirm Password",required:!0,showToggle:!0,passwordUsage:"new",strengthMeter:!0,capsLockWarning:!0,attributes:{}}],submitLabel:"Change Password"});e&&(e.new_password===e.confirm_password?200===(await this.activeUser.save(e)).status?this.toast.success("Password changed successfully"):this.toast.error("Failed to change password"):this.toast.error("Passwords do not match"))}onPortalAction(e){switch(e.action){case"logout":this.tokenManager.clearTokens(),this.rest.clearAuth(),this.setActiveUser(null);break;case"profile":this.showProfile();break;case"change-password":this.changePassword();break;default:console.warn(`Unknown portal action: ${e}`)}}async showProfile(){if(this.activeUser)try{const{UserProfileView:e}=await Promise.resolve().then(()=>Xt),t=new e({model:this.activeUser});await Pt.showDialog({body:t,header:null,size:"lg",noBodyPadding:!0,centered:!1})}catch(e){console.error("Error showing profile:",e),this.showError("Failed to load profile")}else this.showError("No user is currently logged in")}async showPasskeySetup(){if(!localStorage.getItem("passkey_setup_dismissed"))try{const{PasskeySetupView:e}=await Promise.resolve().then(()=>Xt),t=new e;t.on("dismiss",()=>{const e=t.element?.closest(".modal");if(e){const t=bootstrap.Modal.getInstance(e);t&&t.hide()}}),await Pt.showDialog({header:null,body:t,size:"sm",centered:!0,buttons:[]})}catch(e){console.error("Error showing passkey setup:",e)}}async destroy(){this.activeGroup=null,this._resizeObserver&&this._resizeObserver.disconnect(),this._resizeHandler&&window.removeEventListener("resize",this._resizeHandler),this.topbar&&(await this.topbar.destroy(),this.topbar=null,this.topnav=null),this.sidebar&&(await this.sidebar.destroy(),this.sidebar=null),await super.destroy()}static create(e={}){return new PortalApp(e)}}class FormPage extends h{constructor(e={}){super({title:"Form Page",description:"A page for submitting forms",icon:"form",fields:[],template:'<div data-container="form-view-container"></div>',className:"form-page container-sm",...e})}async onInit(){await super.onInit(),await this.recreateFormView()}async onEnter(){await super.onEnter(),this.formView&&await this.recreateFormView()}async onGroupChange(e){this.formView&&await this.recreateFormView()}async getModel(){return this.model?this.model:this.getApp().activeGroup?this.getApp().activeGroup:null}async recreateFormView(){this.formView&&(await this.formView.destroy(),this.removeChild(this.formView)),this.formView=new Tt({containerId:"form-view-container",fields:this.options.fields,autosaveModelField:!0}),this.addChild(this.formView);const e=await this.getModel();e&&this.formView.setModel(e)}}const qt=new class{constructor(){this.formatter=r,this.compiledTemplates=/* @__PURE__ */new Map}render(e,t,s={}){return l.render(e,t,s)}compile(e){const t=l.parse(e);return this.compiledTemplates.set(e,t),t}renderCompiled(e,t,s={}){return l.render(e,t,s)}clearCache(){this.compiledTemplates.clear(),l.clearCache()}cache(e,t){return{key:e,template:t,compiled:this.compile(t)}}getCached(e){for(const[t,s]of this.compiledTemplates)if(t===e||s===e)return{key:e,template:t,compiled:s};return null}registerFormatter(e,t){return this.formatter.register(e,t),this}hasPipes(e){return/\{\{[{]?[^}|]+\|[^}]+\}[}]?\}/.test(e)}processData(e,t){const s={...e};for(const[i,a]of Object.entries(t))if(e&&"function"==typeof e.get)s[i]=e.get(`${i}|${a}`);else{const t=this.getValueFromPath(e,i);s[i]=this.formatter.pipe(t,a)}return s}getValueFromPath(e,t){if(!e||!t)return;if(e&&"function"==typeof e.get)return e.get(t);const s=t.split(".");let i=e;for(const a of s){if(null==i)return;i=!isNaN(a)&&Array.isArray(i)?i[parseInt(a)]:i[a]}return i}processTemplate(e,t){return{template:e,data:t}}};class ProfileOverviewSection extends o{constructor(e={}){super({className:"profile-overview-section",template:'\n <style>\n .po-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .po-section-label:first-child { margin-top: 0; }\n .po-field-row { display: flex; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #f0f0f0; }\n .po-field-row:last-child { border-bottom: none; }\n .po-field-label { width: 130px; font-size: 0.8rem; color: #6c757d; flex-shrink: 0; }\n .po-field-value { flex: 1; font-size: 0.88rem; color: #212529; display: flex; align-items: center; gap: 0.4rem; }\n .po-field-action { color: #6c757d; cursor: pointer; font-size: 0.8rem; margin-left: auto; padding: 0.15rem 0.4rem; border-radius: 4px; background: none; border: none; }\n .po-field-action:hover { background: #f0f0f0; color: #0d6efd; }\n .po-badge-warn { font-size: 0.65rem; padding: 0.15em 0.45em; background: #fff3cd; color: #856404; border-radius: 3px; }\n .po-badge-ok { font-size: 0.65rem; padding: 0.15em 0.45em; background: #d1e7dd; color: #0f5132; border-radius: 3px; }\n .po-badge-muted { font-size: 0.65rem; padding: 0.15em 0.45em; background: #f0f0f0; color: #6c757d; border-radius: 3px; }\n .po-not-set { color: #adb5bd; font-style: italic; font-size: 0.85rem; }\n .po-perm-pill { display: inline-block; font-size: 0.72rem; padding: 0.2em 0.55em; background: #e7f1ff; color: #0d6efd; border-radius: 3px; margin: 0.1rem; }\n .po-perm-more { font-size: 0.72rem; color: #6c757d; cursor: pointer; }\n .po-perm-more:hover { color: #0d6efd; text-decoration: underline; }\n </style>\n\n \x3c!-- Contact --\x3e\n <div class="po-section-label">Contact</div>\n <div class="po-field-row">\n <div class="po-field-label">Email</div>\n <div class="po-field-value">\n {{model.email}}\n {{#model.is_email_verified|bool}}\n <span class="po-badge-ok">Verified</span>\n {{/model.is_email_verified|bool}}\n {{^model.is_email_verified|bool}}\n <span class="po-badge-warn">Unverified</span>\n {{/model.is_email_verified|bool}}\n </div>\n {{^model.is_email_verified|bool}}\n <button type="button" class="po-field-action" data-action="verify-email" title="Send verification email"><i class="bi bi-envelope-check"></i></button>\n {{/model.is_email_verified|bool}}\n <button type="button" class="po-field-action" data-action="update-email" title="Change email"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Phone</div>\n <div class="po-field-value">\n {{#hasPhone|bool}}\n {{model.phone_number}}\n {{#model.is_phone_verified|bool}}\n <span class="po-badge-ok">Verified</span>\n {{/model.is_phone_verified|bool}}\n {{^model.is_phone_verified|bool}}\n <span class="po-badge-warn">Unverified</span>\n {{/model.is_phone_verified|bool}}\n {{/hasPhone|bool}}\n {{^hasPhone|bool}}\n <span class="po-not-set">Not set</span>\n {{/hasPhone|bool}}\n </div>\n {{^hasPhone|bool}}\n <button type="button" class="po-field-action" data-action="add-phone" title="Add phone number"><i class="bi bi-plus"></i></button>\n {{/hasPhone|bool}}\n {{#hasPhone|bool}}\n {{^model.is_phone_verified|bool}}\n <button type="button" class="po-field-action" data-action="verify-phone" title="Send verification"><i class="bi bi-phone-vibrate"></i></button>\n {{/model.is_phone_verified|bool}}\n <button type="button" class="po-field-action" data-action="update-phone" title="Change phone number"><i class="bi bi-pencil"></i></button>\n <button type="button" class="po-field-action" data-action="remove-phone" title="Remove phone number"><i class="bi bi-x-lg"></i></button>\n {{/hasPhone|bool}}\n </div>\n\n \x3c!-- Account --\x3e\n <div class="po-section-label">Account</div>\n <div class="po-field-row">\n <div class="po-field-label">Username</div>\n <div class="po-field-value">{{model.username}}</div>\n <button type="button" class="po-field-action" data-action="edit-username" title="Change username"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Status</div>\n <div class="po-field-value">\n {{#model.is_active|bool}}<span class="po-badge-ok">Active</span>{{/model.is_active|bool}}\n {{^model.is_active|bool}}<span class="po-badge-warn">Inactive</span>{{/model.is_active|bool}}\n </div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Role</div>\n <div class="po-field-value">\n {{roleLabel}}\n {{#model.is_staff|bool}}<span class="po-badge-muted">Staff</span>{{/model.is_staff|bool}}\n </div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">MFA</div>\n <div class="po-field-value">\n {{#model.requires_mfa|bool}}<span class="po-badge-ok">Required</span>{{/model.requires_mfa|bool}}\n {{^model.requires_mfa|bool}}<span class="po-badge-muted">Not required</span>{{/model.requires_mfa|bool}}\n </div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Member Since</div>\n <div class="po-field-value">{{model.date_joined|date}}</div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Last Login</div>\n <div class="po-field-value">{{model.last_login|relative}}</div>\n </div>\n\n \x3c!-- Permissions peek --\x3e\n <div class="po-section-label">Permissions</div>\n <div class="po-perms-peek">\n {{#permissionPeek}}\n {{#items}}\n <span class="po-perm-pill">{{.}}</span>\n {{/items}}\n {{#remaining|bool}}\n <span class="po-perm-more" data-action="navigate" data-section="permissions">+{{remaining}} more</span>\n {{/remaining|bool}}\n {{/permissionPeek}}\n {{^permissionPeek|bool}}\n <span class="po-not-set">No permissions assigned</span>\n {{/permissionPeek|bool}}\n </div>\n\n \x3c!-- Danger zone --\x3e\n <div style="margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid #f0f0f0;">\n <button type="button" class="btn btn-link text-danger p-0" style="font-size: 0.8rem; text-decoration: none;" data-action="deactivate-account">\n <i class="bi bi-exclamation-triangle me-1"></i>Deactivate Account\n </button>\n </div>\n ',...e})}get hasPhone(){return!(!this.model||!this.model.get("phone_number"))}get roleLabel(){return this.model&&this.model.get("is_superuser")?"Superuser":"User"}get permissionPeek(){if(!this.model)return null;const e=this.model.get("permissions");if(!e)return null;const t={};ut.PERMISSIONS.forEach(e=>{t[e.name]=e.label});const s=Object.keys(e).filter(t=>!0===e[t]).map(e=>t[e]||e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase()));return 0===s.length?null:{items:s.slice(0,5),remaining:Math.max(0,s.length-5)}}async onActionEditUsername(){const e=this.getApp(),t=await Pt.showForm({title:"Change Username",size:"sm",submitText:"Change",fields:[{name:"new_username",type:"text",label:"New Username",required:!0,placeholder:"Enter new username",attributes:{autocomplete:"off"},cols:12},{name:"confirm_identity",type:"password",label:"Current Password",required:!0,placeholder:"Required to confirm identity",showToggle:!0,attributes:{autocomplete:"off"},cols:12}]});if(!t)return!0;const s=await d.POST("/api/auth/username/change",{username:t.new_username,current_password:t.confirm_identity});return s.success?(e?.toast?.success("Username updated"),await this.model.fetch({params:{graph:"full"}}),await this.render(),this.parent&&await this.parent.render()):e?.toast?.error(s.message||"Failed to change username"),!0}async onActionDeactivateAccount(){const e=this.getApp();if(!(await Pt.confirm("Are you sure you want to deactivate your account? A confirmation email will be sent to complete the process. This action cannot be undone.","Deactivate Account")))return!0;const t=await d.POST("/api/account/deactivate");return t.success?e?.toast?.success("A confirmation email has been sent. Follow the link to complete deactivation."):e?.toast?.error(t.message||"Failed to request deactivation"),!0}async onActionVerifyEmail(){const e=this.getApp(),t=this.model.get("email"),s=await d.POST("/api/auth/verify/email/send",{method:"code"});if(!s.success)return e?.toast?.error(s.message||"Failed to send verification code"),!0;const i=await Pt.prompt(`Enter the 6-digit code sent to <strong>${t}</strong>`,"Verify Email",{placeholder:"000000"});if(!i)return!0;const a=await d.POST("/api/auth/verify/email/confirm",{code:i.trim()});return a.success?(e?.toast?.success("Email verified"),this.model.set("is_email_verified",!0),await this.render()):e?.toast?.error(a.message||"Invalid or expired code"),!0}async onActionVerifyPhone(){const e=this.getApp(),t=this.model.get("phone_number"),s=await d.POST("/api/auth/verify/phone/send");if(!s.success)return e?.toast?.error(s.message||"Failed to send verification code"),!0;const i=await Pt.prompt(`Enter the 6-digit code sent to <strong>${t}</strong>`,"Verify Phone",{placeholder:"000000"});if(!i)return!0;const a=await d.POST("/api/auth/verify/phone/confirm",{code:i.trim()});return a.success?(e?.toast?.success("Phone verified"),this.model.set("is_phone_verified",!0),await this.render()):e?.toast?.error(a.message||"Invalid or expired code"),!0}async onActionAddPhone(){const e=this.getApp(),t=await Pt.prompt("Enter your phone number in E.164 format:","Add Phone Number",{placeholder:"+14155550123"});if(!t||!t.trim())return!0;const s=await this.model.save({phone_number:t.trim()});if(200!==s.status)return e?.toast?.error(s.message||"Failed to save phone number"),!0;const i=await d.POST("/api/auth/verify/phone/send");if(!i.success)return e?.toast?.error(i.message||"Failed to send verification code"),await this.render(),!0;const a=await Pt.prompt(`Enter the 6-digit code sent to <strong>${t.trim()}</strong>`,"Verify Phone",{placeholder:"000000"});if(!a)return await this.render(),!0;const n=await d.POST("/api/auth/verify/phone/confirm",{code:a.trim()});return n.success?(e?.toast?.success("Phone number added and verified"),this.model.set("is_phone_verified",!0),await this.render()):(e?.toast?.error(n.message||"Invalid or expired code"),await this.render()),!0}async onActionRemovePhone(){const e=this.getApp();if(!(await Pt.confirm("Remove your phone number? You will need to add it again to use phone-based verification.","Remove Phone")))return!0;const t=await this.model.save({phone_number:null});return 200===t.status?(e?.toast?.success("Phone number removed"),this.model.set("is_phone_verified",!1),await this.render()):e?.toast?.error(t.message||"Failed to remove phone number"),!0}async onActionNavigate(e,t){return!this.parent||!this.parent.onActionNavigate||this.parent.onActionNavigate(e,t)}}class Passkey extends v{constructor(e={},t={}){super(e,{endpoint:"/api/account/passkeys",...t})}static async registerBegin(e={}){try{const t="/api/account/passkeys/register/begin";return await d.POST(t,{},e.params)}catch(t){return{success:!1,status:t?.status||500,error:t?.message||"Failed to begin passkey registration"}}}static async registerComplete(e={},t={}){if(!e.challenge_id)return{success:!1,status:400,error:"Missing challenge_id"};if(!e.credential)return{success:!1,status:400,error:"Missing credential data"};try{const s="/api/account/passkeys/register/complete";return await d.POST(s,e,t.params)}catch(s){return{success:!1,status:s?.status||500,error:s?.message||"Failed to complete passkey registration"}}}}class PasskeyList extends y{constructor(e={}){super({ModelClass:Passkey,endpoint:"/api/account/passkeys",size:10,...e})}}const jt={edit:{fields:[{name:"friendly_name",type:"text",label:"Name",placeholder:"My iPhone",required:!0,columns:12,help:"A friendly name to identify this passkey"},{name:"is_enabled",type:"switch",label:"Enabled",columns:12,help:"Disable to prevent this passkey from being used for authentication"}]}},Kt={google:"bi-google",github:"bi-github",microsoft:"bi-microsoft",apple:"bi-apple",facebook:"bi-facebook",twitter:"bi-twitter-x",linkedin:"bi-linkedin"};class SessionItem extends _t{get browserName(){const e=this.model?.get("user_device");return e?.device_info?.user_agent?.family||"Unknown"}get deviceName(){const e=this.model?.get("user_device"),t=e?.device_info?.device||{};return`${t.brand||""} ${t.family||""}`.trim()||"Unknown Device"}get location(){const e=this.model?.get("geolocation")||{},t=[e.city,e.region].filter(Boolean);return t.length?t.join(", "):e.country_name||""}get isMobile(){const e=this.model?.get("user_device"),t=e?.device_info?.device||{},s=e?.device_info?.os||{};return["iPhone","Android"].some(e=>(t.family||"").includes(e)||(s.family||"").includes(e))}get deviceIcon(){return this.isMobile?"bi-phone":"bi-laptop"}}class DeviceItem extends _t{get deviceName(){const e=(this.model?.get("device_info")||{}).device||{};return`${e.brand||""} ${e.family||""}`.trim()||"Unknown Device"}get browserInfo(){const e=(this.model?.get("device_info")||{}).user_agent||{};return e.family?`${e.family} ${e.major||""}`.trim():"Unknown Browser"}get osName(){const e=this.model?.get("device_info")||{};return e.os?.family||""}get isMobile(){const e=this.model?.get("device_info")||{},t=e.device||{},s=e.os||{};return["iPhone","Android"].some(e=>(t.family||"").includes(e)||(s.family||"").includes(e))}get deviceIcon(){return this.isMobile?"bi-phone":"bi-laptop"}}class SecurityEvent extends v{constructor(e={},t={}){super(e,{endpoint:"/api/account/security-events",...t})}}class SecurityEventList extends y{constructor(e={}){super({ModelClass:SecurityEvent,endpoint:"/api/account/security-events",size:15,...e})}}const $t=["invalid_password","totp:login_failed","totp:confirm_failed","passkey:login_failed","sessions:revoke_failed","email_change:invalid","email_change:expired"],Jt=["login","oauth","email_verify:confirmed","email_verify:confirmed_code","phone_verify:confirmed","phone_change:confirmed","username:changed"];class SecurityEventRow extends x{get kindBadgeClass(){const e=this.model?.get("kind")||"";return $t.includes(e)?"bg-danger":Jt.includes(e)?"bg-success":"bg-secondary"}get kindLabel(){return(this.model?.get("kind")||"").replace(/[_:]/g," ").replace(/\b\w/g,e=>e.toUpperCase())}get kindIcon(){const e=this.model?.get("kind")||"";return $t.includes(e)?"bi-exclamation-triangle":Jt.includes(e)?"bi-check-circle":"bi-info-circle"}}const Wt={in_app:"In-App",email:"Email",push:"Push"},Yt=["in_app","email","push"],Qt=["#667eea","#f5576c","#38b2ac","#ed8936","#9f7aea","#48bb78","#4299e1","#ed64a6"];class GroupMemberItem extends _t{get groupName(){return this.model?.get("group")?.name||"Unknown Group"}get groupKind(){return(this.model?.get("group")?.kind||"").replace(/^\w/,e=>e.toUpperCase())}get initials(){return this.groupName.split(/\s+/).map(e=>e[0]).join("").substring(0,2).toUpperCase()}get avatarColor(){const e=this.groupName;let t=0;for(let s=0;s<e.length;s++)t=e.charCodeAt(s)+((t<<5)-t);return Qt[Math.abs(t)%Qt.length]}get roleName(){let e=this.model?.get("metadata")?.role||"";return!e&&this.model?.get("permissions")?.manage_group&&(e="Admin"),e}get hasRole(){return!!this.roleName}get roleBadgeClass(){const e=(this.roleName||"").toLowerCase();return"owner"===e?"bg-primary":"admin"===e?"bg-info":"bg-secondary"}}const Zt={profile:ProfileOverviewSection,personal:class extends o{constructor(e={}){super({className:"profile-personal-section",template:'\n <style>\n .pp-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .pp-section-label:first-child { margin-top: 0; }\n .pp-field-row { display: flex; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #f0f0f0; }\n .pp-field-row:last-child { border-bottom: none; }\n .pp-field-label { width: 130px; font-size: 0.8rem; color: #6c757d; flex-shrink: 0; }\n .pp-field-value { flex: 1; font-size: 0.88rem; color: #212529; display: flex; align-items: center; gap: 0.4rem; }\n .pp-field-action { color: #6c757d; cursor: pointer; font-size: 0.8rem; margin-left: auto; padding: 0.15rem 0.4rem; border-radius: 4px; background: none; border: none; }\n .pp-field-action:hover { background: #f0f0f0; color: #0d6efd; }\n .pp-badge-ok { font-size: 0.65rem; padding: 0.15em 0.45em; background: #d1e7dd; color: #0f5132; border-radius: 3px; }\n .pp-badge-warn { font-size: 0.65rem; padding: 0.15em 0.45em; background: #fff3cd; color: #856404; border-radius: 3px; }\n .pp-not-set { color: #adb5bd; font-style: italic; font-size: 0.85rem; }\n </style>\n\n \x3c!-- Name --\x3e\n <div class="pp-section-label">Name</div>\n <div class="pp-field-row">\n <div class="pp-field-label">Display Name</div>\n <div class="pp-field-value">\n {{#model.display_name}}{{model.display_name}}{{/model.display_name}}\n {{^model.display_name}}<span class="pp-not-set">Not set</span>{{/model.display_name}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-display-name" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="pp-field-row">\n <div class="pp-field-label">First Name</div>\n <div class="pp-field-value">\n {{#model.first_name}}{{model.first_name}}{{/model.first_name}}\n {{^model.first_name}}<span class="pp-not-set">Not set</span>{{/model.first_name}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-first-name" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="pp-field-row">\n <div class="pp-field-label">Last Name</div>\n <div class="pp-field-value">\n {{#model.last_name}}{{model.last_name}}{{/model.last_name}}\n {{^model.last_name}}<span class="pp-not-set">Not set</span>{{/model.last_name}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-last-name" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n\n \x3c!-- Details --\x3e\n <div class="pp-section-label">Details</div>\n <div class="pp-field-row">\n <div class="pp-field-label">Date of Birth</div>\n <div class="pp-field-value">\n {{#hasDob|bool}}\n {{dobFormatted}}\n {{#model.is_dob_verified|bool}}<span class="pp-badge-ok">Verified</span>{{/model.is_dob_verified|bool}}\n {{^model.is_dob_verified|bool}}<span class="pp-badge-warn">Unverified</span>{{/model.is_dob_verified|bool}}\n {{/hasDob|bool}}\n {{^hasDob|bool}}<span class="pp-not-set">Not set</span>{{/hasDob|bool}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-dob" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="pp-field-row">\n <div class="pp-field-label">Timezone</div>\n <div class="pp-field-value">{{timezoneDisplay}}</div>\n <button type="button" class="pp-field-action" data-action="edit-timezone" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n\n \x3c!-- Address --\x3e\n <div class="pp-section-label">Address</div>\n <div class="pp-field-row">\n <div class="pp-field-label">Address</div>\n <div class="pp-field-value">\n {{#hasAddress|bool}}\n {{addressSummary}}\n {{/hasAddress|bool}}\n {{^hasAddress|bool}}\n <span class="pp-not-set">Not set</span>\n {{/hasAddress|bool}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-address" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n ',...e})}get hasDob(){return!!this.model?.get("dob")}get dobFormatted(){const e=this.model?.get("dob");if(!e)return"";try{const[t,s,i]=e.split("-");return`${s}/${i}/${t}`}catch{return e}}get timezoneDisplay(){return(this.model?.get("metadata")||{}).timezone||"Not set"}get hasAddress(){const e=this.model?.get("metadata")||{};return!!(e.street||e.city||e.state||e.zip||e.country)}get addressSummary(){const e=this.model?.get("metadata")||{};return[e.street,e.city,e.state,e.zip,e.country].filter(Boolean).join(", ")}async onActionEditDisplayName(){const e=await Pt.prompt("Enter your display name:","Display Name",{defaultValue:this.model.get("display_name")||""});return null!==e&&e.trim()&&(200===(await this.model.save({display_name:e.trim()})).status?(this.getApp()?.toast?.success("Display name updated"),await this.render()):this.getApp()?.toast?.error("Failed to update display name")),!0}async onActionEditFirstName(){const e=await Pt.prompt("Enter your first name:","First Name",{defaultValue:this.model.get("first_name")||""});return null!==e&&(200===(await this.model.save({first_name:e.trim()})).status?(this.getApp()?.toast?.success("First name updated"),await this.render()):this.getApp()?.toast?.error("Failed to update first name")),!0}async onActionEditLastName(){const e=await Pt.prompt("Enter your last name:","Last Name",{defaultValue:this.model.get("last_name")||""});return null!==e&&(200===(await this.model.save({last_name:e.trim()})).status?(this.getApp()?.toast?.success("Last name updated"),await this.render()):this.getApp()?.toast?.error("Failed to update last name")),!0}async onActionEditDob(){const e=await Pt.showForm({title:"Date of Birth",size:"sm",fields:[{name:"dob",type:"date",label:"Date of Birth",cols:12}],data:{dob:this.model.get("dob")||""}});return!e||(200===(await this.model.save({dob:e.dob||null})).status?(this.getApp()?.toast?.success("Date of birth updated"),await this.render()):this.getApp()?.toast?.error("Failed to update date of birth"),!0)}async onActionEditTimezone(){const e=this.model.get("metadata")||{},t=await Pt.showForm({title:"Change Timezone",fields:[{name:"timezone",type:"select",label:"Timezone",cols:12,options:[{value:"America/New_York",text:"Eastern Time (ET)"},{value:"America/Chicago",text:"Central Time (CT)"},{value:"America/Denver",text:"Mountain Time (MT)"},{value:"America/Los_Angeles",text:"Pacific Time (PT)"},{value:"America/Anchorage",text:"Alaska Time (AKT)"},{value:"Pacific/Honolulu",text:"Hawaii Time (HT)"},{value:"UTC",text:"UTC"},{value:"Europe/London",text:"London (GMT/BST)"},{value:"Europe/Paris",text:"Paris (CET/CEST)"},{value:"Europe/Berlin",text:"Berlin (CET/CEST)"},{value:"Asia/Tokyo",text:"Tokyo (JST)"},{value:"Asia/Shanghai",text:"Shanghai (CST)"},{value:"Australia/Sydney",text:"Sydney (AEST)"}]}],data:{timezone:e.timezone||""},size:"sm"});if(!t)return!0;const s={...e,timezone:t.timezone};return 200===(await this.model.save({metadata:s})).status?(this.getApp()?.toast?.success("Timezone updated"),await this.render()):this.getApp()?.toast?.error("Failed to update timezone"),!0}async onActionEditAddress(){const e=this.model.get("metadata")||{},t=await Pt.showForm({title:"Edit Address",size:"md",fields:[{name:"street",type:"text",label:"Street",placeholder:"123 Main St",cols:12},{name:"city",type:"text",label:"City",cols:6},{name:"state",type:"text",label:"State / Province",cols:6},{name:"zip",type:"text",label:"Zip / Postal Code",cols:6},{name:"country",type:"text",label:"Country",cols:6}],data:{street:e.street||"",city:e.city||"",state:e.state||"",zip:e.zip||"",country:e.country||""}});if(!t)return!0;const s={...e,street:t.street||"",city:t.city||"",state:t.state||"",zip:t.zip||"",country:t.country||""};return 200===(await this.model.save({metadata:s})).status?(this.getApp()?.toast?.success("Address updated"),await this.render()):this.getApp()?.toast?.error("Failed to update address"),!0}},security:class extends o{constructor(e={}){super({className:"profile-security-section",template:'\n <style>\n .ps-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .ps-section-label:first-child { margin-top: 0; }\n .ps-item { display: flex; align-items: center; gap: 0.85rem; padding: 0.85rem 1rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.5rem; cursor: pointer; transition: border-color 0.15s, background 0.15s; }\n .ps-item:hover { border-color: #dee2e6; background: #fafbfd; }\n .ps-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1rem; flex-shrink: 0; }\n .ps-info { flex: 1; min-width: 0; }\n .ps-title { font-weight: 600; font-size: 0.88rem; }\n .ps-desc { font-size: 0.78rem; color: #6c757d; }\n .ps-badge { font-size: 0.72rem; padding: 0.15em 0.5em; border-radius: 3px; flex-shrink: 0; }\n .ps-chevron { color: #ced4da; font-size: 0.8rem; flex-shrink: 0; }\n </style>\n\n <div class="ps-section-label">Authentication</div>\n\n <div class="ps-item" data-action="change-password">\n <div class="ps-icon bg-primary bg-opacity-10 text-primary"><i class="bi bi-lock"></i></div>\n <div class="ps-info">\n <div class="ps-title">Password</div>\n <div class="ps-desc">Change your account password</div>\n </div>\n <span class="ps-badge bg-light text-muted border">Change</span>\n </div>\n\n <div class="ps-item" data-action="manage-passkeys">\n <div class="ps-icon bg-success bg-opacity-10 text-success"><i class="bi bi-fingerprint"></i></div>\n <div class="ps-info">\n <div class="ps-title">Passkeys</div>\n <div class="ps-desc">Passwordless sign-in with biometrics</div>\n </div>\n <i class="bi bi-chevron-right ps-chevron"></i>\n </div>\n\n <div class="ps-item" data-action="manage-totp">\n <div class="ps-icon bg-purple bg-opacity-10" style="background: rgba(111,66,193,0.1); color: #6f42c1;"><i class="bi bi-shield-lock"></i></div>\n <div class="ps-info">\n <div class="ps-title">Authenticator App</div>\n <div class="ps-desc">Two-factor authentication with TOTP codes</div>\n </div>\n {{#model.requires_mfa|bool}}\n <span class="ps-badge bg-success bg-opacity-10 text-success border">Enabled</span>\n {{/model.requires_mfa|bool}}\n {{^model.requires_mfa|bool}}\n <span class="ps-badge bg-light text-muted border">Setup</span>\n {{/model.requires_mfa|bool}}\n </div>\n\n {{#model.requires_mfa|bool}}\n <div class="ps-item" data-action="manage-recovery-codes">\n <div class="ps-icon" style="background: rgba(111,66,193,0.1); color: #6f42c1;"><i class="bi bi-file-earmark-lock"></i></div>\n <div class="ps-info">\n <div class="ps-title">Recovery Codes</div>\n <div class="ps-desc">Backup codes for when you lose your authenticator</div>\n </div>\n <i class="bi bi-chevron-right ps-chevron"></i>\n </div>\n {{/model.requires_mfa|bool}}\n\n <div class="ps-section-label">Sessions</div>\n\n <div class="ps-item" data-action="revoke-all-sessions">\n <div class="ps-icon" style="background: rgba(220,53,69,0.1); color: #dc3545;"><i class="bi bi-box-arrow-right"></i></div>\n <div class="ps-info">\n <div class="ps-title">Revoke All Sessions</div>\n <div class="ps-desc">Sign out of all devices except this one</div>\n </div>\n </div>\n ',...e})}async onActionChangePassword(){const e=this.getApp();return e&&e.changePassword&&await e.changePassword(),!0}async onActionManagePasskeys(){const e=new PasskeyList({params:{user:this.model.id}});try{await e.fetch()}catch(i){}const t=e.models||[],s=new o({template:'\n <style>\n .pk-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.75rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.4rem; }\n .pk-icon { width: 32px; height: 32px; background: #e7f1ff; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #0d6efd; font-size: 0.9rem; flex-shrink: 0; }\n .pk-info { flex: 1; min-width: 0; }\n .pk-name { font-weight: 600; font-size: 0.85rem; }\n .pk-meta { font-size: 0.73rem; color: #6c757d; }\n .pk-actions { display: flex; gap: 0.25rem; }\n .pk-actions .btn { padding: 0.2rem 0.4rem; font-size: 0.75rem; }\n .pk-empty { text-align: center; padding: 2rem 1rem; color: #6c757d; }\n .pk-empty i { font-size: 2rem; color: #ced4da; display: block; margin-bottom: 0.5rem; }\n </style>\n {{#passkeys}}\n <div class="pk-row">\n <div class="pk-icon"><i class="bi bi-fingerprint"></i></div>\n <div class="pk-info">\n <div class="pk-name">{{.friendly_name|default:\'Unnamed Passkey\'}}</div>\n <div class="pk-meta">Created {{.created|date}} · Last used {{.last_used|relative|default:\'never\'}} · {{.sign_count}} uses</div>\n </div>\n <div class="pk-actions">\n <button type="button" class="btn btn-outline-secondary" data-action="edit-passkey" data-id="{{.id}}" title="Edit"><i class="bi bi-pencil"></i></button>\n <button type="button" class="btn btn-outline-danger" data-action="delete-passkey" data-id="{{.id}}" title="Delete"><i class="bi bi-trash"></i></button>\n </div>\n </div>\n {{/passkeys}}\n {{^passkeys|bool}}\n <div class="pk-empty">\n <i class="bi bi-fingerprint"></i>\n No passkeys registered yet\n </div>\n {{/passkeys|bool}}\n '});return s.passkeys=t.map(e=>e.toJSON?e.toJSON():e),s.onActionEditPasskey=async(e,s)=>{const i=s.dataset.id,a=t.find(e=>String(e.id)===String(i));return a&&await Pt.showModelForm({title:"Edit Passkey",model:a,fields:jt.edit.fields,size:"sm"}),!0},s.onActionDeletePasskey=async(e,s)=>{const i=s.dataset.id;if(await Pt.confirm("Delete this passkey? You won't be able to use it to sign in anymore.","Delete Passkey")){const e=t.find(e=>String(e.id)===String(i));e&&(await e.destroy(),this.getApp()?.toast?.success("Passkey deleted"))}return!0},"add"===await Pt.showDialog({title:"Passkeys",body:s,size:"md",buttons:[{text:"Add Passkey",icon:"bi-plus-lg",class:"btn-primary",value:"add"},{text:"Close",class:"btn-outline-secondary",dismiss:!0}]})&&await this._registerPasskey()&&this.getApp()?.toast?.success("Passkey registered successfully"),!0}_base64urlToBytes(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),s=t+"=".repeat((4-t.length%4)%4);return Uint8Array.from(atob(s),e=>e.charCodeAt(0))}async _registerPasskey(){try{const e=await Passkey.registerBegin();if(!e.success||!e.data)return void this.getApp()?.toast?.error("Failed to start passkey registration");const t=e.data.data||e.data,s=t.publicKey;s.rp&&(s.rp.id=window.location.hostname),s.challenge&&"string"==typeof s.challenge&&(s.challenge=this._base64urlToBytes(s.challenge)),s.user&&s.user.id&&"string"==typeof s.user.id&&(s.user.id=this._base64urlToBytes(s.user.id)),s.excludeCredentials&&(s.excludeCredentials=s.excludeCredentials.map(e=>({...e,id:"string"==typeof e.id?this._base64urlToBytes(e.id):e.id})));const i=await navigator.credentials.create({publicKey:s});if(!i)return void this.getApp()?.toast?.error("Passkey creation was cancelled");const a=await Pt.prompt("Name this passkey:","Passkey Name",{defaultValue:"",placeholder:"e.g., My MacBook"}),n={id:i.id,rawId:btoa(String.fromCharCode(...new Uint8Array(i.rawId))),type:i.type,response:{clientDataJSON:btoa(String.fromCharCode(...new Uint8Array(i.response.clientDataJSON))),attestationObject:btoa(String.fromCharCode(...new Uint8Array(i.response.attestationObject)))}};i.response.getTransports&&(n.transports=i.response.getTransports());const o=await Passkey.registerComplete({challenge_id:t.challenge_id,credential:n,friendly_name:a||"My Passkey"});return!!o.success||(this.getApp()?.toast?.error(o.error||"Failed to register passkey"),!1)}catch(e){return"NotAllowedError"===e.name?(this.getApp()?.toast?.error("Passkey creation was blocked or cancelled. Check your browser settings if this persists."),!1):("SecurityError"===e.name?this.getApp()?.toast?.error("Passkeys are not supported on this domain"):(console.error("Passkey registration error:",e),this.getApp()?.toast?.error("Passkey registration failed")),!1)}}async onActionManageTotp(){const e=this.getApp();if(this.model.get("requires_mfa")){if(!(await Pt.confirm("Disable authenticator app? You will no longer need a TOTP code to sign in.","Disable Authenticator")))return!0;const t=await d.DELETE("/api/account/totp");return t.success?(e?.toast?.success("Authenticator app disabled"),this.model.set("requires_mfa",!1),await this.render()):e?.toast?.error(t.message||"Failed to disable authenticator"),!0}const t=await d.POST("/api/account/totp/setup",{},{},{dataOnly:!0});if(!t.success||!t.data)return e?.toast?.error(t.message||"Failed to start authenticator setup"),!0;const{secret:s,qr_code:i}=t.data,a=new o({template:'\n <div style="text-align: center; padding: 0.5rem 0;">\n <p style="font-size: 0.85rem; color: #6c757d; margin-bottom: 1rem;">\n Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password, etc.)\n </p>\n <img src="{{{qrCode}}}" alt="TOTP QR Code" style="width: 200px; height: 200px; margin: 0 auto; display: block; border: 1px solid #e9ecef; border-radius: 8px; padding: 8px;" />\n <div style="margin-top: 1rem;">\n <p style="font-size: 0.75rem; color: #adb5bd; margin-bottom: 0.25rem;">Or enter this key manually:</p>\n <code style="font-size: 0.85rem; letter-spacing: 0.1em; user-select: all; padding: 0.35rem 0.75rem; background: #f8f9fa; border-radius: 4px;">{{secret}}</code>\n </div>\n </div>\n '});if(a.qrCode=i,a.secret=s,"next"!==await Pt.showDialog({title:"Set Up Authenticator",body:a,size:"sm",buttons:[{text:"Cancel",class:"btn-secondary",dismiss:!0},{text:"Next",class:"btn-primary",value:"next"}]}))return!0;const n=await Pt.prompt("Enter the 6-digit code from your authenticator app to verify setup:","Verify Authenticator",{placeholder:"000000"});if(!n)return!0;const r=await d.POST("/api/account/totp/confirm",{code:n.trim()});return r.success?(e?.toast?.success("Authenticator app enabled"),this.model.set("requires_mfa",!0),await this.render()):e?.toast?.error(r.message||"Invalid code. Please try setup again."),!0}async onActionManageRecoveryCodes(){const e=this.getApp(),t=await d.GET("/api/account/totp/recovery-codes",{},{dataOnly:!0});if(!t.success||!t.data)return e?.toast?.error(t.message||"Failed to load recovery codes"),!0;const{remaining:s,codes:i}=t.data,a=new o({template:'\n <style>\n .rc-info { font-size: 0.82rem; color: #6c757d; margin-bottom: 1rem; }\n .rc-remaining { font-weight: 600; color: #495057; }\n .rc-list { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem; }\n .rc-code { font-family: monospace; font-size: 0.85rem; padding: 0.35rem 0.6rem; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; text-align: center; }\n </style>\n <div class="rc-info">\n <span class="rc-remaining">{{remaining}}</span> recovery codes remaining\n </div>\n <div class="rc-list">\n {{#codes}}\n <div class="rc-code">{{.}}</div>\n {{/codes}}\n </div>\n '});if(a.remaining=s,a.codes=i||[],"regenerate"===await Pt.showDialog({title:"Recovery Codes",body:a,size:"sm",buttons:[{text:"Regenerate",icon:"bi-arrow-repeat",class:"btn-warning",value:"regenerate"},{text:"Close",class:"btn-outline-secondary",dismiss:!0}]})){const t=await Pt.prompt("Enter your current authenticator code to regenerate recovery codes:","Regenerate Recovery Codes",{placeholder:"000000"});if(!t)return!0;const s=await d.POST("/api/account/totp/recovery-codes/regenerate",{code:t.trim()},{},{dataOnly:!0});if(s.success&&s.data?.recovery_codes){const t=s.data.recovery_codes,i=t.join("\n"),a=new o({template:'\n <style>\n .rc-warning { padding: 0.65rem 0.85rem; background: #fff3cd; border: 1px solid #ffecb5; border-radius: 6px; margin-bottom: 1rem; font-size: 0.8rem; color: #664d03; }\n .rc-new-list { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem; margin-bottom: 1rem; }\n .rc-new-code { font-family: monospace; font-size: 0.85rem; padding: 0.35rem 0.6rem; background: #d1e7dd; border: 1px solid #badbcc; border-radius: 4px; text-align: center; }\n </style>\n <div class="rc-warning">\n <i class="bi bi-exclamation-triangle me-1"></i>\n <strong>Save these codes now.</strong> They will not be shown again. Old codes are invalidated.\n </div>\n <div class="rc-new-list">\n {{#newCodes}}\n <div class="rc-new-code">{{.}}</div>\n {{/newCodes}}\n </div>\n '});a.newCodes=t,await Pt.showDialog({title:"New Recovery Codes",body:a,size:"sm",buttons:[{text:"Copy All",icon:"bi-clipboard",class:"btn-primary",handler:async()=>{try{await navigator.clipboard.writeText(i),e?.toast?.success("Recovery codes copied")}catch{e?.toast?.error("Failed to copy codes")}return!1}},{text:"Done",class:"btn-outline-secondary",dismiss:!0}]})}else e?.toast?.error(s.message||"Failed to regenerate recovery codes")}return!0}async onActionRevokeAllSessions(){const e=this.getApp();if(!(await Pt.confirm("This will sign you out of all other devices and sessions. Your current session will remain active. This cannot be undone.","Revoke All Sessions")))return!0;const t=await Pt.prompt("Enter your current password to confirm:","Confirm Password",{placeholder:"Current password"});if(!t)return!0;const s=await d.POST("/api/auth/sessions/revoke",{current_password:t.trim()},{},{dataOnly:!0});return s.success&&s.data?(s.data.access_token&&e?.auth?.setTokens?.(s.data),e?.toast?.success("All other sessions have been revoked")):e?.toast?.error(s.message||"Failed to revoke sessions"),!0}async onActionNavigate(e,t){return!this.parent||!this.parent.onActionNavigate||this.parent.onActionNavigate(e,t)}},connected:class extends o{constructor(e={}){super({className:"profile-connected-section",template:'\n <style>\n .pc-row { display: flex; align-items: center; gap: 0.85rem; padding: 0.85rem 1rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.5rem; }\n .pc-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1rem; flex-shrink: 0; background: #f0f0f0; color: #495057; }\n .pc-info { flex: 1; min-width: 0; }\n .pc-provider { font-weight: 600; font-size: 0.88rem; text-transform: capitalize; }\n .pc-meta { font-size: 0.78rem; color: #6c757d; }\n .pc-actions { flex-shrink: 0; }\n .pc-actions .btn { font-size: 0.75rem; padding: 0.25rem 0.5rem; }\n .pc-empty { text-align: center; padding: 2rem 1rem; color: #6c757d; }\n .pc-empty i { font-size: 2rem; color: #ced4da; display: block; margin-bottom: 0.5rem; }\n </style>\n\n {{#connections}}\n <div class="pc-row">\n <div class="pc-icon"><i class="bi {{.icon}}"></i></div>\n <div class="pc-info">\n <div class="pc-provider">{{.provider}}</div>\n <div class="pc-meta">{{.email}} · Connected {{.created|relative}}</div>\n </div>\n <div class="pc-actions">\n <button type="button" class="btn btn-outline-danger" data-action="unlink" data-id="{{.id}}" title="Unlink"><i class="bi bi-x-lg me-1"></i>Unlink</button>\n </div>\n </div>\n {{/connections}}\n {{^connections|bool}}\n <div class="pc-empty">\n <i class="bi bi-plug"></i>\n No connected accounts\n <div style="font-size: 0.78rem; margin-top: 0.5rem;">\n Connect a social account by signing in with a provider while logged in.\n </div>\n </div>\n {{/connections|bool}}\n ',...e}),this.connections=[]}async onBeforeRender(){try{const e=await d.GET("/api/account/oauth_connection"),t=e?.data?.results||e?.data||[];this.connections=t.map(e=>({...e,icon:Kt[e.provider]||"bi-link-45deg"}))}catch(e){this.connections=[]}}async onActionUnlink(e,t){const s=t.dataset.id,i=this.connections.find(e=>String(e.id)===String(s)),a=i?.provider||"this account";if(!(await Pt.confirm(`Unlink ${a}? You won't be able to sign in with this provider anymore.`,"Unlink Account")))return!0;const n=await d.DELETE(`/api/account/oauth_connection/${s}`);return n.success?(this.getApp()?.toast?.success(`${a} account unlinked`),await this.render()):this.getApp()?.toast?.error(n.message||"Failed to unlink account"),!0}},sessions:class extends o{constructor(e={}){super({className:"profile-sessions-section",template:'\n <style>\n .pss-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.75rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.4rem; }\n .pss-icon { width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex-shrink: 0; background: #f0f0f0; color: #6c757d; }\n .pss-info { flex: 1; min-width: 0; }\n .pss-name { font-weight: 600; font-size: 0.85rem; }\n .pss-meta { font-size: 0.73rem; color: #6c757d; }\n </style>\n <div id="sessions-list"></div>\n ',...e})}async onInit(){await super.onInit(),this.listView=new St({containerId:"sessions-list",collection:new ht({size:20}),defaultQuery:{user:this.model.id},itemClass:SessionItem,itemTemplate:'\n <div class="pss-row">\n <div class="pss-icon"><i class="bi {{deviceIcon}}"></i></div>\n <div class="pss-info">\n <div class="pss-name">{{browserName}} on {{deviceName}}</div>\n <div class="pss-meta">{{location}} · {{model.ip_address}} · Last seen {{model.last_seen|relative}}</div>\n </div>\n </div>\n ',emptyMessage:"No session data available"}),this.addChild(this.listView)}},devices:class extends o{constructor(e={}){super({className:"profile-devices-section",template:'\n <style>\n .pd-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.75rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.4rem; }\n .pd-icon { width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex-shrink: 0; background: #f0f0f0; color: #495057; }\n .pd-info { flex: 1; min-width: 0; }\n .pd-name { font-weight: 600; font-size: 0.85rem; }\n .pd-meta { font-size: 0.73rem; color: #6c757d; }\n </style>\n <div id="devices-list"></div>\n ',...e})}async onInit(){await super.onInit(),this.listView=new St({containerId:"devices-list",collection:new mt({size:20}),defaultQuery:{user:this.model.id},itemClass:DeviceItem,itemTemplate:'\n <div class="pd-row">\n <div class="pd-icon"><i class="bi {{deviceIcon}}"></i></div>\n <div class="pd-info">\n <div class="pd-name">{{deviceName}}</div>\n <div class="pd-meta">{{browserInfo}} · {{osName}} · {{model.last_ip}} · Last seen {{model.last_seen|relative}}</div>\n </div>\n </div>\n ',emptyMessage:"No registered devices"}),this.addChild(this.listView)}},security_events:class extends o{constructor(e={}){super({className:"profile-security-events-section",template:'<div id="security-events-table"></div>',...e})}async onInit(){await super.onInit(),this.tableView=new k({containerId:"security-events-table",collection:new SecurityEventList,defaultQuery:{sort:"-created"},hideActivePillNames:["sort"],itemClass:SecurityEventRow,columns:[{key:"kind",label:"Event",template:'<span class="badge {{kindBadgeClass}}"><i class="bi {{kindIcon}} me-1"></i>{{kindLabel}}</span>'},{key:"summary",label:"Details"},{key:"ip",label:"IP",visibility:"lg"},{key:"created|relative",label:"Time",sortable:!0}],searchable:!0,sortable:!0,filterable:!1,paginated:!0,showAdd:!1,showExport:!1,tableOptions:{striped:!1,hover:!0,size:"sm"},emptyMessage:"No security events"}),this.addChild(this.tableView)}},notifications:class extends o{constructor(e={}){super({className:"profile-notifications-section",template:'\n <style>\n .pn-table { width: 100%; border-collapse: collapse; }\n .pn-table th { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; padding: 0.5rem 0.75rem; border-bottom: 2px solid #e9ecef; }\n .pn-table th:first-child { text-align: left; }\n .pn-table th:not(:first-child) { text-align: center; width: 80px; }\n .pn-table td { padding: 0.65rem 0.75rem; border-bottom: 1px solid #f0f0f0; }\n .pn-table td:first-child { font-size: 0.88rem; font-weight: 500; text-transform: capitalize; }\n .pn-table td:not(:first-child) { text-align: center; }\n .pn-table tr:last-child td { border-bottom: none; }\n .pn-empty { text-align: center; padding: 2rem 1rem; color: #6c757d; }\n .pn-empty i { font-size: 2rem; color: #ced4da; display: block; margin-bottom: 0.5rem; }\n </style>\n\n {{#hasPreferences|bool}}\n <table class="pn-table">\n <thead>\n <tr>\n <th>Type</th>\n {{#channels}}\n <th>{{.label}}</th>\n {{/channels}}\n </tr>\n </thead>\n <tbody>\n {{#preferenceRows}}\n <tr>\n <td>{{.kindLabel}}</td>\n {{#.toggles}}\n <td>\n <input type="checkbox" class="form-check-input"\n data-action="toggle-pref"\n data-kind="{{.kind}}"\n data-channel="{{.channel}}"\n {{#.checked}}checked{{/.checked}}>\n </td>\n {{/.toggles}}\n </tr>\n {{/preferenceRows}}\n </tbody>\n </table>\n {{/hasPreferences|bool}}\n {{^hasPreferences|bool}}\n <div class="pn-empty">\n <i class="bi bi-bell"></i>\n No notification preferences configured\n <div style="font-size: 0.78rem; margin-top: 0.5rem;">\n Preferences will appear here once notification types are defined.\n </div>\n </div>\n {{/hasPreferences|bool}}\n ',...e}),this.preferences={}}get channels(){return Yt.map(e=>({key:e,label:Wt[e]||e}))}get hasPreferences(){return Object.keys(this.preferences).length>0}get preferenceRows(){return Object.keys(this.preferences).sort().map(e=>({kind:e,kindLabel:e.replace(/[_-]/g," ").replace(/\b\w/g,e=>e.toUpperCase()),toggles:Yt.map(t=>({kind:e,channel:t,checked:!1!==this.preferences[e]?.[t]}))}))}async onBeforeRender(){try{const e=await d.GET("/api/account/notification/preferences",{},{dataOnly:!0});this.preferences=e?.data?.preferences||e?.data||{}}catch(e){this.preferences={}}}async onActionTogglePref(e,t){const s=t.dataset.kind,i=t.dataset.channel,a=t.checked;this.preferences[s]||(this.preferences[s]={}),this.preferences[s][i]=a;try{const e=await d.POST("/api/account/notification/preferences",{preferences:{[s]:{[i]:a}}});e.success||(this.getApp()?.toast?.error(e.message||"Failed to update preference"),t.checked=!a)}catch(n){this.getApp()?.toast?.error("Failed to update preference"),t.checked=!a}return!0}},api_keys:class extends o{constructor(e={}){super({className:"profile-api-keys-section",template:'\n <style>\n .pak-warning { padding: 0.85rem 1rem; background: #fff3cd; border: 1px solid #ffecb5; border-radius: 8px; margin-bottom: 1.25rem; font-size: 0.82rem; color: #664d03; display: flex; align-items: flex-start; gap: 0.6rem; }\n .pak-warning i { font-size: 1rem; flex-shrink: 0; margin-top: 0.1rem; }\n .pak-form-label { font-size: 0.8rem; font-weight: 600; color: #495057; margin-bottom: 0.35rem; }\n .pak-help { font-size: 0.73rem; color: #6c757d; margin-top: 0.25rem; }\n .pak-field { margin-bottom: 1rem; }\n .pak-generate { margin-top: 0.5rem; }\n .pak-result { padding: 1rem; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; margin-top: 1.25rem; }\n .pak-result-label { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #0f5132; margin-bottom: 0.5rem; }\n .pak-token-wrap { display: flex; gap: 0.5rem; align-items: center; }\n .pak-token { flex: 1; font-family: monospace; font-size: 0.78rem; padding: 0.5rem 0.75rem; background: #fff; border: 1px solid #dee2e6; border-radius: 4px; word-break: break-all; max-height: 80px; overflow-y: auto; }\n .pak-token-warning { font-size: 0.75rem; color: #dc3545; margin-top: 0.5rem; font-weight: 600; }\n </style>\n\n <div class="pak-warning">\n <i class="bi bi-exclamation-triangle-fill"></i>\n <div>\n <strong>Treat API keys like passwords.</strong> They carry your full account permissions.\n Store them securely and never expose them in client-side code.\n </div>\n </div>\n\n <div class="pak-field">\n <div class="pak-form-label">Allowed IPs</div>\n <input type="text" id="pak-allowed-ips" class="form-control form-control-sm"\n placeholder="e.g., 203.0.113.0/24, 10.0.0.1" />\n <div class="pak-help">Optional. Comma-separated IP addresses or CIDR ranges to restrict access.</div>\n </div>\n\n <div class="pak-field">\n <div class="pak-form-label">Expiration</div>\n <select id="pak-expire-days" class="form-select form-select-sm" style="max-width: 200px;">\n <option value="30">30 days</option>\n <option value="60">60 days</option>\n <option value="90" selected>90 days</option>\n <option value="180">180 days</option>\n <option value="360">360 days</option>\n </select>\n </div>\n\n <button type="button" class="btn btn-primary btn-sm pak-generate" data-action="generate-key">\n <i class="bi bi-key me-1"></i>Generate API Key\n </button>\n\n <div id="pak-result" style="display: none;">\n <div class="pak-result">\n <div class="pak-result-label">Your API Key</div>\n <div class="pak-token-wrap">\n <div class="pak-token" id="pak-token-display"></div>\n <button type="button" class="btn btn-outline-secondary btn-sm" data-action="copy-token" title="Copy">\n <i class="bi bi-clipboard"></i>\n </button>\n </div>\n <div class="pak-token-warning">\n <i class="bi bi-exclamation-circle me-1"></i>This token will not be shown again. Copy it now.\n </div>\n </div>\n </div>\n ',...e}),this.generatedToken=null}async onActionGenerateKey(){const e=this.element.querySelector("#pak-allowed-ips")?.value?.trim(),t=e?e.split(",").map(e=>e.trim()).filter(Boolean):[],s=parseInt(this.element.querySelector("#pak-expire-days")?.value||"90",10);if(!(await Pt.confirm("Generate a new API key? This key will have full access to your account."+(t.length?"":" No IP restrictions will be applied."),"Generate API Key")))return!0;const i={expire_days:s};t.length&&(i.allowed_ips=t);const a=await d.POST("/api/auth/generate_api_key",i,{},{dataOnly:!0});if(a.success&&a.data?.token){this.generatedToken=a.data.token;const e=this.element.querySelector("#pak-result"),t=this.element.querySelector("#pak-token-display");e&&t&&(t.textContent=this.generatedToken,e.style.display="block"),this.getApp()?.toast?.success("API key generated")}else this.getApp()?.toast?.error(a.message||"Failed to generate API key");return!0}async onActionCopyToken(){if(this.generatedToken)try{await navigator.clipboard.writeText(this.generatedToken),this.getApp()?.toast?.success("Token copied to clipboard")}catch{this.getApp()?.toast?.error("Failed to copy token")}return!0}},groups:class extends o{constructor(e={}){super({className:"profile-groups-section",template:'\n <style>\n .pg-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0; border-bottom: 1px solid #f0f0f0; }\n .pg-row:last-child { border-bottom: none; }\n .pg-avatar { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; color: #fff; flex-shrink: 0; }\n .pg-info { flex: 1; }\n .pg-name { font-weight: 600; font-size: 0.88rem; }\n .pg-meta { font-size: 0.73rem; color: #6c757d; }\n .pg-role { font-size: 0.7rem; }\n </style>\n <div id="groups-list"></div>\n ',...e})}async onInit(){await super.onInit(),this.listView=new St({containerId:"groups-list",collection:new A({size:50}),defaultQuery:{user:this.model.id},itemClass:GroupMemberItem,itemTemplate:'\n <div class="pg-row">\n <div class="pg-avatar" style="background: {{avatarColor}};">{{initials}}</div>\n <div class="pg-info">\n <div class="pg-name">{{groupName}}</div>\n <div class="pg-meta">{{groupKind}}</div>\n </div>\n {{#hasRole|bool}}\n <span class="pg-role badge {{roleBadgeClass}}">{{roleName}}</span>\n {{/hasRole|bool}}\n </div>\n ',emptyMessage:"You are not a member of any groups"}),this.addChild(this.listView)}},permissions:class extends o{constructor(e={}){super({className:"profile-permissions-section",template:'\n <style>\n .pp-role-bar { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 1rem; background: #f0f0ff; border-radius: 8px; margin-bottom: 1.25rem; font-size: 0.85rem; }\n .pp-role-bar i { color: #6f42c1; font-size: 1rem; }\n .pp-role-bar strong { color: #6f42c1; }\n .pp-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; }\n .pp-grid { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 1.25rem; }\n .pp-tag { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.78rem; padding: 0.25em 0.6em; background: #f8f9fc; border: 1px solid #e9ecef; border-radius: 4px; color: #495057; }\n .pp-tag i { font-size: 0.65rem; color: #198754; }\n .pp-note { font-size: 0.78rem; color: #adb5bd; margin-top: 1rem; }\n .pp-empty { color: #6c757d; font-style: italic; font-size: 0.85rem; padding: 1rem 0; }\n </style>\n\n {{#model.is_superuser|bool}}\n <div class="pp-role-bar">\n <i class="bi bi-star-fill"></i>\n <span><strong>Superuser</strong> — full system access</span>\n </div>\n {{/model.is_superuser|bool}}\n\n <div class="pp-section-label">Granted Permissions</div>\n\n {{#permissionTags|bool}}\n <div class="pp-grid">\n {{#permissionTags}}\n <span class="pp-tag"><i class="bi bi-check-circle-fill"></i> {{.}}</span>\n {{/permissionTags}}\n </div>\n {{/permissionTags|bool}}\n\n {{^permissionTags|bool}}\n <div class="pp-empty">No permissions assigned</div>\n {{/permissionTags|bool}}\n\n <div class="pp-note">\n <i class="bi bi-info-circle me-1"></i>\n Permissions are managed by your administrator.\n </div>\n ',...e})}get permissionTags(){if(!this.model)return[];const e=this.model.get("permissions");if(!e)return[];const t={};return ut.PERMISSIONS.forEach(e=>{t[e.name]=e.label}),Object.keys(e).filter(t=>!0===e[t]).map(e=>t[e]||e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase()))}}};class UserProfileView extends o{constructor(e={}){super({className:"user-profile-view",template:'\n <style>\n .up-layout { display: flex; height: 100%; }\n .up-nav { width: 200px; background: #f8f9fc; border-right: 1px solid #e9ecef; padding: 0.75rem 0; flex-shrink: 0; overflow-y: auto; }\n .up-nav-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; padding: 0.75rem 1.25rem 0.25rem; }\n .up-nav a { color: #495057; padding: 0.45rem 1.25rem; font-size: 0.85rem; display: flex; align-items: center; gap: 0.5rem; text-decoration: none; }\n .up-nav a:hover { background: #e9ecef; }\n .up-nav a.active { background: #e7f1ff; color: #0d6efd; font-weight: 600; border-right: 2px solid #0d6efd; }\n .up-nav a i { width: 18px; text-align: center; font-size: 0.9rem; }\n .up-content { flex: 1; overflow-y: auto; padding: 1.5rem 2.5rem; }\n .up-accent { height: 4px; background: linear-gradient(90deg, #1a73e8, #4fc3f7); border-radius: var(--bs-modal-border-radius, 0.5rem) var(--bs-modal-border-radius, 0.5rem) 0 0; }\n .up-header { display: flex; align-items: center; gap: 1rem; padding: 1rem 1.5rem; border-bottom: 1px solid #e9ecef; }\n .up-avatar-wrap { position: relative; flex-shrink: 0; cursor: pointer; }\n .up-avatar-wrap img { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; }\n .up-avatar-initials { width: 56px; height: 56px; border-radius: 50%; background: #e7f1ff; color: #0d6efd; display: flex; align-items: center; justify-content: center; font-size: 1.3rem; font-weight: 700; }\n .up-header-info { flex: 1; min-width: 0; }\n .up-header-name { display: flex; align-items: center; gap: 0.5rem; }\n .up-header-name h5 { margin: 0; font-weight: 700; font-size: 1.05rem; }\n .up-header-badge { font-size: 0.65rem; padding: 0.15em 0.5em; border-radius: 3px; font-weight: 600; }\n .up-header-badge-staff { background: #e7f1ff; color: #0d6efd; }\n .up-header-badge-su { background: #fff3cd; color: #856404; }\n .up-header-sub { font-size: 0.78rem; color: #6c757d; display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }\n .up-header-sub .up-dot { color: #ced4da; }\n .up-header-verified { display: inline-flex; align-items: center; gap: 0.2rem; font-size: 0.72rem; color: #198754; }\n .up-header-verified i { font-size: 0.7rem; }\n .up-close { flex-shrink: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border: none; background: none; color: #6c757d; font-size: 1.1rem; border-radius: 6px; cursor: pointer; padding: 0; align-self: flex-start; margin-top: -0.15rem; }\n .up-close:hover { background: #f0f0f0; color: #212529; }\n @media (max-width: 576px) {\n .up-nav { display: none; }\n .up-content { padding: 1.25rem; }\n }\n </style>\n <div class="up-layout" style="flex-direction: column; min-height: 480px;">\n <div class="up-accent"></div>\n <div class="up-header">\n <div class="up-avatar-wrap" data-action="change-avatar" title="Change avatar">\n {{{model.avatar|avatar}}}\n </div>\n <div class="up-header-info">\n <div class="up-header-name">\n <h5>{{model.display_name}}</h5>\n {{#model.is_superuser|bool}}<span class="up-header-badge up-header-badge-su">Superuser</span>{{/model.is_superuser|bool}}\n {{^model.is_superuser|bool}}\n {{#model.is_staff|bool}}<span class="up-header-badge up-header-badge-staff">Staff</span>{{/model.is_staff|bool}}\n {{/model.is_superuser|bool}}\n </div>\n <div class="up-header-sub">\n <span>{{model.email}}</span>\n {{#model.is_email_verified|bool}}\n <span class="up-dot">·</span>\n <span class="up-header-verified"><i class="bi bi-patch-check-fill"></i> Email</span>\n {{/model.is_email_verified|bool}}\n {{#model.requires_mfa|bool}}\n <span class="up-dot">·</span>\n <span class="up-header-verified"><i class="bi bi-shield-fill-check"></i> MFA</span>\n {{/model.requires_mfa|bool}}\n </div>\n </div>\n <button type="button" class="up-close" data-action="close-dialog" title="Close"><i class="bi bi-x-lg"></i></button>\n </div>\n <div class="up-layout" style="flex: 1; min-height: 0;">\n <nav class="up-nav">\n <a href="#" class="nav-link active" data-action="navigate" data-section="profile"><i class="bi bi-person"></i> Profile</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="personal"><i class="bi bi-person-vcard"></i> Personal</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="security"><i class="bi bi-shield-lock"></i> Security</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="connected"><i class="bi bi-plug"></i> Connected</a>\n <div class="up-nav-label">Activity</div>\n <a href="#" class="nav-link" data-action="navigate" data-section="sessions"><i class="bi bi-clock-history"></i> Sessions</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="devices"><i class="bi bi-laptop"></i> Devices</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="security_events"><i class="bi bi-shield-exclamation"></i> Security Events</a>\n <div class="up-nav-label">Settings</div>\n <a href="#" class="nav-link" data-action="navigate" data-section="notifications"><i class="bi bi-bell"></i> Notifications</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="api_keys"><i class="bi bi-key"></i> API Keys</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="groups"><i class="bi bi-people"></i> Groups</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="permissions"><i class="bi bi-shield-check"></i> Permissions</a>\n </nav>\n <div class="up-content" id="profile-section"></div>\n </div>\n </div>\n ',...e}),this.activeSection="profile",this.sectionView=null}get hasAvatar(){return!!(this.model&&this.model.get("avatar")&&this.model.get("avatar").url)}async onBeforeRender(){this.model&&await this.model.fetch({params:{graph:"full"}})}async onAfterRender(){this.element?.querySelectorAll(".up-nav a").forEach(e=>{e.classList.toggle("active",e.dataset.section===this.activeSection)})}async onInit(){await super.onInit(),this.sectionView=new ProfileOverviewSection({model:this.model,containerId:"profile-section"}),this.addChild(this.sectionView)}async onActionNavigate(e,t){e.preventDefault();const s=t.dataset.section;if(!s||s===this.activeSection)return!0;const i=Zt[s];return!i||(this.sectionView&&this.removeChild(this.sectionView),this.sectionView=new i({model:this.model,containerId:"profile-section"}),this.addChild(this.sectionView),await this.sectionView.render(),this.activeSection=s,this.element.querySelectorAll(".up-nav a").forEach(e=>{e.classList.toggle("active",e.dataset.section===s)}),!0)}async onActionChangeAvatar(){const e=await Pt.updateModelImage({model:this.model,field:"avatar",title:"Change Avatar",upload:!0},{name:"avatar",size:"lg",imageSize:{width:200,height:200},placeholder:"Upload your avatar"});e&&200===e.status&&await this.render()}async onActionCloseDialog(){const e=this.element?.closest(".modal");if(e){const t=bootstrap?.Modal?.getInstance(e);t&&t.hide()}return!0}async onActionChangePassword(){const e=this.getApp();return e&&e.changePassword&&await e.changePassword(),!0}async onActionUpdateEmail(){const e=this.getApp(),t=e.rest,s=await Pt.showForm({title:"Change Email Address",size:"sm",submitText:"Send Code",fields:[{name:"new_email_address",type:"text",label:"New Email Address",required:!0,placeholder:"Enter new email address",attributes:{autocomplete:"off",inputmode:"email"},cols:12}]});if(!s)return!0;const i=await t.POST("/api/auth/email/change/request",{email:s.new_email_address,method:"code"});if(!i.success)return e.toast.error(i.message||"Failed to request email change"),!0;const a=await Pt.prompt(`Enter the 6-digit code sent to <strong>${s.new_email_address}</strong>`,"Confirm Email Change",{placeholder:"000000"});if(!a)return!0;const n=await t.POST("/api/auth/email/change/confirm",{code:a.trim()},{},{dataOnly:!0});return n.success&&n.data?(n.data.access_token&&e.auth?.setTokens?.(n.data),e.toast.success("Email address updated"),await this.model.fetch({params:{graph:"full"}}),await this.render()):e.toast.error(n.message||"Invalid or expired code"),!0}async onActionUpdatePhone(){const e=this.getApp(),t=e.rest;if(!this.model.get("phone_number"))return e.toast.info("Use the profile section to add a phone number"),!0;const s=await Pt.showForm({title:"Change Phone Number",size:"sm",submitText:"Send Code",fields:[{name:"new_phone",type:"tel",label:"New Phone Number",required:!0,placeholder:"+14155550123",attributes:{autocomplete:"off"},cols:12}]});if(!s)return!0;const i=await t.POST("/api/auth/phone/change/request",{phone_number:s.new_phone},{},{dataOnly:!0});if(!i.success)return e.toast.error(i.message||"Failed to request phone change"),!0;const a=i.data?.session_token,n=await Pt.prompt(`Enter the 6-digit code sent to <strong>${s.new_phone}</strong>`,"Confirm Phone Change",{placeholder:"000000"});if(!n)return!0;const o=await t.POST("/api/auth/phone/change/confirm",{session_token:a,code:n.trim()});return o.success?(e.toast.success("Phone number updated"),await this.model.fetch({params:{graph:"full"}}),await this.render()):e.toast.error(o.message||"Invalid or expired code"),!0}}class PasskeySetupView extends o{constructor(e={}){super({className:"passkey-setup-view",template:'\n <style>\n .pks-body { padding: 2rem 1.75rem 1rem; text-align: center; }\n .pks-icon { width: 56px; height: 56px; background: #e7f1ff; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 1.5rem; color: #0d6efd; margin-bottom: 1rem; }\n .pks-body h5 { font-weight: 700; font-size: 1.05rem; margin-bottom: 0.35rem; }\n .pks-body p { font-size: 0.83rem; color: #6c757d; margin-bottom: 1.25rem; line-height: 1.45; }\n .pks-footer { padding: 0 1.75rem 1.5rem; display: flex; flex-direction: column; gap: 0.4rem; }\n .pks-footer .btn-create { padding: 0.6rem; font-weight: 600; font-size: 0.9rem; border-radius: 8px; }\n .pks-footer .btn-skip { background: none; border: none; color: #6c757d; font-size: 0.82rem; padding: 0.4rem; cursor: pointer; }\n .pks-footer .btn-skip:hover { color: #495057; }\n .pks-dont-show { text-align: center; padding: 0 1.75rem 1.25rem; }\n .pks-dont-show label { font-size: 0.73rem; color: #adb5bd; cursor: pointer; }\n </style>\n\n <div class="pks-body">\n <div class="pks-icon"><i class="bi bi-fingerprint"></i></div>\n <h5>Add a Passkey</h5>\n <p>Sign in faster with Face ID, Touch ID, or your device PIN. No passwords needed.</p>\n </div>\n <div class="pks-footer">\n <button class="btn btn-primary btn-create" data-action="create-passkey"><i class="bi bi-fingerprint me-1"></i>Create Passkey</button>\n <button class="btn-skip" data-action="skip">Not now</button>\n </div>\n <div class="pks-dont-show">\n <label><input type="checkbox" class="form-check-input form-check-input-sm me-1" data-action="dont-ask"> Don\'t ask again</label>\n </div>\n ',...e})}_base64urlToBytes(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),s=t+"=".repeat((4-t.length%4)%4);return Uint8Array.from(atob(s),e=>e.charCodeAt(0))}async onActionCreatePasskey(){try{const e=await Passkey.registerBegin();if(!e.success||!e.data)return this.getApp()?.toast?.error("Failed to start passkey registration"),!0;const t=e.data.data||e.data,s=t.publicKey;s.rp&&(s.rp.id=window.location.hostname),s.challenge&&"string"==typeof s.challenge&&(s.challenge=this._base64urlToBytes(s.challenge)),s.user&&s.user.id&&"string"==typeof s.user.id&&(s.user.id=this._base64urlToBytes(s.user.id)),s.excludeCredentials&&(s.excludeCredentials=s.excludeCredentials.map(e=>({...e,id:"string"==typeof e.id?this._base64urlToBytes(e.id):e.id})));const i=await navigator.credentials.create({publicKey:s});if(!i)return this.getApp()?.toast?.error("Passkey creation was cancelled"),!0;const a=await Pt.prompt("Name this passkey:","Passkey Name",{defaultValue:"",placeholder:"e.g., My MacBook"}),n={id:i.id,rawId:btoa(String.fromCharCode(...new Uint8Array(i.rawId))),type:i.type,response:{clientDataJSON:btoa(String.fromCharCode(...new Uint8Array(i.response.clientDataJSON))),attestationObject:btoa(String.fromCharCode(...new Uint8Array(i.response.attestationObject)))}};i.response.getTransports&&(n.transports=i.response.getTransports());const o=await Passkey.registerComplete({challenge_id:t.challenge_id,credential:n,friendly_name:a||"My Passkey"});o.success?(this.getApp()?.toast?.success("Passkey registered successfully"),localStorage.setItem("passkey_setup_dismissed","1"),this.emit("dismiss")):this.getApp()?.toast?.error(o.error||"Failed to register passkey")}catch(e){if("NotAllowedError"===e.name)return!0;"SecurityError"===e.name?this.getApp()?.toast?.error("Passkeys are not supported on this domain"):(console.error("Passkey registration error:",e),this.getApp()?.toast?.error("Passkey registration failed"))}return!0}async onActionSkip(){return this.emit("dismiss"),!0}async onActionDontAsk(){const e=this.element?.querySelector('.pks-dont-show input[type="checkbox"]');return e&&e.checked?(localStorage.setItem("passkey_setup_dismissed","1"),this.emit("dismiss")):localStorage.removeItem("passkey_setup_dismissed"),!0}}const Xt=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,PasskeySetupView:PasskeySetupView,UserProfileView:UserProfileView},Symbol.toStringTag,{value:"Module"}));Bt.install({level:"warn"});const es="MOJO",ts="web-mojo",ss={FRAMEWORK_NAME:es,PACKAGE_NAME:ts};export{e as BUILD_TIME,P as BundleByOptions,S as ChatInputView,_ as ChatMessageView,C as ChatView,y as Collection,T as CommonEventFields,I as CommonScopeOptions,M as ComparatorOptions,Bt as ConsoleSilencer,m as ContextMenu,Ct as DataView,c as DataWrapper,Pt as Dialog,N as DjangoLookups,E as EmailDomain,L as EmailDomainForms,z as EmailDomainList,D as EmailTemplate,F as EmailTemplateForms,O as EmailTemplateList,f as EventBus,p as EventDelegate,es as FRAMEWORK_NAME,tt as File,st as FileForms,it as FileList,at as FileManager,nt as FileManagerForms,ot as FileManagerList,R as FilePreviewView,rt as FileUpload,FormPage,Tt as FormView,V as GeoLocatedIP,G as GeoLocatedIPList,ct as Group,gt as GroupForms,dt as GroupList,U as Incident,B as IncidentEvent,H as IncidentEventForms,q as IncidentEventList,j as IncidentForms,K as IncidentHistory,$ as IncidentHistoryList,J as IncidentList,W as IncidentRule,Y as IncidentRuleList,Q as IncidentRuleSet,Z as IncidentRuleSetList,X as IncidentStats,ee as Job,te as JobEvent,se as JobEventList,ie as JobForms,ae as JobList,ne as JobLog,oe as JobLogList,re as JobRunner,le as JobRunnerForms,de as JobRunnerList,ce as JobsEngineStats,pe as LOOKUPS,St as ListView,_t as ListViewItem,ue as Log,he as LogList,u as MOJOUtils,me as Mailbox,ge as MailboxForms,fe as MailboxList,be as MatchByOptions,w as Member,ve as MemberForms,A as MemberList,ye as MetricsForms,we as MetricsPermission,ke as MetricsPermissionList,v as Model,qt as MustacheFormatter,ts as PACKAGE_NAME,h as Page,PasskeySetupView,PortalApp,lt as ProgressView,xe as PushConfig,Ae as PushConfigForms,Pe as PushConfigList,Se as PushDelivery,_e as PushDeliveryList,Ce as PushDevice,Te as PushDeviceList,Ie as PushTemplate,Me as PushTemplateForms,Ne as PushTemplateList,d as Rest,b as Router,Ee as Rule,Le as RuleForms,ze as RuleList,De as RuleSet,Fe as RuleSetForms,Oe as RuleSetList,Re as S3Bucket,Ve as S3BucketForms,Ge as S3BucketList,Ue as SentMessage,Be as SentMessageForms,He as SentMessageList,Sidebar,kt as SimpleSearchView,qe as TabView,je as TablePage,x as TableRow,k as TableView,Ke as Ticket,$e as TicketCategories,Je as TicketForms,We as TicketList,Ye as TicketNote,Qe as TicketNoteList,pt as ToastService,xt as TokenManager,At as TopNav,ut as User,ft as UserDataView,bt as UserDevice,mt as UserDeviceList,vt as UserDeviceLocation,ht as UserDeviceLocationList,yt as UserForms,wt as UserList,UserProfileView,t as VERSION,s as VERSION_INFO,i as VERSION_MAJOR,a as VERSION_MINOR,n as VERSION_REVISION,Ze as ValueTypeOptions,o as View,g as WebApp,Mt as WebSocketClient,It as applyFileDropMixin,r as dataFormatter,ss as default,Xe as formatFilterDisplay,Ht as installConsoleSilencer,et as parseFilterKey};
|
|
2
2
|
//# sourceMappingURL=index.es.js.map
|