Service categories (described in README roadmap, never wired): - Backend: POST /services now persists category/containerId/port/ip/tailscaleOnly - Backend: POST /services/update accepts category for in-place changes - Frontend: category <select> in add-service modal (local + external) - Frontend: category <select> in edit-service modal with current value - Frontend: All Categories dropdown in service filter bar (auto-populated from both API categories and any categories present on rendered cards) - Frontend: colored category badge (icon + name) on service cards - Frontend: filter auto-refreshes after buildGrid Monitoring on main dashboard (replaces orphaned monitoring-dashboard.html): - New monitoring-widgets.js embeds a 5-card System Overview panel above the filter bar: Services, Containers Up, Avg CPU, Avg Memory, Health - Pulls /api/v1/monitoring/stats + /api/v1/health-checks/status - Auto-refreshes on DC.POLL.STATS (5s), color-coded bars (warn >=65%, bad >=85%) Build: - Added monitoring-widgets.js to init.js bundle in build.js - Rebuilt dist/ bundles (core.js, features.js, init.js) - sw.js cache version bumped automatically - CSP hash regenerated
332 lines
25 KiB
JavaScript
332 lines
25 KiB
JavaScript
(function(){function v(){const a=safeGet("custom-services");if(a)try{JSON.parse(a).forEach(e=>{window.APPS.find(n=>n.id===e.id)||window.APPS.push(e)})}catch(i){console.warn("Failed to load custom services:",i)}}v();function k(){const a=document.querySelectorAll(".top .card");a.forEach((i,e)=>{i.style.transitionDelay=`${Math.min(e*60,300)}ms`}),requestAnimationFrame(()=>{a.forEach(i=>i.classList.add("loaded"))})}function u(){if(!("serviceWorker"in navigator)||!window.isSecureContext&&location.hostname!=="localhost"&&location.hostname!=="127.0.0.1")return;const a=()=>{navigator.serviceWorker.register("/sw.js",{updateViaCache:"none"}).catch(i=>{console.warn("[init] Service worker registration failed:",i)})};document.readyState==="complete"?a():window.addEventListener("load",a,{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 i=await(await fetch("/api/v1/totp/config",{cache:"no-store"})).json();i.success&&window._updateAuthCard(i.config.enabled&&i.config.isSetUp,i.config.sessionDuration)}catch{}if(window.__dashcaddySiteConfigLoaded)try{await window.__dashcaddySiteConfigLoaded}catch{}S(),C()&&b()}function b(){if(document.querySelector('script[src="/dist/onboarding.js"]'))return;const a=document.createElement("script");if(a.src="/dist/onboarding.js",a.defer=!0,document.head.appendChild(a),!document.querySelector('link[href="/css/driver.min.css"]')){const i=document.createElement("link");i.rel="stylesheet",i.href="/css/driver.min.css",document.head.appendChild(i)}if(!document.querySelector('link[href="/css/onboarding.css"]')){const i=document.createElement("link");i.rel="stylesheet",i.href="/css/onboarding.css",document.head.appendChild(i)}}function C(){if(typeof SITE<"u"&&SITE.onboardingCompleted)return!1;try{const a=JSON.parse(localStorage.getItem("dashcaddy_onboarding"));return!a||!a.tourCompleted&&a.currentStep===0}catch{return!0}}function E(){const a=document.querySelectorAll(".tools-section");if(!a.length)return;let i={};try{i=JSON.parse(localStorage.getItem("toolbar-sections")||"{}")}catch{}a.forEach(e=>{const n=e.dataset.section,c=e.querySelector(".tools-section-header");c&&(i[n]&&(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 a=typeof SITE<"u"&&SITE.onboardingCompleted;try{const n=JSON.parse(localStorage.getItem("dashcaddy_onboarding"));a=a||!!(n&&n.tourCompleted)}catch{}const i=a?document.querySelector('.tools-section[data-section="admin"] .tools-section-items'):document.querySelector(".tools-primary");if(!i)return;const e=document.createElement("button");e.id="restart-tour-btn",e.textContent=a?"Help Tour":"\u{1F393} Help Tour",e.title="Restart the onboarding tour",e.onclick=()=>{if(window.DashCaddyOnboarding)window.DashCaddyOnboarding.restartTour();else{b();const n=setInterval(()=>{window.DashCaddyOnboarding&&(clearInterval(n),window.DashCaddyOnboarding.restartTour())},100);setTimeout(()=>clearInterval(n),5e3)}},i.appendChild(e)}window.initializeDashboard=f,window.loadCustomServices=v,u();async function y(){try{const a=await fetch("/api/v1/templates",{cache:"no-store"});if(!a.ok)return;const i=await a.json();i&&i.categories&&(window.DC_CATEGORIES=i.categories,typeof DC<"u"&&(DC.CATEGORIES=i.categories),q())}catch(a){console.warn("[init] Failed to load template categories:",a)}}function q(){const a=window.DC_CATEGORIES||typeof DC<"u"&&DC.CATEGORIES;a&&document.querySelectorAll('select[data-role="service-category"]').forEach(i=>{const e=i.dataset.current||"",n=i.querySelector('option[value=""]');if(i.innerHTML="",n)i.appendChild(n);else{const c=document.createElement("option");c.value="",c.textContent="\u2014 Select category \u2014",i.appendChild(c)}Object.entries(a).forEach(([c,s])=>{const l=document.createElement("option");l.value=c,l.textContent=`${s.icon||""} ${c}`.trim(),c===e&&(l.selected=!0),i.appendChild(l)})})}window.populateCategorySelects=q,window.loadTemplateCategories=y,(async()=>{try{const i=await(await fetch("/api/v1/totp/config",{cache:"no-store"})).json();if(i.success&&i.config.enabled&&(await fetch("/api/v1/totp/check-session",{cache:"no-store"})).status===401){window._showTotpOverlay();return}}catch(a){console.warn("TOTP check failed, proceeding normally:",a)}f()})()})(),(function(){const v=document.createElement("style");v.textContent=`
|
|
.dc-monitor {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
padding: 12px 16px;
|
|
background: var(--card-base);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
}
|
|
.dc-monitor-card {
|
|
padding: 10px 12px;
|
|
background: var(--card-bg, rgba(255,255,255,0.04));
|
|
border-radius: 8px;
|
|
border: 1px solid var(--border);
|
|
}
|
|
.dc-monitor-label {
|
|
font-size: 0.7rem;
|
|
color: var(--muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 4px;
|
|
}
|
|
.dc-monitor-value {
|
|
font-size: 1.4rem;
|
|
font-weight: 600;
|
|
color: var(--fg);
|
|
}
|
|
.dc-monitor-sub {
|
|
font-size: 0.7rem;
|
|
color: var(--muted);
|
|
margin-top: 4px;
|
|
}
|
|
.dc-monitor-bar {
|
|
margin-top: 6px;
|
|
width: 100%;
|
|
height: 4px;
|
|
background: color-mix(in srgb, var(--muted) 20%, transparent);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
.dc-monitor-bar-fill {
|
|
height: 100%;
|
|
width: 0%;
|
|
background: var(--ok-fg, #27ae60);
|
|
transition: width 0.3s ease, background 0.3s ease;
|
|
}
|
|
.dc-monitor-bar-fill.warn { background: #f39c12; }
|
|
.dc-monitor-bar-fill.bad { background: #e74c3c; }
|
|
.dc-monitor-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
}
|
|
.dc-monitor-title {
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
color: var(--muted);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.dc-monitor-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
font-size: 0.7rem;
|
|
font-weight: 500;
|
|
}
|
|
.dc-monitor-pill.ok { background: color-mix(in srgb, #27ae60 20%, transparent); color: #27ae60; }
|
|
.dc-monitor-pill.warn { background: color-mix(in srgb, #f39c12 20%, transparent); color: #f39c12; }
|
|
.dc-monitor-pill.bad { background: color-mix(in srgb, #e74c3c 20%, transparent); color: #e74c3c; }
|
|
.dc-monitor-refresh {
|
|
font-size: 0.7rem;
|
|
color: var(--muted);
|
|
opacity: 0.7;
|
|
}
|
|
`,document.head.appendChild(v);const k=document.getElementById("service-filter-bar");if(!k)return;const u=document.createElement("div");u.className="dc-monitor",u.id="dc-monitor-panel",u.innerHTML=`
|
|
<div class="dc-monitor-header" style="grid-column: 1 / -1;">
|
|
<div class="dc-monitor-title">\u{1F4CA} System Overview</div>
|
|
<span class="dc-monitor-refresh" id="dc-monitor-refresh-stamp">\u2014</span>
|
|
</div>
|
|
<div class="dc-monitor-card">
|
|
<div class="dc-monitor-label">Services</div>
|
|
<div class="dc-monitor-value" id="dc-monitor-services">\u2014</div>
|
|
<div class="dc-monitor-sub" id="dc-monitor-services-sub">loading\u2026</div>
|
|
</div>
|
|
<div class="dc-monitor-card">
|
|
<div class="dc-monitor-label">Containers Up</div>
|
|
<div class="dc-monitor-value" id="dc-monitor-containers">\u2014</div>
|
|
<div class="dc-monitor-sub" id="dc-monitor-containers-sub">loading\u2026</div>
|
|
</div>
|
|
<div class="dc-monitor-card">
|
|
<div class="dc-monitor-label">Avg CPU</div>
|
|
<div class="dc-monitor-value" id="dc-monitor-cpu">\u2014</div>
|
|
<div class="dc-monitor-bar"><div class="dc-monitor-bar-fill" id="dc-monitor-cpu-bar"></div></div>
|
|
</div>
|
|
<div class="dc-monitor-card">
|
|
<div class="dc-monitor-label">Avg Memory</div>
|
|
<div class="dc-monitor-value" id="dc-monitor-mem">\u2014</div>
|
|
<div class="dc-monitor-bar"><div class="dc-monitor-bar-fill" id="dc-monitor-mem-bar"></div></div>
|
|
</div>
|
|
<div class="dc-monitor-card">
|
|
<div class="dc-monitor-label">Health</div>
|
|
<div class="dc-monitor-value" id="dc-monitor-health">\u2014</div>
|
|
<div class="dc-monitor-sub" id="dc-monitor-health-sub">\u2014</div>
|
|
</div>
|
|
`,k.parentNode.insertBefore(u,k);function h(e,n){const c=document.getElementById(e);if(!c)return;const s=Math.max(0,Math.min(100,Number(n)||0));c.style.width=s+"%",c.classList.remove("warn","bad"),s>=85?c.classList.add("bad"):s>=65&&c.classList.add("warn")}function f(e){return e==null||isNaN(e)?"\u2014":Math.round(e*10)/10+"%"}function b(e){if(e==null||isNaN(e))return"\u2014";const n=["B","KB","MB","GB","TB"];let c=0;for(;e>=1024&&c<n.length-1;)e/=1024,c++;return e.toFixed(1)+" "+n[c]}function C(){const e=(window.APPS||[]).length;let n=0;document.querySelectorAll("#cards .card").forEach(l=>{l.dataset.status==="on"&&n++});const c=document.getElementById("dc-monitor-services"),s=document.getElementById("dc-monitor-services-sub");c&&(c.textContent=`${n} / ${e}`),s&&(s.textContent=e===0?"no services yet":`${n} online \xB7 ${e-n} offline`)}function E(e){const n=document.getElementById("dc-monitor-health"),c=document.getElementById("dc-monitor-health-sub");if(!n)return;if(!e||e.summary==null){n.textContent="\u2014",c&&(c.textContent="no data");return}const s=e.summary,l=s.healthy??s.up??0,m=s.unhealthy??s.down??0,t=s.total??l+m;n.textContent=`${l}/${t}`,c&&(m===0?c.innerHTML='<span class="dc-monitor-pill ok">\u25CF all healthy</span>':m<=2?c.innerHTML=`<span class="dc-monitor-pill warn">\u25CF ${m} degraded</span>`:c.innerHTML=`<span class="dc-monitor-pill bad">\u25CF ${m} down</span>`)}async function S(){try{const e=await fetch("/api/v1/monitoring/stats",{cache:"no-store"});if(!e.ok)return null;const n=await e.json();return n&&n.stats?n.stats:null}catch{return null}}async function y(){try{const e=await fetch("/api/v1/health-checks/status",{cache:"no-store"});return e.ok?await e.json():null}catch{return null}}function q(e){const n=document.getElementById("dc-monitor-containers"),c=document.getElementById("dc-monitor-containers-sub"),s=document.getElementById("dc-monitor-cpu"),l=document.getElementById("dc-monitor-mem");if(!e){n&&(n.textContent="\u2014"),s&&(s.textContent="\u2014"),l&&(l.textContent="\u2014");return}const m=Object.values(e);if(m.length===0){n&&(n.textContent="0"),c&&(c.textContent="no containers reporting"),s&&(s.textContent="0%"),l&&(l.textContent="0%"),h("dc-monitor-cpu-bar",0),h("dc-monitor-mem-bar",0);return}let t=0,o=0,r=0,p=0,d=0;m.forEach(g=>{if(g.cpu!=null){const x=Number(g.cpu);isNaN(x)||(t+=x>1?x:x*100,p++)}if(g.memory!=null){const x=Number(g.memory);isNaN(x)||(o+=x,r+=Number(g.memoryUsage||0),d++)}});const w=p?t/p:0,L=d?o/d:0;if(n&&(n.textContent=String(m.length)),c){const g=r?` \xB7 ${b(r)} RAM`:"";c.textContent=`running${g}`}s&&(s.textContent=f(w)),l&&(l.textContent=f(L)),h("dc-monitor-cpu-bar",w),h("dc-monitor-mem-bar",L)}let a=!1;async function i(){if(!a){a=!0;try{C();const[e,n]=await Promise.all([S(),y()]);q(e),E(n);const c=document.getElementById("dc-monitor-refresh-stamp");if(c){const s=new Date;c.textContent=`updated ${s.toLocaleTimeString()}`}}finally{a=!1}}}window.refreshMonitoringWidgets=i,setInterval(i,typeof DC<"u"&&DC.POLL&&DC.POLL.STATS||5e3),setTimeout(i,200)})(),(function(){"use strict";const v=(...t)=>{window.DASHCADDY_DEBUG&&console.log(...t)},k=["#app-selector-modal","#app-deploy-modal","#weather-modal","#token-management-modal","#service-edit-modal","#notifications-modal","#backup-modal","#stats-modal","#arr-setup-modal","#add-service-modal","#error-log-modal","#logs-modal","#dns-template-modal"];let u=null,h=null,f=null;function b(){try{C(),document.addEventListener("keydown",E),v("[Keyboard Shortcuts] Initialized"),v("[Keyboard Shortcuts] Press Ctrl+K to open quick search"),v("[Keyboard Shortcuts] Press Escape to close modals")}catch(t){console.warn("[Keyboard Shortcuts] Failed to initialize:",t.message)}}function C(){u=document.createElement("div"),u.id="quick-search-modal",u.className="quick-search-modal",u.innerHTML=`
|
|
<div class="quick-search-content">
|
|
<div class="quick-search-input-wrapper">
|
|
<span class="quick-search-icon">\u{1F50D}</span>
|
|
<input type="text" id="quick-search-input" placeholder="Search services, apps, or actions..." autocomplete="off">
|
|
<span class="quick-search-shortcut">ESC</span>
|
|
</div>
|
|
<div id="quick-search-results" class="quick-search-results"></div>
|
|
<div class="quick-search-footer">
|
|
<span><kbd>\u2191\u2193</kbd> Navigate</span>
|
|
<span><kbd>Enter</kbd> Select</span>
|
|
<span><kbd>Esc</kbd> Close</span>
|
|
</div>
|
|
</div>
|
|
`;const t=document.createElement("style");t.textContent=`
|
|
.quick-search-modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
z-index: 100000;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
padding-top: 15vh;
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.quick-search-modal.show {
|
|
display: flex;
|
|
}
|
|
|
|
.quick-search-content {
|
|
background: var(--card-base, #1a1a2e);
|
|
border: 1px solid var(--border, #333);
|
|
border-radius: 12px;
|
|
width: 90%;
|
|
max-width: 600px;
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.quick-search-input-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid var(--border, #333);
|
|
gap: 12px;
|
|
}
|
|
|
|
.quick-search-icon {
|
|
font-size: 20px;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
#quick-search-input {
|
|
flex: 1;
|
|
background: transparent;
|
|
border: none;
|
|
outline: none;
|
|
color: var(--fg, #fff);
|
|
font-size: 18px;
|
|
font-family: inherit;
|
|
}
|
|
|
|
#quick-search-input::placeholder {
|
|
color: var(--muted, #666);
|
|
}
|
|
|
|
.quick-search-shortcut {
|
|
background: var(--card-hover, #2a2a4e);
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
color: var(--muted, #666);
|
|
font-family: monospace;
|
|
}
|
|
|
|
.quick-search-results {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.quick-search-category {
|
|
padding: 8px 20px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
color: var(--muted, #666);
|
|
background: var(--card-hover, #2a2a4e);
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.quick-search-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12px 20px;
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
gap: 12px;
|
|
}
|
|
|
|
.quick-search-item:hover,
|
|
.quick-search-item.selected {
|
|
background: var(--accent, #3498db);
|
|
}
|
|
|
|
.quick-search-item-icon {
|
|
font-size: 24px;
|
|
width: 32px;
|
|
text-align: center;
|
|
}
|
|
|
|
.quick-search-item-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.quick-search-item-title {
|
|
font-weight: 500;
|
|
color: var(--fg, #fff);
|
|
}
|
|
|
|
.quick-search-item-description {
|
|
font-size: 12px;
|
|
color: var(--muted, #aaa);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.quick-search-item-badge {
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
font-size: 11px;
|
|
background: var(--card-hover, #2a2a4e);
|
|
color: var(--muted, #888);
|
|
}
|
|
|
|
.quick-search-empty {
|
|
padding: 40px 20px;
|
|
text-align: center;
|
|
color: var(--muted, #666);
|
|
}
|
|
|
|
.quick-search-footer {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 24px;
|
|
padding: 12px 20px;
|
|
border-top: 1px solid var(--border, #333);
|
|
background: var(--card-hover, #2a2a4e);
|
|
font-size: 12px;
|
|
color: var(--muted, #666);
|
|
}
|
|
|
|
.quick-search-footer kbd {
|
|
background: var(--card-base, #1a1a2e);
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
margin-right: 4px;
|
|
}
|
|
`,document.head.appendChild(t),document.body.appendChild(u),h=document.getElementById("quick-search-input"),f=document.getElementById("quick-search-results"),h.addEventListener("input",e),h.addEventListener("keydown",l),u.addEventListener("click",o=>{o.target===u&&y()})}function E(t){try{if((t.ctrlKey||t.metaKey)&&t.key==="k"){t.preventDefault(),S();return}if(t.key==="Escape"){if(u&&u.classList.contains("show")){y();return}q()}}catch(o){console.warn("[Keyboard Shortcuts] Error handling keydown:",o.message)}}function S(){try{u.classList.add("show"),h.value="",h.focus(),a()}catch(t){console.warn("[Keyboard Shortcuts] Error opening quick search:",t.message)}}function y(){try{u.classList.remove("show"),h.value="",f.innerHTML=""}catch(t){console.warn("[Keyboard Shortcuts] Error closing quick search:",t.message)}}function q(){for(const t of k){const o=document.querySelector(t);if(o&&(o.classList.contains("show")||o.style.display==="flex"))return o.classList.remove("show"),o.style.display="none",!0}return!1}function a(){const t=`
|
|
<div class="quick-search-category">Quick Actions</div>
|
|
<div class="quick-search-item" data-action="refresh">
|
|
<span class="quick-search-item-icon">\u{1F504}</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">Refresh Dashboard</div>
|
|
<div class="quick-search-item-description">Refresh all service statuses</div>
|
|
</div>
|
|
</div>
|
|
<div class="quick-search-item" data-action="reload-caddy">
|
|
<span class="quick-search-item-icon">\u26A1</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">Reload Caddy</div>
|
|
<div class="quick-search-item-description">Reload Caddy configuration</div>
|
|
</div>
|
|
</div>
|
|
<div class="quick-search-item" data-action="add-service">
|
|
<span class="quick-search-item-icon">\u2795</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">Add Service</div>
|
|
<div class="quick-search-item-description">Open service configuration modal</div>
|
|
</div>
|
|
</div>
|
|
<div class="quick-search-item" data-action="app-selector">
|
|
<span class="quick-search-item-icon">\u{1F4F1}</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">App Selector</div>
|
|
<div class="quick-search-item-description">Deploy new applications</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="quick-search-category">Services</div>
|
|
${i()}
|
|
`;f.innerHTML=t,s()}function i(){const t=document.querySelectorAll(".card[data-app], #cards .card");let o="";return t.forEach(r=>{const p=r.querySelector(".name")?.textContent||"Unknown",d=r.dataset.status||"unknown",w=r.dataset.app||"";p&&p!=="--"&&(o+=`
|
|
<div class="quick-search-item" data-action="open-service" data-service="${w}">
|
|
<span class="quick-search-item-icon">${d==="on"?"\u{1F7E2}":"\u{1F534}"}</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">${p}</div>
|
|
<div class="quick-search-item-description">Click to open service</div>
|
|
</div>
|
|
<span class="quick-search-item-badge">${d.toUpperCase()}</span>
|
|
</div>
|
|
`)}),o||'<div class="quick-search-empty">No services found</div>'}function e(t){try{const o=t.target.value.toLowerCase().trim();if(!o){a();return}const r=n(o);c(r)}catch(o){console.warn("[Keyboard Shortcuts] Error handling search input:",o.message)}}function n(t){const o={actions:[],services:[]};return[{id:"refresh",title:"Refresh Dashboard",icon:"\u{1F504}",keywords:"refresh reload update status"},{id:"reload-caddy",title:"Reload Caddy",icon:"\u26A1",keywords:"reload caddy proxy config"},{id:"add-service",title:"Add Service",icon:"\u2795",keywords:"add new service create"},{id:"app-selector",title:"App Selector",icon:"\u{1F4F1}",keywords:"app deploy install docker container"},{id:"backup",title:"Backup & Restore",icon:"\u{1F4BE}",keywords:"backup restore export import"},{id:"stats",title:"Container Stats",icon:"\u{1F4CA}",keywords:"stats resources cpu memory"},{id:"logs",title:"View Logs",icon:"\u{1F4CB}",keywords:"logs error debug"},{id:"tokens",title:"Manage Tokens",icon:"\u{1F511}",keywords:"tokens api keys credentials"},{id:"notifications",title:"Notifications",icon:"\u{1F514}",keywords:"alerts notifications discord telegram"},{id:"theme",title:"Change Theme",icon:"\u{1F3A8}",keywords:"theme dark light appearance"},{id:"tour",title:"Help Tour",icon:"\u{1F393}",keywords:"help tour guide onboarding"}].forEach(d=>{(d.title.toLowerCase().includes(t)||d.keywords.includes(t))&&o.actions.push(d)}),document.querySelectorAll(".card[data-app], #cards .card").forEach(d=>{const w=d.querySelector(".name")?.textContent||"",L=d.dataset.app||"",g=d.dataset.status||"unknown";(w.toLowerCase().includes(t)||L.toLowerCase().includes(t))&&o.services.push({id:L,title:w,status:g,icon:g==="on"?"\u{1F7E2}":"\u{1F534}"})}),o}function c(t){let o="";t.actions.length>0&&(o+='<div class="quick-search-category">Actions</div>',t.actions.forEach(r=>{o+=`
|
|
<div class="quick-search-item" data-action="${r.id}">
|
|
<span class="quick-search-item-icon">${r.icon}</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">${r.title}</div>
|
|
</div>
|
|
</div>
|
|
`})),t.services.length>0&&(o+='<div class="quick-search-category">Services</div>',t.services.forEach(r=>{o+=`
|
|
<div class="quick-search-item" data-action="open-service" data-service="${r.id}">
|
|
<span class="quick-search-item-icon">${r.icon}</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">${r.title}</div>
|
|
</div>
|
|
<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}})();
|