fix: stop aggressive API polling and SSE reconnect when auth lost
When unauthenticated (TOTP gate active), the dashboard was polling /api/v1/services/status every few seconds and reconnecting SSE every 2-8s indefinitely. Now: 401/403 sets _dcAuthLost flag to skip the polling interval, and SSE stops after 5 consecutive failures.
This commit is contained in:
5 files changed
+56
-35
No files matched your search
Vendored
+32
-32
@@ -1,4 +1,4 @@
|
||||
(function(l){"use strict";class o{constructor(){this.errors=[],this.maxErrors=50}logError(h,f,m={}){const b={timestamp:new Date().toISOString(),context:h,message:f instanceof Error?f.message:f,stack:f instanceof Error?f.stack:null,metadata:m};this.errors.push(b),this.errors.length>this.maxErrors&&this.errors.shift(),console.error(`[Onboarding Error] ${h}:`,f,m)}recoverFromError(h,f){switch(this.classifyError(h)){case"ELEMENT_NOT_FOUND":return this.logError("Element Not Found",h,{currentStep:f}),{action:"SKIP_STEP",nextStep:f+1,message:"Target element not found, skipping to next step"};case"STORAGE_UNAVAILABLE":return this.logError("Storage Unavailable",h),{action:"USE_MEMORY_STORAGE",message:"Local storage unavailable, using in-memory storage"};case"DRIVER_NOT_LOADED":return this.logError("Driver.js Not Loaded",h),{action:"ABORT_TOUR",message:"Driver.js library not loaded, cannot start tour"};case"INVALID_TOOLTIP":return this.logError("Invalid Tooltip Configuration",h,{currentStep:f}),{action:"SKIP_STEP",nextStep:f+1,message:"Invalid tooltip configuration, skipping"};case"THEME_DETECTION_FAILED":return this.logError("Theme Detection Failed",h),{action:"USE_DEFAULT_THEME",message:"Using default dark theme"};default:return this.logError("Unknown Error",h,{currentStep:f}),{action:"ABORT_TOUR",message:"Unexpected error occurred, aborting tour"}}}classifyError(h){const f=h.message||h.toString();return f.includes("element")&&f.includes("not found")?"ELEMENT_NOT_FOUND":f.includes("storage")||f.includes("quota")?"STORAGE_UNAVAILABLE":f.includes("driver")||f.includes("undefined")?"DRIVER_NOT_LOADED":f.includes("invalid")||f.includes("validation")?"INVALID_TOOLTIP":f.includes("theme")?"THEME_DETECTION_FAILED":"UNKNOWN"}getErrors(){return[...this.errors]}clearErrors(){this.errors=[]}getStatistics(){const h={total:this.errors.length,byContext:{},byType:{},recent:this.errors.slice(-10)};return this.errors.forEach(f=>{h.byContext[f.context]=(h.byContext[f.context]||0)+1;const m=this.classifyError({message:f.message});h.byType[m]=(h.byType[m]||0)+1}),h}handleDriverLoadFailure(){this.logError("Driver.js Load Failure","Driver.js library failed to load");const h=document.createElement("div");return h.id="onboarding-fallback",h.style.cssText=`
|
||||
(function(l){"use strict";class o{constructor(){this.errors=[],this.maxErrors=50}logError(b,f,v={}){const g={timestamp:new Date().toISOString(),context:b,message:f instanceof Error?f.message:f,stack:f instanceof Error?f.stack:null,metadata:v};this.errors.push(g),this.errors.length>this.maxErrors&&this.errors.shift(),console.error(`[Onboarding Error] ${b}:`,f,v)}recoverFromError(b,f){switch(this.classifyError(b)){case"ELEMENT_NOT_FOUND":return this.logError("Element Not Found",b,{currentStep:f}),{action:"SKIP_STEP",nextStep:f+1,message:"Target element not found, skipping to next step"};case"STORAGE_UNAVAILABLE":return this.logError("Storage Unavailable",b),{action:"USE_MEMORY_STORAGE",message:"Local storage unavailable, using in-memory storage"};case"DRIVER_NOT_LOADED":return this.logError("Driver.js Not Loaded",b),{action:"ABORT_TOUR",message:"Driver.js library not loaded, cannot start tour"};case"INVALID_TOOLTIP":return this.logError("Invalid Tooltip Configuration",b,{currentStep:f}),{action:"SKIP_STEP",nextStep:f+1,message:"Invalid tooltip configuration, skipping"};case"THEME_DETECTION_FAILED":return this.logError("Theme Detection Failed",b),{action:"USE_DEFAULT_THEME",message:"Using default dark theme"};default:return this.logError("Unknown Error",b,{currentStep:f}),{action:"ABORT_TOUR",message:"Unexpected error occurred, aborting tour"}}}classifyError(b){const f=b.message||b.toString();return f.includes("element")&&f.includes("not found")?"ELEMENT_NOT_FOUND":f.includes("storage")||f.includes("quota")?"STORAGE_UNAVAILABLE":f.includes("driver")||f.includes("undefined")?"DRIVER_NOT_LOADED":f.includes("invalid")||f.includes("validation")?"INVALID_TOOLTIP":f.includes("theme")?"THEME_DETECTION_FAILED":"UNKNOWN"}getErrors(){return[...this.errors]}clearErrors(){this.errors=[]}getStatistics(){const b={total:this.errors.length,byContext:{},byType:{},recent:this.errors.slice(-10)};return this.errors.forEach(f=>{b.byContext[f.context]=(b.byContext[f.context]||0)+1;const v=this.classifyError({message:f.message});b.byType[v]=(b.byType[v]||0)+1}),b}handleDriverLoadFailure(){this.logError("Driver.js Load Failure","Driver.js library failed to load");const b=document.createElement("div");return b.id="onboarding-fallback",b.style.cssText=`
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
@@ -10,20 +10,20 @@
|
||||
z-index: 9999;
|
||||
max-width: 300px;
|
||||
font-size: 14px;
|
||||
`,h.innerHTML=`
|
||||
`,b.innerHTML=`
|
||||
<strong>Welcome to DashCaddy!</strong><br>
|
||||
<p style="margin: 10px 0 0 0; font-size: 12px;">
|
||||
The interactive tour is unavailable, but you can explore the dashboard freely.
|
||||
Check the documentation for help getting started.
|
||||
</p>
|
||||
`,document.body.appendChild(h),setTimeout(()=>{h.parentNode&&h.parentNode.removeChild(h)},1e4),!0}handleStorageUnavailable(){this.logError("Storage Unavailable","Local storage is not available");const h={data:{},getItem(f){return this.data[f]||null},setItem(f,m){this.data[f]=m},removeItem(f){delete this.data[f]},clear(){this.data={}}};return console.warn("[ErrorHandler] Using in-memory storage - progress will not persist"),h}sendToErrorTracking(h){}}l.ErrorHandler=o,console.log("[ErrorHandler] Module loaded")})(window),(function(){try{var o=typeof localStorage<"u"&&localStorage.getItem("dashcaddy-health-settings")||null;if(o){var g=JSON.parse(o);g.statsPollingInterval&&g.statsPollingInterval>=5&&g.statsPollingInterval<=3600&&(window.__DC_STATS_OVERRIDE=g.statsPollingInterval*1e3)}}catch{}})();const DC={NAME:"DashCaddy",POLL:{DASHBOARD:1e4,LOGS:3e3,STATS:typeof window<"u"&&window.__DC_STATS_OVERRIDE||5e3,WEATHER:6e5,HEALTH:1e3,DEPLOY_SSL:5e3},DELAYS:{BTN_RESET:2e3,RELOAD:5e3,MODAL_CLOSE:500,PORT_CHECK:500,DEPLOY_INIT:3e3},DEFAULTS:{DNS_PORT:"5380",SERVICE_PORT:"8080",TTL:300,CADDYFILE:"C:\\caddy\\Caddyfile"}},errorHandler=new ErrorHandler,_cachedCfg=JSON.parse(localStorage.getItem("dashcaddy_site_config")||"null"),SITE={tld:_cachedCfg&&_cachedCfg.tld||".home",dnsIp:"",dnsPort:DC.DEFAULTS.DNS_PORT,dnsServers:{},configurationType:_cachedCfg&&_cachedCfg.configurationType||"homelab",domain:_cachedCfg&&_cachedCfg.domain||"",defaults:_cachedCfg&&_cachedCfg.defaults||{},routingMode:_cachedCfg&&_cachedCfg.routingMode||"subdomain",onboardingCompleted:!1};window.__dashcaddySiteConfigLoaded=(async function(){try{const h=await fetch("/api/v1/config");if(h.ok){const f=await h.json();if(f.tld&&(SITE.tld=f.tld.startsWith(".")?f.tld:"."+f.tld),f.dns&&(SITE.dnsIp=f.dns.ip||"",SITE.dnsPort=f.dns.port||DC.DEFAULTS.DNS_PORT),f.dnsServers&&typeof f.dnsServers=="object")for(const[b,r]of Object.entries(f.dnsServers))b!=="__proto__"&&b!=="constructor"&&b!=="prototype"&&(SITE.dnsServers[b]=r);f.configurationType&&(SITE.configurationType=f.configurationType),f.domain&&(SITE.domain=f.domain),f.defaults&&(SITE.defaults=f.defaults),f.routingMode&&(SITE.routingMode=f.routingMode),SITE.onboardingCompleted=f.onboardingCompleted===!0,localStorage.setItem("dashcaddy_site_config",JSON.stringify({tld:SITE.tld,configurationType:SITE.configurationType,domain:SITE.domain,routingMode:SITE.routingMode})),renderDnsCards();const m=document.getElementById("manage-tokens");m&&(m.style.display=Object.keys(SITE.dnsServers).length?"":"none")}}catch{}document.querySelectorAll("[data-tld]").forEach(h=>h.textContent=SITE.tld);const o=document.getElementById("edit-tld-suffix");o&&(o.textContent=SITE.tld);const g=document.getElementById("external-proxy-ip");g&&SITE.dnsIp&&(g.value=SITE.dnsIp,g.placeholder=SITE.dnsIp)})();function buildDomain(l){return l+SITE.tld}function buildServiceUrl(l){return SITE.routingMode==="subdirectory"&&SITE.domain?"https://"+SITE.domain+"/"+l:SITE.configurationType==="public"&&SITE.domain?"https://"+l+"."+SITE.domain:"https://"+buildDomain(l)}function getDnsServerAddr(l){const o=SITE.dnsServers[l];return o?`${o.ip}:${o.port}`:buildDomain(l)}function getPrimaryDnsId(){if(!SITE.dnsIp)return null;for(const[l,o]of Object.entries(SITE.dnsServers))if(o.ip===SITE.dnsIp)return l;return null}function renderDnsCards(){const l=document.querySelector(".top");if(!l)return;const o=Object.keys(SITE.dnsServers);if(!o.length)return;const g='<svg viewBox="0 0 24 24" class="service-icon"><rect x="3" y="4" width="18" height="16" rx="2" fill="#34495e"/><rect x="5" y="6" width="14" height="2" rx="1" fill="#3498db"/><rect x="5" y="9" width="10" height="1" fill="#ecf0f1"/><rect x="5" y="11" width="12" height="1" fill="#ecf0f1"/><rect x="5" y="13" width="8" height="1" fill="#ecf0f1"/><rect x="5" y="15" width="14" height="1" fill="#ecf0f1"/><circle cx="17" cy="11" r="2" fill="#e74c3c"/><path d="M17 9v4M15 11h4" stroke="white" stroke-width="1"/></svg>',h=l.firstElementChild;o.forEach(f=>{const m=escapeHtml(f),b=escapeHtml((SITE.dnsServers[f].name||f).toUpperCase()),r=document.createElement("div");r.className="card",r.setAttribute("data-app",f),r.setAttribute("data-status","off"),r.innerHTML=`<span id="${m}-dot" class="dot bad at-bl"></span><div class="row"><div class="logo-wrap">${g}</div><span class="name">${b}</span><span class="spacer"></span><span id="${m}-pill" class="badge off">OFF</span></div><div class="response-row"><span id="${m}-time" class="response-time">--</span></div><div class="health-row" id="health-${m}"><span id="uptime-${m}" class="uptime-chip">--</span><div class="uptime-mini-bar"><div class="fill" id="uptime-bar-${m}" style="width: 0%"></div></div></div><div class="btn-row"><button id="${m}-restart" class="restart-btn">Restart</button><button id="${m}-update" class="update-btn" title="Update DNS server">\u2B06\uFE0F</button><button id="${m}-open">Open</button><button id="${m}-logs" class="logs-btn">Logs</button><button id="${m}-sLine truncated
|
||||
`,document.body.appendChild(b),setTimeout(()=>{b.parentNode&&b.parentNode.removeChild(b)},1e4),!0}handleStorageUnavailable(){this.logError("Storage Unavailable","Local storage is not available");const b={data:{},getItem(f){return this.data[f]||null},setItem(f,v){this.data[f]=v},removeItem(f){delete this.data[f]},clear(){this.data={}}};return console.warn("[ErrorHandler] Using in-memory storage - progress will not persist"),b}sendToErrorTracking(b){}}l.ErrorHandler=o,console.log("[ErrorHandler] Module loaded")})(window),(function(){try{var o=typeof localStorage<"u"&&localStorage.getItem("dashcaddy-health-settings")||null;if(o){var m=JSON.parse(o);m.statsPollingInterval&&m.statsPollingInterval>=5&&m.statsPollingInterval<=3600&&(window.__DC_STATS_OVERRIDE=m.statsPollingInterval*1e3)}}catch{}})();const DC={NAME:"DashCaddy",POLL:{DASHBOARD:1e4,LOGS:3e3,STATS:typeof window<"u"&&window.__DC_STATS_OVERRIDE||5e3,WEATHER:6e5,HEALTH:1e3,DEPLOY_SSL:5e3},DELAYS:{BTN_RESET:2e3,RELOAD:5e3,MODAL_CLOSE:500,PORT_CHECK:500,DEPLOY_INIT:3e3},DEFAULTS:{DNS_PORT:"5380",SERVICE_PORT:"8080",TTL:300,CADDYFILE:"C:\\caddy\\Caddyfile"}},errorHandler=new ErrorHandler,_cachedCfg=JSON.parse(localStorage.getItem("dashcaddy_site_config")||"null"),SITE={tld:_cachedCfg&&_cachedCfg.tld||".home",dnsIp:"",dnsPort:DC.DEFAULTS.DNS_PORT,dnsServers:{},configurationType:_cachedCfg&&_cachedCfg.configurationType||"homelab",domain:_cachedCfg&&_cachedCfg.domain||"",defaults:_cachedCfg&&_cachedCfg.defaults||{},routingMode:_cachedCfg&&_cachedCfg.routingMode||"subdomain",onboardingCompleted:!1};window.__dashcaddySiteConfigLoaded=(async function(){try{const b=await fetch("/api/v1/config");if(b.ok){const f=await b.json();if(f.tld&&(SITE.tld=f.tld.startsWith(".")?f.tld:"."+f.tld),f.dns&&(SITE.dnsIp=f.dns.ip||"",SITE.dnsPort=f.dns.port||DC.DEFAULTS.DNS_PORT),f.dnsServers&&typeof f.dnsServers=="object")for(const[g,r]of Object.entries(f.dnsServers))g!=="__proto__"&&g!=="constructor"&&g!=="prototype"&&(SITE.dnsServers[g]=r);f.configurationType&&(SITE.configurationType=f.configurationType),f.domain&&(SITE.domain=f.domain),f.defaults&&(SITE.defaults=f.defaults),f.routingMode&&(SITE.routingMode=f.routingMode),SITE.onboardingCompleted=f.onboardingCompleted===!0,localStorage.setItem("dashcaddy_site_config",JSON.stringify({tld:SITE.tld,configurationType:SITE.configurationType,domain:SITE.domain,routingMode:SITE.routingMode})),renderDnsCards();const v=document.getElementById("manage-tokens");v&&(v.style.display=Object.keys(SITE.dnsServers).length?"":"none")}}catch{}document.querySelectorAll("[data-tld]").forEach(b=>b.textContent=SITE.tld);const o=document.getElementById("edit-tld-suffix");o&&(o.textContent=SITE.tld);const m=document.getElementById("external-proxy-ip");m&&SITE.dnsIp&&(m.value=SITE.dnsIp,m.placeholder=SITE.dnsIp)})();function buildDomain(l){return l+SITE.tld}function buildServiceUrl(l){return SITE.routingMode==="subdirectory"&&SITE.domain?"https://"+SITE.domain+"/"+l:SITE.configurationType==="public"&&SITE.domain?"https://"+l+"."+SITE.domain:"https://"+buildDomain(l)}function getDnsServerAddr(l){const o=SITE.dnsServers[l];return o?`${o.ip}:${o.port}`:buildDomain(l)}function getPrimaryDnsId(){if(!SITE.dnsIp)return null;for(const[l,o]of Object.entries(SITE.dnsServers))if(o.ip===SITE.dnsIp)return l;return null}function renderDnsCards(){const l=document.querySelector(".top");if(!l)return;const o=Object.keys(SITE.dnsServers);if(!o.length)return;const m='<svg viewBox="0 0 24 24" class="service-icon"><rect x="3" y="4" width="18" height="16" rx="2" fill="#34495e"/><rect x="5" y="6" width="14" height="2" rx="1" fill="#3498db"/><rect x="5" y="9" width="10" height="1" fill="#ecf0f1"/><rect x="5" y="11" width="12" height="1" fill="#ecf0f1"/><rect x="5" y="13" width="8" height="1" fill="#ecf0f1"/><rect x="5" y="15" width="14" height="1" fill="#ecf0f1"/><circle cx="17" cy="11" r="2" fill="#e74c3c"/><path d="M17 9v4M15 11h4" stroke="white" stroke-width="1"/></svg>',b=l.firstElementChild;o.forEach(f=>{const v=escapeHtml(f),g=escapeHtml((SITE.dnsServers[f].name||f).toUpperCase()),r=document.createElement("div");r.className="card",r.setAttribute("data-app",f),r.setAttribute("data-status","off"),r.innerHTML=`<span id="${v}-dot" class="dot bad at-bl"></span><div class="row"><div class="logo-wrap">${m}</div><span class="name">${g}</span><span class="spacer"></span><span id="${v}-pill" class="badge off">OFF</span></div><div class="response-row"><span id="${v}-time" class="response-time">--</span></div><div class="health-row" id="health-${v}"><span id="uptime-${v}" class="uptime-chip">--</span><div class="uptime-mini-bar"><div class="fill" id="uptime-bar-${v}" style="width: 0%"></div></div></div><div class="btn-row"><button id="${v}-restart" class="restart-btn">Restart</button><button id="${v}-update" class="update-btn" title="Update DNS server">\u2B06\uFE0F</button><button id="${v}-open">Open</button><button id="${v}-logs" class="logs-btn">Logs</button><button id="${v}-sLine truncated
|
||||
position: fixed; top: 20px; right: 20px;
|
||||
background: ${m.bg}; color: ${m.fg};
|
||||
background: ${v.bg}; color: ${v.fg};
|
||||
padding: 16px 24px; border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,.3);
|
||||
z-index: 10000; animation: slideIn 0.3s ease-out;
|
||||
max-width: 400px; white-space: pre-line; font-size: 14px;
|
||||
`,document.body.appendChild(b),g>0&&setTimeout(()=>b.remove(),g)}function timeAgo(l){const o=Date.now()-new Date(l).getTime();return o<6e4?"just now":o<36e5?Math.floor(o/6e4)+"m ago":o<864e5?Math.floor(o/36e5)+"h ago":Math.floor(o/864e5)+"d ago"}function safeGet(l,o=null){try{const g=localStorage.getItem(l);return g!==null?g:o}catch{return o}}function safeSet(l,o){try{localStorage.setItem(l,o)}catch{}}function safeRemove(l){try{localStorage.removeItem(l)}catch{}}function safeSessionGet(l,o=null){try{const g=sessionStorage.getItem(l);return g!==null?g:o}catch{return o}}function safeSessionSet(l,o){try{sessionStorage.setItem(l,o)}catch{}}function safeGetJSON(l,o=null){try{const g=localStorage.getItem(l);return g?JSON.parse(g):o}catch{return o}}function escapeHtml(l){return String(l??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function injectModal(l,o){document.getElementById(l)||document.body.insertAdjacentHTML("beforeend",o)}const DC_BUS={_handlers:{},on(l,o){var g;((g=this._handlers)[l]||(g[l]=[])).push(o)},off(l,o){this._handlers[l]=this._handlers[l]?.filter(g=>g!==o)},emit(l,o){this._handlers[l]?.forEach(g=>g(o))}},AppState={_apps:[],getApps(){return this._apps},setApps(l){this._apps=l,window.APPS=l,DC_BUS.emit("apps:changed",l)},findApp(l){return this._apps.find(o=>o.id===l)},addApp(l){this._apps.push(l),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)},removeApp(l){const o=this._apps.findIndex(g=>g.id===l);return o>-1&&(this._apps.splice(o,1),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)),o>-1},updateApp(l,o){const g=this._apps.find(h=>h.id===l);if(g){for(const[h,f]of Object.entries(o))h!=="__proto__"&&h!=="constructor"&&h!=="prototype"&&(g[h]=f);DC_BUS.emit("apps:changed",this._apps)}return g}};(function(){function l(){const h=document.createElement("div");return h.className="skeleton-card",h.innerHTML='<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px"><div class="skeleton-bar" style="width:48px;height:48px;border-radius:8px;flex-shrink:0"></div><div style="flex:1"><div class="skeleton-bar" style="width:60%;height:14px;margin-bottom:6px"></div><div class="skeleton-bar" style="width:35%;height:10px"></div></div><div class="skeleton-bar" style="width:42px;height:22px;border-radius:11px"></div></div><div class="skeleton-bar" style="width:45%;height:12px;margin-bottom:10px"></div><div style="display:flex;gap:8px;margin-top:auto"><div class="skeleton-bar" style="width:64px;height:28px;border-radius:8px"></div><div class="skeleton-bar" style="width:64px;height:28px;border-radius:8px"></div></div>',h}function o(h){const f=document.getElementById("cards");if(!(!f||f.querySelector(".card"))){h=h||6;for(let m=0;m<h;m++){const b=l();f.appendChild(b),setTimeout(function(){b.classList.add("loaded")},m*60)}}}function g(){const h=document.getElementById("cards");if(h){var f=h.querySelectorAll(".skeleton-card");f.length&&(f.forEach(function(m,b){setTimeout(function(){m.style.opacity="0",m.style.transform="translateY(-10px)"},b*25)}),setTimeout(function(){f.forEach(function(m){m.parentNode&&m.remove()})},f.length*25+300))}}window.SkeletonLoader={show:o,hide:g}})(),(function(){var l="theme",o="user-themes",g="custom-theme",h=["dark","light","blue","black","nord","dracula","solarized-dark","solarized-light","taxi","ocean"],f=h.slice(),m=["bg","fg","muted","fg-muted","card-base","card-bg","border","hover","card-hover","base","ok-bg","ok-fg","bad-bg","bad-fg","dot-ok","dot-bad","uptime","success","error","warning","accent","accent-strong"],b=["bg","fg","muted","card-base","card-bg","border","ok-bg","ok-fg","bad-bg","bad-fg","dot-ok","dot-bad","uptime","accent","accent-strong"],r=["fg-muted","hover","card-hover","base","success","error","warning"],v={dark:{bg:"#0b0f1a",fg:"#e8ecf5",muted:"#9aa6bf","fg-muted":"#6b7a94","card-base":"#121826","card-bg":"#121826",border:"#263552",hover:"#1a2235","card-hover":"#161e2e",base:"#151c2b","ok-bg":"#0c2430","ok-fg":"#7ef2ff","bad-bg":"#2a121a","bad-fg":"#ff9aa3","dot-ok":"#35d1ff","dot-bad":"#ff5f7a",uptime:"#35d1ff",success:"#4caf50",error:"#e74c3c",warning:"#f39c12",accent:"#8FD6FF","accent-strong":"#1F7BFF"},light:{bg:"#f6f7fb",fg:"#0f1115",muted:"#5f6b7a","fg-muted":"#8993a4","card-base":"#ffffff","card-bg":"#ffffff",border:"#e2e7ef",hover:"#eef1f6","card-hover":"#f5f6fa",base:"#ebeef3","ok-bg":"#eafff1","ok-fg":"#0a7c3a","bad-bg":"#ffefef","bad-fg":"#b00020","dot-ok":"#0fb15a","dot-bad":"#d93b3b",uptime:"#0fb15a",success:"#0a7c3a",error:"#b00020",warning:"#d68a00",accent:"#4a90d9","accent-strong":"#2563eb",lightBg:!0},blue:{bg:"#1908AC",fg:"#e8f1ff",muted:"#d6e2ff","fg-muted":"#9eafdb","card-base":"#0d1533","card-bg":"#0d1533",border:"#1c2d6a",hover:"#141f4a","card-hover":"#111a3e",base:"#0f1840","ok-bg":"#162040","ok-fg":"#edffff","bad-bg":"#0a0e24","bad-fg":"#ffb3c0","dot-ok":"#c7e5ff","dot-bad":"#ffd6dc",uptime:"#7ec8ff",success:"#7ec8ff",error:"#ffb3c0",
Line truncated
|
||||
`,document.body.appendChild(g),m>0&&setTimeout(()=>g.remove(),m)}function timeAgo(l){const o=Date.now()-new Date(l).getTime();return o<6e4?"just now":o<36e5?Math.floor(o/6e4)+"m ago":o<864e5?Math.floor(o/36e5)+"h ago":Math.floor(o/864e5)+"d ago"}function safeGet(l,o=null){try{const m=localStorage.getItem(l);return m!==null?m:o}catch{return o}}function safeSet(l,o){try{localStorage.setItem(l,o)}catch{}}function safeRemove(l){try{localStorage.removeItem(l)}catch{}}function safeSessionGet(l,o=null){try{const m=sessionStorage.getItem(l);return m!==null?m:o}catch{return o}}function safeSessionSet(l,o){try{sessionStorage.setItem(l,o)}catch{}}function safeGetJSON(l,o=null){try{const m=localStorage.getItem(l);return m?JSON.parse(m):o}catch{return o}}function escapeHtml(l){return String(l??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function injectModal(l,o){document.getElementById(l)||document.body.insertAdjacentHTML("beforeend",o)}const DC_BUS={_handlers:{},on(l,o){var m;((m=this._handlers)[l]||(m[l]=[])).push(o)},off(l,o){this._handlers[l]=this._handlers[l]?.filter(m=>m!==o)},emit(l,o){this._handlers[l]?.forEach(m=>m(o))}},AppState={_apps:[],getApps(){return this._apps},setApps(l){this._apps=l,window.APPS=l,DC_BUS.emit("apps:changed",l)},findApp(l){return this._apps.find(o=>o.id===l)},addApp(l){this._apps.push(l),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)},removeApp(l){const o=this._apps.findIndex(m=>m.id===l);return o>-1&&(this._apps.splice(o,1),window.APPS=this._apps,DC_BUS.emit("apps:changed",this._apps)),o>-1},updateApp(l,o){const m=this._apps.find(b=>b.id===l);if(m){for(const[b,f]of Object.entries(o))b!=="__proto__"&&b!=="constructor"&&b!=="prototype"&&(m[b]=f);DC_BUS.emit("apps:changed",this._apps)}return m}};(function(){function l(){const b=document.createElement("div");return b.className="skeleton-card",b.innerHTML='<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px"><div class="skeleton-bar" style="width:48px;height:48px;border-radius:8px;flex-shrink:0"></div><div style="flex:1"><div class="skeleton-bar" style="width:60%;height:14px;margin-bottom:6px"></div><div class="skeleton-bar" style="width:35%;height:10px"></div></div><div class="skeleton-bar" style="width:42px;height:22px;border-radius:11px"></div></div><div class="skeleton-bar" style="width:45%;height:12px;margin-bottom:10px"></div><div style="display:flex;gap:8px;margin-top:auto"><div class="skeleton-bar" style="width:64px;height:28px;border-radius:8px"></div><div class="skeleton-bar" style="width:64px;height:28px;border-radius:8px"></div></div>',b}function o(b){const f=document.getElementById("cards");if(!(!f||f.querySelector(".card"))){b=b||6;for(let v=0;v<b;v++){const g=l();f.appendChild(g),setTimeout(function(){g.classList.add("loaded")},v*60)}}}function m(){const b=document.getElementById("cards");if(b){var f=b.querySelectorAll(".skeleton-card");f.length&&(f.forEach(function(v,g){setTimeout(function(){v.style.opacity="0",v.style.transform="translateY(-10px)"},g*25)}),setTimeout(function(){f.forEach(function(v){v.parentNode&&v.remove()})},f.length*25+300))}}window.SkeletonLoader={show:o,hide:m}})(),(function(){var l="theme",o="user-themes",m="custom-theme",b=["dark","light","blue","black","nord","dracula","solarized-dark","solarized-light","taxi","ocean"],f=b.slice(),v=["bg","fg","muted","fg-muted","card-base","card-bg","border","hover","card-hover","base","ok-bg","ok-fg","bad-bg","bad-fg","dot-ok","dot-bad","uptime","success","error","warning","accent","accent-strong"],g=["bg","fg","muted","card-base","card-bg","border","ok-bg","ok-fg","bad-bg","bad-fg","dot-ok","dot-bad","uptime","accent","accent-strong"],r=["fg-muted","hover","card-hover","base","success","error","warning"],h={dark:{bg:"#0b0f1a",fg:"#e8ecf5",muted:"#9aa6bf","fg-muted":"#6b7a94","card-base":"#121826","card-bg":"#121826",border:"#263552",hover:"#1a2235","card-hover":"#161e2e",base:"#151c2b","ok-bg":"#0c2430","ok-fg":"#7ef2ff","bad-bg":"#2a121a","bad-fg":"#ff9aa3","dot-ok":"#35d1ff","dot-bad":"#ff5f7a",uptime:"#35d1ff",success:"#4caf50",error:"#e74c3c",warning:"#f39c12",accent:"#8FD6FF","accent-strong":"#1F7BFF"},light:{bg:"#f6f7fb",fg:"#0f1115",muted:"#5f6b7a","fg-muted":"#8993a4","card-base":"#ffffff","card-bg":"#ffffff",border:"#e2e7ef",hover:"#eef1f6","card-hover":"#f5f6fa",base:"#ebeef3","ok-bg":"#eafff1","ok-fg":"#0a7c3a","bad-bg":"#ffefef","bad-fg":"#b00020","dot-ok":"#0fb15a","dot-bad":"#d93b3b",uptime:"#0fb15a",success:"#0a7c3a",error:"#b00020",warning:"#d68a00",accent:"#4a90d9","accent-strong":"#2563eb",lightBg:!0},blue:{bg:"#1908AC",fg:"#e8f1ff",muted:"#d6e2ff","fg-muted":"#9eafdb","card-base":"#0d1533","card-bg":"#0d1533",border:"#1c2d6a",hover:"#141f4a","card-hover":"#111a3e",base:"#0f1840","ok-bg":"#162040","ok-fg":"#edffff","bad-bg":"#0a0e24","bad-fg":"#ffb3c0","dot-ok":"#c7e5ff","dot-bad":"#ffd6dc",uptime:"#7ec8ff",success:"#7ec8ff",error:"#ffb3c0",
Line truncated
|
||||
background:
|
||||
radial-gradient(1200px 800px at 10% -10%, rgba(`+x.r+","+x.g+","+x.b+`, .08), transparent 60%),
|
||||
radial-gradient(1000px 700px at 110% 10%, rgba(`+x.r+","+x.g+","+x.b+`, .05), transparent 55%),
|
||||
@@ -44,10 +44,10 @@
|
||||
background: color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
|
||||
}
|
||||
`}function t(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function i(){m.forEach(function(C){document.documentElement.style.removeProperty("--"+C)})}function y(C,B){var $=C.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");$||($="custom"),h.indexOf($)!==-1&&($=$+"-custom");for(var k=safeGetJSON(o,{}),x=$,I=2;k[$]&&$!==B;)$=x+"-"+I++;return $}function u(C){var B=document.getElementById("user-theme-styles");B&&B.remove(),f.length=h.length,Object.keys(v).forEach(function(E){h.indexOf(E)===-1&&delete v[E]});var $=C||safeGetJSON(o,{}),k=Object.keys($);if(k=k.filter(function(E){return h.indexOf(E)===-1}),!!k.length){var x="";k.forEach(function(E){var S=$[E];f.indexOf(E)===-1&&f.push(E);var P={};m.forEach(function(O){S[O]&&(P[O]=S[O])}),P["card-bg"]=S["card-base"]||S.bg,S.lightBg&&(P.lightBg=!0);var D=e(P);r.forEach(function(O){!P[O]&&D[O]&&(P[O]=D[O])}),v[E]=P,x+=":root."+E+` {
|
||||
`,m.forEach(function(O){P[O]&&(x+=" --"+O+": "+P[O]+`;
|
||||
`}function t(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function i(){v.forEach(function(C){document.documentElement.style.removeProperty("--"+C)})}function y(C,B){var $=C.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");$||($="custom"),b.indexOf($)!==-1&&($=$+"-custom");for(var k=safeGetJSON(o,{}),x=$,I=2;k[$]&&$!==B;)$=x+"-"+I++;return $}function u(C){var B=document.getElementById("user-theme-styles");B&&B.remove(),f.length=b.length,Object.keys(h).forEach(function(E){b.indexOf(E)===-1&&delete h[E]});var $=C||safeGetJSON(o,{}),k=Object.keys($);if(k=k.filter(function(E){return b.indexOf(E)===-1}),!!k.length){var x="";k.forEach(function(E){var S=$[E];f.indexOf(E)===-1&&f.push(E);var A={};v.forEach(function(O){S[O]&&(A[O]=S[O])}),A["card-bg"]=S["card-base"]||S.bg,S.lightBg&&(A.lightBg=!0);var D=e(A);r.forEach(function(O){!A[O]&&D[O]&&(A[O]=D[O])}),h[E]=A,x+=":root."+E+` {
|
||||
`,v.forEach(function(O){A[O]&&(x+=" --"+O+": "+A[O]+`;
|
||||
`)}),x+=`}
|
||||
`,x+=n(E,P),x+=c(E,P)});var I=document.createElement("style");I.id="user-theme-styles",I.textContent=x,document.head.appendChild(I)}}function w(){secureFetch("/api/v1/themes").then(function(C){return C.json()}).then(function(C){if(!(!C.success||!C.themes)){var B=C.themes,$=safeGetJSON(o,{});if(JSON.stringify(B)!==JSON.stringify($)){safeSet(o,JSON.stringify(B)),u(B);var k=safeGet(l);k&&f.indexOf(k)!==-1&&L(k)}}}).catch(function(){})}function T(){var C=safeGetJSON(g);if(C){var B=C.name||"Custom",$=y(B),k={name:B};m.forEach(function(E){C[E]&&(k[E]=C[E])});var x=safeGetJSON(o,{});x[$]=k,safeSet(o,JSON.stringify(x)),safeGet(l)==="custom"&&safeSet(l,$),safeRemove(g);var I={};m.forEach(function(E){k[E]&&(I[E]=k[E])}),fetch("/api/v1/themes/"+$,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B,colors:I})}).catch(function(){})}}function L(C){document.documentElement.classList.add("theme-transitioning"),f.forEach(function(x){x!=="dark"&&document.documentElement.classList.remove(x)}),i(),C!=="dark"&&document.documentElement.classList.add(C),safeSet(l,C);var B=v[C],$=document.querySelector('meta[name="theme-color"]');$&&B&&$.setAttribute("content",B.bg);var k=B&&B.lightBg;!k&&B&&B.bg&&(k=s(B.bg)>.4),k?document.documentElement.classList.add("light-bg"):document.documentElement.classList.remove("light-bg"),setTimeout(function(){document.documentElement.classList.remove("theme-transitioning")},300)}T(),u();var A=safeGet(l);A==="red"&&(A="black",safeSet(l,"black")),A&&A!=="dark"&&f.indexOf(A)===-1&&(A=null),L(A||t()),w(),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",function(C){safeGet(l)||L(C.matches?"dark":"light")}),window.THEMES=f,window.BUILTIN_THEMES=h,window.THEME_COLORS=v,window.THEME_PROPS=m,window.BASE_PROPS=b,window.DERIVED_PROPS=r,window.USER_THEMES_KEY=o,window.applyTheme=L,window.clearCustomProperties=i,window.injectUserThemeStyles=u,window.syncThemesFromServer=w,window.slugifyThemeName=y,window.getActiveTheme=function(){return safeGet(l)||t()},window.deriveExtendedColors=e,window.hexToRgb=d,window.rgbToHex=a,window.blendColors=p})(),(function(){let l=null;async function o(){if(l)return l;try{const d=await fetch("/api/v1/auth/login/methods",{cache:"no-store"});if(!d.ok)throw new Error(`methods HTTP ${d.status}`);const a=await d.json();return l=Array.isArray(a.providers)?a.providers:[],l}catch(d){return console.warn("[auth-gate] methods fetch failed; falling back to TOTP-only",d),[]}}function g(d){const a=document.getElementById("totp-overlay");if(!a)return;const p=a.querySelector(".totp-card");if(!p)return;const s=p.innerHTML;p.dataset.originalBody||(p.dataset.originalBody=s);const e=d.map(n=>{const c=n.config&&(n.config.label||n.name)||n.name;return`<button class="provider-btn" data-provider="${n.name}"
|
||||
`,x+=n(E,A),x+=c(E,A)});var I=document.createElement("style");I.id="user-theme-styles",I.textContent=x,document.head.appendChild(I)}}function w(){secureFetch("/api/v1/themes").then(function(C){return C.json()}).then(function(C){if(!(!C.success||!C.themes)){var B=C.themes,$=safeGetJSON(o,{});if(JSON.stringify(B)!==JSON.stringify($)){safeSet(o,JSON.stringify(B)),u(B);var k=safeGet(l);k&&f.indexOf(k)!==-1&&L(k)}}}).catch(function(){})}function T(){var C=safeGetJSON(m);if(C){var B=C.name||"Custom",$=y(B),k={name:B};v.forEach(function(E){C[E]&&(k[E]=C[E])});var x=safeGetJSON(o,{});x[$]=k,safeSet(o,JSON.stringify(x)),safeGet(l)==="custom"&&safeSet(l,$),safeRemove(m);var I={};v.forEach(function(E){k[E]&&(I[E]=k[E])}),fetch("/api/v1/themes/"+$,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B,colors:I})}).catch(function(){})}}function L(C){document.documentElement.classList.add("theme-transitioning"),f.forEach(function(x){x!=="dark"&&document.documentElement.classList.remove(x)}),i(),C!=="dark"&&document.documentElement.classList.add(C),safeSet(l,C);var B=h[C],$=document.querySelector('meta[name="theme-color"]');$&&B&&$.setAttribute("content",B.bg);var k=B&&B.lightBg;!k&&B&&B.bg&&(k=s(B.bg)>.4),k?document.documentElement.classList.add("light-bg"):document.documentElement.classList.remove("light-bg"),setTimeout(function(){document.documentElement.classList.remove("theme-transitioning")},300)}T(),u();var P=safeGet(l);P==="red"&&(P="black",safeSet(l,"black")),P&&P!=="dark"&&f.indexOf(P)===-1&&(P=null),L(P||t()),w(),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",function(C){safeGet(l)||L(C.matches?"dark":"light")}),window.THEMES=f,window.BUILTIN_THEMES=b,window.THEME_COLORS=h,window.THEME_PROPS=v,window.BASE_PROPS=g,window.DERIVED_PROPS=r,window.USER_THEMES_KEY=o,window.applyTheme=L,window.clearCustomProperties=i,window.injectUserThemeStyles=u,window.syncThemesFromServer=w,window.slugifyThemeName=y,window.getActiveTheme=function(){return safeGet(l)||t()},window.deriveExtendedColors=e,window.hexToRgb=d,window.rgbToHex=a,window.blendColors=p})(),(function(){let l=null;async function o(){if(l)return l;try{const d=await fetch("/api/v1/auth/login/methods",{cache:"no-store"});if(!d.ok)throw new Error(`methods HTTP ${d.status}`);const a=await d.json();return l=Array.isArray(a.providers)?a.providers:[],l}catch(d){return console.warn("[auth-gate] methods fetch failed; falling back to TOTP-only",d),[]}}function m(d){const a=document.getElementById("totp-overlay");if(!a)return;const p=a.querySelector(".totp-card");if(!p)return;const s=p.innerHTML;p.dataset.originalBody||(p.dataset.originalBody=s);const e=d.map(n=>{const c=n.config&&(n.config.label||n.name)||n.name;return`<button class="provider-btn" data-provider="${n.name}"
|
||||
style="margin: 6px 4px; padding: 10px 16px; background: var(--accent); color: white; border: 0; border-radius: 6px; cursor: pointer; font-size: 0.95rem;">
|
||||
Sign in with ${c}
|
||||
</button>`}).join(`
|
||||
@@ -57,7 +57,7 @@
|
||||
<p class="subtitle">Choose how to sign in</p>
|
||||
<div id="auth-gate-providers" style="margin: 14px 0 10px;">${e}</div>
|
||||
<div id="auth-gate-message" style="margin-top: 8px; color: var(--muted); font-size: 0.8rem;"></div>
|
||||
`,a.classList.add("show"),p.querySelectorAll(".provider-btn").forEach(n=>{n.addEventListener("click",()=>{const c=n.dataset.provider,t=d.find(i=>i.name===c);f(t)})})}function h(){const d=document.getElementById("totp-overlay");if(!d)return;const a=d.querySelector(".totp-card");!a||!a.dataset.originalBody||(a.innerHTML=a.dataset.originalBody,window.location.reload())}function f(d){const a=document.getElementById("totp-overlay");if(!a)return;const p=a.querySelector(".totp-card");if(p){if(d.name==="totp"){window.location.reload();return}if(d.name==="email"){p.innerHTML=`
|
||||
`,a.classList.add("show"),p.querySelectorAll(".provider-btn").forEach(n=>{n.addEventListener("click",()=>{const c=n.dataset.provider,t=d.find(i=>i.name===c);f(t)})})}function b(){const d=document.getElementById("totp-overlay");if(!d)return;const a=d.querySelector(".totp-card");!a||!a.dataset.originalBody||(a.innerHTML=a.dataset.originalBody,window.location.reload())}function f(d){const a=document.getElementById("totp-overlay");if(!a)return;const p=a.querySelector(".totp-card");if(p){if(d.name==="totp"){window.location.reload();return}if(d.name==="email"){p.innerHTML=`
|
||||
<img class="totp-logo totp-logo-dark" src="/assets/dashcaddy-logo-dark.png" alt="DashCaddy" onerror="this.style.display='none'">
|
||||
<img class="totp-logo totp-logo-light" src="/assets/dashcaddy-logo-light.png" alt="DashCaddy" onerror="this.style.display='none'">
|
||||
<p class="subtitle">Sign in with email</p>
|
||||
@@ -78,10 +78,10 @@
|
||||
`,a.classList.add("show");const s=p.querySelector("#auth-gate-email-input"),e=p.querySelector("#auth-gate-email-submit"),n=p.querySelector("#auth-gate-email-status"),c=p.querySelector("#auth-gate-back");e.addEventListener("click",async()=>{const t=(s.value||"").trim();if(!t||!t.includes("@")){n.textContent="Enter a valid email address.",n.style.color="var(--error, #d33)";return}e.disabled=!0,n.textContent="Sending\u2026",n.style.color="";try{const y=await(await fetch("/api/v1/auth/login/email/initiate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t})})).json();if(y.success){const u=y.deliveredVia||"email",w=y.maskedEmail||t;u==="dev-console"?n.innerHTML=`
|
||||
Check the server logs for your one-time link.
|
||||
(Dev mode: no SMTP configured. In production this would email <strong>${w}</strong>.)`:n.innerHTML=`Sign-in link sent to <strong>${w}</strong>.
|
||||
Check your inbox (and spam folder).`,n.style.color="var(--success, #2a7)"}else n.textContent=y.error||"Could not send link.",n.style.color="var(--error, #d33)",e.disabled=!1}catch{n.textContent="Connection error. Try again.",n.style.color="var(--error, #d33)",e.disabled=!1}}),s.addEventListener("keydown",t=>{t.key==="Enter"&&e.click()}),c.addEventListener("click",t=>{t.preventDefault(),g(providers)});return}console.warn("[auth-gate] unknown provider",d.name),window.location.reload()}}async function m(){if(!document.getElementById("totp-overlay"))return;const a=await o();if(a.length===0){typeof window._showTotpOverlay=="function"&&window._showTotpOverlay();return}if(a.length===1&&a[0].name==="totp"){b(a[0]);return}g(a)}function b(d){const a=l&&l.find(n=>n.name==="email");if(!a){typeof window._showTotpOverlay=="function"&&window._showTotpOverlay();return}const p=document.getElementById("totp-overlay"),s=p.querySelector(".totp-card");if(!s)return;if(s.dataset.originalBody||(s.dataset.originalBody=s.innerHTML),!s.querySelector("#auth-gate-email-alt")){const n=document.createElement("div");n.id="auth-gate-email-alt",n.style.cssText="margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); font-size: 0.85rem;",n.innerHTML=`<a href="#" id="auth-gate-email-alt-link"
|
||||
Check your inbox (and spam folder).`,n.style.color="var(--success, #2a7)"}else n.textContent=y.error||"Could not send link.",n.style.color="var(--error, #d33)",e.disabled=!1}catch{n.textContent="Connection error. Try again.",n.style.color="var(--error, #d33)",e.disabled=!1}}),s.addEventListener("keydown",t=>{t.key==="Enter"&&e.click()}),c.addEventListener("click",t=>{t.preventDefault(),m(providers)});return}console.warn("[auth-gate] unknown provider",d.name),window.location.reload()}}async function v(){if(!document.getElementById("totp-overlay"))return;const a=await o();if(a.length===0){typeof window._showTotpOverlay=="function"&&window._showTotpOverlay();return}if(a.length===1&&a[0].name==="totp"){g(a[0]);return}m(a)}function g(d){const a=l&&l.find(n=>n.name==="email");if(!a){typeof window._showTotpOverlay=="function"&&window._showTotpOverlay();return}const p=document.getElementById("totp-overlay"),s=p.querySelector(".totp-card");if(!s)return;if(s.dataset.originalBody||(s.dataset.originalBody=s.innerHTML),!s.querySelector("#auth-gate-email-alt")){const n=document.createElement("div");n.id="auth-gate-email-alt",n.style.cssText="margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); font-size: 0.85rem;",n.innerHTML=`<a href="#" id="auth-gate-email-alt-link"
|
||||
style="color: var(--accent); text-decoration: none;">
|
||||
Or sign in with email instead \u2192
|
||||
</a>`,s.appendChild(n),n.querySelector("#auth-gate-email-alt-link").addEventListener("click",c=>{c.preventDefault(),f(a)})}p.classList.add("show")}window.__dc_049_handled=!0;function r(d){try{const a=new URL(d,window.location.origin);if(!["http:","https:"].includes(a.protocol))return!1;if(a.origin===window.location.origin)return!0;if(a.protocol!=="https:")return!1;const p=SITE.tld.startsWith(".")?SITE.tld:`.${SITE.tld}`;return a.hostname===p.slice(1)||a.hostname.endsWith(p)}catch{return!1}}const v=new URLSearchParams(window.location.search);if(v.get("auth")==="required"){const d=v.get("return");if(d&&r(d))try{sessionStorage.setItem("totp_redirect",d)}catch{}window.history.replaceState({},"",window.location.pathname),setTimeout(m,0)}window._showAuthGate=m,window._authGateMethods=o})(),(function(){function l(){const r=document.querySelector(".totp-card");if(!r)return;const d=getComputedStyle(r).backgroundColor.match(/\d+/g);if(!d)return;const a=(.299*+d[0]+.587*+d[1]+.114*+d[2])/255,p=r.querySelector(".totp-logo-dark"),s=r.querySelector(".totp-logo-light");p&&(p.style.display=a>.5?"none":""),s&&(s.style.display=a>.5?"":"none")}function o(){const r=document.getElementById("totp-overlay");if(r){r.classList.add("show"),setTimeout(l,50);const v=r.querySelector(".totp-digits input");v&&setTimeout(()=>v.focus(),100)}typeof window._refreshRecoveryLink=="function"&&window._refreshRecoveryLink()}function g(){const r=document.getElementById("totp-overlay");r&&r.classList.remove("show")}function h(r,v){const d=new URL(r,window.location.origin);if(d.origin===window.location.origin)return d.toString();const a=SITE.tld.startsWith(".")?SITE.tld:`.${SITE.tld}`,p=d.hostname===a.slice(1)||d.hostname.endsWith(a);if(d.protocol!=="https:"||!p)return null;if(!v)return d.toString();const s=`${d.pathname}${d.search}${d.hash}`;return d.pathname="/dashcaddy-sso",d.search="",d.hash="",d.searchParams.set("token",v),d.searchParams.set("return",s),d.toString()}const f=document.getElementById("totp-digits");if(f){const r=f.querySelectorAll("input");r.forEach((v,d)=>{v.addEventListener("input",a=>{const p=a.target.value.replace(/\D/g,"");a.target.value=p.slice(0,1),p&&d<r.length-1&&r[d+1].focus();const s=Array.from(r).map(e=>e.value).join("");s.length===6&&m(s)}),v.addEventListener("keydown",a=>{a.key==="Backspace"&&!a.target.value&&d>0&&(r[d-1].focus(),r[d-1].value="")}),v.addEventListener("paste",a=>{a.preventDefault();const p=(a.clipboardData.getData("text")||"").replace(/\D/g,"");p.length>=6&&(r.forEach((s,e)=>{s.value=p[e]||""}),r[5].focus(),m(p.slice(0,6)))})})}async function m(r){const v=document.getElementById("totp-error");v.textContent="Verifying...",v.className="totp-error verifying";try{const a=await(await secureFetch("/api/v1/totp/verify",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:r})})).json();if(a.success){v.textContent="",a.csrfToken&&(csrfToken=a.csrfToken),g();const p=safeSessionGet("totp_redirect");if(p){try{sessionStorage.removeItem("totp_redirect")}catch{}const s=h(p,a.ssoToken);if(!s)return;window.location.href=s;return}typeof window.initializeDashboard=="function"&&window.initializeDashboard()}else{v.textContent=a.error||"Invalid code",v.className="totp-error";const p=document.querySelectorAll("#totp-digits input");p.forEach(s=>{s.value=""}),p[0]?.focus()}}catch{v.textContent="Connection error",v.className="totp-error"}}if(!!!window.__dc_049_handled&&urlParams.get("auth")==="required"){const r=urlParams.get("return");if(r)try{const v=new URL(r,window.location.origin),d=v.hostname,a=v.origin===window.location.origin,p=SITE.tld.startsWith(".")?SITE.tld:"."+SITE.tld,s=d.endsWith(p)||d===p.substring(1);(a||s)&&safeSessionSet("totp_redirect",r)}catch{}window.history.replaceState({},"",window.location.pathname)}window._showTotpOverlay=o})(),(function(){"use strict";async function l(){try{return await(await fetch("/api/v1/totp/recovery-info",{cache:"no-store"})).json()}catch{return{success:!1,status:"unknown",hint:"Could not contact server"}}}function o(b){const r=document.getElementById("totp-recovery-link");r&&(r.style.display=b?"":"none")}function g(){const b=document.getElementById("totp-recovery-panel");b&&(b.style.display="");const r=document.getElementById("totp-recovery-status"),v=document.getElementById("totp-recovery-import"),d=document.getElementById("totp-recovery-verify");v&&(v.style.display=""),d&&(d.style.display="none"),document.getElementById("totp-recovery-error").textContent="",document.getElementById("totp-recovery-confirm-error").textContent="",document.getElementById("totp-recovery-secret").value="",document.getElementById("totp-recovery-code").value="",l().then(a=>{r.textContent=a.hint||"",a.status==="healthy"?r.style.borderColor="var(--ok-fg, #7ef2ff)":a.status==="unreadable"?(r.style.borderColor="var(--bad-fg, #ff9aa3)",r.style.background="color-mix(in srgb, var(--bad-fg) 6%, transparent)"):a.status==="not_configured"?r.style.borderColor="var(--muted)"Line truncated
|
||||
</a>`,s.appendChild(n),n.querySelector("#auth-gate-email-alt-link").addEventListener("click",c=>{c.preventDefault(),f(a)})}p.classList.add("show")}window.__dc_049_handled=!0;function r(d){try{const a=new URL(d,window.location.origin);if(!["http:","https:"].includes(a.protocol))return!1;if(a.origin===window.location.origin)return!0;if(a.protocol!=="https:")return!1;const p=SITE.tld.startsWith(".")?SITE.tld:`.${SITE.tld}`;return a.hostname===p.slice(1)||a.hostname.endsWith(p)}catch{return!1}}const h=new URLSearchParams(window.location.search);if(h.get("auth")==="required"){const d=h.get("return");if(d&&r(d))try{sessionStorage.setItem("totp_redirect",d)}catch{}window.history.replaceState({},"",window.location.pathname),setTimeout(v,0)}window._showAuthGate=v,window._authGateMethods=o})(),(function(){function l(){const r=document.querySelector(".totp-card");if(!r)return;const d=getComputedStyle(r).backgroundColor.match(/\d+/g);if(!d)return;const a=(.299*+d[0]+.587*+d[1]+.114*+d[2])/255,p=r.querySelector(".totp-logo-dark"),s=r.querySelector(".totp-logo-light");p&&(p.style.display=a>.5?"none":""),s&&(s.style.display=a>.5?"":"none")}function o(){const r=document.getElementById("totp-overlay");if(r){r.classList.add("show"),setTimeout(l,50);const h=r.querySelector(".totp-digits input");h&&setTimeout(()=>h.focus(),100)}typeof window._refreshRecoveryLink=="function"&&window._refreshRecoveryLink()}function m(){const r=document.getElementById("totp-overlay");r&&r.classList.remove("show")}function b(r,h){const d=new URL(r,window.location.origin);if(d.origin===window.location.origin)return d.toString();const a=SITE.tld.startsWith(".")?SITE.tld:`.${SITE.tld}`,p=d.hostname===a.slice(1)||d.hostname.endsWith(a);if(d.protocol!=="https:"||!p)return null;if(!h)return d.toString();const s=`${d.pathname}${d.search}${d.hash}`;return d.pathname="/dashcaddy-sso",d.search="",d.hash="",d.searchParams.set("token",h),d.searchParams.set("return",s),d.toString()}const f=document.getElementById("totp-digits");if(f){const r=f.querySelectorAll("input");r.forEach((h,d)=>{h.addEventListener("input",a=>{const p=a.target.value.replace(/\D/g,"");a.target.value=p.slice(0,1),p&&d<r.length-1&&r[d+1].focus();const s=Array.from(r).map(e=>e.value).join("");s.length===6&&v(s)}),h.addEventListener("keydown",a=>{a.key==="Backspace"&&!a.target.value&&d>0&&(r[d-1].focus(),r[d-1].value="")}),h.addEventListener("paste",a=>{a.preventDefault();const p=(a.clipboardData.getData("text")||"").replace(/\D/g,"");p.length>=6&&(r.forEach((s,e)=>{s.value=p[e]||""}),r[5].focus(),v(p.slice(0,6)))})})}async function v(r){const h=document.getElementById("totp-error");h.textContent="Verifying...",h.className="totp-error verifying";try{const a=await(await secureFetch("/api/v1/totp/verify",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:r})})).json();if(a.success){h.textContent="",a.csrfToken&&(csrfToken=a.csrfToken),m();const p=safeSessionGet("totp_redirect");if(p){try{sessionStorage.removeItem("totp_redirect")}catch{}const s=b(p,a.ssoToken);if(!s)return;window.location.href=s;return}typeof window.initializeDashboard=="function"&&window.initializeDashboard()}else{h.textContent=a.error||"Invalid code",h.className="totp-error";const p=document.querySelectorAll("#totp-digits input");p.forEach(s=>{s.value=""}),p[0]?.focus()}}catch{h.textContent="Connection error",h.className="totp-error"}}if(!!!window.__dc_049_handled&&urlParams.get("auth")==="required"){const r=urlParams.get("return");if(r)try{const h=new URL(r,window.location.origin),d=h.hostname,a=h.origin===window.location.origin,p=SITE.tld.startsWith(".")?SITE.tld:"."+SITE.tld,s=d.endsWith(p)||d===p.substring(1);(a||s)&&safeSessionSet("totp_redirect",r)}catch{}window.history.replaceState({},"",window.location.pathname)}window._showTotpOverlay=o})(),(function(){"use strict";async function l(){try{return await(await fetch("/api/v1/totp/recovery-info",{cache:"no-store"})).json()}catch{return{success:!1,status:"unknown",hint:"Could not contact server"}}}function o(g){const r=document.getElementById("totp-recovery-link");r&&(r.style.display=g?"":"none")}function m(){const g=document.getElementById("totp-recovery-panel");g&&(g.style.display="");const r=document.getElementById("totp-recovery-status"),h=document.getElementById("totp-recovery-import"),d=document.getElementById("totp-recovery-verify");h&&(h.style.display=""),d&&(d.style.display="none"),document.getElementById("totp-recovery-error").textContent="",document.getElementById("totp-recovery-confirm-error").textContent="",document.getElementById("totp-recovery-secret").value="",document.getElementById("totp-recovery-code").value="",l().then(a=>{r.textContent=a.hint||"",a.status==="healthy"?r.style.borderColor="var(--ok-fg, #7ef2ff)":a.status==="unreadable"?(r.style.borderColor="var(--bad-fg, #ff9aa3)",r.style.background="color-mix(in srgb, var(--bad-fg) 6%, transparent)"):a.status==="not_configured"?r.style.borderColor="var(--muted)"Line truncated
|
||||
<div class="weather-modal-content" style="min-width: 500px; max-width: 700px; max-height: 80vh;">
|
||||
<h3>\u{1F4C2} Browse for Media Folders</h3>
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);const o=document.getElementById("service-creds-modal");let g=null;const h=["sonarr","radarr","prowlarr","overseerr"],f=["sonarr","radarr"];function m(a){return a.externalUrl||a.url||""}function b(a){const p=document.getElementById("svc-creds-error");p.textContent=a,p.style.display=""}function r(){const a=document.getElementById("svc-creds-error");a.textContent="",a.style.display="none"}window.openServiceCredsModal=async function(a){g=a,r();const p=document.getElementById("svc-creds-title"),s=document.getElementById("svc-creds-desc"),e=document.getElementById("svc-creds-seedhost"),n=document.getElementById("svc-creds-apikey"),c=document.getElementById("svc-creds-basic"),t=document.getElementById("svc-creds-quality");p.textContent=a.name+" Credentials";const i=!!a.isExternal,y=h.includes(a.id)||h.includes(a.appTemplate),u=f.includes(a.id)||f.includes(a.appTemplate);e.style.display=i?"":"none",n.style.display=y?"":"none",t.style.display=u?"":"none",c.style.display=i?"none":"";const w=document.getElementById("svc-quality-select");w.innerHTML='<option value="">-- Enter API key first --</option>',document.getElementById("svc-quality-status").textContent="",i?(s.textContent="Seedhost credentials auto-login past the HTTP prompt. API key bypasses the app login.",document.getElementById("svc-seedhost-pass").placeholder=`Password for ${a.name}`):y?s.textContent="API key bypasses the app login screen automatically.":s.textContent="Credentials are injected automatically when accessing this service.",await v(a),o.classList.add("show")};async function v(a){const p=document.getElementById("svc-creds-dot"),s=document.getElementById("svc-creds-status"),e=document.getElementById("svc-creds-clear");let n=!1;if(a.isExternal){try{const i=await(await fetch(`/api/v1/seedhost-creds?serviceId=${a.id}`)).json();i.success?(document.getElementById("svc-seedhost-user").value=i.username||"",i.hasCredentials&&(n=!0)):document.getElementById("svc-seedhost-user").value=""}catch{}document.getElementById("svc-seedhost-pass").value=""}try{const i=await(await fetch(`/api/v1/services/${a.id}/credentials`)).json();i.success&&(i.hasApiKey?(document.getElementById("svc-apikey-input").value="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",n=!0):document.getElementById("svc-apikey-input").value="",i.hasBasicAuth&&!a.isExternal?(document.getElementById("svc-basic-user").value=i.username||"",n=!0):document.getElementById("svc-basic-user").value="")}catch{}document.getElementById("svc-basic-pass")&&(document.getElementById("svc-basic-pass").value="");const c=a.id||a.appTemplate;if(f.includes(c)&&await d(a),n){p.style.background="var(--ok-fg, #74dfc4)",s.style.color="var(--ok-fg, #74dfc4)",s.textContent="Credentials stored",e.style.display="";const t=document.getElementById(`creds-btn-${a.id}`);t&&t.classList.add("has-creds")}else p.style.background="var(--muted)",s.style.color="var(--muted)",s.textContent="No credentials stored",e.style.display="none"}async function d(a){const p=document.getElementById("svc-quality-select"),s=document.getElementById("svc-quality-status"),e=a.id||a.appTemplate,n=m(a);if(!n){p.innerHTML='<option value="">-- No service URL --</option>';return}p.innerHTML='<option value="">Loading...</option>',s.textContent="";try{const c=new URLSearchParams({service:e,url:n}),i=await(await fetch(`/api/v1/arr/quality-profiles?${c}`)).json();if(!i.success||!i.profiles?.length){p.innerHTML='<option value="">-- No profiles found (enter API key and click Fetch) --</option>';return}p.innerHTML="";for(const y of i.profiles){const u=document.createElement("option");u.value=y.id,u.textContent=y.name,p.appendChild(u)}if(i.storedProfileId&&(p.value=String(i.storedProfileId)),!p.value){const y=i.profiles.find(u=>/720/i.test(u.name));y&&(p.value=String(y.id))}!p.value&&i.profiles.length&&(p.value=String(i.profiles[0].id)),s.innerHTML=`<span style="color: var(--ok-fg);">${i.profiles.length} profiles loaded</span>`}catch(c){p.innerHTML='<option value="">-- Failed to load --</option>',s.innerHTML=`<span style="color: var(--error, #c62828);">Error: ${c.message}</span>`}}document.getElementById("svc-quality-fetch")?.addEventListener("click",async()=>{if(!g)return;const a=g.id||g.appTemplate,p=m(g),e=document.getElementById("svc-apikey-input")?.value.trim(),n=document.getElementById("svc-quality-select"),c=document.getElementById("svc-quality-status");if(!p){c.innerHTML='<span style="color: var(--error, #c62828);">No service URL available</span>';return}if(!e||e==="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"){c.innerHTML='<span style="color: var(--error, #c62828);">Enter an API key first</span>';return}n.innerHTML='<option value="">Fetching...</option>',c.textContent="";try{const t=new URLSearchParams({service:a,url:p,apiKey:e}),y=await(await fetch(`/api/v1/arr/quality-profiles?${t}`)).json();if(!y.success){n.innerHTML='<option value="">-- Error --</option>',c.innerHTML=`<span style="color: var(--error, #c62828);">${y.error||"Failed to feLine truncated
|
||||
</div>`);const o=document.getElementById("service-creds-modal");let m=null;const b=["sonarr","radarr","prowlarr","overseerr"],f=["sonarr","radarr"];function v(a){return a.externalUrl||a.url||""}function g(a){const p=document.getElementById("svc-creds-error");p.textContent=a,p.style.display=""}function r(){const a=document.getElementById("svc-creds-error");a.textContent="",a.style.display="none"}window.openServiceCredsModal=async function(a){m=a,r();const p=document.getElementById("svc-creds-title"),s=document.getElementById("svc-creds-desc"),e=document.getElementById("svc-creds-seedhost"),n=document.getElementById("svc-creds-apikey"),c=document.getElementById("svc-creds-basic"),t=document.getElementById("svc-creds-quality");p.textContent=a.name+" Credentials";const i=!!a.isExternal,y=b.includes(a.id)||b.includes(a.appTemplate),u=f.includes(a.id)||f.includes(a.appTemplate);e.style.display=i?"":"none",n.style.display=y?"":"none",t.style.display=u?"":"none",c.style.display=i?"none":"";const w=document.getElementById("svc-quality-select");w.innerHTML='<option value="">-- Enter API key first --</option>',document.getElementById("svc-quality-status").textContent="",i?(s.textContent="Seedhost credentials auto-login past the HTTP prompt. API key bypasses the app login.",document.getElementById("svc-seedhost-pass").placeholder=`Password for ${a.name}`):y?s.textContent="API key bypasses the app login screen automatically.":s.textContent="Credentials are injected automatically when accessing this service.",await h(a),o.classList.add("show")};async function h(a){const p=document.getElementById("svc-creds-dot"),s=document.getElementById("svc-creds-status"),e=document.getElementById("svc-creds-clear");let n=!1;if(a.isExternal){try{const i=await(await fetch(`/api/v1/seedhost-creds?serviceId=${a.id}`)).json();i.success?(document.getElementById("svc-seedhost-user").value=i.username||"",i.hasCredentials&&(n=!0)):document.getElementById("svc-seedhost-user").value=""}catch{}document.getElementById("svc-seedhost-pass").value=""}try{const i=await(await fetch(`/api/v1/services/${a.id}/credentials`)).json();i.success&&(i.hasApiKey?(document.getElementById("svc-apikey-input").value="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",n=!0):document.getElementById("svc-apikey-input").value="",i.hasBasicAuth&&!a.isExternal?(document.getElementById("svc-basic-user").value=i.username||"",n=!0):document.getElementById("svc-basic-user").value="")}catch{}document.getElementById("svc-basic-pass")&&(document.getElementById("svc-basic-pass").value="");const c=a.id||a.appTemplate;if(f.includes(c)&&await d(a),n){p.style.background="var(--ok-fg, #74dfc4)",s.style.color="var(--ok-fg, #74dfc4)",s.textContent="Credentials stored",e.style.display="";const t=document.getElementById(`creds-btn-${a.id}`);t&&t.classList.add("has-creds")}else p.style.background="var(--muted)",s.style.color="var(--muted)",s.textContent="No credentials stored",e.style.display="none"}async function d(a){const p=document.getElementById("svc-quality-select"),s=document.getElementById("svc-quality-status"),e=a.id||a.appTemplate,n=v(a);if(!n){p.innerHTML='<option value="">-- No service URL --</option>';return}p.innerHTML='<option value="">Loading...</option>',s.textContent="";try{const c=new URLSearchParams({service:e,url:n}),i=await(await fetch(`/api/v1/arr/quality-profiles?${c}`)).json();if(!i.success||!i.profiles?.length){p.innerHTML='<option value="">-- No profiles found (enter API key and click Fetch) --</option>';return}p.innerHTML="";for(const y of i.profiles){const u=document.createElement("option");u.value=y.id,u.textContent=y.name,p.appendChild(u)}if(i.storedProfileId&&(p.value=String(i.storedProfileId)),!p.value){const y=i.profiles.find(u=>/720/i.test(u.name));y&&(p.value=String(y.id))}!p.value&&i.profiles.length&&(p.value=String(i.profiles[0].id)),s.innerHTML=`<span style="color: var(--ok-fg);">${i.profiles.length} profiles loaded</span>`}catch(c){p.innerHTML='<option value="">-- Failed to load --</option>',s.innerHTML=`<span style="color: var(--error, #c62828);">Error: ${c.message}</span>`}}document.getElementById("svc-quality-fetch")?.addEventListener("click",async()=>{if(!m)return;const a=m.id||m.appTemplate,p=v(m),e=document.getElementById("svc-apikey-input")?.value.trim(),n=document.getElementById("svc-quality-select"),c=document.getElementById("svc-quality-status");if(!p){c.innerHTML='<span style="color: var(--error, #c62828);">No service URL available</span>';return}if(!e||e==="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"){c.innerHTML='<span style="color: var(--error, #c62828);">Enter an API key first</span>';return}n.innerHTML='<option value="">Fetching...</option>',c.textContent="";try{const t=new URLSearchParams({service:a,url:p,apiKey:e}),y=await(await fetch(`/api/v1/arr/quality-profiles?${t}`)).json();if(!y.success){n.innerHTML='<option value="">-- Error --</option>',c.innerHTML=`<span style="color: var(--error, #c62828);">${y.error||"Failed to feLine truncated
|
||||
<div class="weather-modal-content" style="min-width: 420px; max-width: 520px;">
|
||||
<h3 style="margin: 0 0 16px; font-size: 1.1rem;">Authentication Settings</h3>
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);async function o(){try{const m=await(await fetch("/api/v1/totp/config")).json();if(!m.success)return;const{enabled:b,sessionDuration:r,isSetUp:v}=m.config,d=document.getElementById("totp-status-dot"),a=document.getElementById("totp-status-text"),p=document.getElementById("totp-status-banner"),s=document.getElementById("totp-setup-section"),e=document.getElementById("totp-qr-section"),n=document.getElementById("totp-duration-section"),c=document.getElementById("totp-disable-section");if(b&&v){d.style.background="var(--ok-fg, #7ef2ff)",p.style.borderColor="var(--ok-fg, #7ef2ff)",p.style.background="color-mix(in srgb, var(--ok-fg) 8%, transparent)",a.textContent="TOTP is active",a.style.color="var(--ok-fg, #7ef2ff)",s.style.display="block";const t=document.getElementById("totp-setup-btn");t&&(t.textContent="Generate New Secret"),e.style.display="none",n.style.display="block",c.style.display="block",document.getElementById("totp-duration-select").value=r}else d.style.background="var(--muted)",p.style.borderColor="var(--border)",p.style.background="transparent",a.textContent="TOTP is not configured",a.style.color="var(--muted)",s.style.display="block",e.style.display="none",n.style.display="none",c.style.display="none";h(b&&v,r)}catch(f){console.warn("Failed to load TOTP settings:",f)}}const g={"15m":"15 min","30m":"30 min","1h":"1 hour","2h":"2 hours","4h":"4 hours","8h":"8 hours","12h":"12 hours","24h":"24 hours",never:"Disabled"};function h(f,m){const b=document.getElementById("auth-card"),r=document.getElementById("auth-pill"),v=document.getElementById("auth-dot"),d=document.getElementById("auth-status-text");b&&(f?(b.setAttribute("data-status","on"),r.className="badge on",r.textContent="YES",v.className="dot ok at-bl",d.textContent="Session: "+(g[m]||m)):(b.setAttribute("data-status","off"),r.className="badge off",r.textContent="NO",v.className="dot bad at-bl",d.textContent="Not configured"))}document.getElementById("totp-setup-btn")?.addEventListener("click",async()=>{try{const m=await(await secureFetch("/api/v1/totp/setup",{method:"POST"})).json();m.success&&(document.getElementById("totp-qr-image").src=m.qrCode,document.getElementById("totp-manual-key").textContent=m.manualKey,document.getElementById("totp-setup-section").style.display="none",document.getElementById("totp-qr-section").style.display="block",document.getElementById("totp-setup-code").value="",document.getElementById("totp-setup-error").textContent="",document.getElementById("totp-setup-code").focus())}catch(f){l.logError("[TOTP] Setup Failed",f,{function:"setupTOTP"})}}),document.getElementById("totp-import-btn")?.addEventListener("click",async()=>{const f=document.getElementById("totp-import-key").value.trim(),m=document.getElementById("totp-import-error");if(m.textContent="",!f){m.textContent="Paste a Base32 secret key first";return}try{const r=await(await secureFetch("/api/v1/totp/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({secret:f})})).json();r.success?(m.textContent="",document.getElementById("totp-qr-image").src=r.qrCode,document.getElementById("totp-manual-key").textContent=r.manualKey,document.getElementById("totp-setup-section").style.display="none",document.getElementById("totp-qr-section").style.display="block",document.getElementById("totp-setup-code").value="",document.getElementById("totp-setup-error").textContent="",document.getElementById("totp-setup-code").focus()):m.textContent=r.error||r.message||"Import failed"}catch{m.textContent="Connection error \u2014 try refreshing the page"}}),document.getElementById("totp-copy-key")?.addEventListener("click",()=>{const f=document.getElementById("totp-manual-key").textContent;navigator.clipboard.writeText(f).then(()=>{const m=document.getElementById("totp-copy-key");m.textContent="\u2705",setTimeout(()=>{m.textContent="\u{1F4CB}"},2e3)})}),document.getElementById("totp-download-backup")?.addEventListener("click",()=>{const f=document.getElementById("totp-manual-key").textContent.trim();if(!f)return;const m={service:"DashCaddy",type:"totp-secret",secret:f,issuer:"DashCaddy",algorithm:"SHA1",digits:6,period:30,issued:new Date().toISOString(),recovery_url:`${window.location.origin}/ (login screen \u2192 "Lost access?")`,note:'Keep this file somewhere safe. Anyone with this secret can generate your login codes. Use it ONLY to recover TOTP access via the "Lost access?" link on the DashCaddy login screen.'},b=new Blob([JSON.stringify(m,null,2)],{type:"application/json"}),r=URL.createObjectURL(b),v=document.createElement("a");v.href=r,v.download=`dashcaddy-totp-backup-${new Date().toISOString().slice(0,10)}.json`,document.body.appendChild(v),v.click(),document.body.removeChild(v),URL.revokeObjectURL(r);const d=document.getElementById("totp-download-backup");d.textContent="\u2705 Saved",setTimeout(()=>{d.textContent="\u2B07 Download"},2e3)}),document.getElementById("totp-confirm-setup")?.addEventListener("click",async()=>{const f=doLine truncated
|
||||
</div>`);async function o(){try{const v=await(await fetch("/api/v1/totp/config")).json();if(!v.success)return;const{enabled:g,sessionDuration:r,isSetUp:h}=v.config,d=document.getElementById("totp-status-dot"),a=document.getElementById("totp-status-text"),p=document.getElementById("totp-status-banner"),s=document.getElementById("totp-setup-section"),e=document.getElementById("totp-qr-section"),n=document.getElementById("totp-duration-section"),c=document.getElementById("totp-disable-section");if(g&&h){d.style.background="var(--ok-fg, #7ef2ff)",p.style.borderColor="var(--ok-fg, #7ef2ff)",p.style.background="color-mix(in srgb, var(--ok-fg) 8%, transparent)",a.textContent="TOTP is active",a.style.color="var(--ok-fg, #7ef2ff)",s.style.display="block";const t=document.getElementById("totp-setup-btn");t&&(t.textContent="Generate New Secret"),e.style.display="none",n.style.display="block",c.style.display="block",document.getElementById("totp-duration-select").value=r}else d.style.background="var(--muted)",p.style.borderColor="var(--border)",p.style.background="transparent",a.textContent="TOTP is not configured",a.style.color="var(--muted)",s.style.display="block",e.style.display="none",n.style.display="none",c.style.display="none";b(g&&h,r)}catch(f){console.warn("Failed to load TOTP settings:",f)}}const m={"15m":"15 min","30m":"30 min","1h":"1 hour","2h":"2 hours","4h":"4 hours","8h":"8 hours","12h":"12 hours","24h":"24 hours",never:"Disabled"};function b(f,v){const g=document.getElementById("auth-card"),r=document.getElementById("auth-pill"),h=document.getElementById("auth-dot"),d=document.getElementById("auth-status-text");g&&(f?(g.setAttribute("data-status","on"),r.className="badge on",r.textContent="YES",h.className="dot ok at-bl",d.textContent="Session: "+(m[v]||v)):(g.setAttribute("data-status","off"),r.className="badge off",r.textContent="NO",h.className="dot bad at-bl",d.textContent="Not configured"))}document.getElementById("totp-setup-btn")?.addEventListener("click",async()=>{try{const v=await(await secureFetch("/api/v1/totp/setup",{method:"POST"})).json();v.success&&(document.getElementById("totp-qr-image").src=v.qrCode,document.getElementById("totp-manual-key").textContent=v.manualKey,document.getElementById("totp-setup-section").style.display="none",document.getElementById("totp-qr-section").style.display="block",document.getElementById("totp-setup-code").value="",document.getElementById("totp-setup-error").textContent="",document.getElementById("totp-setup-code").focus())}catch(f){l.logError("[TOTP] Setup Failed",f,{function:"setupTOTP"})}}),document.getElementById("totp-import-btn")?.addEventListener("click",async()=>{const f=document.getElementById("totp-import-key").value.trim(),v=document.getElementById("totp-import-error");if(v.textContent="",!f){v.textContent="Paste a Base32 secret key first";return}try{const r=await(await secureFetch("/api/v1/totp/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({secret:f})})).json();r.success?(v.textContent="",document.getElementById("totp-qr-image").src=r.qrCode,document.getElementById("totp-manual-key").textContent=r.manualKey,document.getElementById("totp-setup-section").style.display="none",document.getElementById("totp-qr-section").style.display="block",document.getElementById("totp-setup-code").value="",document.getElementById("totp-setup-error").textContent="",document.getElementById("totp-setup-code").focus()):v.textContent=r.error||r.message||"Import failed"}catch{v.textContent="Connection error \u2014 try refreshing the page"}}),document.getElementById("totp-copy-key")?.addEventListener("click",()=>{const f=document.getElementById("totp-manual-key").textContent;navigator.clipboard.writeText(f).then(()=>{const v=document.getElementById("totp-copy-key");v.textContent="\u2705",setTimeout(()=>{v.textContent="\u{1F4CB}"},2e3)})}),document.getElementById("totp-download-backup")?.addEventListener("click",()=>{const f=document.getElementById("totp-manual-key").textContent.trim();if(!f)return;const v={service:"DashCaddy",type:"totp-secret",secret:f,issuer:"DashCaddy",algorithm:"SHA1",digits:6,period:30,issued:new Date().toISOString(),recovery_url:`${window.location.origin}/ (login screen \u2192 "Lost access?")`,note:'Keep this file somewhere safe. Anyone with this secret can generate your login codes. Use it ONLY to recover TOTP access via the "Lost access?" link on the DashCaddy login screen.'},g=new Blob([JSON.stringify(v,null,2)],{type:"application/json"}),r=URL.createObjectURL(g),h=document.createElement("a");h.href=r,h.download=`dashcaddy-totp-backup-${new Date().toISOString().slice(0,10)}.json`,document.body.appendChild(h),h.click(),document.body.removeChild(h),URL.revokeObjectURL(r);const d=document.getElementById("totp-download-backup");d.textContent="\u2705 Saved",setTimeout(()=>{d.textContent="\u2B07 Download"},2e3)}),document.getElementById("totp-confirm-setup")?.addEventListener("click",async()=>{const f=doLine truncated
|
||||
<div id="token-management-modal" class="weather-modal">
|
||||
<div class="weather-modal-content" style="min-width: 500px; max-width: 600px;">
|
||||
<h3>\u{1F511} DNS Credentials</h3>
|
||||
@@ -306,7 +306,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);function l(){return Object.keys(SITE.dnsServers||{})}function o(t){return(SITE.dnsServers||{})[t]?.name||t.toUpperCase()}function g(){const t=document.getElementById("dns-cred-sections");if(!t)return;t.innerHTML="";const i=l();if(i.length===0){t.innerHTML='<p style="color: var(--muted); text-align: center; padding: 20px;">No DNS servers configured.</p>';return}for(const y of i)t.insertAdjacentHTML("beforeend",`
|
||||
`);function l(){return Object.keys(SITE.dnsServers||{})}function o(t){return(SITE.dnsServers||{})[t]?.name||t.toUpperCase()}function m(){const t=document.getElementById("dns-cred-sections");if(!t)return;t.innerHTML="";const i=l();if(i.length===0){t.innerHTML='<p style="color: var(--muted); text-align: center; padding: 20px;">No DNS servers configured.</p>';return}for(const y of i)t.insertAdjacentHTML("beforeend",`
|
||||
<div class="token-section">
|
||||
<h4 class="token-section-title">${o(y)}</h4>
|
||||
<div class="token-grid">
|
||||
@@ -329,7 +329,7 @@
|
||||
</div>
|
||||
<div class="token-status" id="${y}-token-status"></div>
|
||||
</div>
|
||||
`)}function h(){let t=safeSessionGet("dashcaddy-encryption-key");if(t)return t;const i=safeGet("dashcaddy-encryption-key");if(i)return safeSessionSet("dashcaddy-encryption-key",i),safeRemove("dashcaddy-encryption-key"),i;const y=new Uint8Array(32);return crypto.getRandomValues(y),t=Array.from(y,u=>u.toString(16).padStart(2,"0")).join(""),safeSessionSet("dashcaddy-encryption-key",t),t}const f=h();function m(t,i){if(!t)return"";const y=crypto.getRandomValues(new Uint8Array(8)),u=Array.from(y,L=>L.toString(16).padStart(2,"0")).join(""),w=new TextEncoder().encode(i+u);let T="";for(let L=0;L<t.length;L++){const A=t.charCodeAt(L)^w[L%w.length]^y[L%y.length]^L*31+17&255;T+=String.fromCharCode(A)}return u+":"+btoa(T)}function b(t,i){if(!t)return"";try{if(t.indexOf(":")===16){const T=t.substring(0,16),L=new Uint8Array(T.match(/.{2}/g).map($=>parseInt($,16))),A=atob(t.substring(17)),C=new TextEncoder().encode(i+T);let B="";for(let $=0;$<A.length;$++){const k=A.charCodeAt($)^C[$%C.length]^L[$%L.length]^$*31+17&255;B+=String.fromCharCode(k)}return B}const u=atob(t);let w="";for(let T=0;T<u.length;T++){const L=u.charCodeAt(T)^i.charCodeAt(T%i.length);w+=String.fromCharCode(L)}return w}catch{return""}}function r(t,i,y){const u=safeGet(`${t}-${i}-${y}-enc`);return b(u,f)}function v(t,i,y,u){const w=`${t}-${i}-${y}-enc`;u?safeSet(w,m(u,f)):safeRemove(w)}function d(t,i){return r(t,i,"token")}function a(t,i){return r(t,i,"username")}function p(t,i,y){v(t,i,"token",y)}function s(t,i,y){v(t,i,"username",y)}function e(){const t={};for(const i of l())t[i]={readonly:{username:a(i,"readonly"),token:d(i,"readonly")},admin:{username:a(i,"admin"),token:d(i,"admin")}};return t}function n(){l().forEach(t=>{["readonly","admin"].forEach(i=>{["token","username"].forEach(y=>{safeRemove(`${t}-${i}-${y}-enc`)})}),safeRemove(`${t}-token-enc`),safeRemove(`${t}-username-enc`)})}function c(t){const i=d(t,"readonly"),y=a(t,"readonly"),u=d(t,"admin"),w=a(t,"admin"),T=b(safeGet(`${t}-token-enc`),f),L=b(safeGet(`${t}-username-enc`),f);return{username:w||y||L,token:u||i||T,readonlyToken:i||T,readonlyUsername:y||L,adminToken:u||T,adminUsername:w||L}}document.getElementById("manage-tokens")?.addEventListener("click",()=>{g();const t=document.getElementById("token-management-modal"),i=e();l().forEach(y=>{const u=i[y];document.getElementById(`${y}-readonly-username`).value=u.readonly.username,document.getElementById(`${y}-readonly-token`).value=u.readonly.token,document.getElementById(`${y}-admin-username`).value=u.admin.username,document.getElementById(`${y}-admin-token`).value=u.admin.token,document.getElementById(`${y}-token-status`).textContent=""}),t.classList.add("show")}),document.getElementById("token-management-modal")?.addEventListener("click",t=>{const i=t.target.closest(".token-toggle");if(i){const y=i.dataset.target,u=document.getElementById(y);u.type==="password"?(u.type="text",i.textContent="\u{1F648}"):(u.type="password",i.textContent="\u{1F441}");return}t.target.id==="token-management-modal"&&t.target.classList.remove("show")}),document.getElementById("token-save")?.addEventListener("click",async()=>{const t=l();t.forEach(u=>{s(u,"readonly",document.getElementById(`${u}-readonly-username`).value.trim()),p(u,"readonly",document.getElementById(`${u}-readonly-token`).value.trim()),s(u,"admin",document.getElementById(`${u}-admin-username`).value.trim()),p(u,"admin",document.getElementById(`${u}-admin-token`).value.trim())});const i={};let y=!1;if(t.forEach(u=>{const w={},T=document.getElementById(`${u}-readonly-username`).value.trim(),L=document.getElementById(`${u}-readonly-token`).value.trim(),A=document.getElementById(`${u}-admin-username`).value.trim(),C=document.getElementById(`${u}-admin-token`).value.trim();T&&L&&(w.readonly={username:T,password:L},y=!0),A&&C&&(w.admin={username:A,password:C},y=!0),Object.keys(w).length>0&&(i[u]=w)}),y){t.forEach(u=>{i[u]&&(document.getElementById(`${u}-token-status`).textContent="Verifying...",document.getElementById(`${u}-token-status`).className="token-status")});try{const w=await(await secureFetch("/api/v1/dns/credentials",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({servers:i})})).json();w.results?t.forEach(T=>{const L=document.getElementById(`${T}-token-status`);if(!i[T]){L.textContent="";return}const A=w.results[T];A?.success?(L.textContent="\u2713 Verified & saved",L.className="token-status success"):A?.partial?(L.textContent="\u2713 "+A.partial,L.className="token-status success"):(L.textContent="\u2717 "+(A?.error||"Login failed"),L.className="token-status error")}):w.success?t.forEach(T=>{i[T]&&(document.getElementById(`${T}-token-status`).textContent="\u2713 Saved",document.getElementById(`${T}-token-status`).className="token-status success")}):t.forEach(T=>{i[T]&&(document.getElementById(`${T}-token-status`).textContent="\u2717 "+(w.error||"Failed"),document.getElementById(`${T}-token-status`).className="token-status error")})}catch(u){console.erro
Line truncated
|
||||
`)}function b(){let t=safeSessionGet("dashcaddy-encryption-key");if(t)return t;const i=safeGet("dashcaddy-encryption-key");if(i)return safeSessionSet("dashcaddy-encryption-key",i),safeRemove("dashcaddy-encryption-key"),i;const y=new Uint8Array(32);return crypto.getRandomValues(y),t=Array.from(y,u=>u.toString(16).padStart(2,"0")).join(""),safeSessionSet("dashcaddy-encryption-key",t),t}const f=b();function v(t,i){if(!t)return"";const y=crypto.getRandomValues(new Uint8Array(8)),u=Array.from(y,L=>L.toString(16).padStart(2,"0")).join(""),w=new TextEncoder().encode(i+u);let T="";for(let L=0;L<t.length;L++){const P=t.charCodeAt(L)^w[L%w.length]^y[L%y.length]^L*31+17&255;T+=String.fromCharCode(P)}return u+":"+btoa(T)}function g(t,i){if(!t)return"";try{if(t.indexOf(":")===16){const T=t.substring(0,16),L=new Uint8Array(T.match(/.{2}/g).map($=>parseInt($,16))),P=atob(t.substring(17)),C=new TextEncoder().encode(i+T);let B="";for(let $=0;$<P.length;$++){const k=P.charCodeAt($)^C[$%C.length]^L[$%L.length]^$*31+17&255;B+=String.fromCharCode(k)}return B}const u=atob(t);let w="";for(let T=0;T<u.length;T++){const L=u.charCodeAt(T)^i.charCodeAt(T%i.length);w+=String.fromCharCode(L)}return w}catch{return""}}function r(t,i,y){const u=safeGet(`${t}-${i}-${y}-enc`);return g(u,f)}function h(t,i,y,u){const w=`${t}-${i}-${y}-enc`;u?safeSet(w,v(u,f)):safeRemove(w)}function d(t,i){return r(t,i,"token")}function a(t,i){return r(t,i,"username")}function p(t,i,y){h(t,i,"token",y)}function s(t,i,y){h(t,i,"username",y)}function e(){const t={};for(const i of l())t[i]={readonly:{username:a(i,"readonly"),token:d(i,"readonly")},admin:{username:a(i,"admin"),token:d(i,"admin")}};return t}function n(){l().forEach(t=>{["readonly","admin"].forEach(i=>{["token","username"].forEach(y=>{safeRemove(`${t}-${i}-${y}-enc`)})}),safeRemove(`${t}-token-enc`),safeRemove(`${t}-username-enc`)})}function c(t){const i=d(t,"readonly"),y=a(t,"readonly"),u=d(t,"admin"),w=a(t,"admin"),T=g(safeGet(`${t}-token-enc`),f),L=g(safeGet(`${t}-username-enc`),f);return{username:w||y||L,token:u||i||T,readonlyToken:i||T,readonlyUsername:y||L,adminToken:u||T,adminUsername:w||L}}document.getElementById("manage-tokens")?.addEventListener("click",()=>{m();const t=document.getElementById("token-management-modal"),i=e();l().forEach(y=>{const u=i[y];document.getElementById(`${y}-readonly-username`).value=u.readonly.username,document.getElementById(`${y}-readonly-token`).value=u.readonly.token,document.getElementById(`${y}-admin-username`).value=u.admin.username,document.getElementById(`${y}-admin-token`).value=u.admin.token,document.getElementById(`${y}-token-status`).textContent=""}),t.classList.add("show")}),document.getElementById("token-management-modal")?.addEventListener("click",t=>{const i=t.target.closest(".token-toggle");if(i){const y=i.dataset.target,u=document.getElementById(y);u.type==="password"?(u.type="text",i.textContent="\u{1F648}"):(u.type="password",i.textContent="\u{1F441}");return}t.target.id==="token-management-modal"&&t.target.classList.remove("show")}),document.getElementById("token-save")?.addEventListener("click",async()=>{const t=l();t.forEach(u=>{s(u,"readonly",document.getElementById(`${u}-readonly-username`).value.trim()),p(u,"readonly",document.getElementById(`${u}-readonly-token`).value.trim()),s(u,"admin",document.getElementById(`${u}-admin-username`).value.trim()),p(u,"admin",document.getElementById(`${u}-admin-token`).value.trim())});const i={};let y=!1;if(t.forEach(u=>{const w={},T=document.getElementById(`${u}-readonly-username`).value.trim(),L=document.getElementById(`${u}-readonly-token`).value.trim(),P=document.getElementById(`${u}-admin-username`).value.trim(),C=document.getElementById(`${u}-admin-token`).value.trim();T&&L&&(w.readonly={username:T,password:L},y=!0),P&&C&&(w.admin={username:P,password:C},y=!0),Object.keys(w).length>0&&(i[u]=w)}),y){t.forEach(u=>{i[u]&&(document.getElementById(`${u}-token-status`).textContent="Verifying...",document.getElementById(`${u}-token-status`).className="token-status")});try{const w=await(await secureFetch("/api/v1/dns/credentials",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({servers:i})})).json();w.results?t.forEach(T=>{const L=document.getElementById(`${T}-token-status`);if(!i[T]){L.textContent="";return}const P=w.results[T];P?.success?(L.textContent="\u2713 Verified & saved",L.className="token-status success"):P?.partial?(L.textContent="\u2713 "+P.partial,L.className="token-status success"):(L.textContent="\u2717 "+(P?.error||"Login failed"),L.className="token-status error")}):w.success?t.forEach(T=>{i[T]&&(document.getElementById(`${T}-token-status`).textContent="\u2713 Saved",document.getElementById(`${T}-token-status`).className="token-status success")}):t.forEach(T=>{i[T]&&(document.getElementById(`${T}-token-status`).textContent="\u2717 "+(w.error||"Failed"),document.getElementById(`${T}-token-status`).className="token-status error")})}catch(u){console.erro
Line truncated
|
||||
|
||||
Current: ${s.currentVersion}
|
||||
New: ${s.updateVersion}
|
||||
@@ -337,9 +337,9 @@ New: ${s.updateVersion}
|
||||
`+(s.updateTitle?`${s.updateTitle}
|
||||
|
||||
`:"")+`The DNS server will restart during the update.
|
||||
Proceed?`)){d.textContent=a,d.disabled=!1,d.title="Update DNS server";return}d.textContent="\u{1F504}",d.title="Updating...";const c=await(await secureFetch(`/api/v1/dns/update?server=${encodeURIComponent(v)}`,{method:"POST"})).json();if(!c.success)throw new Error(c.error||"Update failed");if(c.manualUpdateRequired){d.textContent="\u2B06\uFE0F",d.title=`Update available: ${c.newVersion}`;const t=c.downloadLink?`
|
||||
Proceed?`)){d.textContent=a,d.disabled=!1,d.title="Update DNS server";return}d.textContent="\u{1F504}",d.title="Updating...";const c=await(await secureFetch(`/api/v1/dns/update?server=${encodeURIComponent(h)}`,{method:"POST"})).json();if(!c.success)throw new Error(c.error||"Update failed");if(c.manualUpdateRequired){d.textContent="\u2B06\uFE0F",d.title=`Update available: ${c.newVersion}`;const t=c.downloadLink?`
|
||||
Download: ${c.downloadLink}`:"",i=c.instructionsLink?`
|
||||
Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} update requires manual installation. Current: ${c.previousVersion} \u2192 ${c.newVersion}. Please update manually on the host machine.`,"warning",8e3),d.disabled=!1;return}d.textContent="\u2705",d.title="Updated successfully!",showNotification(`${r.toUpperCase()} updated successfully! ${c.previousVersion} \u2192 ${c.newVersion}. Server is restarting...`,"success"),setTimeout(()=>{d.textContent=a,d.disabled=!1,d.title="Update DNS server",window.refreshAll()},1e4)}catch(p){console.error("DNS update error:",p),d.textContent="\u274C",d.title="Update failed",showNotification(`Failed to update ${r.toUpperCase()}: ${p.message}`,"error"),setTimeout(()=>{d.textContent=a,d.disabled=!1,d.title="Update DNS server"},3e3)}}document.querySelector(".top")?.addEventListener("click",r=>{const v=r.target.closest('[id$="-update"]');if(!v)return;const d=v.id.replace("-update","");SITE.dnsServers[d]&&o(d,SITE.dnsServers[d]?.ip)}),injectModal("dns-settings-modal",`
|
||||
Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} update requires manual installation. Current: ${c.previousVersion} \u2192 ${c.newVersion}. Please update manually on the host machine.`,"warning",8e3),d.disabled=!1;return}d.textContent="\u2705",d.title="Updated successfully!",showNotification(`${r.toUpperCase()} updated successfully! ${c.previousVersion} \u2192 ${c.newVersion}. Server is restarting...`,"success"),setTimeout(()=>{d.textContent=a,d.disabled=!1,d.title="Update DNS server",window.refreshAll()},1e4)}catch(p){console.error("DNS update error:",p),d.textContent="\u274C",d.title="Update failed",showNotification(`Failed to update ${r.toUpperCase()}: ${p.message}`,"error"),setTimeout(()=>{d.textContent=a,d.disabled=!1,d.title="Update DNS server"},3e3)}}document.querySelector(".top")?.addEventListener("click",r=>{const h=r.target.closest('[id$="-update"]');if(!h)return;const d=h.id.replace("-update","");SITE.dnsServers[d]&&o(d,SITE.dnsServers[d]?.ip)}),injectModal("dns-settings-modal",`
|
||||
<div id="dns-settings-modal" class="weather-modal">
|
||||
<div class="weather-modal-content" style="min-width: 420px; max-width: 520px;">
|
||||
<h3 id="dns-settings-title">DNS Settings</h3>
|
||||
@@ -366,7 +366,7 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
<button id="dns-settings-save" class="btn-accent">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);let g=null;function h(r){g=r;const v=SITE.dnsServers[r]||{},d=document.getElementById("dns-settings-modal");document.getElementById("dns-settings-title").textContent=`${(v.name||r).toUpperCase()} Settings`,document.getElementById("dns-edit-ip").value=v.ip||"",document.getElementById("dns-edit-port").value=v.port||DC.DEFAULTS.DNS_PORT,document.getElementById("dns-edit-name").value=v.name||"",d.classList.add("show")}async function f(){if(!g)return;const r=document.getElementById("dns-edit-ip").value.trim(),v=document.getElementById("dns-edit-port").value.trim()||DC.DEFAULTS.DNS_PORT,d=document.getElementById("dns-edit-name").value.trim();if(!r){showNotification("Server IP is required","warning");return}const a={dnsServers:{}};a.dnsServers[g]={ip:r,port:String(v)},d&&(a.dnsServers[g].name=d);try{const s=await(await secureFetch("/api/v1/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();s.success?(SITE.dnsServers[g]=a.dnsServers[g],showNotification(`${g.toUpperCase()} settings saved`,"success"),b(),window.refreshAll()):showNotification(s.error||"Failed to save settings","error")}catch(p){showNotification("Failed to save: "+p.message,"error")}}async function m(){if(g&&confirm(`Remove ${g.toUpperCase()} from dashboard? This won't affect the actual DNS server.`))try{const v=await(await secureFetch("/api/v1/config")).json();v.dnsServers&&delete v.dnsServers[g];const a=await(await secureFetch("/api/v1/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({dnsServers:v.dnsServers||{}})})).json();if(a.success){delete SITE.dnsServers[g];const p=document.querySelector(`.top [data-app="${g}"]`);p&&p.remove(),showNotification(`${g.toUpperCase()} removed from dashboard`,"success"),b()}else showNotification(a.error||"Failed to remove","error")}catch(r){showNotification("Failed to remove: "+r.message,"error")}}function b(){closeModal("dns-settings-modal"),g=null}document.getElementById("dns-settings-cancel")?.addEventListener("click",b),document.getElementById("dns-settings-save")?.addEventListener("click",f),document.getElementById("dns-settings-delete")?.addEventListener("click",m),document.getElementById("dns-settings-modal")?.addEventListener("click",r=>{r.target.id==="dns-settings-modal"&&b()}),document.querySelector(".top")?.addEventListener("click",r=>{const v=r.target.closest('[id$="-settings"]');if(!v)return;const d=v.id.replace("-settings","");SITE.dnsServers[d]&&(r.stopPropagation(),h(d))}),document.getElementById("refresh")?.addEventListener("click",window.refreshAll)})(),(function(){injectModal("logs-modal",`
|
||||
</div>`);let m=null;function b(r){m=r;const h=SITE.dnsServers[r]||{},d=document.getElementById("dns-settings-modal");document.getElementById("dns-settings-title").textContent=`${(h.name||r).toUpperCase()} Settings`,document.getElementById("dns-edit-ip").value=h.ip||"",document.getElementById("dns-edit-port").value=h.port||DC.DEFAULTS.DNS_PORT,document.getElementById("dns-edit-name").value=h.name||"",d.classList.add("show")}async function f(){if(!m)return;const r=document.getElementById("dns-edit-ip").value.trim(),h=document.getElementById("dns-edit-port").value.trim()||DC.DEFAULTS.DNS_PORT,d=document.getElementById("dns-edit-name").value.trim();if(!r){showNotification("Server IP is required","warning");return}const a={dnsServers:{}};a.dnsServers[m]={ip:r,port:String(h)},d&&(a.dnsServers[m].name=d);try{const s=await(await secureFetch("/api/v1/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();s.success?(SITE.dnsServers[m]=a.dnsServers[m],showNotification(`${m.toUpperCase()} settings saved`,"success"),g(),window.refreshAll()):showNotification(s.error||"Failed to save settings","error")}catch(p){showNotification("Failed to save: "+p.message,"error")}}async function v(){if(m&&confirm(`Remove ${m.toUpperCase()} from dashboard? This won't affect the actual DNS server.`))try{const h=await(await secureFetch("/api/v1/config")).json();h.dnsServers&&delete h.dnsServers[m];const a=await(await secureFetch("/api/v1/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({dnsServers:h.dnsServers||{}})})).json();if(a.success){delete SITE.dnsServers[m];const p=document.querySelector(`.top [data-app="${m}"]`);p&&p.remove(),showNotification(`${m.toUpperCase()} removed from dashboard`,"success"),g()}else showNotification(a.error||"Failed to remove","error")}catch(r){showNotification("Failed to remove: "+r.message,"error")}}function g(){closeModal("dns-settings-modal"),m=null}document.getElementById("dns-settings-cancel")?.addEventListener("click",g),document.getElementById("dns-settings-save")?.addEventListener("click",f),document.getElementById("dns-settings-delete")?.addEventListener("click",v),document.getElementById("dns-settings-modal")?.addEventListener("click",r=>{r.target.id==="dns-settings-modal"&&g()}),document.querySelector(".top")?.addEventListener("click",r=>{const h=r.target.closest('[id$="-settings"]');if(!h)return;const d=h.id.replace("-settings","");SITE.dnsServers[d]&&(r.stopPropagation(),b(d))}),document.getElementById("refresh")?.addEventListener("click",window.refreshAll)})(),(function(){injectModal("logs-modal",`
|
||||
<div id="logs-modal" class="logs-modal">
|
||||
<div class="logs-modal-content" style="min-width: 800px; max-width: 1000px;">
|
||||
<div class="logs-header">
|
||||
@@ -390,13 +390,13 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);let l=null,o=null,g=!1,h=null,f=null,m=!1,b=null,r=null,v=!1,d=null,a=!1;async function p(k,x=25){try{const I=getDnsServerAddr(k),E=await fetch(`/api/v1/dns/logs?server=${I}&limit=${x}`,{cache:"no-store",headers:{Accept:"application/json","Cache-Control":"no-cache"}});if(E.ok){const S=await E.json();return S.success&&S.logs?{logs:S.logs,count:S.count,server:S.server}:{error:S.error||"Failed to fetch logs"}}else return E.status===401?{error:"DNS auto-auth failed - check credentials in settings"}:{error:`HTTP ${E.status}`}}catch(I){return console.error("DNS logs fetch failed:",I),{error:I.message}}}function s(k){return{NoError:"var(--ok-fg)",NOERROR:"var(--ok-fg)",NxDomain:"var(--muted)",NXDOMAIN:"var(--muted)",Refused:"var(--bad-fg)",REFUSED:"var(--bad-fg)",ServerFailure:"#f39c12",SERVFAIL:"#f39c12"}[k]||"var(--fg)"}function e(k){const x=document.createElement("div");if(x.className="log-entry",x.style.cssText="display: grid; grid-template-columns: 140px 110px 1fr 70px 80px; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: center;",k.parsed===!1)return x.style.gridTemplateColumns="1fr",x.innerHTML=`<span style="color: var(--muted); font-family: monospace; font-size: 0.75rem;">${escapeHtml(k.raw)}</span>`,x;const I=s(k.rcode),E=k.rcode==="Refused"||k.rcode==="REFUSED";return x.innerHTML=`
|
||||
</div>`);let l=null,o=null,m=!1,b=null,f=null,v=!1,g=null,r=null,h=!1,d=null,a=!1;async function p(k,x=25){try{const I=getDnsServerAddr(k),E=await fetch(`/api/v1/dns/logs?server=${I}&limit=${x}`,{cache:"no-store",headers:{Accept:"application/json","Cache-Control":"no-cache"}});if(E.ok){const S=await E.json();return S.success&&S.logs?{logs:S.logs,count:S.count,server:S.server}:{error:S.error||"Failed to fetch logs"}}else return E.status===401?{error:"DNS auto-auth failed - check credentials in settings"}:{error:`HTTP ${E.status}`}}catch(I){return console.error("DNS logs fetch failed:",I),{error:I.message}}}function s(k){return{NoError:"var(--ok-fg)",NOERROR:"var(--ok-fg)",NxDomain:"var(--muted)",NXDOMAIN:"var(--muted)",Refused:"var(--bad-fg)",REFUSED:"var(--bad-fg)",ServerFailure:"#f39c12",SERVFAIL:"#f39c12"}[k]||"var(--fg)"}function e(k){const x=document.createElement("div");if(x.className="log-entry",x.style.cssText="display: grid; grid-template-columns: 140px 110px 1fr 70px 80px; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: center;",k.parsed===!1)return x.style.gridTemplateColumns="1fr",x.innerHTML=`<span style="color: var(--muted); font-family: monospace; font-size: 0.75rem;">${escapeHtml(k.raw)}</span>`,x;const I=s(k.rcode),E=k.rcode==="Refused"||k.rcode==="REFUSED";return x.innerHTML=`
|
||||
<span style="color: var(--muted); font-size: 0.75rem;">${escapeHtml(k.timestamp)}</span>
|
||||
<span style="color: var(--accent); font-size: 0.75rem;" title="${escapeHtml(k.client)}">${escapeHtml(k.client)}</span>
|
||||
<span style="font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ${E?"text-decoration: line-through; opacity: 0.6;":""}" title="${escapeHtml(k.domain)}">${escapeHtml(k.domain)}</span>
|
||||
<span style="color: var(--muted); font-size: 0.75rem;">${escapeHtml(k.type)}</span>
|
||||
<span style="color: ${I}; font-weight: 500; font-size: 0.75rem;">${escapeHtml(k.rcode)}</span>
|
||||
`,x}async function n(){if(v){await B();return}if(m){await T();return}if(g||!l)return;const k=parseInt(document.getElementById("log-lines").value),x=document.getElementById("logs-content");try{const I=await p(l,k);if(I.error){x.innerHTML=`
|
||||
`,x}async function n(){if(h){await B();return}if(v){await T();return}if(m||!l)return;const k=parseInt(document.getElementById("log-lines").value),x=document.getElementById("logs-content");try{const I=await p(l,k);if(I.error){x.innerHTML=`
|
||||
<div style="padding: 20px; text-align: center; color: var(--bad-fg);">
|
||||
<div style="font-size: 1.2rem; margin-bottom: 8px;">\u26A0\uFE0F Error</div>
|
||||
<div>${escapeHtml(I.error)}</div>
|
||||
@@ -413,13 +413,13 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
</div>`}catch(I){x.innerHTML=`
|
||||
<div style="padding: 20px; text-align: center; color: var(--bad-fg);">
|
||||
Failed to fetch logs: ${escapeHtml(I.message)}
|
||||
</div>`}}function c(k){l=k,g=!1,m=!1;const x=document.getElementById("logs-modal"),I=document.getElementById("logs-title"),E=document.getElementById("logs-pause"),S=document.getElementById("logs-stream");I.textContent=`${k.toUpperCase()} DNS Logs`,E.textContent="\u23F8\uFE0F Pause",E.classList.remove("paused"),S&&(S.style.display="none"),x.classList.add("show"),n(),o=setInterval(n,DC.POLL.LOGS)}function t(){document.getElementById("logs-modal").classList.remove("show"),o&&(clearInterval(o),o=null),y(),l=null,m=!1,h=null,f=null,v=!1,b=null,r=null,g=!1}function i(k){d&&y();const x=document.getElementById("logs-stream"),I=document.getElementById("logs-pause"),E=document.getElementById("logs-content");o&&(clearInterval(o),o=null);try{d=new EventSource(`/api/v1/logs/stream/${k}`),a=!0,x.classList.add("active"),x.textContent="\u{1F534} Live",x.title="Streaming - click to stop",I.style.display="none";const S=document.getElementById("logs-title");S.textContent.includes("\u{1F534}")||(S.innerHTML=S.textContent.replace("\u{1F4CB}","\u{1F4CB} \u{1F534}")),d.onmessage=P=>{try{const D=JSON.parse(P.data);if(D.error){console.error("Stream error:",D.error),y();return}const O=document.createElement("div");O.className="log-entry",O.style.cssText="display: flex; gap: 12px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: flex-start; font-family: monospace;";const R=(D.stream||"stdout")==="stderr",N=R?"var(--bad-fg)":"var(--fg)",M=`<span style="background: ${R?"var(--bad-bg)":"var(--ok-bg)"}; color: ${N}; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; min-width: 50px; text-align: center;">${R?"STDERR":"STDOUT"}</span>`;for(O.innerHTML=`
|
||||
</div>`}}function c(k){l=k,m=!1,v=!1;const x=document.getElementById("logs-modal"),I=document.getElementById("logs-title"),E=document.getElementById("logs-pause"),S=document.getElementById("logs-stream");I.textContent=`${k.toUpperCase()} DNS Logs`,E.textContent="\u23F8\uFE0F Pause",E.classList.remove("paused"),S&&(S.style.display="none"),x.classList.add("show"),n(),o=setInterval(n,DC.POLL.LOGS)}function t(){document.getElementById("logs-modal").classList.remove("show"),o&&(clearInterval(o),o=null),y(),l=null,v=!1,b=null,f=null,h=!1,g=null,r=null,m=!1}function i(k){d&&y();const x=document.getElementById("logs-stream"),I=document.getElementById("logs-pause"),E=document.getElementById("logs-content");o&&(clearInterval(o),o=null);try{d=new EventSource(`/api/v1/logs/stream/${k}`),a=!0,x.classList.add("active"),x.textContent="\u{1F534} Live",x.title="Streaming - click to stop",I.style.display="none";const S=document.getElementById("logs-title");S.textContent.includes("\u{1F534}")||(S.innerHTML=S.textContent.replace("\u{1F4CB}","\u{1F4CB} \u{1F534}")),d.onmessage=A=>{try{const D=JSON.parse(A.data);if(D.error){console.error("Stream error:",D.error),y();return}const O=document.createElement("div");O.className="log-entry",O.style.cssText="display: flex; gap: 12px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: flex-start; font-family: monospace;";const R=(D.stream||"stdout")==="stderr",N=R?"var(--bad-fg)":"var(--fg)",M=`<span style="background: ${R?"var(--bad-bg)":"var(--ok-bg)"}; color: ${N}; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; min-width: 50px; text-align: center;">${R?"STDERR":"STDOUT"}</span>`;for(O.innerHTML=`
|
||||
<div style="flex-shrink: 0;">${M}</div>
|
||||
<div style="flex: 1; color: ${N}; word-break: break-all; white-space: pre-wrap;">${escapeHtml(D.text)}</div>
|
||||
`,E.appendChild(O),E.scrollTop=E.scrollHeight;E.children.length>500;)E.removeChild(E.firstChild)}catch(D){console.error("Error parsing stream data:",D)}},d.onerror=P=>{console.error("EventSource error:",P),y()}}catch(S){console.error("Failed to start streaming:",S),y()}}function y(){d&&(d.close(),d=null),a=!1;const k=document.getElementById("logs-stream"),x=document.getElementById("logs-pause"),I=document.getElementById("logs-title");k&&(k.classList.remove("active"),k.textContent="\u{1F4E1} Live",k.title="Enable real-time streaming"),x&&(x.style.display=""),I&&(I.textContent=I.textContent.replace(" \u{1F534}","")),m&&h&&!o&&(o=setInterval(T,DC.POLL.LOGS))}async function u(k,x=100){try{const I=`/api/v1/logs/container/${k}?tail=${x}×tamps=true`,E=await fetch(I,{cache:"no-store",headers:{Accept:"application/json","Cache-Control":"no-cache"}});if(E.ok){const S=await E.json();return S.success&&S.logs?{logs:S.logs,count:S.count,containerName:S.containerName,containerId:S.containerId}:{error:S.error||"Failed to fetch container logs"}}else return{error:`HTTP ${E.status}: ${E.statusText}`}}catch(I){return console.error("Container logs fetch failed:",I),{error:I.message}}}function w(k){const x=document.createElement("div");x.className="log-entry",x.style.cssText="display: flex; gap: 12px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: flex-start; font-family: monospace;";const I=k.stream==="stderr"?"var(--bad-fg)":"var(--fg)",E=k.stream==="stderr"?'<span style="background: var(--bad-bg); color: var(--bad-fg); padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600;">STDERR</span>':'<span style="background: var(--ok-bg); color: var(--ok-fg); padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600;">STDOUT</span>';return x.innerHTML=`
|
||||
`,E.appendChild(O),E.scrollTop=E.scrollHeight;E.children.length>500;)E.removeChild(E.firstChild)}catch(D){console.error("Error parsing stream data:",D)}},d.onerror=A=>{console.error("EventSource error:",A),y()}}catch(S){console.error("Failed to start streaming:",S),y()}}function y(){d&&(d.close(),d=null),a=!1;const k=document.getElementById("logs-stream"),x=document.getElementById("logs-pause"),I=document.getElementById("logs-title");k&&(k.classList.remove("active"),k.textContent="\u{1F4E1} Live",k.title="Enable real-time streaming"),x&&(x.style.display=""),I&&(I.textContent=I.textContent.replace(" \u{1F534}","")),v&&b&&!o&&(o=setInterval(T,DC.POLL.LOGS))}async function u(k,x=100){try{const I=`/api/v1/logs/container/${k}?tail=${x}×tamps=true`,E=await fetch(I,{cache:"no-store",headers:{Accept:"application/json","Cache-Control":"no-cache"}});if(E.ok){const S=await E.json();return S.success&&S.logs?{logs:S.logs,count:S.count,containerName:S.containerName,containerId:S.containerId}:{error:S.error||"Failed to fetch container logs"}}else return{error:`HTTP ${E.status}: ${E.statusText}`}}catch(I){return console.error("Container logs fetch failed:",I),{error:I.message}}}function w(k){const x=document.createElement("div");x.className="log-entry",x.style.cssText="display: flex; gap: 12px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: flex-start; font-family: monospace;";const I=k.stream==="stderr"?"var(--bad-fg)":"var(--fg)",E=k.stream==="stderr"?'<span style="background: var(--bad-bg); color: var(--bad-fg); padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600;">STDERR</span>':'<span style="background: var(--ok-bg); color: var(--ok-fg); padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600;">STDOUT</span>';return x.innerHTML=`
|
||||
<div style="flex-shrink: 0;">${E}</div>
|
||||
<div style="flex: 1; color: ${I}; word-break: break-all; white-space: pre-wrap;">${escapeHtml(k.text)}</div>
|
||||
`,x}async function T(){if(g||!h||!m)return;const k=parseInt(document.getElementById("log-lines").value),x=document.getElementById("logs-content");try{const I=await u(h,k);if(I.error){x.innerHTML=`
|
||||
`,x}async function T(){if(m||!b||!v)return;const k=parseInt(document.getElementById("log-lines").value),x=document.getElementById("logs-content");try{const I=await u(b,k);if(I.error){x.innerHTML=`
|
||||
<div style="padding: 20px; text-align: center; color: var(--bad-fg);">
|
||||
<div style="font-size: 1.2rem; margin-bottom: 8px;">\u26A0\uFE0F Error</div>
|
||||
<div>${escapeHtml(I.error)}</div>
|
||||
@@ -433,10 +433,10 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
</div>`}catch(I){x.innerHTML=`
|
||||
<div style="padding: 20px; text-align: center; color: var(--bad-fg);">
|
||||
Failed to fetch logs: ${escapeHtml(I.message)}
|
||||
</div>`}}function L(k,x){h=k,f=x,m=!0,v=!1,g=!1,y();const I=document.getElementById("logs-modal"),E=document.getElementById("logs-title"),S=document.getElementById("logs-pause"),P=document.getElementById("logs-stream");E.textContent=`\u{1F4CB} ${x} - Container Logs`,S.textContent="\u23F8\uFE0F Pause",S.classList.remove("paused"),P&&(P.style.display=""),I.classList.add("show"),T(),o=setInterval(T,DC.POLL.LOGS)}async function A(k,x=100){try{const I=`/api/v1/logs/file?path=${encodeURIComponent(k)}&tail=${x}`,E=await fetch(I,{cache:"no-store",headers:{Accept:"application/json","Cache-Control":"no-cache"}});if(E.ok){const S=await E.json();return S.success&&S.logs?{logs:S.logs,count:S.count,logPath:S.logPath,totalLines:S.totalLines}:{error:S.error||"Failed to fetch file logs"}}else return{error:(await E.json().catch(()=>({}))).error||`HTTP ${E.status}`}}catch(I){return console.error("File logs fetch failed:",I),{error:I.message}}}function C(k){const x=document.createElement("div");x.className="log-entry",x.style.cssText="display: flex; gap: 12px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: flex-start; font-family: monospace;";const I=k.text;let E="INFO",S="var(--fg)";I.match(/ERROR|FATAL|CRITICAL/i)?(E="ERROR",S="var(--bad-fg)"):I.match(/WARN|WARNING/i)?(E="WARN",S="#f39c12"):I.match(/DEBUG/i)&&(E="DEBUG",S="var(--muted)");const D=`<span style="background: ${S==="var(--bad-fg)"?"var(--bad-bg)":"var(--ok-bg)"}; color: ${S}; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; min-width: 50px; text-align: center;">${E}</span>`;return x.innerHTML=`
|
||||
</div>`}}function L(k,x){b=k,f=x,v=!0,h=!1,m=!1,y();const I=document.getElementById("logs-modal"),E=document.getElementById("logs-title"),S=document.getElementById("logs-pause"),A=document.getElementById("logs-stream");E.textContent=`\u{1F4CB} ${x} - Container Logs`,S.textContent="\u23F8\uFE0F Pause",S.classList.remove("paused"),A&&(A.style.display=""),I.classList.add("show"),T(),o=setInterval(T,DC.POLL.LOGS)}async function P(k,x=100){try{const I=`/api/v1/logs/file?path=${encodeURIComponent(k)}&tail=${x}`,E=await fetch(I,{cache:"no-store",headers:{Accept:"application/json","Cache-Control":"no-cache"}});if(E.ok){const S=await E.json();return S.success&&S.logs?{logs:S.logs,count:S.count,logPath:S.logPath,totalLines:S.totalLines}:{error:S.error||"Failed to fetch file logs"}}else return{error:(await E.json().catch(()=>({}))).error||`HTTP ${E.status}`}}catch(I){return console.error("File logs fetch failed:",I),{error:I.message}}}function C(k){const x=document.createElement("div");x.className="log-entry",x.style.cssText="display: flex; gap: 12px; padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.8rem; align-items: flex-start; font-family: monospace;";const I=k.text;let E="INFO",S="var(--fg)";I.match(/ERROR|FATAL|CRITICAL/i)?(E="ERROR",S="var(--bad-fg)"):I.match(/WARN|WARNING/i)?(E="WARN",S="#f39c12"):I.match(/DEBUG/i)&&(E="DEBUG",S="var(--muted)");const D=`<span style="background: ${S==="var(--bad-fg)"?"var(--bad-bg)":"var(--ok-bg)"}; color: ${S}; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; min-width: 50px; text-align: center;">${E}</span>`;return x.innerHTML=`
|
||||
<div style="flex-shrink: 0;">${D}</div>
|
||||
<div style="flex: 1; color: ${S}; word-break: break-all; white-space: pre-wrap;">${escapeHtml(I)}</div>
|
||||
`,x}async function B(){if(g||!b||!v)return;const k=parseInt(document.getElementById("log-lines").value),x=document.getElementById("logs-content");try{const I=await A(b,k);if(I.error){x.innerHTML=`
|
||||
`,x}async function B(){if(m||!g||!h)return;const k=parseInt(document.getElementById("log-lines").value),x=document.getElementById("logs-content");try{const I=await P(g,k);if(I.error){x.innerHTML=`
|
||||
<div style="padding: 20px; text-align: center; color: var(--bad-fg);">
|
||||
<div style="font-size: 1.2rem; margin-bottom: 8px;">\u26A0\uFE0F Error</div>
|
||||
<div>${escapeHtml(I.error)}</div>
|
||||
@@ -449,7 +449,7 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
</div>`}catch(I){x.innerHTML=`
|
||||
<div style="padding: 20px; text-align: center; color: var(--bad-fg);">
|
||||
Failed to fetch logs: ${escapeHtml(I.message)}
|
||||
</div>`}}function $(k,x){b=k,r=x,v=!0,m=!1,g=!1;const I=document.getElementById("logs-modal"),E=document.getElementById("logs-title"),S=document.getElementById("logs-pause"),P=document.getElementById("logs-stream");E.textContent=`\u{1F4CB} ${x} - Application Logs`,S.textContent="\u23F8\uFE0F Pause",S.classList.remove("paused"),P&&(P.style.display="none"),I.classList.add("show"),B(),o=setInterval(B,DC.POLL.LOGS)}document.querySelector(".top")?.addEventListener("click",k=>{const x=k.target.closest('[id$="-logs"]');if(!x)return;const I=x.id.replace("-logs","");SITE.dnsServers[I]&&c(I)}),document.getElementById("logs-close")?.addEventListener("click",t),document.getElementById("logs-pause")?.addEventListener("click",()=>{g=!g;const k=document.getElementById("logs-pause");g?(k.textContent="\u25B6\uFE0F Resume",k.classList.add("paused")):(k.textContent="\u23F8\uFE0F Pause",k.classList.remove("paused"),n())}),document.getElementById("log-lines")?.addEventListener("change",()=>{g||n()}),document.getElementById("logs-stream")?.addEventListener("click",()=>{!m||!h||(a?y():i(h))}),document.getElementById("logs-modal")?.addEventListener("click",k=>{k.target.id==="logs-modal"&&t()}),document.addEventListener("keydown",k=>{k.key==="Escape"&&document.getElementById("logs-modal")?.classList.contains("show")&&t()}),window.openContainerLogsModal=L,window.openFileLogsModal=$,window.openLogsModal=c})(),(function(){injectModal("service-edit-modal",`
|
||||
</div>`}}function $(k,x){g=k,r=x,h=!0,v=!1,m=!1;const I=document.getElementById("logs-modal"),E=document.getElementById("logs-title"),S=document.getElementById("logs-pause"),A=document.getElementById("logs-stream");E.textContent=`\u{1F4CB} ${x} - Application Logs`,S.textContent="\u23F8\uFE0F Pause",S.classList.remove("paused"),A&&(A.style.display="none"),I.classList.add("show"),B(),o=setInterval(B,DC.POLL.LOGS)}document.querySelector(".top")?.addEventListener("click",k=>{const x=k.target.closest('[id$="-logs"]');if(!x)return;const I=x.id.replace("-logs","");SITE.dnsServers[I]&&c(I)}),document.getElementById("logs-close")?.addEventListener("click",t),document.getElementById("logs-pause")?.addEventListener("click",()=>{m=!m;const k=document.getElementById("logs-pause");m?(k.textContent="\u25B6\uFE0F Resume",k.classList.add("paused")):(k.textContent="\u23F8\uFE0F Pause",k.classList.remove("paused"),n())}),document.getElementById("log-lines")?.addEventListener("change",()=>{m||n()}),document.getElementById("logs-stream")?.addEventListener("click",()=>{!v||!b||(a?y():i(b))}),document.getElementById("logs-modal")?.addEventListener("click",k=>{k.target.id==="logs-modal"&&t()}),document.addEventListener("keydown",k=>{k.key==="Escape"&&document.getElementById("logs-modal")?.classList.contains("show")&&t()}),window.openContainerLogsModal=L,window.openFileLogsModal=$,window.openLogsModal=c})(),(function(){injectModal("service-edit-modal",`
|
||||
<div id="service-edit-modal" class="weather-modal">
|
||||
<div class="weather-modal-content" style="min-width: 500px; max-width: 600px;">
|
||||
<h3 id="service-edit-title">Edit Service</h3>
|
||||
@@ -805,7 +805,7 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
<button id="add-service-create" class="btn-accent">Create Service</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`)})(),(function(){async function l(m){try{const b=await fetch(`/api/v1/caddy/cas?caddyfilePath=${encodeURIComponent(m)}`);if(!b.ok)throw new Error(`Failed to load CAs: ${b.status}`);const r=await b.json();if(r.success){const v=document.getElementById("existing-ca-select");return v.innerHTML="",r.cas.length===0?v.innerHTML='<option value="">No CAs found in Caddyfile</option>':(v.innerHTML='<option value="">Select existing CA...</option>',r.cas.forEach(d=>{const a=document.createElement("option");typeof d=="object"?(a.value=d.id,a.textContent=d.displayName||d.name):(a.value=d,a.textContent=d),v.appendChild(a)})),r.data.cas}else throw new Error(r.message)}catch(b){console.error("Error loading CAs:",b);const r=document.getElementById("existing-ca-select");return r.innerHTML='<option value="">Error loading CAs</option>',[]}}function o(m){const{subdomain:b,port:r,ip:v,sslType:d,caName:a,existingCa:p,enableAuth:s,enableCors:e,customHeaders:n,upstreamPath:c,healthCheck:t,timeout:i,tailscaleOnly:y}=m;let u=`${buildDomain(b)} {
|
||||
</div>`)})(),(function(){async function l(v){try{const g=await fetch(`/api/v1/caddy/cas?caddyfilePath=${encodeURIComponent(v)}`);if(!g.ok)throw new Error(`Failed to load CAs: ${g.status}`);const r=await g.json();if(r.success){const h=document.getElementById("existing-ca-select");return h.innerHTML="",r.cas.length===0?h.innerHTML='<option value="">No CAs found in Caddyfile</option>':(h.innerHTML='<option value="">Select existing CA...</option>',r.cas.forEach(d=>{const a=document.createElement("option");typeof d=="object"?(a.value=d.id,a.textContent=d.displayName||d.name):(a.value=d,a.textContent=d),h.appendChild(a)})),r.data.cas}else throw new Error(r.message)}catch(g){console.error("Error loading CAs:",g);const r=document.getElementById("existing-ca-select");return r.innerHTML='<option value="">Error loading CAs</option>',[]}}function o(v){const{subdomain:g,port:r,ip:h,sslType:d,caName:a,existingCa:p,enableAuth:s,enableCors:e,customHeaders:n,upstreamPath:c,healthCheck:t,timeout:i,tailscaleOnly:y}=v;let u=`${buildDomain(g)} {
|
||||
`;switch(y&&(u+=` @blocked not remote_ip 100.64.0.0/10
|
||||
`,u+=` respond @blocked "Access denied. Tailscale connection required." 403
|
||||
`),d){case"letsencrypt":break;case"caddy-managed":u+=` tls internal
|
||||
@@ -827,7 +827,7 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
`,Object.entries(w).forEach(([T,L])=>{u+=` ${T} "${L}"
|
||||
`}),u+=` }
|
||||
`}catch{console.warn("Invalid JSON in custom headers")}return t&&(u+=` health_uri ${t}
|
||||
`),u+=` reverse_proxy ${v}:${r} {
|
||||
`),u+=` reverse_proxy ${h}:${r} {
|
||||
`,c&&c!=="/"&&(u+=` rewrite ${c}
|
||||
`),i&&i!==30&&(u+=` transport http {
|
||||
`,u+=` dial_timeout ${i}s
|
||||
@@ -835,15 +835,15 @@ Instructions: ${c.instructionsLink}`:"";showNotification(`${r.toUpperCase()} upd
|
||||
`,u+=` }
|
||||
`),u+=` }
|
||||
`,u+=`}
|
||||
`,u}async function g(m,b,r=DC.DEFAULTS.TTL){const v=window.getToken(getPrimaryDnsId(),"admin");if(!v)throw new Error("DNS admin token not configured. Please set it in the Tokens menu.");const d=buildDomain(m),a=await secureFetch("/api/v1/dns/record",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:d,ip:b,ttl:r,token:v,server:SITE.dnsIp})});if(!a.ok){const s=await a.text();throw new Error(`DNS API Error: ${a.status} - ${s}`)}const p=await a.json();if(!p.success)throw new Error(`DNS Error: ${p.error||"Unknown error"}`);return p}async function h(m){const b={id:m.subdomain,name:m.name,logo:m.logo||`/assets/${m.subdomain}.png`};m.category&&(b.category=m.category),m.containerId&&(b.containerId=m.containerId);try{const r=await secureFetch("/api/v1/services",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(b)});if(!r.ok){const v=await r.json();throw new Error(v.error||"Failed to save service")}return await window.loadServices(),window.buildGrid(),b}catch(r){throw console.error("Failed to add service to config:",r),r}}async function f(m){const b=document.getElementById("service-subdomain-input").value.trim(),r=document.getElementById("service-ip-input").value.trim()||"localhost",v=document.getElementById("service-port-input").value.trim()||"80",d=await secureFetch("/api/v1/site",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:buildDomain(b),upstream:`${r}:${v}`,config:m})}),a=await d.json();if(!d.ok||!a.success)throw new Error(a.error||`Caddy API Error: ${d.status}`);return a}window.loadExistingCAs=l,window.generateCaddyConfig=o,window.createDnsRecord=g,window.addServiceToConfig=h,window.addToCaddyfile=f})(),(function(){let l=null;function o(r){l=r;const v=document.getElementById("service-edit-modal");document.getElementById("service-edit-title").textContent=`Edit ${r.name}`,document.getElementById("edit-service-name").value=r.name,document.getElementById("edit-service-url-display").textContent=r.url||buildServiceUrl(r.id);const d=document.getElementById("edit-service-logo-preview"),a=r.logo||`/assets/${r.id}.png`;d.src=a,d.onerror=function(){this.style.display="none"},d.onload=function(){this.style.display=""},d.style.display="",document.getElementById("edit-subdomain").value=r.id,document.getElementById("edit-port").value=r.port||"",document.getElementById("edit-ip").value=r.ip||"localhost",document.getElementById("edit-tailscale-only").checked=r.tailscaleOnly||!1,document.getElementById("edit-logo-url").value=r.logo||"";const p=document.getElementById("edit-service-category");p&&(p.dataset.current=r.category||"",typeof window.populateCategorySelects=="function"&&window.populateCategorySelects()),v.classList.add("show")}function g(){closeModal("service-edit-modal"),l=null}async function h(){if(!l)return;const r=document.getElementById("edit-subdomain").value.trim().toLowerCase(),v=document.getElementById("edit-service-name").value.trim(),d=document.getElementById("edit-port").value.trim(),a=document.getElementById("edit-ip").value.trim()||"localhost",p=document.getElementById("edit-tailscale-only").checked,s=document.getElementById("edit-logo-url").value.trim(),e=document.getElementById("edit-service-category")?.value||"";if(!r){showNotification("Subdomain is required","warning");return}const n=l.id,c=[];if(r!==n&&c.push("subdomain"),v&&v!==l.name&&c.push("name"),d&&d!==String(l.port)&&c.push("port"),a!==l.ip&&c.push("ip"),p!==(l.tailscaleOnly||!1)&&c.push("tailscale"),s&&s!==l.logo&&c.push("logo"),e!==(l.category||"")&&c.push("category"),c.length===0){g();return}const t=document.getElementById("service-edit-save");t.textContent="Saving...",t.disabled=!0;try{const y=await(await secureFetch("/api/v1/services/update",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubdomain:n,newSubdomain:r,name:v||l.name,port:d||l.port,ip:a,tailscaleOnly:p,logo:s||void 0,category:e})})).json();if(!y.success)throw new Error(y.error||"Failed to update service");const u=window.APPS.findIndex(w=>w.id===n);u!==-1&&(window.APPS[u]={...window.APPS[u],id:r,name:v||window.APPS[u].name,port:d||window.APPS[u].port,ip:a,tailscaleOnly:p,logo:s||window.APPS[u].logo,category:e||void 0}),g(),window.buildGrid(),window.refreshAll()}catch(i){console.error("Error saving service changes:",i),showNotification(`Error saving changes: ${i.message}`,"error")}finally{t.textContent="Save Changes",t.disabled=!1}}document.getElementById("edit-logo-file")?.addEventListener("change",async r=>{const v=r.target.files[0];if(!v)return;if(!v.type.startsWith("image/")){showNotification("Please select an image file","warning");return}const d=new FileReader;d.onload=async a=>{const p=a.target.result;if(document.getElementById("edit-service-logo-preview").src=p,document.getElementById("edit-logo-url").value=p,l)try{const e=await(await secureFetch("/api/v1/assets/upload",{method:"POST",headers:{"Content-Type":"application/json"},bod
Line truncated
|
||||
`,u}async function m(v,g,r=DC.DEFAULTS.TTL){const h=window.getToken(getPrimaryDnsId(),"admin");if(!h)throw new Error("DNS admin token not configured. Please set it in the Tokens menu.");const d=buildDomain(v),a=await secureFetch("/api/v1/dns/record",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:d,ip:g,ttl:r,token:h,server:SITE.dnsIp})});if(!a.ok){const s=await a.text();throw new Error(`DNS API Error: ${a.status} - ${s}`)}const p=await a.json();if(!p.success)throw new Error(`DNS Error: ${p.error||"Unknown error"}`);return p}async function b(v){const g={id:v.subdomain,name:v.name,logo:v.logo||`/assets/${v.subdomain}.png`};v.category&&(g.category=v.category),v.containerId&&(g.containerId=v.containerId);try{const r=await secureFetch("/api/v1/services",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)});if(!r.ok){const h=await r.json();throw new Error(h.error||"Failed to save service")}return await window.loadServices(),window.buildGrid(),g}catch(r){throw console.error("Failed to add service to config:",r),r}}async function f(v){const g=document.getElementById("service-subdomain-input").value.trim(),r=document.getElementById("service-ip-input").value.trim()||"localhost",h=document.getElementById("service-port-input").value.trim()||"80",d=await secureFetch("/api/v1/site",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:buildDomain(g),upstream:`${r}:${h}`,config:v})}),a=await d.json();if(!d.ok||!a.success)throw new Error(a.error||`Caddy API Error: ${d.status}`);return a}window.loadExistingCAs=l,window.generateCaddyConfig=o,window.createDnsRecord=m,window.addServiceToConfig=b,window.addToCaddyfile=f})(),(function(){let l=null;function o(r){l=r;const h=document.getElementById("service-edit-modal");document.getElementById("service-edit-title").textContent=`Edit ${r.name}`,document.getElementById("edit-service-name").value=r.name,document.getElementById("edit-service-url-display").textContent=r.url||buildServiceUrl(r.id);const d=document.getElementById("edit-service-logo-preview"),a=r.logo||`/assets/${r.id}.png`;d.src=a,d.onerror=function(){this.style.display="none"},d.onload=function(){this.style.display=""},d.style.display="",document.getElementById("edit-subdomain").value=r.id,document.getElementById("edit-port").value=r.port||"",document.getElementById("edit-ip").value=r.ip||"localhost",document.getElementById("edit-tailscale-only").checked=r.tailscaleOnly||!1,document.getElementById("edit-logo-url").value=r.logo||"";const p=document.getElementById("edit-service-category");p&&(p.dataset.current=r.category||"",typeof window.populateCategorySelects=="function"&&window.populateCategorySelects()),h.classList.add("show")}function m(){closeModal("service-edit-modal"),l=null}async function b(){if(!l)return;const r=document.getElementById("edit-subdomain").value.trim().toLowerCase(),h=document.getElementById("edit-service-name").value.trim(),d=document.getElementById("edit-port").value.trim(),a=document.getElementById("edit-ip").value.trim()||"localhost",p=document.getElementById("edit-tailscale-only").checked,s=document.getElementById("edit-logo-url").value.trim(),e=document.getElementById("edit-service-category")?.value||"";if(!r){showNotification("Subdomain is required","warning");return}const n=l.id,c=[];if(r!==n&&c.push("subdomain"),h&&h!==l.name&&c.push("name"),d&&d!==String(l.port)&&c.push("port"),a!==l.ip&&c.push("ip"),p!==(l.tailscaleOnly||!1)&&c.push("tailscale"),s&&s!==l.logo&&c.push("logo"),e!==(l.category||"")&&c.push("category"),c.length===0){m();return}const t=document.getElementById("service-edit-save");t.textContent="Saving...",t.disabled=!0;try{const y=await(await secureFetch("/api/v1/services/update",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubdomain:n,newSubdomain:r,name:h||l.name,port:d||l.port,ip:a,tailscaleOnly:p,logo:s||void 0,category:e})})).json();if(!y.success)throw new Error(y.error||"Failed to update service");const u=window.APPS.findIndex(w=>w.id===n);u!==-1&&(window.APPS[u]={...window.APPS[u],id:r,name:h||window.APPS[u].name,port:d||window.APPS[u].port,ip:a,tailscaleOnly:p,logo:s||window.APPS[u].logo,category:e||void 0}),m(),window.buildGrid(),window.refreshAll()}catch(i){console.error("Error saving service changes:",i),showNotification(`Error saving changes: ${i.message}`,"error")}finally{t.textContent="Save Changes",t.disabled=!1}}document.getElementById("edit-logo-file")?.addEventListener("change",async r=>{const h=r.target.files[0];if(!h)return;if(!h.type.startsWith("image/")){showNotification("Please select an image file","warning");return}const d=new FileReader;d.onload=async a=>{const p=a.target.result;if(document.getElementById("edit-service-logo-preview").src=p,document.getElementById("edit-logo-url").value=p,l)try{const e=await(await secureFetch("/api/v1/assets/upload",{method:"POST",headers:{"Content-Type":"application/json"},bod
Line truncated
|
||||
|
||||
This will:
|
||||
1. Pull the latest image
|
||||
2. Stop the container
|
||||
3. Recreate with same settings
|
||||
|
||||
The service will be briefly unavailable.`))try{a&&(a.textContent="\u{1F504}",a.disabled=!0,a.title="Updating...");const e=await(await secureFetch(`/api/v1/containers/${r}/update`,{method:"POST"})).json();if(e.success){const n=window.APPS.find(c=>c.id===d);n&&e.newContainerId&&(n.containerId=e.newContainerId),a&&(a.textContent="\u2705",a.title="Updated successfully!",setTimeout(()=>{a.textContent=p,a.disabled=!1,a.title="Update container to latest version"},3e3)),setTimeout(()=>window.refreshAll(),2e3),showNotification(`${v} updated successfully!`,"success")}else throw new Error(e.error||"Update failed")}catch(s){console.error("Update error:",s),a&&(a.textContent="\u274C",a.title="Update failed",setTimeout(()=>{a.textContent=p,a.disabled=!1,a.title="Update container to latest version"},3e3)),showNotification(`Failed to update ${v}: ${s.message}`,"error")}}async function b(r,v){const d=window.APPS.find(u=>u.id===r),a=d?buildDomain(d.id):null,p=d?.containerId,s=await f(v||r,p,d?.containerId);if(s===null)return;let e={dashboard:!1,container:null,dns:null,caddy:null,service:null};if(s&&p)try{const u=new URLSearchParams({containerId:d.containerId,subdomain:d.id,ip:d.ip||"localhost",deleteContainer:"true"}),T=await(await secureFetch(`/api/v1/apps/${encodeURIComponent(d.id)}?${u.toString()}`,{method:"DELETE"})).json();T.success?e={...e,...T.results,dashboard:!1}:console.error("App removal failed:",T.error)}catch(u){console.error("App removal error:",u)}else if(s&&a){try{const u=d?.ip||"localhost",T=await(await secureFetch(`/api/v1/dns/record?domain=${encodeURIComponent(a)}&type=A&ipAddress=${encodeURIComponent(u)}&server=${SITE.dnsIp}`,{method:"DELETE"})).json();e.dns=T.success?"deleted":T.error||"failed"}catch(u){e.dns=u.message}try{const w=await(await secureFetch(`/api/v1/site/${encodeURIComponent(a)}`,{method:"DELETE"})).json();e.caddy=w.success||w.error&&w.error.includes("not found")?"removed":w.error||"failed"}catch(u){e.caddy=u.message}}const n=window.APPS.findIndex(u=>u.id===r);n>-1&&(window.APPS.splice(n,1),e.dashboard=!0);try{const u=safeGetJSON("custom-apps",[]),w=u.findIndex(T=>T.id===r);w>-1&&(u.splice(w,1),safeSet("custom-apps",JSON.stringify(u)))}catch{}try{const w=await(await secureFetch(`/api/v1/services/${encodeURIComponent(r)}`,{method:"DELETE"})).json();e.service=w.success?"removed":w.error||"failed"}catch(u){e.service=u.message}window.buildGrid(),window.refreshAll();let c=!1,t=[];e.dashboard||(c=!0,t.push("\u2717 Failed to remove from dashboard"));const i=["removed","already removed","not found","deleted","kept (user choice)","skipped","no such record","does not exist"],y=u=>!u||i.some(w=>u.toLowerCase().includes(w.toLowerCase()));e.container&&!y(e.container)&&(c=!0,t.push(`\u26A0 Container: ${e.container}`)),e.dns&&!y(e.dns)&&(c=!0,t.push(`\u26A0 DNS Record: ${e.dns}`)),e.caddy&&!y(e.caddy)&&(c=!0,t.push(`\u26A0 Caddy Config: ${e.caddy}`)),e.service&&!y(e.service)&&(c=!0,t.push(`\u26A0 Service File: ${e.service}`)),c&&showNotification(`Error deleting "${v||r}": ${t.join(", ")}`,"error",6e3)}window.openServiceEditModal=o,window.showDeleteModal=f,window.updateContainer=m,window.deleteService=b})(),(function(){function l(e){return e.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function o(){return SITE.defaults?.sslType||(SITE.configurationType==="public"?"letsencrypt":"caddy-managed")}function g(){const e=document.getElementById("service-subdomain-input").value||"subdomain",n=document.getElementById("service-ip-input").value||h.lan||"localhost",c=document.getElementById("service-port-input").value||DC.DEFAULTS.SERVICE_PORT,t=document.getElementById("ssl-type-select").value,i=document.getElementById("ca-name-input").value||"sami-ca",y=document.getElementById("existing-ca-select").value,u=document.getElementById("enable-auth").checked,w=document.getElementById("enable-cors").checked,T=document.getElementById("custom-headers-input").value,L=document.getElementById("upstream-path-input").value||"/",A=document.getElementById("health-check-input").value,C=document.getElementById("timeout-input").value||30,B=document.getElementById("dns-preview");B&&(B.textContent=`${buildDomain(e)} \u2192 ${n}`);const $=document.getElementById("url-preview");$&&($.textContent=buildServiceUrl(e));const k={subdomain:e,port:c,ip:n,sslType:t,caName:i,existingCa:y,enableAuth:u,enableCors:w,customHeaders:T,upstreamPath:L,healthCheck:A,timeout:C},x=window.generateCaddyConfig(k),I=document.getElementById("caddy-config-preview");I&&(I.value=x)}const h={localhost:"127.0.0.1",lan:"",tailscale:""};async function f(){try{const t=await fetch("/api/v1/network/ips",{signal:AbortSignal.timeout(2e3)});if(t.ok){const i=await t.json();i.lan&&(h.lan=i.lan),i.tailscale&&(h.tailscale=i.tailscale)}}catch{}const e=document.getElementById("quick-ip-lan"),n=document.getElementById("quick-ip-tailscale");e&&(h.lan?(e.dataset.ip=h.lan,e.textContent=`LAN (${h.lan})`,e.title=`LAN IP: ${h.lan}`):e
Line truncated
|
||||
The service will be briefly unavailable.`))try{a&&(a.textContent="\u{1F504}",a.disabled=!0,a.title="Updating...");const e=await(await secureFetch(`/api/v1/containers/${r}/update`,{method:"POST"})).json();if(e.success){const n=window.APPS.find(c=>c.id===d);n&&e.newContainerId&&(n.containerId=e.newContainerId),a&&(a.textContent="\u2705",a.title="Updated successfully!",setTimeout(()=>{a.textContent=p,a.disabled=!1,a.title="Update container to latest version"},3e3)),setTimeout(()=>window.refreshAll(),2e3),showNotification(`${h} updated successfully!`,"success")}else throw new Error(e.error||"Update failed")}catch(s){console.error("Update error:",s),a&&(a.textContent="\u274C",a.title="Update failed",setTimeout(()=>{a.textContent=p,a.disabled=!1,a.title="Update container to latest version"},3e3)),showNotification(`Failed to update ${h}: ${s.message}`,"error")}}async function g(r,h){const d=window.APPS.find(u=>u.id===r),a=d?buildDomain(d.id):null,p=d?.containerId,s=await f(h||r,p,d?.containerId);if(s===null)return;let e={dashboard:!1,container:null,dns:null,caddy:null,service:null};if(s&&p)try{const u=new URLSearchParams({containerId:d.containerId,subdomain:d.id,ip:d.ip||"localhost",deleteContainer:"true"}),T=await(await secureFetch(`/api/v1/apps/${encodeURIComponent(d.id)}?${u.toString()}`,{method:"DELETE"})).json();T.success?e={...e,...T.results,dashboard:!1}:console.error("App removal failed:",T.error)}catch(u){console.error("App removal error:",u)}else if(s&&a){try{const u=d?.ip||"localhost",T=await(await secureFetch(`/api/v1/dns/record?domain=${encodeURIComponent(a)}&type=A&ipAddress=${encodeURIComponent(u)}&server=${SITE.dnsIp}`,{method:"DELETE"})).json();e.dns=T.success?"deleted":T.error||"failed"}catch(u){e.dns=u.message}try{const w=await(await secureFetch(`/api/v1/site/${encodeURIComponent(a)}`,{method:"DELETE"})).json();e.caddy=w.success||w.error&&w.error.includes("not found")?"removed":w.error||"failed"}catch(u){e.caddy=u.message}}const n=window.APPS.findIndex(u=>u.id===r);n>-1&&(window.APPS.splice(n,1),e.dashboard=!0);try{const u=safeGetJSON("custom-apps",[]),w=u.findIndex(T=>T.id===r);w>-1&&(u.splice(w,1),safeSet("custom-apps",JSON.stringify(u)))}catch{}try{const w=await(await secureFetch(`/api/v1/services/${encodeURIComponent(r)}`,{method:"DELETE"})).json();e.service=w.success?"removed":w.error||"failed"}catch(u){e.service=u.message}window.buildGrid(),window.refreshAll();let c=!1,t=[];e.dashboard||(c=!0,t.push("\u2717 Failed to remove from dashboard"));const i=["removed","already removed","not found","deleted","kept (user choice)","skipped","no such record","does not exist"],y=u=>!u||i.some(w=>u.toLowerCase().includes(w.toLowerCase()));e.container&&!y(e.container)&&(c=!0,t.push(`\u26A0 Container: ${e.container}`)),e.dns&&!y(e.dns)&&(c=!0,t.push(`\u26A0 DNS Record: ${e.dns}`)),e.caddy&&!y(e.caddy)&&(c=!0,t.push(`\u26A0 Caddy Config: ${e.caddy}`)),e.service&&!y(e.service)&&(c=!0,t.push(`\u26A0 Service File: ${e.service}`)),c&&showNotification(`Error deleting "${h||r}": ${t.join(", ")}`,"error",6e3)}window.openServiceEditModal=o,window.showDeleteModal=f,window.updateContainer=v,window.deleteService=g})(),(function(){function l(e){return e.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function o(){return SITE.defaults?.sslType||(SITE.configurationType==="public"?"letsencrypt":"caddy-managed")}function m(){const e=document.getElementById("service-subdomain-input").value||"subdomain",n=document.getElementById("service-ip-input").value||b.lan||"localhost",c=document.getElementById("service-port-input").value||DC.DEFAULTS.SERVICE_PORT,t=document.getElementById("ssl-type-select").value,i=document.getElementById("ca-name-input").value||"sami-ca",y=document.getElementById("existing-ca-select").value,u=document.getElementById("enable-auth").checked,w=document.getElementById("enable-cors").checked,T=document.getElementById("custom-headers-input").value,L=document.getElementById("upstream-path-input").value||"/",P=document.getElementById("health-check-input").value,C=document.getElementById("timeout-input").value||30,B=document.getElementById("dns-preview");B&&(B.textContent=`${buildDomain(e)} \u2192 ${n}`);const $=document.getElementById("url-preview");$&&($.textContent=buildServiceUrl(e));const k={subdomain:e,port:c,ip:n,sslType:t,caName:i,existingCa:y,enableAuth:u,enableCors:w,customHeaders:T,upstreamPath:L,healthCheck:P,timeout:C},x=window.generateCaddyConfig(k),I=document.getElementById("caddy-config-preview");I&&(I.value=x)}const b={localhost:"127.0.0.1",lan:"",tailscale:""};async function f(){try{const t=await fetch("/api/v1/network/ips",{signal:AbortSignal.timeout(2e3)});if(t.ok){const i=await t.json();i.lan&&(b.lan=i.lan),i.tailscale&&(b.tailscale=i.tailscale)}}catch{}const e=document.getElementById("quick-ip-lan"),n=document.getElementById("quick-ip-tailscale");e&&(b.lan?(e.dataset.ip=b.lan,e.textContent=`LAN (${b.lan})`,e.title=`LAN IP: ${b.lan}`):e
Line truncated
|
||||
<span style="color: #4caf50;">\u2713 Connected</span>
|
||||
<span style="color: var(--muted); margin-left: 6px;">${w.self?.hostname} (${w.self?.ip})</span>
|
||||
`,y.disabled=!1):w.installed?(i.innerHTML='<span style="color: #ff9800;">\u26A0 Not connected</span>',y.disabled=!0):(i.innerHTML='<span style="color: var(--muted);">Not available</span>',y.disabled=!0)}catch{i.innerHTML='<span style="color: var(--muted);">Could not check</span>',y.disabled=!0}y.checked=!1,g()}function r(){const e=document.getElementById("service-type-local"),n=document.getElementById("service-type-external"),c=document.getElementById("local-service-config"),t=document.getElementById("external-service-config"),i=document.getElementById("tab-local"),y=document.getElementById("tab-external");function u(){e.checked?(c.style.display="grid",t.style.display="none",i&&(i.style.background="var(--accent)",i.style.color="var(--bg)"),y&&(y.style.background="transparent",y.style.color="var(--muted)")):(c.style.display="none",t.style.display="block",y&&(y.style.background="var(--accent)",y.style.color="var(--bg)"),i&&(i.style.background="transparent",i.style.color="var(--muted)"))}e?.addEventListener("change",u),n?.addEventListener("change",u)}function v(){const e=document.getElementById("service-name-input"),n=document.getElementById("service-subdomain-input"),c=document.getElementById("subdomain-preview");let t=!1;e?.addEventListener("input",()=>{const L=l(e.value);!t&&n&&(n.value=L),c&&(c.textContent=L?`\u2192 ${buildDomain(L)}`:""),g()}),n?.addEventListener("input",()=>{t=n.value!==l(e?.value||"");const L=n.value.trim()||l(e?.value||"");c&&(c.textContent=L?`\u2192 ${buildDomain(L)}`:""),g()});const i=document.getElementById("external-service-name"),y=document.getElementById("external-service-subdomain"),u=document.getElementById("external-subdomain-preview"),w=document.getElementById("external-domain-preview");let T=!1;i?.addEventListener("input",()=>{const L=l(i.value);!T&&y&&(y.value=L);const A=y?.value||L;u&&(u.textContent=A?`\u2192 ${buildDomain(A)}`:""),w&&(w.textContent=A?buildDomain(A):"")}),y?.addEventListener("input",()=>{T=y.value!==l(i?.value||"");const L=y.value.trim()||l(i?.value||"");u&&(u.textContent=L?`\u2192 ${buildDomain(L)}`:""),w&&(w.textContent=L?buildDomain(L):"")})}async function d(){const e=document.getElementById("external-service-name").value.trim(),n=document.getElementById("external-service-url").value.trim(),c=(document.getElementById("external-service-subdomain").value.trim()||l(e)).toLowerCase(),t=document.getElementById("external-service-logo").value.trim(),i=document.getElementById("external-service-icon").value.trim(),y=document.getElementById("external-create-dns").checked,u=document.getElementById("external-create-caddy").checked,w=document.getElementById("external-proxy-ip").value.trim()||SITE.dnsIp||"localhost",T=document.getElementById("external-preserve-host").checked,L=document.getElementById("external-follow-redirects").checked,A=document.getElementById("external-service-category")?.value||"";if(!e||!n){showNotification("Please fill in Name and External URL","warning");return}if(!c){showNotification("Could not derive subdomain from name. Please set one in Options.","warning");return}if(!n.startsWith("http://")&&!n.startsWith("https://")){showNotification("External URL must start with http:// or https://","warning");return}const C=buildDomain(c);try{const B={dns:null,caddy:null,dashboard:!1};if(y)if(window.getToken(getPrimaryDnsId(),"admin"))try{const P=await(await secureFetch("/api/v1/dns/record",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:C,ip:w,ttl:DC.DEFAULTS.TTL,server:SITE.dnsIp})})).json();B.dns=P.success?"created":P.error||"failed"}catch(S){B.dns=S.message}else B.dns="no admin token (configure in \u{1F511} Tokens)";if(u)try{const E={subdomain:c,externalUrl:n,preserveHost:T,followRedirects:L,sslType:"caddy-managed",caddyfilePath:DC.DEFAULTS.CADDYFILE,reloadCaddy:!0},P=await(await secureFetch("/api/v1/site/external",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(E)})).json();B.caddy=P.success?"created":P.error||"failed"}catch(E){B.caddy=E.message}const $={id:c,name:e,url:`https://${C}`,externalUrl:n,logo:t||i||"\u{1F310}",isExternal:!0,isCustom:!0};A&&($.category=A),window.APPS.push($),B.dashboard=!0;const k=["plex","router","chat","sync","torrent","radarr","sonarr","prowlarr","portainer","requests","jellyfin","emby"],x=window.APPS.filter(E=>!k.includes(E.id));safeSet("custom-services",JSON.stringify(x));try{await secureFetch("/api/v1/services",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(window.APPS)})}catch(E){console.warn("Failed to save to services.json:",E)}window.buildGrid(),window.refreshAll(),a();const I=[`External service "${e}" added!`];y&&I.push(`DNS: ${B.dns==="created"?"\u2713":"\u26A0 "+B.dns}`),u&&I.push(`Caddy: ${B.caddy==="created"?"\u2713":"\u26A0 "+B.caddy}`),I.push(`Access at: https://${C}`),showNotification(I.join(" | "),"success",6e3)}catch(B){console.error("Failed to create external service:",B),showNotification(`F
Line truncated
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiPHN0ZGluPiJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBFcnJvciBIYW5kbGVyXG4gKiBIYW5kbGVzIGVycm9ycyBncmFjZWZ1bGx5IHdpdGhvdXQgYnJlYWtpbmcgdGhlIG9uYm9hcmRpbmcgdG91clxuICovXG5cbihmdW5jdGlvbih3aW5kb3cpIHtcbiAgJ3VzZSBzdHJpY3QnO1xuXG4gIGNsYXNzIEVycm9ySGFuZGxlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICB0aGlzLmVycm9ycyA9IFtdO1xuICAgICAgdGhpcy5tYXhFcnJvcnMgPSA1MDsgLy8gS2VlcCBsYXN0IDUwIGVycm9yc1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIExvZyBhbiBlcnJvciB3aXRob3V0IGJyZWFraW5nIHRoZSB0b3VyXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGNvbnRleHQgLSBDb250ZXh0IHdoZXJlIGVycm9yIG9jY3VycmVkXG4gICAgICogQHBhcmFtIHtFcnJvcnxzdHJpbmd9IGVycm9yIC0gVGhlIGVycm9yIG9iamVjdCBvciBtZXNzYWdlXG4gICAgICogQHBhcmFtIHtPYmplY3R9IG1ldGFkYXRhIC0gQWRkaXRpb25hbCBtZXRhZGF0YVxuICAgICAqL1xuICAgIGxvZ0Vycm9yKGNvbnRleHQsIGVycm9yLCBtZXRhZGF0YSA9IHt9KSB7XG4gICAgICBjb25zdCBlcnJvckVudHJ5ID0ge1xuICAgICAgICB0aW1lc3RhbXA6IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKSxcbiAgICAgICAgY29udGV4dCxcbiAgICAgICAgbWVzc2FnZTogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBlcnJvcixcbiAgICAgICAgc3RhY2s6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5zdGFjayA6IG51bGwsXG4gICAgICAgIG1ldGFkYXRhXG4gICAgICB9O1xuXG4gICAgICAvLyBBZGQgdG8gZXJyb3JzIGFycmF5XG4gICAgICB0aGlzLmVycm9ycy5wdXNoKGVycm9yRW50cnkpO1xuXG4gICAgICAvLyBLZWVwIG9ubHkgbGFzdCBtYXhFcnJvcnNcbiAgICAgIGlmICh0aGlzLmVycm9ycy5sZW5ndGggPiB0aGlzLm1heEVycm9ycykge1xuICAgICAgICB0aGlzLmVycm9ycy5zaGlmdCgpO1xuICAgICAgfVxuXG4gICAgICAvLyBMb2cgdG8gY29uc29sZVxuICAgICAgY29uc29sZS5lcnJvcihgW09uYm9hcmRpbmcgRXJyb3JdICR7Y29udGV4dH06YCwgZXJyb3IsIG1ldGFkYXRhKTtcblxuICAgICAgLy8gT3B0aW9uYWxseSBzZW5kIHRvIGVycm9yIHRyYWNraW5nIHNlcnZpY2VcbiAgICAgIC8vIHRoaXMuc2VuZFRvRXJyb3JUcmFja2luZyhlcnJvckVudHJ5KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBdHRlbXB0IHRvIHJlY292ZXIgZnJvbSBhbiBlcnJvciBhbmQgY29udGludWUgdG91clxuICAgICAqIEBwYXJhbSB7RXJyb3J9IGVycm9yIC0gVGhlIGVycm9yIG9iamVjdFxuICAgICAqIEBwYXJhbSB7bnVtYmVyfSBjdXJyZW50U3RlcCAtIEN1cnJlbnQgc3RlcCBpbmRleFxuICAgICAqIEByZXR1cm5zIHtPYmplY3R9IFJlY292ZXJ5IGFjdGlvblxuICAgICAqL1xuICAgIHJlY292ZXJGcm9tRXJyb3IoZXJyb3IsIGN1cnJlbnRTdGVwKSB7XG4gICAgICBjb25zdCBlcnJvclR5cGUgPSB0aGlzLmNsYXNzaWZ5RXJyb3IoZXJyb3IpO1xuXG4gICAgICBzd2l0Y2ggKGVycm9yVHlwZSkge1xuICAgICAgICBjYXNlICdFTEVNRU5UX05PVF9GT1VORCc6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignRWxlbWVudCBOb3QgRm91bmQnLCBlcnJvciwgeyBjdXJyZW50U3RlcCB9KTtcbiAgICAgICAgICByZXR1cm4geyBcbiAgICAgICAgICAgIGFjdGlvbjogJ1NLSVBfU1RFUCcsIFxuICAgICAgICAgICAgbmV4dFN0ZXA6IGN1cnJlbnRTdGVwICsgMSxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdUYXJnZXQgZWxlbWVudCBub3QgZm91bmQsIHNraXBwaW5nIHRvIG5leHQgc3RlcCdcbiAgICAgICAgICB9O1xuXG4gICAgICAgIGNhc2UgJ1NUT1JBR0VfVU5BVkFJTEFCTEUnOlxuICAgICAgICAgIHRoaXMubG9nRXJyb3IoJ1N0b3JhZ2UgVW5hdmFpbGFibGUnLCBlcnJvcik7XG4gICAgICAgICAgcmV0dXJuIHsgXG4gICAgICAgICAgICBhY3Rpb246ICdVU0VfTUVNT1JZX1NUT1JBR0UnLFxuICAgICAgICAgICAgbWVzc2FnZTogJ0xvY2FsIHN0b3JhZ2UgdW5hdmFpbGFibGUsIHVzaW5nIGluLW1lbW9yeSBzdG9yYWdlJ1xuICAgICAgICAgIH07XG5cbiAgICAgICAgY2FzZSAnRFJJVkVSX05PVF9MT0FERUQnOlxuICAgICAgICAgIHRoaXMubG9nRXJyb3IoJ0RyaXZlci5qcyBOb3QgTG9hZGVkJywgZXJyb3IpO1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBhY3Rpb246ICdBQk9SVF9UT1VSJyxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdEcml2ZXIuanMgbGlicmFyeSBub3QgbG9hZGVkLCBjYW5ub3Qgc3RhcnQgdG91cidcbiAgICAgICAgICB9O1xuXG4gICAgICAgIGNhc2UgJ0lOVkFMSURfVE9PTFRJUCc6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignSW52YWxpZCBUb29sdGlwIENvbmZpZ3VyYXRpb24nLCBlcnJvciwgeyBjdXJyZW50U3RlcCB9KTtcbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgYWN0aW9uOiAnU0tJUF9TVEVQJyxcbiAgICAgICAgICAgIG5leHRTdGVwOiBjdXJyZW50U3RlcCArIDEsXG4gICAgICAgICAgICBtZXNzYWdlOiAnSW52YWxpZCB0b29sdGlwIGNvbmZpZ3VyYXRpb24sIHNraXBwaW5nJ1xuICAgICAgICAgIH07XG5cbiAgICAgICAgY2FzZSAnVEhFTUVfREVURUNUSU9OX0ZBSUxFRCc6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignVGhlbWUgRGV0ZWN0aW9uIEZhaWxlZCcsIGVycm9yKTtcbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgYWN0aW9uOiAnVVNFX0RFRkFVTFRfVEhFTUUnLFxuICAgICAgICAgICAgbWVzc2FnZTogJ1VzaW5nIGRlZmF1bHQgZGFyayB0aGVtZSdcbiAgICAgICAgICB9O1xuXG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignVW5rbm93biBFcnJvcicsIGVycm9yLCB7IGN1cnJlbnRTdGVwIH0pO1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBhY3Rpb246ICdBQk9SVF9UT1VSJyxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdVbmV4cGVjdGVkIGVycm9yIG9jY3VycmVkLCBhYm9ydGluZyB0b3VyJ1xuICAgICAgICAgIH07XG4gICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ2xhc3NpZnkgZXJyb3IgdHlwZVxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHBhcmFtIHtFcnJvcn0gZXJyb3IgLSBUaGUgZXJyb3Igb2JqZWN0XG4gICAgICogQHJldHVybnMge3N0cmluZ30gRXJyb3IgdHlwZVxuICAgICAqL1xuICAgIGNsYXNzaWZ5RXJyb3IoZXJyb3IpIHtcbiAgICAgIGNvbnN0IG1lc3NhZ2UgPSBlcnJvci5tZXNzYWdlIHx8IGVycm9yLnRvU3RyaW5nKCk7XG5cbiAgICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKCdlbGVtZW50JykgJiYgbWVzc2FnZS5pbmNsdWRlcygnbm90IGZvdW5kJykpIHtcbiAgICAgICAgcmV0dXJuICdFTEVNRU5UX05PVF9GT1VORCc7XG4gICAgICB9XG4gICAgICBpZiAobWVzc2FnZS5pbmNsdWRlcygnc3RvcmFnZScpIHx8IG1lc3NhZ2UuaW5jbHVkZXMoJ3F1b3RhJykpIHtcbiAgICAgICAgcmV0dXJuICdTVE9SQUdFX1VOQVZBSUxBQkxFJztcbiAgICAgIH1cbiAgICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKCdkcml2ZXInKSB8fCBtZXNzYWdlLmluY2x1ZGVzKCd1bmRlZmluZWQnKSkge1xuICAgI
Line truncated
|
||||
`,y.disabled=!1):w.installed?(i.innerHTML='<span style="color: #ff9800;">\u26A0 Not connected</span>',y.disabled=!0):(i.innerHTML='<span style="color: var(--muted);">Not available</span>',y.disabled=!0)}catch{i.innerHTML='<span style="color: var(--muted);">Could not check</span>',y.disabled=!0}y.checked=!1,m()}function r(){const e=document.getElementById("service-type-local"),n=document.getElementById("service-type-external"),c=document.getElementById("local-service-config"),t=document.getElementById("external-service-config"),i=document.getElementById("tab-local"),y=document.getElementById("tab-external");function u(){e.checked?(c.style.display="grid",t.style.display="none",i&&(i.style.background="var(--accent)",i.style.color="var(--bg)"),y&&(y.style.background="transparent",y.style.color="var(--muted)")):(c.style.display="none",t.style.display="block",y&&(y.style.background="var(--accent)",y.style.color="var(--bg)"),i&&(i.style.background="transparent",i.style.color="var(--muted)"))}e?.addEventListener("change",u),n?.addEventListener("change",u)}function h(){const e=document.getElementById("service-name-input"),n=document.getElementById("service-subdomain-input"),c=document.getElementById("subdomain-preview");let t=!1;e?.addEventListener("input",()=>{const L=l(e.value);!t&&n&&(n.value=L),c&&(c.textContent=L?`\u2192 ${buildDomain(L)}`:""),m()}),n?.addEventListener("input",()=>{t=n.value!==l(e?.value||"");const L=n.value.trim()||l(e?.value||"");c&&(c.textContent=L?`\u2192 ${buildDomain(L)}`:""),m()});const i=document.getElementById("external-service-name"),y=document.getElementById("external-service-subdomain"),u=document.getElementById("external-subdomain-preview"),w=document.getElementById("external-domain-preview");let T=!1;i?.addEventListener("input",()=>{const L=l(i.value);!T&&y&&(y.value=L);const P=y?.value||L;u&&(u.textContent=P?`\u2192 ${buildDomain(P)}`:""),w&&(w.textContent=P?buildDomain(P):"")}),y?.addEventListener("input",()=>{T=y.value!==l(i?.value||"");const L=y.value.trim()||l(i?.value||"");u&&(u.textContent=L?`\u2192 ${buildDomain(L)}`:""),w&&(w.textContent=L?buildDomain(L):"")})}async function d(){const e=document.getElementById("external-service-name").value.trim(),n=document.getElementById("external-service-url").value.trim(),c=(document.getElementById("external-service-subdomain").value.trim()||l(e)).toLowerCase(),t=document.getElementById("external-service-logo").value.trim(),i=document.getElementById("external-service-icon").value.trim(),y=document.getElementById("external-create-dns").checked,u=document.getElementById("external-create-caddy").checked,w=document.getElementById("external-proxy-ip").value.trim()||SITE.dnsIp||"localhost",T=document.getElementById("external-preserve-host").checked,L=document.getElementById("external-follow-redirects").checked,P=document.getElementById("external-service-category")?.value||"";if(!e||!n){showNotification("Please fill in Name and External URL","warning");return}if(!c){showNotification("Could not derive subdomain from name. Please set one in Options.","warning");return}if(!n.startsWith("http://")&&!n.startsWith("https://")){showNotification("External URL must start with http:// or https://","warning");return}const C=buildDomain(c);try{const B={dns:null,caddy:null,dashboard:!1};if(y)if(window.getToken(getPrimaryDnsId(),"admin"))try{const A=await(await secureFetch("/api/v1/dns/record",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({domain:C,ip:w,ttl:DC.DEFAULTS.TTL,server:SITE.dnsIp})})).json();B.dns=A.success?"created":A.error||"failed"}catch(S){B.dns=S.message}else B.dns="no admin token (configure in \u{1F511} Tokens)";if(u)try{const E={subdomain:c,externalUrl:n,preserveHost:T,followRedirects:L,sslType:"caddy-managed",caddyfilePath:DC.DEFAULTS.CADDYFILE,reloadCaddy:!0},A=await(await secureFetch("/api/v1/site/external",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(E)})).json();B.caddy=A.success?"created":A.error||"failed"}catch(E){B.caddy=E.message}const $={id:c,name:e,url:`https://${C}`,externalUrl:n,logo:t||i||"\u{1F310}",isExternal:!0,isCustom:!0};P&&($.category=P),window.APPS.push($),B.dashboard=!0;const k=["plex","router","chat","sync","torrent","radarr","sonarr","prowlarr","portainer","requests","jellyfin","emby"],x=window.APPS.filter(E=>!k.includes(E.id));safeSet("custom-services",JSON.stringify(x));try{await secureFetch("/api/v1/services",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(window.APPS)})}catch(E){console.warn("Failed to save to services.json:",E)}window.buildGrid(),window.refreshAll(),a();const I=[`External service "${e}" added!`];y&&I.push(`DNS: ${B.dns==="created"?"\u2713":"\u26A0 "+B.dns}`),u&&I.push(`Caddy: ${B.caddy==="created"?"\u2713":"\u26A0 "+B.caddy}`),I.push(`Access at: https://${C}`),showNotification(I.join(" | "),"success",6e3)}catch(B){console.error("Failed to create external service:",B),showNotification(`F
Line truncated
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiPHN0ZGluPiJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBFcnJvciBIYW5kbGVyXG4gKiBIYW5kbGVzIGVycm9ycyBncmFjZWZ1bGx5IHdpdGhvdXQgYnJlYWtpbmcgdGhlIG9uYm9hcmRpbmcgdG91clxuICovXG5cbihmdW5jdGlvbih3aW5kb3cpIHtcbiAgJ3VzZSBzdHJpY3QnO1xuXG4gIGNsYXNzIEVycm9ySGFuZGxlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICB0aGlzLmVycm9ycyA9IFtdO1xuICAgICAgdGhpcy5tYXhFcnJvcnMgPSA1MDsgLy8gS2VlcCBsYXN0IDUwIGVycm9yc1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIExvZyBhbiBlcnJvciB3aXRob3V0IGJyZWFraW5nIHRoZSB0b3VyXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGNvbnRleHQgLSBDb250ZXh0IHdoZXJlIGVycm9yIG9jY3VycmVkXG4gICAgICogQHBhcmFtIHtFcnJvcnxzdHJpbmd9IGVycm9yIC0gVGhlIGVycm9yIG9iamVjdCBvciBtZXNzYWdlXG4gICAgICogQHBhcmFtIHtPYmplY3R9IG1ldGFkYXRhIC0gQWRkaXRpb25hbCBtZXRhZGF0YVxuICAgICAqL1xuICAgIGxvZ0Vycm9yKGNvbnRleHQsIGVycm9yLCBtZXRhZGF0YSA9IHt9KSB7XG4gICAgICBjb25zdCBlcnJvckVudHJ5ID0ge1xuICAgICAgICB0aW1lc3RhbXA6IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKSxcbiAgICAgICAgY29udGV4dCxcbiAgICAgICAgbWVzc2FnZTogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBlcnJvcixcbiAgICAgICAgc3RhY2s6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5zdGFjayA6IG51bGwsXG4gICAgICAgIG1ldGFkYXRhXG4gICAgICB9O1xuXG4gICAgICAvLyBBZGQgdG8gZXJyb3JzIGFycmF5XG4gICAgICB0aGlzLmVycm9ycy5wdXNoKGVycm9yRW50cnkpO1xuXG4gICAgICAvLyBLZWVwIG9ubHkgbGFzdCBtYXhFcnJvcnNcbiAgICAgIGlmICh0aGlzLmVycm9ycy5sZW5ndGggPiB0aGlzLm1heEVycm9ycykge1xuICAgICAgICB0aGlzLmVycm9ycy5zaGlmdCgpO1xuICAgICAgfVxuXG4gICAgICAvLyBMb2cgdG8gY29uc29sZVxuICAgICAgY29uc29sZS5lcnJvcihgW09uYm9hcmRpbmcgRXJyb3JdICR7Y29udGV4dH06YCwgZXJyb3IsIG1ldGFkYXRhKTtcblxuICAgICAgLy8gT3B0aW9uYWxseSBzZW5kIHRvIGVycm9yIHRyYWNraW5nIHNlcnZpY2VcbiAgICAgIC8vIHRoaXMuc2VuZFRvRXJyb3JUcmFja2luZyhlcnJvckVudHJ5KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBBdHRlbXB0IHRvIHJlY292ZXIgZnJvbSBhbiBlcnJvciBhbmQgY29udGludWUgdG91clxuICAgICAqIEBwYXJhbSB7RXJyb3J9IGVycm9yIC0gVGhlIGVycm9yIG9iamVjdFxuICAgICAqIEBwYXJhbSB7bnVtYmVyfSBjdXJyZW50U3RlcCAtIEN1cnJlbnQgc3RlcCBpbmRleFxuICAgICAqIEByZXR1cm5zIHtPYmplY3R9IFJlY292ZXJ5IGFjdGlvblxuICAgICAqL1xuICAgIHJlY292ZXJGcm9tRXJyb3IoZXJyb3IsIGN1cnJlbnRTdGVwKSB7XG4gICAgICBjb25zdCBlcnJvclR5cGUgPSB0aGlzLmNsYXNzaWZ5RXJyb3IoZXJyb3IpO1xuXG4gICAgICBzd2l0Y2ggKGVycm9yVHlwZSkge1xuICAgICAgICBjYXNlICdFTEVNRU5UX05PVF9GT1VORCc6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignRWxlbWVudCBOb3QgRm91bmQnLCBlcnJvciwgeyBjdXJyZW50U3RlcCB9KTtcbiAgICAgICAgICByZXR1cm4geyBcbiAgICAgICAgICAgIGFjdGlvbjogJ1NLSVBfU1RFUCcsIFxuICAgICAgICAgICAgbmV4dFN0ZXA6IGN1cnJlbnRTdGVwICsgMSxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdUYXJnZXQgZWxlbWVudCBub3QgZm91bmQsIHNraXBwaW5nIHRvIG5leHQgc3RlcCdcbiAgICAgICAgICB9O1xuXG4gICAgICAgIGNhc2UgJ1NUT1JBR0VfVU5BVkFJTEFCTEUnOlxuICAgICAgICAgIHRoaXMubG9nRXJyb3IoJ1N0b3JhZ2UgVW5hdmFpbGFibGUnLCBlcnJvcik7XG4gICAgICAgICAgcmV0dXJuIHsgXG4gICAgICAgICAgICBhY3Rpb246ICdVU0VfTUVNT1JZX1NUT1JBR0UnLFxuICAgICAgICAgICAgbWVzc2FnZTogJ0xvY2FsIHN0b3JhZ2UgdW5hdmFpbGFibGUsIHVzaW5nIGluLW1lbW9yeSBzdG9yYWdlJ1xuICAgICAgICAgIH07XG5cbiAgICAgICAgY2FzZSAnRFJJVkVSX05PVF9MT0FERUQnOlxuICAgICAgICAgIHRoaXMubG9nRXJyb3IoJ0RyaXZlci5qcyBOb3QgTG9hZGVkJywgZXJyb3IpO1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBhY3Rpb246ICdBQk9SVF9UT1VSJyxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdEcml2ZXIuanMgbGlicmFyeSBub3QgbG9hZGVkLCBjYW5ub3Qgc3RhcnQgdG91cidcbiAgICAgICAgICB9O1xuXG4gICAgICAgIGNhc2UgJ0lOVkFMSURfVE9PTFRJUCc6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignSW52YWxpZCBUb29sdGlwIENvbmZpZ3VyYXRpb24nLCBlcnJvciwgeyBjdXJyZW50U3RlcCB9KTtcbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgYWN0aW9uOiAnU0tJUF9TVEVQJyxcbiAgICAgICAgICAgIG5leHRTdGVwOiBjdXJyZW50U3RlcCArIDEsXG4gICAgICAgICAgICBtZXNzYWdlOiAnSW52YWxpZCB0b29sdGlwIGNvbmZpZ3VyYXRpb24sIHNraXBwaW5nJ1xuICAgICAgICAgIH07XG5cbiAgICAgICAgY2FzZSAnVEhFTUVfREVURUNUSU9OX0ZBSUxFRCc6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignVGhlbWUgRGV0ZWN0aW9uIEZhaWxlZCcsIGVycm9yKTtcbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgYWN0aW9uOiAnVVNFX0RFRkFVTFRfVEhFTUUnLFxuICAgICAgICAgICAgbWVzc2FnZTogJ1VzaW5nIGRlZmF1bHQgZGFyayB0aGVtZSdcbiAgICAgICAgICB9O1xuXG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgdGhpcy5sb2dFcnJvcignVW5rbm93biBFcnJvcicsIGVycm9yLCB7IGN1cnJlbnRTdGVwIH0pO1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBhY3Rpb246ICdBQk9SVF9UT1VSJyxcbiAgICAgICAgICAgIG1lc3NhZ2U6ICdVbmV4cGVjdGVkIGVycm9yIG9jY3VycmVkLCBhYm9ydGluZyB0b3VyJ1xuICAgICAgICAgIH07XG4gICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ2xhc3NpZnkgZXJyb3IgdHlwZVxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHBhcmFtIHtFcnJvcn0gZXJyb3IgLSBUaGUgZXJyb3Igb2JqZWN0XG4gICAgICogQHJldHVybnMge3N0cmluZ30gRXJyb3IgdHlwZVxuICAgICAqL1xuICAgIGNsYXNzaWZ5RXJyb3IoZXJyb3IpIHtcbiAgICAgIGNvbnN0IG1lc3NhZ2UgPSBlcnJvci5tZXNzYWdlIHx8IGVycm9yLnRvU3RyaW5nKCk7XG5cbiAgICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKCdlbGVtZW50JykgJiYgbWVzc2FnZS5pbmNsdWRlcygnbm90IGZvdW5kJykpIHtcbiAgICAgICAgcmV0dXJuICdFTEVNRU5UX05PVF9GT1VORCc7XG4gICAgICB9XG4gICAgICBpZiAobWVzc2FnZS5pbmNsdWRlcygnc3RvcmFnZScpIHx8IG1lc3NhZ2UuaW5jbHVkZXMoJ3F1b3RhJykpIHtcbiAgICAgICAgcmV0dXJuICdTVE9SQUdFX1VOQVZBSUxBQkxFJztcbiAgICAgIH1cbiAgICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKCdkcml2ZXInKSB8fCBtZXNzYWdlLmluY2x1ZGVzKCd1bmRlZmluZWQnKSkge1xuICAgI
Line truncated
|
||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
||||
(function(){function v(){const i=safeGet("custom-services");if(i)try{JSON.parse(i).forEach(e=>{window.APPS.find(a=>a.id===e.id)||window.APPS.push(e)})}catch(n){console.warn("Failed to load custom services:",n)}}v();function k(){const i=document.querySelectorAll(".top .card");i.forEach((n,e)=>{n.style.transitionDelay=`${Math.min(e*60,300)}ms`}),requestAnimationFrame(()=>{i.forEach(n=>n.classList.add("loaded"))})}function u(){if(!("serviceWorker"in navigator)||!window.isSecureContext&&location.hostname!=="localhost"&&location.hostname!=="127.0.0.1")return;const i=()=>{navigator.serviceWorker.register("/sw.js",{updateViaCache:"none"}).catch(n=>{console.warn("[init] Service worker registration failed:",n)})};document.readyState==="complete"?i():window.addEventListener("load",i,{once:!0})}let h=!1;async function f(){if(h){console.warn("[init] initializeDashboard called again, skipping duplicate");return}if(h=!0,await window.loadServices(),await y(),window.buildGrid(),k(),window.refreshAll(),setInterval(window.refreshAll,DC.POLL.DASHBOARD),typeof window.refreshCredsButtons=="function"&&window.refreshCredsButtons(),typeof window.refreshMonitoringWidgets=="function"&&window.refreshMonitoringWidgets(),typeof window._updateAuthCard=="function")try{const n=await(await fetch("/api/v1/totp/config",{cache:"no-store"})).json();n.success&&window._updateAuthCard(n.config.enabled&&n.config.isSetUp,n.config.sessionDuration)}catch{}if(window.__dashcaddySiteConfigLoaded)try{await window.__dashcaddySiteConfigLoaded}catch{}if(S(),C()&&b(),window.AdminPanel&&typeof window.AdminPanel.attachTrigger=="function")try{await window.AdminPanel.attachTrigger(document.body)}catch(i){console.warn("[init] AdminPanel attachTrigger failed:",i)}}function b(){if(document.querySelector('script[src="/dist/onboarding.js"]'))return;const i=document.createElement("script");if(i.src="/dist/onboarding.js",i.defer=!0,document.head.appendChild(i),!document.querySelector('link[href="/css/driver.min.css"]')){const n=document.createElement("link");n.rel="stylesheet",n.href="/css/driver.min.css",document.head.appendChild(n)}if(!document.querySelector('link[href="/css/onboarding.css"]')){const n=document.createElement("link");n.rel="stylesheet",n.href="/css/onboarding.css",document.head.appendChild(n)}}function C(){if(typeof SITE<"u"&&SITE.onboardingCompleted)return!1;try{const i=JSON.parse(localStorage.getItem("dashcaddy_onboarding"));return!i||!i.tourCompleted&&i.currentStep===0}catch{return!0}}function E(){const i=document.querySelectorAll(".tools-section");if(!i.length)return;let n={};try{n=JSON.parse(localStorage.getItem("toolbar-sections")||"{}")}catch{}i.forEach(e=>{const a=e.dataset.section,c=e.querySelector(".tools-section-header");c&&(n[a]&&(e.classList.add("open"),c.setAttribute("aria-expanded","true")),c.addEventListener("click",s=>{s.preventDefault();const l=e.classList.toggle("open");c.setAttribute("aria-expanded",l?"true":"false");const m={};document.querySelectorAll(".tools-section").forEach(t=>{m[t.dataset.section]=t.classList.contains("open")}),localStorage.setItem("toolbar-sections",JSON.stringify(m))}))})}E();function S(){if(document.getElementById("restart-tour-btn"))return;let i=typeof SITE<"u"&&SITE.onboardingCompleted;try{const a=JSON.parse(localStorage.getItem("dashcaddy_onboarding"));i=i||!!(a&&a.tourCompleted)}catch{}const n=i?document.querySelector('.tools-section[data-section="admin"] .tools-section-items'):document.querySelector(".tools-primary");if(!n)return;const e=document.createElement("button");e.id="restart-tour-btn",e.textContent=i?"Help Tour":"\u{1F393} Help Tour",e.title="Restart the onboarding tour",e.onclick=()=>{if(window.DashCaddyOnboarding)window.DashCaddyOnboarding.restartTour();else{b();const a=setInterval(()=>{window.DashCaddyOnboarding&&(clearInterval(a),window.DashCaddyOnboarding.restartTour())},100);setTimeout(()=>clearInterval(a),5e3)}},n.appendChild(e)}window.initializeDashboard=f,window.loadCustomServices=v,u();async function y(){try{const i=await fetch("/api/v1/templates",{cache:"no-store"});if(!i.ok)return;const n=await i.json();n&&n.categories&&(window.DC_CATEGORIES=n.categories,typeof DC<"u"&&(DC.CATEGORIES=n.categories),q())}catch(i){console.warn("[init] Failed to load template categories:",i)}}function q(){const i=window.DC_CATEGORIES||typeof DC<"u"&&DC.CATEGORIES;i&&document.querySelectorAll('select[data-role="service-category"]').forEach(n=>{const e=n.dataset.current||"",a=n.querySelector('option[value=""]');if(n.innerHTML="",a)n.appendChild(a);else{const c=document.createElement("option");c.value="",c.textContent="\u2014 Select category \u2014",n.appendChild(c)}Object.entries(i).forEach(([c,s])=>{const l=document.createElement("option");l.value=c,l.textContent=`${s.icon||""} ${c}`.trim(),c===e&&(l.selected=!0),n.appendChild(l)})})}window.populateCategorySelects=q,window.loadTemplateCategories=y,(async()=>{try{const n=await(await fetch("/api/v1/totp/config",{cache:"no-store"})).json();if(n.success&&Line truncated
|
||||
(function(){function v(){const i=safeGet("custom-services");if(i)try{JSON.parse(i).forEach(e=>{window.APPS.find(a=>a.id===e.id)||window.APPS.push(e)})}catch(n){console.warn("Failed to load custom services:",n)}}v();function k(){const i=document.querySelectorAll(".top .card");i.forEach((n,e)=>{n.style.transitionDelay=`${Math.min(e*60,300)}ms`}),requestAnimationFrame(()=>{i.forEach(n=>n.classList.add("loaded"))})}function u(){if(!("serviceWorker"in navigator)||!window.isSecureContext&&location.hostname!=="localhost"&&location.hostname!=="127.0.0.1")return;const i=()=>{navigator.serviceWorker.register("/sw.js",{updateViaCache:"none"}).catch(n=>{console.warn("[init] Service worker registration failed:",n)})};document.readyState==="complete"?i():window.addEventListener("load",i,{once:!0})}let h=!1;async function f(){if(h){console.warn("[init] initializeDashboard called again, skipping duplicate");return}if(h=!0,await window.loadServices(),await y(),window.buildGrid(),k(),window.refreshAll(),setInterval(()=>{window._dcAuthLost||window.refreshAll()},DC.POLL.DASHBOARD),typeof window.refreshCredsButtons=="function"&&window.refreshCredsButtons(),typeof window.refreshMonitoringWidgets=="function"&&window.refreshMonitoringWidgets(),typeof window._updateAuthCard=="function")try{const n=await(await fetch("/api/v1/totp/config",{cache:"no-store"})).json();n.success&&window._updateAuthCard(n.config.enabled&&n.config.isSetUp,n.config.sessionDuration)}catch{}if(window.__dashcaddySiteConfigLoaded)try{await window.__dashcaddySiteConfigLoaded}catch{}if(S(),C()&&b(),window.AdminPanel&&typeof window.AdminPanel.attachTrigger=="function")try{await window.AdminPanel.attachTrigger(document.body)}catch(i){console.warn("[init] AdminPanel attachTrigger failed:",i)}}function b(){if(document.querySelector('script[src="/dist/onboarding.js"]'))return;const i=document.createElement("script");if(i.src="/dist/onboarding.js",i.defer=!0,document.head.appendChild(i),!document.querySelector('link[href="/css/driver.min.css"]')){const n=document.createElement("link");n.rel="stylesheet",n.href="/css/driver.min.css",document.head.appendChild(n)}if(!document.querySelector('link[href="/css/onboarding.css"]')){const n=document.createElement("link");n.rel="stylesheet",n.href="/css/onboarding.css",document.head.appendChild(n)}}function C(){if(typeof SITE<"u"&&SITE.onboardingCompleted)return!1;try{const i=JSON.parse(localStorage.getItem("dashcaddy_onboarding"));return!i||!i.tourCompleted&&i.currentStep===0}catch{return!0}}function E(){const i=document.querySelectorAll(".tools-section");if(!i.length)return;let n={};try{n=JSON.parse(localStorage.getItem("toolbar-sections")||"{}")}catch{}i.forEach(e=>{const a=e.dataset.section,c=e.querySelector(".tools-section-header");c&&(n[a]&&(e.classList.add("open"),c.setAttribute("aria-expanded","true")),c.addEventListener("click",s=>{s.preventDefault();const l=e.classList.toggle("open");c.setAttribute("aria-expanded",l?"true":"false");const m={};document.querySelectorAll(".tools-section").forEach(t=>{m[t.dataset.section]=t.classList.contains("open")}),localStorage.setItem("toolbar-sections",JSON.stringify(m))}))})}E();function S(){if(document.getElementById("restart-tour-btn"))return;let i=typeof SITE<"u"&&SITE.onboardingCompleted;try{const a=JSON.parse(localStorage.getItem("dashcaddy_onboarding"));i=i||!!(a&&a.tourCompleted)}catch{}const n=i?document.querySelector('.tools-section[data-section="admin"] .tools-section-items'):document.querySelector(".tools-primary");if(!n)return;const e=document.createElement("button");e.id="restart-tour-btn",e.textContent=i?"Help Tour":"\u{1F393} Help Tour",e.title="Restart the onboarding tour",e.onclick=()=>{if(window.DashCaddyOnboarding)window.DashCaddyOnboarding.restartTour();else{b();const a=setInterval(()=>{window.DashCaddyOnboarding&&(clearInterval(a),window.DashCaddyOnboarding.restartTour())},100);setTimeout(()=>clearInterval(a),5e3)}},n.appendChild(e)}window.initializeDashboard=f,window.loadCustomServices=v,u();async function y(){try{const i=await fetch("/api/v1/templates",{cache:"no-store"});if(!i.ok)return;const n=await i.json();n&&n.categories&&(window.DC_CATEGORIES=n.categories,typeof DC<"u"&&(DC.CATEGORIES=n.categories),q())}catch(i){console.warn("[init] Failed to load template categories:",i)}}function q(){const i=window.DC_CATEGORIES||typeof DC<"u"&&DC.CATEGORIES;i&&document.querySelectorAll('select[data-role="service-category"]').forEach(n=>{const e=n.dataset.current||"",a=n.querySelector('option[value=""]');if(n.innerHTML="",a)n.appendChild(a);else{const c=document.createElement("option");c.value="",c.textContent="\u2014 Select category \u2014",n.appendChild(c)}Object.entries(i).forEach(([c,s])=>{const l=document.createElement("option");l.value=c,l.textContent=`${s.icon||""} ${c}`.trim(),c===e&&(l.selected=!0),n.appendChild(l)})})}window.populateCategorySelects=q,window.loadTemplateCategories=y,(async()=>{try{const n=await(await fetch("/api/v1/totp/config",{cache:"no-stoLine truncated
|
||||
.dc-monitor {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
@@ -329,4 +329,4 @@
|
||||
<span class="quick-search-item-badge">${r.status.toUpperCase()}</span>
|
||||
</div>
|
||||
`})),o||(o='<div class="quick-search-empty">No results found</div>'),f.innerHTML=o,s()}function s(){f.querySelectorAll(".quick-search-item").forEach((o,r)=>{o.addEventListener("click",()=>m(o)),r===0&&o.classList.add("selected")})}function l(t){try{const o=f.querySelectorAll(".quick-search-item"),r=f.querySelector(".quick-search-item.selected"),p=Array.from(o).indexOf(r);if(t.key==="ArrowDown"){t.preventDefault(),r&&r.classList.remove("selected");const d=(p+1)%o.length;o[d]?.classList.add("selected"),o[d]?.scrollIntoView({block:"nearest"})}else if(t.key==="ArrowUp"){t.preventDefault(),r&&r.classList.remove("selected");const d=p<=0?o.length-1:p-1;o[d]?.classList.add("selected"),o[d]?.scrollIntoView({block:"nearest"})}else t.key==="Enter"&&(t.preventDefault(),r&&m(r))}catch(o){console.warn("[Keyboard Shortcuts] Error handling search navigation:",o.message)}}function m(t){try{const o=t.dataset.action,r=t.dataset.service;switch(y(),o){case"refresh":document.getElementById("refresh")?.click();break;case"reload-caddy":document.getElementById("reload-caddy-top")?.click();break;case"add-service":document.getElementById("add-service")?.click();break;case"app-selector":document.getElementById("add-service-btn")?.click();break;case"backup":document.getElementById("backup-restore-btn")?.click();break;case"stats":document.getElementById("container-stats-btn")?.click();break;case"logs":document.getElementById("view-error-logs")?.click();break;case"tokens":document.getElementById("manage-tokens")?.click();break;case"notifications":document.getElementById("manage-notifications")?.click();break;case"theme":document.getElementById("theme")?.click();break;case"tour":document.getElementById("restart-tour-btn")?.click();break;case"open-service":if(r){const p=document.querySelector(`[data-app="${r}"] [id$="-open"], [data-app="${r}"] button:not(.restart-btn):not(.logs-btn):not(.settings-btn)`);if(p)p.click();else{const d=document.querySelector(`[data-app="${r}"]`);d&&d.click()}}break;default:v("[Keyboard Shortcuts] Unknown action:",o)}}catch(o){console.warn("[Keyboard Shortcuts] Error executing action:",o.message)}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",b):b(),window.DashCaddyKeyboardShortcuts={openQuickSearch:S,closeQuickSearch:y}})();
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiPHN0ZGluPiJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLy8gPT09PT09PT09PSBEQVNIQk9BUkQgSU5JVElBTElaQVRJT04gPT09PT09PT09PVxuKGZ1bmN0aW9uICgpIHtcblxuICBmdW5jdGlvbiBsb2FkQ3VzdG9tU2VydmljZXMoKSB7XG4gICAgY29uc3QgY3VzdG9tU2VydmljZXMgPSBzYWZlR2V0KCdjdXN0b20tc2VydmljZXMnKTtcbiAgICBpZiAoY3VzdG9tU2VydmljZXMpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHNlcnZpY2VzID0gSlNPTi5wYXJzZShjdXN0b21TZXJ2aWNlcyk7XG4gICAgICAgIC8vIE1lcmdlIHdpdGggZGVmYXVsdCBBUFBTLCBhdm9pZGluZyBkdXBsaWNhdGVzXG4gICAgICAgIHNlcnZpY2VzLmZvckVhY2goc2VydmljZSA9PiB7XG4gICAgICAgICAgaWYgKCF3aW5kb3cuQVBQUy5maW5kKGFwcCA9PiBhcHAuaWQgPT09IHNlcnZpY2UuaWQpKSB7XG4gICAgICAgICAgICB3aW5kb3cuQVBQUy5wdXNoKHNlcnZpY2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUud2FybignRmFpbGVkIHRvIGxvYWQgY3VzdG9tIHNlcnZpY2VzOicsIGUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIEluaXRpYWxpemUgY3VzdG9tIHNlcnZpY2VzIGltbWVkaWF0ZWx5IHNvIHdpbmRvdy5BUFBTIGlzIHBvcHVsYXRlZCBiZWZvcmUgYnVpbGRHcmlkIHJ1bnNcbiAgbG9hZEN1c3RvbVNlcnZpY2VzKCk7XG5cbiAgLy8gU3RhZ2dlcmVkIGFuaW1hdGlvbiBmb3IgdG9wIGNhcmRzIHRvb1xuICBmdW5jdGlvbiBhbmltYXRlVG9wQ2FyZHMoKSB7XG4gICAgY29uc3QgdG9wQ2FyZHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcudG9wIC5jYXJkJyk7XG4gICAgdG9wQ2FyZHMuZm9yRWFjaCgoY2FyZCwgaW5kZXgpID0+IHtcbiAgICAgIGNhcmQuc3R5bGUudHJhbnNpdGlvbkRlbGF5ID0gYCR7TWF0aC5taW4oaW5kZXggKiA2MCwgMzAwKX1tc2A7XG4gICAgfSk7XG4gICAgcmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpID0+IHtcbiAgICAgIHRvcENhcmRzLmZvckVhY2goY2FyZCA9PiBjYXJkLmNsYXNzTGlzdC5hZGQoJ2xvYWRlZCcpKTtcbiAgICB9KTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlZ2lzdGVyU2VydmljZVdvcmtlcigpIHtcbiAgICBpZiAoISgnc2VydmljZVdvcmtlcicgaW4gbmF2aWdhdG9yKSkgcmV0dXJuO1xuICAgIGlmICghd2luZG93LmlzU2VjdXJlQ29udGV4dCAmJiBsb2NhdGlvbi5ob3N0bmFtZSAhPT0gJ2xvY2FsaG9zdCcgJiYgbG9jYXRpb24uaG9zdG5hbWUgIT09ICcxMjcuMC4wLjEnKSByZXR1cm47XG5cbiAgICBjb25zdCByZWdpc3RlciA9ICgpID0+IHtcbiAgICAgIG5hdmlnYXRvci5zZXJ2aWNlV29ya2VyLnJlZ2lzdGVyKCcvc3cuanMnLCB7IHVwZGF0ZVZpYUNhY2hlOiAnbm9uZScgfSkuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgICAgIGNvbnNvbGUud2FybignW2luaXRdIFNlcnZpY2Ugd29ya2VyIHJlZ2lzdHJhdGlvbiBmYWlsZWQ6JywgZXJyb3IpO1xuICAgICAgfSk7XG4gICAgfTtcblxuICAgIGlmIChkb2N1bWVudC5yZWFkeVN0YXRlID09PSAnY29tcGxldGUnKSB7XG4gICAgICByZWdpc3RlcigpO1xuICAgIH0gZWxzZSB7XG4gICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsIHJlZ2lzdGVyLCB7IG9uY2U6IHRydWUgfSk7XG4gICAgfVxuICB9XG5cbiAgLy8gSW5pdGlhbGl6ZSBkYXNoYm9hcmQgKGNhbGxlZCBhZnRlciBUT1RQIGdhdGUgY2hlY2sgb3IgZGlyZWN0bHkgaWYgVE9UUCBkaXNhYmxlZClcbiAgLy8gTk9URTogbG9hZFNlcnZpY2VzIGNvbWVzIGZyb20gd2luZG93LmxvYWRTZXJ2aWNlcyAoZXhwb3J0ZWQgYnkgZ3JpZC5qcylcbiAgbGV0IF9kYXNoYm9hcmRJbml0aWFsaXplZCA9IGZhbHNlO1xuICBhc3luYyBmdW5jdGlvbiBpbml0aWFsaXplRGFzaGJvYXJkKCkge1xuICAgIGlmIChfZGFzaGJvYXJkSW5pdGlhbGl6ZWQpIHtcbiAgICAgIGNvbnNvbGUud2FybignW2luaXRdIGluaXRpYWxpemVEYXNoYm9hcmQgY2FsbGVkIGFnYWluLCBza2lwcGluZyBkdXBsaWNhdGUnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgX2Rhc2hib2FyZEluaXRpYWxpemVkID0gdHJ1ZTtcbiAgICBhd2FpdCB3aW5kb3cubG9hZFNlcnZpY2VzKCk7XG4gICAgYXdhaXQgbG9hZFRlbXBsYXRlQ2F0ZWdvcmllcygpO1xuICAgIHdpbmRvdy5idWlsZEdyaWQoKTtcbiAgICBhbmltYXRlVG9wQ2FyZHMoKTtcbiAgICB3aW5kb3cucmVmcmVzaEFsbCgpO1xuICAgIHNldEludGVydmFsKHdpbmRvdy5yZWZyZXNoQWxsLCBEQy5QT0xMLkRBU0hCT0FSRCk7XG4gICAgaWYgKHR5cGVvZiB3aW5kb3cucmVmcmVzaENyZWRzQnV0dG9ucyA9PT0gJ2Z1bmN0aW9uJykgd2luZG93LnJlZnJlc2hDcmVkc0J1dHRvbnMoKTtcbiAgICBpZiAodHlwZW9mIHdpbmRvdy5yZWZyZXNoTW9uaXRvcmluZ1dpZGdldHMgPT09ICdmdW5jdGlvbicpIHdpbmRvdy5yZWZyZXNoTW9uaXRvcmluZ1dpZGdldHMoKTtcbiAgICAvLyBVcGRhdGUgYXV0aCBjYXJkIChtYXkgaGF2ZSBhbHJlYWR5IGJlZW4gdXBkYXRlZCBieSB0aGUgYXV0by1sb2FkIElJRkUgYnV0IGVuc3VyZSBpdCdzIGNvcnJlY3QpXG4gICAgaWYgKHR5cGVvZiB3aW5kb3cuX3VwZGF0ZUF1dGhDYXJkID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCByID0gYXdhaXQgZmV0Y2goJy9hcGkvdjEvdG90cC9jb25maWcnLCB7IGNhY2hlOiAnbm8tc3RvcmUnIH0pO1xuICAgICAgICBjb25zdCBkID0gYXdhaXQgci5qc29uKCk7XG4gICAgICAgIGlmIChkLnN1Y2Nlc3MpIHdpbmRvdy5fdXBkYXRlQXV0aENhcmQoZC5jb25maWcuZW5hYmxlZCAmJiBkLmNvbmZpZy5pc1NldFVwLCBkLmNvbmZpZy5zZXNzaW9uRHVyYXRpb24pO1xuICAgICAgfSBjYXRjaCAoZSkgeyAvKiBpZ25vcmUgKi8gfVxuICAgIH1cbiAgICBpZiAod2luZG93Ll9fZGFzaGNhZGR5U2l0ZUNvbmZpZ0xvYWRlZCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgd2luZG93Ll9fZGFzaGNhZGR5U2l0ZUNvbmZpZ0xvYWRlZDtcbiAgICAgIH0gY2F0Y2ggKF8pIHsgLyogaWdub3JlICovIH1cbiAgICB9XG4gICAgLy8gTGF6eS1sb2FkIG9uYm9hcmRpbmcgb25seSBvbmNlIHBlciBpbnN0YWxsLCBvdGhlcndpc2UganVzdCBhZGQgdGhlIHRvdXIgYnV0dG9uXG4gICAgYWRkVG91ckJ1dHRvbigpO1xuICAgIGlmIChzaG91bGRMb2FkT25ib2FyZGluZygpKSB7XG4gICAgICBsb2FkT25ib2FyZGluZygpO1xuICAgIH1cblxuICAgIC8vIERDLTA0ODogaW5qZWN0IHRoZSBcIkFkbWluXCIgdHJpZ2dlciBidXR0b24gaW50byB0aGUgdG9wIGJhci4gVGhlXG4gICAgLy8gYnV0dG9uIG9ubHkgcmVuZGVycyB3aGVuIC9tZSByZXR1cm5zIGlzQWRtaW49dHJ1ZTsgdGhlIG1vZHVsZVxuICAgIC8vIHJlLWNoZWNrcyBldmVyeSA2MHMgc28gYSBwZXJtaXNzaW9uIGRvd25ncmFkZSB0YWtlcyBlZmZlY3QuXG4gICAgaWYgKHdpbmRvdy5BZG1pblBhbmVsICYmIHR5cGVvZiB3aW5kb3cuQWRtaW5QYW5lbC5hdHRhY2hUcmlnZ2VyID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB3aW5kb3cuQWRtaW5QYW5lbC5hdHRhY2hUcmlnZ2VyKGRvY3VtZW50LmJvZHkpO1xuICAgICAgfSBjYXRjaCAoZ
Line truncated
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiPHN0ZGluPiJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLy8gPT09PT09PT09PSBEQVNIQk9BUkQgSU5JVElBTElaQVRJT04gPT09PT09PT09PVxuKGZ1bmN0aW9uICgpIHtcblxuICBmdW5jdGlvbiBsb2FkQ3VzdG9tU2VydmljZXMoKSB7XG4gICAgY29uc3QgY3VzdG9tU2VydmljZXMgPSBzYWZlR2V0KCdjdXN0b20tc2VydmljZXMnKTtcbiAgICBpZiAoY3VzdG9tU2VydmljZXMpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHNlcnZpY2VzID0gSlNPTi5wYXJzZShjdXN0b21TZXJ2aWNlcyk7XG4gICAgICAgIC8vIE1lcmdlIHdpdGggZGVmYXVsdCBBUFBTLCBhdm9pZGluZyBkdXBsaWNhdGVzXG4gICAgICAgIHNlcnZpY2VzLmZvckVhY2goc2VydmljZSA9PiB7XG4gICAgICAgICAgaWYgKCF3aW5kb3cuQVBQUy5maW5kKGFwcCA9PiBhcHAuaWQgPT09IHNlcnZpY2UuaWQpKSB7XG4gICAgICAgICAgICB3aW5kb3cuQVBQUy5wdXNoKHNlcnZpY2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUud2FybignRmFpbGVkIHRvIGxvYWQgY3VzdG9tIHNlcnZpY2VzOicsIGUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIEluaXRpYWxpemUgY3VzdG9tIHNlcnZpY2VzIGltbWVkaWF0ZWx5IHNvIHdpbmRvdy5BUFBTIGlzIHBvcHVsYXRlZCBiZWZvcmUgYnVpbGRHcmlkIHJ1bnNcbiAgbG9hZEN1c3RvbVNlcnZpY2VzKCk7XG5cbiAgLy8gU3RhZ2dlcmVkIGFuaW1hdGlvbiBmb3IgdG9wIGNhcmRzIHRvb1xuICBmdW5jdGlvbiBhbmltYXRlVG9wQ2FyZHMoKSB7XG4gICAgY29uc3QgdG9wQ2FyZHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcudG9wIC5jYXJkJyk7XG4gICAgdG9wQ2FyZHMuZm9yRWFjaCgoY2FyZCwgaW5kZXgpID0+IHtcbiAgICAgIGNhcmQuc3R5bGUudHJhbnNpdGlvbkRlbGF5ID0gYCR7TWF0aC5taW4oaW5kZXggKiA2MCwgMzAwKX1tc2A7XG4gICAgfSk7XG4gICAgcmVxdWVzdEFuaW1hdGlvbkZyYW1lKCgpID0+IHtcbiAgICAgIHRvcENhcmRzLmZvckVhY2goY2FyZCA9PiBjYXJkLmNsYXNzTGlzdC5hZGQoJ2xvYWRlZCcpKTtcbiAgICB9KTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlZ2lzdGVyU2VydmljZVdvcmtlcigpIHtcbiAgICBpZiAoISgnc2VydmljZVdvcmtlcicgaW4gbmF2aWdhdG9yKSkgcmV0dXJuO1xuICAgIGlmICghd2luZG93LmlzU2VjdXJlQ29udGV4dCAmJiBsb2NhdGlvbi5ob3N0bmFtZSAhPT0gJ2xvY2FsaG9zdCcgJiYgbG9jYXRpb24uaG9zdG5hbWUgIT09ICcxMjcuMC4wLjEnKSByZXR1cm47XG5cbiAgICBjb25zdCByZWdpc3RlciA9ICgpID0+IHtcbiAgICAgIG5hdmlnYXRvci5zZXJ2aWNlV29ya2VyLnJlZ2lzdGVyKCcvc3cuanMnLCB7IHVwZGF0ZVZpYUNhY2hlOiAnbm9uZScgfSkuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgICAgIGNvbnNvbGUud2FybignW2luaXRdIFNlcnZpY2Ugd29ya2VyIHJlZ2lzdHJhdGlvbiBmYWlsZWQ6JywgZXJyb3IpO1xuICAgICAgfSk7XG4gICAgfTtcblxuICAgIGlmIChkb2N1bWVudC5yZWFkeVN0YXRlID09PSAnY29tcGxldGUnKSB7XG4gICAgICByZWdpc3RlcigpO1xuICAgIH0gZWxzZSB7XG4gICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsIHJlZ2lzdGVyLCB7IG9uY2U6IHRydWUgfSk7XG4gICAgfVxuICB9XG5cbiAgLy8gSW5pdGlhbGl6ZSBkYXNoYm9hcmQgKGNhbGxlZCBhZnRlciBUT1RQIGdhdGUgY2hlY2sgb3IgZGlyZWN0bHkgaWYgVE9UUCBkaXNhYmxlZClcbiAgLy8gTk9URTogbG9hZFNlcnZpY2VzIGNvbWVzIGZyb20gd2luZG93LmxvYWRTZXJ2aWNlcyAoZXhwb3J0ZWQgYnkgZ3JpZC5qcylcbiAgbGV0IF9kYXNoYm9hcmRJbml0aWFsaXplZCA9IGZhbHNlO1xuICBhc3luYyBmdW5jdGlvbiBpbml0aWFsaXplRGFzaGJvYXJkKCkge1xuICAgIGlmIChfZGFzaGJvYXJkSW5pdGlhbGl6ZWQpIHtcbiAgICAgIGNvbnNvbGUud2FybignW2luaXRdIGluaXRpYWxpemVEYXNoYm9hcmQgY2FsbGVkIGFnYWluLCBza2lwcGluZyBkdXBsaWNhdGUnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgX2Rhc2hib2FyZEluaXRpYWxpemVkID0gdHJ1ZTtcbiAgICBhd2FpdCB3aW5kb3cubG9hZFNlcnZpY2VzKCk7XG4gICAgYXdhaXQgbG9hZFRlbXBsYXRlQ2F0ZWdvcmllcygpO1xuICAgIHdpbmRvdy5idWlsZEdyaWQoKTtcbiAgICBhbmltYXRlVG9wQ2FyZHMoKTtcbiAgICB3aW5kb3cucmVmcmVzaEFsbCgpO1xuICAgIHNldEludGVydmFsKCgpID0+IHtcbiAgICAgIC8vIFN0b3AgcG9sbGluZyBpZiB0aGUgc2Vzc2lvbiBoYXMgYmVlbiBpbnZhbGlkYXRlZCAoZS5nLiBUT1RQIGdhdGVcbiAgICAgIC8vIG5vdyBhY3RpdmUsIG9yIHVzZXIgbG9nZ2VkIG91dCkuIEF2b2lkcyByZWxlbnRsZXNzIDQwMS80MDMgbm9pc2UuXG4gICAgICBpZiAod2luZG93Ll9kY0F1dGhMb3N0KSByZXR1cm47XG4gICAgICB3aW5kb3cucmVmcmVzaEFsbCgpO1xuICAgIH0sIERDLlBPTEwuREFTSEJPQVJEKTtcbiAgICBpZiAodHlwZW9mIHdpbmRvdy5yZWZyZXNoQ3JlZHNCdXR0b25zID09PSAnZnVuY3Rpb24nKSB3aW5kb3cucmVmcmVzaENyZWRzQnV0dG9ucygpO1xuICAgIGlmICh0eXBlb2Ygd2luZG93LnJlZnJlc2hNb25pdG9yaW5nV2lkZ2V0cyA9PT0gJ2Z1bmN0aW9uJykgd2luZG93LnJlZnJlc2hNb25pdG9yaW5nV2lkZ2V0cygpO1xuICAgIC8vIFVwZGF0ZSBhdXRoIGNhcmQgKG1heSBoYXZlIGFscmVhZHkgYmVlbiB1cGRhdGVkIGJ5IHRoZSBhdXRvLWxvYWQgSUlGRSBidXQgZW5zdXJlIGl0J3MgY29ycmVjdClcbiAgICBpZiAodHlwZW9mIHdpbmRvdy5fdXBkYXRlQXV0aENhcmQgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHIgPSBhd2FpdCBmZXRjaCgnL2FwaS92MS90b3RwL2NvbmZpZycsIHsgY2FjaGU6ICduby1zdG9yZScgfSk7XG4gICAgICAgIGNvbnN0IGQgPSBhd2FpdCByLmpzb24oKTtcbiAgICAgICAgaWYgKGQuc3VjY2Vzcykgd2luZG93Ll91cGRhdGVBdXRoQ2FyZChkLmNvbmZpZy5lbmFibGVkICYmIGQuY29uZmlnLmlzU2V0VXAsIGQuY29uZmlnLnNlc3Npb25EdXJhdGlvbik7XG4gICAgICB9IGNhdGNoIChlKSB7IC8qIGlnbm9yZSAqLyB9XG4gICAgfVxuICAgIGlmICh3aW5kb3cuX19kYXNoY2FkZHlTaXRlQ29uZmlnTG9hZGVkKSB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB3aW5kb3cuX19kYXNoY2FkZHlTaXRlQ29uZmlnTG9hZGVkO1xuICAgICAgfSBjYXRjaCAoXykgeyAvKiBpZ25vcmUgKi8gfVxuICAgIH1cbiAgICAvLyBMYXp5LWxvYWQgb25ib2FyZGluZyBvbmx5IG9uY2UgcGVyIGluc3RhbGwsIG90aGVyd2lzZSBqdXN0IGFkZCB0aGUgdG91ciBidXR0b25cbiAgICBhZGRUb3VyQnV0dG9uKCk7XG4gICAgaWYgKHNob3VsZExvYWRPbmJvYXJkaW5nKCkpIHtcbiAgICAgIGxvYWRPbmJvYXJkaW5nKCk7XG4gICAgfVxuXG4gICAgLy8gREMtMDQ4OiBpbmplY3QgdGhlIFwiQWRtaW5cIiB0cmlnZ2VyIGJ1dHRvbiBpbnRvIHRoZSB0b3AgYmFyLiBUaGVcbiAgICAvLyBidXR0b24gb25seSByZW5kZXJzIHdoZW4gL21lIHJldHVybnMgaXNBZG1pbj10cnVlOyB0aGUgbW9kdWxlXG4gICAgLy8gcmUtY2hlY2tzIGV2ZXJ5IDYwcyBzbyBhI
Line truncated
|
||||
Reference in new issue
Block a user