modified on local at 2026-07-12 23:12:57
| 1 | 1 | <!doctype html> |
| 2 | 2 | <html lang="en"> |
| 3 | 3 | <head> |
| 4 | 4 | <meta charset="utf-8"> |
| 5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 6 | 6 | <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0"> |
| 7 | 7 | <title>$page_title -- $brand_name</title> |
| 8 | 8 | <link rel="stylesheet" href="/assets/styles/drift_sense.css?v=$asset_version"> |
| 9 | 9 | <link rel="stylesheet" href="/assets/styles/help_tips.css?v=$asset_version"> |
| 10 | 10 | <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect x='0.75' y='0.75' width='30.5' height='30.5' rx='7.5' fill='%230a1220' stroke='%2314b8a6' stroke-width='1.5'/><text x='16' y='22' font-family='Arial,Helvetica,sans-serif' font-size='14' font-weight='700' fill='%232dd4bf' text-anchor='middle'>DS</text></svg>"> |
| 11 | 11 | </head> |
| 12 | 12 | <body> |
| 13 | 13 | <div class="app-shell app"> |
| 14 | 14 | |
| 15 | 15 | <aside class="sidebar"> |
| 16 | 16 | <a class="sidebar-brand" href="[url:dashboard]"> |
| 17 | 17 | <div class="logo">DS</div> |
| 18 | 18 | <div class="brand-text"> |
| 19 | 19 | <div class="brand-name">Drift<span class="accent">Sense</span></div> |
| 20 | 20 | <div class="brand-sub">$brand_tagline</div> |
| 21 | 21 | </div> |
| 22 | 22 | </a> |
| 23 | 23 | |
| 24 | 24 | <div class="sidebar-section-label">Monitor</div> |
| 25 | 25 | <a href="[url:dashboard]" class="sidebar-link[if:$active_dashboard] active[/if]"> |
| 26 | 26 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg> |
| 27 | 27 | <span>Dashboard</span> |
| 28 | 28 | </a> |
| 29 | 29 | <a href="[url:file_changes]" class="sidebar-link[if:$active_file_changes] active[/if]"> |
| 30 | 30 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg> |
| 31 | 31 | <span>File changes</span> |
| 32 | 32 | </a> |
| 33 | 33 | <a href="/search.cgi" class="sidebar-link[if:$active_search] active[/if]"> |
| 34 | 34 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> |
| 35 | 35 | <span>Search captures</span> |
| 36 | 36 | </a> |
| 37 | 37 | <a href="/portfolio_drift.cgi" class="sidebar-link[if:$active_portfolio_drift] active[/if]"> |
| 38 | 38 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg> |
| 39 | 39 | <span>Portfolio drift</span> |
| 40 | </a> | |
| 41 | <a href="/broadcast.cgi" class="sidebar-link[if:$active_broadcast] active[/if]"> | |
| 42 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/></svg> | |
| 43 | <span>Broadcast</span> | |
| 44 | </a> | |
| 45 | <a href="/watchlist.cgi" class="sidebar-link[if:$active_watchlist] active[/if]"> | |
| 46 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.8 5.8 21 7 14 2 9.3 9 8.5 12 2"/></svg> | |
| 47 | <span>Watch-list</span> | |
| 48 | </a> | |
| 49 | <a href="/file_bio.cgi" class="sidebar-link[if:$active_file_bio] active[/if]"> | |
| 50 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> | |
| 51 | <span>File biography</span> | |
| 40 | 52 | </a> |
| 41 | 53 | <a href="[url:schema_changes]" class="sidebar-link[if:$active_schema_changes] active[/if]"> |
| 42 | 54 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v6c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 11v6c0 1.7 4 3 9 3s9-1.3 9-3v-6"/></svg> |
| 43 | 55 | <span>Schema changes</span> |
| 44 | 56 | </a> |
| 45 | 57 | <a href="[url:table_locks]" class="sidebar-link[if:$active_table_locks] active[/if]"> |
| 46 | 58 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg> |
| 47 | 59 | <span>Table locks</span> |
| 48 | 60 | </a> |
| 49 | 61 | <a href="[url:named_releases]" class="sidebar-link[if:$active_named_releases] active[/if]"> |
| 50 | 62 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 13V7a2 2 0 0 0-2-2h-4l-2-3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5"/><path d="M14 19l2 2 4-4"/></svg> |
| 51 | 63 | <span>Named releases</span> |
| 64 | </a> | |
| 65 | <a href="/release_diff.cgi" class="sidebar-link[if:$active_release_diff] active[/if]"> | |
| 66 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="8 3 4 7 8 11"/><polyline points="16 21 20 17 16 13"/><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="17" x2="20" y2="17"/></svg> | |
| 67 | <span>Release diff</span> | |
| 52 | 68 | </a> |
| 53 | 69 | <a href="/sites.cgi" class="sidebar-link[if:$active_sites] active[/if]"> |
| 54 | 70 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg> |
| 55 | 71 | <span>Sites ($sites_ct)</span> |
| 56 | 72 | </a> |
| 57 | 73 | |
| 58 | 74 | [if:$has_stables] |
| 59 | 75 | <div class="sidebar-section-label">Stable versions</div> |
| 60 | 76 | [loop:@stable_releases] |
| 61 | 77 | <a href="$restore_url" class="sidebar-link ds-stable-link" title="Restore all pinned to this stable version"> |
| 62 | 78 | <span class="ds-stable-tag">v</span> |
| 63 | 79 | <span>$label</span> |
| 64 | 80 | </a> |
| 65 | 81 | [/loop] |
| 66 | 82 | [/if] |
| 67 | 83 | |
| 68 | 84 | <div class="sidebar-section-label">Notify & export</div> |
| 69 | 85 | <a href="/alerts.cgi" class="sidebar-link[if:$active_alerts] active[/if]"> |
| 70 | 86 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg> |
| 71 | 87 | <span>Alerts</span> |
| 72 | 88 | </a> |
| 73 | 89 | <a href="/export.cgi" class="sidebar-link[if:$active_export] active[/if]"> |
| 74 | 90 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> |
| 75 | 91 | <span>Compliance export</span> |
| 76 | 92 | </a> |
| 77 | 93 | <a href="/purge_log.cgi" class="sidebar-link[if:$active_purge_log] active[/if]"> |
| 78 | 94 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg> |
| 79 | 95 | <span>Purge log</span> |
| 96 | </a> | |
| 97 | <a href="/orphan_blobs.cgi" class="sidebar-link[if:$active_orphan_blobs] active[/if]"> | |
| 98 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><line x1="7" y1="7" x2="17" y2="17"/></svg> | |
| 99 | <span>Orphan blobs</span> | |
| 80 | 100 | </a> |
| 81 | 101 | |
| 82 | 102 | <div class="sidebar-section-label">Configuration</div> |
| 83 | 103 | <a href="[url:databases]" class="sidebar-link[if:$active_databases] active[/if]"> |
| 84 | 104 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.7-4 3-9 3s-9-1.3-9-3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/></svg> |
| 85 | 105 | <span>Databases</span> |
| 86 | 106 | </a> |
| 87 | 107 | <a href="[url:file_monitors]" class="sidebar-link[if:$active_file_monitors] active[/if]"> |
| 88 | 108 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> |
| 89 | 109 | <span>File monitors</span> |
| 90 | 110 | </a> |
| 91 | 111 | <a href="[url:servers]" class="sidebar-link[if:$active_servers] active[/if]"> |
| 92 | 112 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg> |
| 93 | 113 | <span>Servers & agents</span> |
| 94 | 114 | </a> |
| 95 | 115 | <a href="[url:containers]" class="sidebar-link[if:$active_containers] active[/if]"> |
| 96 | 116 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="7.5 4.21 12 6.81 16.5 4.21"/><polyline points="7.5 19.79 7.5 14.6 3 12"/><polyline points="21 12 16.5 14.6 16.5 19.79"/></svg> |
| 97 | 117 | <span>Containers</span> |
| 98 | 118 | </a> |
| 99 | 119 | <a href="[url:settings]" class="sidebar-link[if:$active_settings] active[/if]"> |
| 100 | 120 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v6m0 10v6M4.22 4.22l4.24 4.24m7.08 7.08l4.24 4.24M1 12h6m10 0h6M4.22 19.78l4.24-4.24m7.08-7.08l4.24-4.24"/></svg> |
| 101 | 121 | <span>Settings</span> |
| 102 | 122 | </a> |
| 103 | 123 | |
| 104 | 124 | <div class="sidebar-section-label">Account</div> |
| 105 | 125 | <a href="/logout.cgi" class="sidebar-link sidebar-link-logout"> |
| 106 | 126 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg> |
| 107 | 127 | <span>Sign out</span> |
| 108 | 128 | </a> |
| 109 | 129 | </aside> |
| 110 | 130 | |
| 111 | 131 | <div class="app-main"> |
| 112 | 132 | <header class="topbar"> |
| 113 | 133 | <button type="button" class="topbar-iconbtn" id="sidebarToggle" title="Collapse sidebar"> |
| 114 | 134 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg> |
| 115 | 135 | </button> |
| 116 | 136 | <div class="topbar-title">$page_title</div> |
| 117 | 137 | <div class="topbar-spacer"></div> |
| 118 | 138 | <form class="topbar-search" method="GET" action="/search.cgi"> |
| 119 | 139 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><line x1="20" y1="20" x2="16.65" y2="16.65"/></svg> |
| 120 | 140 | <input type="text" name="q" placeholder="Search captures..." autocomplete="off"> |
| 121 | 141 | </form> |
| 122 | 142 | <div class="topbar-actions"> |
| 123 | 143 | <a href="/alerts.cgi" class="topbar-iconbtn" title="Alerts"> |
| 124 | 144 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg> |
| 125 | 145 | </a> |
| 126 | 146 | <div class="profile-dropdown" id="profileDropdown"> |
| 127 | 147 | <button type="button" class="profile-btn" onclick="document.getElementById('profileMenu').classList.toggle('open');"> |
| 128 | 148 | <div class="avatar">$user_initials</div> |
| 129 | 149 | <div style="text-align:left;line-height:1.2"> |
| 130 | 150 | <div style="font-size:13px;font-weight:600">$user_name</div> |
| 131 | 151 | <div style="font-size:11px;color:var(--text-dim)">Watchtower Console</div> |
| 132 | 152 | </div> |
| 133 | 153 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="6 9 12 15 18 9"/></svg> |
| 134 | 154 | </button> |
| 135 | 155 | <div class="profile-dropdown-menu" id="profileMenu"> |
| 136 | 156 | <div style="padding:12px 12px 10px;border-bottom:1px solid var(--border);margin-bottom:6px"> |
| 137 | 157 | <div style="font-weight:600;font-size:13.5px">$user_name</div> |
| 138 | 158 | <div style="font-size:11.5px;color:var(--text-muted)">$user_email</div> |
| 139 | 159 | <div style="margin-top:6px;display:flex;gap:6px"><span class="badge primary" style="background:linear-gradient(135deg,var(--accent),var(--accent-deep));color:#030710;font-size:10.5px;font-weight:700;padding:2px 8px;border-radius:6px">Operator</span></div> |
| 140 | 160 | </div> |
| 141 | 161 | <div class="profile-dropdown-section">Monitor</div> |
| 142 | 162 | <a href="/dashboard.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg></span> Dashboard</a> |
| 143 | 163 | <a href="/file_changes.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></span> File changes</a> |
| 144 | 164 | <a href="/search.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></span> Search captures</a> |
| 145 | 165 | <a href="/portfolio_drift.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></span> Portfolio drift</a> |
| 146 | 166 | <a href="/named_releases.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M20 13V7a2 2 0 0 0-2-2h-4l-2-3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5"/><path d="M14 19l2 2 4-4"/></svg></span> Named releases</a> |
| 147 | 167 | <a href="/sites.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg></span> Sites ($sites_ct)</a> |
| 148 | 168 | |
| 149 | 169 | <div class="profile-dropdown-divider"></div> |
| 150 | 170 | <div class="profile-dropdown-section">Notify & export</div> |
| 151 | 171 | <a href="/alerts.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/></svg></span> Alerts</a> |
| 152 | 172 | <a href="/export.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg></span> Compliance export</a> |
| 153 | 173 | <a href="/purge_log.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></span> Purge log</a> |
| 154 | 174 | |
| 155 | 175 | <div class="profile-dropdown-divider"></div> |
| 156 | 176 | <div class="profile-dropdown-section">Configuration</div> |
| 157 | 177 | <a href="/databases.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.7-4 3-9 3s-9-1.3-9-3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/></svg></span> Databases</a> |
| 158 | 178 | <a href="/file_monitors.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></span> File monitors</a> |
| 159 | 179 | <a href="/servers.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/></svg></span> Servers & agents</a> |
| 160 | 180 | <a href="/settings.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><circle cx="12" cy="12" r="3"/><path d="M12 1v6m0 10v6"/></svg></span> Settings</a> |
| 161 | 181 | |
| 162 | 182 | <div class="profile-dropdown-divider"></div> |
| 163 | 183 | <div class="profile-dropdown-section">Account</div> |
| 164 | 184 | <a href="/logout.cgi"><span class="dropdown-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg></span> Sign out</a> |
| 165 | 185 | </div> |
| 166 | 186 | </div> |
| 167 | 187 | </div> |
| 168 | 188 | </header> |
| 169 | 189 | <div class="app-content"> |
| 170 | 190 | $page_body |
| 171 | 191 | </div> |
| 172 | 192 | </div> |
| 173 | 193 | |
| 174 | 194 | </div> |
| 175 | 195 | <script src="/assets/js/tz_localize.js?v=$asset_version"></script> |
| 176 | 196 | <script src="/assets/js/ds_charts.js?v=$asset_version"></script> |
| 177 | 197 | <script src="/assets/js/ds_help.js?v=$asset_version"></script> |
| 178 | 198 | <script> |
| 179 | 199 | (function(){ |
| 180 | 200 | // Sidebar collapse toggle (icons-only) persisted in localStorage |
| 181 | 201 | var btn = document.getElementById('sidebarToggle'); |
| 182 | 202 | var KEY = 'ds-sidebar-collapsed'; |
| 183 | 203 | try { if (localStorage.getItem(KEY) === '1') document.documentElement.classList.add('sidebar-collapsed'); } catch(e){} |
| 184 | 204 | if (btn) btn.addEventListener('click', function(){ |
| 185 | 205 | document.documentElement.classList.toggle('sidebar-collapsed'); |
| 186 | 206 | try { localStorage.setItem(KEY, document.documentElement.classList.contains('sidebar-collapsed') ? '1' : '0'); } catch(e){} |
| 187 | 207 | }); |
| 188 | 208 | // Close profile dropdown on outside click |
| 189 | 209 | document.addEventListener('click', function(e){ |
| 190 | 210 | var m = document.getElementById('profileMenu'); |
| 191 | 211 | var d = document.getElementById('profileDropdown'); |
| 192 | 212 | if (m && d && !d.contains(e.target)) m.classList.remove('open'); |
| 193 | 213 | }); |
| 194 | 214 | })(); |
| 195 | 215 | </script> |
| 196 | 216 | </body> |
| 197 | 217 | </html> |