Three logical changes grouped:
1. Widget bundle rebuild + sami-files logo (from previous session)
- status/dist/{init,core,features,onboarding}.js rebuilt from latest source
- status/sw.js cache bumped to dashcaddy-shell-594ec75648 to force SW refresh
- status/assets/sami-files.png added (Sami Files service card logo)
2. status/build.js: include monitoring-widgets.js in bundle
- The original build.js was missing monitoring-widgets.js from its JS()
bundle list — that's why the System Overview widget never showed up
in the live init.js until we ran the live /var/www/dashcaddy-status/
build.js. Now consistent.
3. dashcaddy-api/scripts/dashcaddy-update.sh restart_container(): preserve
TOTP secret across container recreates
- Was only setting SERVICES_FILE; container fell back to image-local
/app/credentials.json + /app/.encryption-key (auto-generated fresh
every recreate), which broke TOTP for the bind-mounted secret at
/app/data/credentials.json
- Added CREDENTIALS_FILE + ENCRYPTION_KEY_FILE env vars pointing at
/app/data/ so the container reads from the bind-mounted host data dir
- See skill: software-development/dashcaddy/references/totp-and-system-overview-pitfalls.md §9
4. Auto-updater integration (pulled from upstream release):
- dashcaddy-api/VERSION: dev → c64bbe2
- dashcaddy-api/health-checker.js, middleware.js, package.json,
routes/backups.js, src/app.js: new release code (bundled workflows,
/api/auth/ → /api/v1/ back-compat rewrite, backup storage limits)
332 lines
26 KiB
JavaScript
332 lines
26 KiB
JavaScript
(function(){function v(){const i=safeGet("custom-services");if(i)try{JSON.parse(i).forEach(d=>{window.APPS.find(o=>o.id===d.id)||window.APPS.push(d)})}catch(a){console.warn("Failed to load custom services:",a)}}v();function k(){const i=document.querySelectorAll(".top .card");i.forEach((a,d)=>{a.style.transitionDelay=`${Math.min(d*60,300)}ms`}),requestAnimationFrame(()=>{i.forEach(a=>a.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(a=>{console.warn("[init] Service worker registration failed:",a)})};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 g(),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 a=await(await fetch("/api/v1/totp/config",{cache:"no-store"})).json();a.success&&window._updateAuthCard(a.config.enabled&&a.config.isSetUp,a.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 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 a=document.createElement("link");a.rel="stylesheet",a.href="/css/driver.min.css",document.head.appendChild(a)}if(!document.querySelector('link[href="/css/onboarding.css"]')){const a=document.createElement("link");a.rel="stylesheet",a.href="/css/onboarding.css",document.head.appendChild(a)}}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 a={};try{a=JSON.parse(localStorage.getItem("toolbar-sections")||"{}")}catch{}i.forEach(d=>{const o=d.dataset.section,n=d.querySelector(".tools-section-header");n&&(a[o]&&(d.classList.add("open"),n.setAttribute("aria-expanded","true")),n.addEventListener("click",c=>{c.preventDefault();const r=d.classList.toggle("open");n.setAttribute("aria-expanded",r?"true":"false");const m={};document.querySelectorAll(".tools-section").forEach(e=>{m[e.dataset.section]=e.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 o=JSON.parse(localStorage.getItem("dashcaddy_onboarding"));i=i||!!(o&&o.tourCompleted)}catch{}const a=i?document.querySelector('.tools-section[data-section="admin"] .tools-section-items'):document.querySelector(".tools-primary");if(!a)return;const d=document.createElement("button");d.id="restart-tour-btn",d.textContent=i?"Help Tour":"\u{1F393} Help Tour",d.title="Restart the onboarding tour",d.onclick=()=>{if(window.DashCaddyOnboarding)window.DashCaddyOnboarding.restartTour();else{b();const o=setInterval(()=>{window.DashCaddyOnboarding&&(clearInterval(o),window.DashCaddyOnboarding.restartTour())},100);setTimeout(()=>clearInterval(o),5e3)}},a.appendChild(d)}window.initializeDashboard=f,window.loadCustomServices=v,u();async function g(){try{const i=await fetch("/api/v1/templates",{cache:"no-store"});if(!i.ok)return;const a=await i.json();a&&a.categories&&(window.DC_CATEGORIES=a.categories,typeof DC<"u"&&(DC.CATEGORIES=a.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(a=>{const d=a.dataset.current||"",o=a.querySelector('option[value=""]');if(a.innerHTML="",o)a.appendChild(o);else{const n=document.createElement("option");n.value="",n.textContent="\u2014 Select category \u2014",a.appendChild(n)}Object.entries(i).forEach(([n,c])=>{const r=document.createElement("option");r.value=n,r.textContent=`${c.icon||""} ${n}`.trim(),n===d&&(r.selected=!0),a.appendChild(r)})})}window.populateCategorySelects=q,window.loadTemplateCategories=g,(async()=>{try{const a=await(await fetch("/api/v1/totp/config",{cache:"no-store"})).json();if(a.success&&a.config.enabled&&(await fetch("/api/v1/totp/check-session",{cache:"no-store"})).status===401){window._showTotpOverlay();return}}catch(i){console.warn("TOTP check failed, proceeding normally:",i)}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(o,n){const c=document.getElementById(o);if(!c)return;const r=Math.max(0,Math.min(100,Number(n)||0));c.style.width=r+"%",c.classList.remove("warn","bad"),r>=85?c.classList.add("bad"):r>=65&&c.classList.add("warn")}function f(o){return o==null||isNaN(o)?"\u2014":Math.round(o*10)/10+"%"}function b(o){if(o==null||isNaN(o))return"\u2014";const n=["B","KB","MB","GB","TB"];let c=0;for(;o>=1024&&c<n.length-1;)o/=1024,c++;return o.toFixed(1)+" "+n[c]}async function C(){if(Array.isArray(window.APPS)&&window.APPS.length>0){const n=document.querySelectorAll('#cards .card[data-status="on"]').length;return{total:window.APPS.length,up:n,source:"APPS"}}const o=document.querySelectorAll("#cards .card");if(o.length>0){const n=Array.from(o).filter(c=>c.dataset.status==="on").length;return{total:o.length,up:n,source:"DOM"}}try{const n=await fetch("/api/v1/services",{cache:"no-store"});if(!n.ok)return{total:0,up:0,source:"fetch-fail"};const c=await n.json(),r=c&&Array.isArray(c.services)?c.services:Array.isArray(c)?c:[];(Array.isArray(window.APPS)||typeof window.APPS>"u")&&(window.APPS=r);const m=document.querySelectorAll('#cards .card[data-status="on"]').length;return{total:r.length,up:m,source:"fetch"}}catch{return{total:0,up:0,source:"fetch-error"}}}async function E(){const{total:o,up:n}=await C(),c=document.getElementById("dc-monitor-services"),r=document.getElementById("dc-monitor-services-sub");c&&(c.textContent=`${n} / ${o}`),r&&(r.textContent=o===0?"no services yet":`${n} online \xB7 ${o-n} offline`)}function S(o){const n=document.getElementById("dc-monitor-health"),c=document.getElementById("dc-monitor-health-sub");if(!n)return;if(!o||o.summary==null){n.textContent="\u2014",c&&(c.textContent="no data");return}const r=o.summary,m=r.healthy??r.up??0,e=r.unhealthy??r.down??0,t=r.total??m+e;n.textContent=`${m}/${t}`,c&&(e===0?c.innerHTML='<span class="dc-monitor-pill ok">\u25CF all healthy</span>':e<=2?c.innerHTML=`<span class="dc-monitor-pill warn">\u25CF ${e} degraded</span>`:c.innerHTML=`<span class="dc-monitor-pill bad">\u25CF ${e} down</span>`)}async function g(){try{const o=await fetch("/api/v1/monitoring/stats",{cache:"no-store"});if(!o.ok)return null;const n=await o.json();return n&&n.stats?n.stats:null}catch{return null}}async function q(){try{const o=await fetch("/api/v1/health-checks/status",{cache:"no-store"});return o.ok?await o.json():null}catch{return null}}function i(o){const n=document.getElementById("dc-monitor-containers"),c=document.getElementById("dc-monitor-containers-sub"),r=document.getElementById("dc-monitor-cpu"),m=document.getElementById("dc-monitor-mem");if(!o){n&&(n.textContent="\u2014"),r&&(r.textContent="\u2014"),m&&(m.textContent="\u2014");return}const e=Object.values(o);if(e.length===0){n&&(n.textContent="0"),c&&(c.textContent="no containers reporting"),r&&(r.textContent="0%"),m&&(m.textContent="0%"),h("dc-monitor-cpu-bar",0),h("dc-monitor-mem-bar",0);return}let t=0,s=0,p=0,l=0,y=0;e.forEach(w=>{if(w.cpu!=null){const x=Number(w.cpu);isNaN(x)||(t+=x>1?x:x*100,l++)}if(w.memory!=null){const x=Number(w.memory);isNaN(x)||(s+=x,p+=Number(w.memoryUsage||0),y++)}});const A=l?t/l:0,L=y?s/y:0;if(n&&(n.textContent=String(e.length)),c){const w=p?` \xB7 ${b(p)} RAM`:"";c.textContent=`running${w}`}r&&(r.textContent=f(A)),m&&(m.textContent=f(L)),h("dc-monitor-cpu-bar",A),h("dc-monitor-mem-bar",L)}let a=!1;async function d(){if(!a){a=!0;try{E();const[o,n]=await Promise.all([g(),q()]);i(o),S(n);const c=document.getElementById("dc-monitor-refresh-stamp");if(c){const r=new Date;c.textContent=`updated ${r.toLocaleTimeString()}`}}finally{a=!1}}}window.refreshMonitoringWidgets=d,setInterval(d,typeof DC<"u"&&DC.POLL&&DC.POLL.STATS||5e3),setTimeout(d,200)})(),(function(){"use strict";const v=(...e)=>{window.DASHCADDY_DEBUG&&console.log(...e)},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(e){console.warn("[Keyboard Shortcuts] Failed to initialize:",e.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 e=document.createElement("style");e.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(e),document.body.appendChild(u),h=document.getElementById("quick-search-input"),f=document.getElementById("quick-search-results"),h.addEventListener("input",d),h.addEventListener("keydown",r),u.addEventListener("click",t=>{t.target===u&&g()})}function E(e){try{if((e.ctrlKey||e.metaKey)&&e.key==="k"){e.preventDefault(),S();return}if(e.key==="Escape"){if(u&&u.classList.contains("show")){g();return}q()}}catch(t){console.warn("[Keyboard Shortcuts] Error handling keydown:",t.message)}}function S(){try{u.classList.add("show"),h.value="",h.focus(),i()}catch(e){console.warn("[Keyboard Shortcuts] Error opening quick search:",e.message)}}function g(){try{u.classList.remove("show"),h.value="",f.innerHTML=""}catch(e){console.warn("[Keyboard Shortcuts] Error closing quick search:",e.message)}}function q(){for(const e of k){const t=document.querySelector(e);if(t&&(t.classList.contains("show")||t.style.display==="flex"))return t.classList.remove("show"),t.style.display="none",!0}return!1}function i(){const e=`
|
|
<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>
|
|
${a()}
|
|
`;f.innerHTML=e,c()}function a(){const e=document.querySelectorAll(".card[data-app], #cards .card");let t="";return e.forEach(s=>{const p=s.querySelector(".name")?.textContent||"Unknown",l=s.dataset.status||"unknown",y=s.dataset.app||"";p&&p!=="--"&&(t+=`
|
|
<div class="quick-search-item" data-action="open-service" data-service="${y}">
|
|
<span class="quick-search-item-icon">${l==="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">${l.toUpperCase()}</span>
|
|
</div>
|
|
`)}),t||'<div class="quick-search-empty">No services found</div>'}function d(e){try{const t=e.target.value.toLowerCase().trim();if(!t){i();return}const s=o(t);n(s)}catch(t){console.warn("[Keyboard Shortcuts] Error handling search input:",t.message)}}function o(e){const t={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(l=>{(l.title.toLowerCase().includes(e)||l.keywords.includes(e))&&t.actions.push(l)}),document.querySelectorAll(".card[data-app], #cards .card").forEach(l=>{const y=l.querySelector(".name")?.textContent||"",A=l.dataset.app||"",L=l.dataset.status||"unknown";(y.toLowerCase().includes(e)||A.toLowerCase().includes(e))&&t.services.push({id:A,title:y,status:L,icon:L==="on"?"\u{1F7E2}":"\u{1F534}"})}),t}function n(e){let t="";e.actions.length>0&&(t+='<div class="quick-search-category">Actions</div>',e.actions.forEach(s=>{t+=`
|
|
<div class="quick-search-item" data-action="${s.id}">
|
|
<span class="quick-search-item-icon">${s.icon}</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">${s.title}</div>
|
|
</div>
|
|
</div>
|
|
`})),e.services.length>0&&(t+='<div class="quick-search-category">Services</div>',e.services.forEach(s=>{t+=`
|
|
<div class="quick-search-item" data-action="open-service" data-service="${s.id}">
|
|
<span class="quick-search-item-icon">${s.icon}</span>
|
|
<div class="quick-search-item-content">
|
|
<div class="quick-search-item-title">${s.title}</div>
|
|
</div>
|
|
<span class="quick-search-item-badge">${s.status.toUpperCase()}</span>
|
|
</div>
|
|
`})),t||(t='<div class="quick-search-empty">No results found</div>'),f.innerHTML=t,c()}function c(){f.querySelectorAll(".quick-search-item").forEach((t,s)=>{t.addEventListener("click",()=>m(t)),s===0&&t.classList.add("selected")})}function r(e){try{const t=f.querySelectorAll(".quick-search-item"),s=f.querySelector(".quick-search-item.selected"),p=Array.from(t).indexOf(s);if(e.key==="ArrowDown"){e.preventDefault(),s&&s.classList.remove("selected");const l=(p+1)%t.length;t[l]?.classList.add("selected"),t[l]?.scrollIntoView({block:"nearest"})}else if(e.key==="ArrowUp"){e.preventDefault(),s&&s.classList.remove("selected");const l=p<=0?t.length-1:p-1;t[l]?.classList.add("selected"),t[l]?.scrollIntoView({block:"nearest"})}else e.key==="Enter"&&(e.preventDefault(),s&&m(s))}catch(t){console.warn("[Keyboard Shortcuts] Error handling search navigation:",t.message)}}function m(e){try{const t=e.dataset.action,s=e.dataset.service;switch(g(),t){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(s){const p=document.querySelector(`[data-app="${s}"] [id$="-open"], [data-app="${s}"] button:not(.restart-btn):not(.logs-btn):not(.settings-btn)`);if(p)p.click();else{const l=document.querySelector(`[data-app="${s}"]`);l&&l.click()}}break;default:v("[Keyboard Shortcuts] Unknown action:",t)}}catch(t){console.warn("[Keyboard Shortcuts] Error executing action:",t.message)}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",b):b(),window.DashCaddyKeyboardShortcuts={openQuickSearch:S,closeQuickSearch:g}})();
|