mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-10-31 18:29:21 +00:00 
			
		
		
		
	feat(frontend): rename inj roles to sys-front-*, add sys-svc-cdn, cache-busting lookup
Introduce sys-svc-cdn (cdn_paths/cdn_urls/cdn_dirs) and ensure CDN directories + latest symlink. Rename sys-srv-web-inj-* → sys-front-inj-*; update includes/templates; serve shared/per-app CSS & JS via CDN. Add lookup_plugins/local_mtime_qs.py for mtime-based cache busting; split CSS into default.css/bootstrap.css + optional per-app style.css. CSP: use style-src-elem; drop unsafe-inline for styles. Services: fix SYS_SERVICE_ALL_ENABLED bool and controlled flush. BREAKING CHANGE: role names changed; replace includes and references accordingly. Conversation: https://chatgpt.com/share/68b55494-9ec4-800f-b559-44707029141d
This commit is contained in:
		
							
								
								
									
										1
									
								
								roles/sys-front-inj-logout/templates/body_sub.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								roles/sys-front-inj-logout/templates/body_sub.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <script>{{ logout_code_one_liner }}</script> | ||||
							
								
								
									
										1
									
								
								roles/sys-front-inj-logout/templates/head_sub.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								roles/sys-front-inj-logout/templates/head_sub.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <script src="{{ cdn_urls.shared.js }}/{{ INJ_LOGOUT_JS_FILE_NAME }}{{ lookup('local_mtime_qs', [playbook_dir, 'roles', 'sys-front-inj-logout', 'templates', INJ_LOGOUT_JS_FILE_NAME ~ '.j2'] | path_join) }}"></script> | ||||
							
								
								
									
										127
									
								
								roles/sys-front-inj-logout/templates/logout.js.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								roles/sys-front-inj-logout/templates/logout.js.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| /* logoutPatch.js */ | ||||
| (function(global) { | ||||
|   /** | ||||
|    * Initialize the logout patch script. | ||||
|    * @param {string} logoutUrlBase - Base logout URL (e.g., from your OIDC client). | ||||
|    * @param {string} webProtocol - Protocol to use (e.g., "https"). | ||||
|    * @param {string} primaryDomain - Primary domain (e.g., "example.com"). | ||||
|    */ | ||||
|   function initLogoutPatch(logoutUrlBase, webProtocol, primaryDomain) { | ||||
|     const redirectUri = encodeURIComponent(webProtocol + '://' + primaryDomain); | ||||
|     const logoutUrl = logoutUrlBase + '?redirect_uri=' + redirectUri; | ||||
|  | ||||
|     function matchesLogout(str) { | ||||
|       return str && /(?:^|\W)log\s*out(?:\W|$)|logout/i.test(str); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|     * Returns true if any attribute name or value on the given element | ||||
|     * contains the substring "logout" (case-insensitive). | ||||
|     * | ||||
|     * @param {Element} element – The DOM element to inspect. | ||||
|     * @returns {boolean} – True if "logout" appears in any attribute name or value. | ||||
|     */ | ||||
|     function containsLogoutAttribute(element) { | ||||
|       for (const attribute of element.attributes) { | ||||
|         if (/logout/i.test(attribute.name) || /logout/i.test(attribute.value)) { | ||||
|           return true; | ||||
|         } | ||||
|       } | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     function matchesTechnicalIndicators(el) { | ||||
|       const title = el.getAttribute('title'); | ||||
|       const ariaLabel = el.getAttribute('aria-label'); | ||||
|       const onclick = el.getAttribute('onclick'); | ||||
|  | ||||
|       if (matchesLogout(title) || matchesLogout(ariaLabel) || matchesLogout(onclick)) return true; | ||||
|  | ||||
|       for (const attr of el.attributes) { | ||||
|         if (attr.name.startsWith('data-') && matchesLogout(attr.name + attr.value)) return true; | ||||
|       } | ||||
|  | ||||
|       if (typeof el.onclick === 'function' && matchesLogout(el.onclick.toString())) return true; | ||||
|  | ||||
|       if (el.tagName.toLowerCase() === 'use') { | ||||
|         const href = el.getAttribute('xlink:href') || el.getAttribute('href'); | ||||
|         if (matchesLogout(href)) return true; | ||||
|       } | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|     * Apply logout redirect behavior to a matching element: | ||||
|     * – Installs a capturing click‐handler to force navigation to logoutUrl | ||||
|     * – Always sets href/formaction/action to logoutUrl | ||||
|     * – Marks the element as patched to avoid double‐binding | ||||
|     * | ||||
|     * @param {Element} el – The element to override (e.g. <a>, <button>, <form>, <input>) | ||||
|     * @param {string} logoutUrl – The full logout URL including redirect params | ||||
|     */ | ||||
|     function overrideLogout(el, logoutUrl) { | ||||
|       // avoid patching the same element twice | ||||
|       if (el.dataset._logoutHandled) return; | ||||
|       el.dataset._logoutHandled = "true"; | ||||
|  | ||||
|       // show pointer cursor | ||||
|       el.style.cursor = 'pointer'; | ||||
|  | ||||
|       // capture‐phase listener so it fires before any framework handlers | ||||
|       el.addEventListener('click', function(e) { | ||||
|         e.preventDefault(); | ||||
|         window.location.href = logoutUrl; | ||||
|       }, { capture: true }); | ||||
|  | ||||
|       const tag = el.tagName.toLowerCase(); | ||||
|  | ||||
|       // always set the link target on <a> | ||||
|       if (tag === 'a') { | ||||
|         el.setAttribute('href', logoutUrl); | ||||
|       } | ||||
|       // always set the formaction on <button> or <input> | ||||
|       else if ((tag === 'button' || tag === 'input') && el.hasAttribute('formaction')) { | ||||
|         el.setAttribute('formaction', logoutUrl); | ||||
|       } | ||||
|       // always set the form action on <form> | ||||
|       else if (tag === 'form') { | ||||
|         el.setAttribute('action', logoutUrl); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     function scanAndPatch(elements) { | ||||
|       elements.forEach(el => { | ||||
|         const tagName = el.tagName.toLowerCase(); | ||||
|         const isPotential = ['a','button','input','form','use'].includes(tagName); | ||||
|         if (!isPotential) return; | ||||
|         if ( | ||||
|           matchesLogout(el.getAttribute('name')) || | ||||
|           matchesLogout(el.id) || | ||||
|           matchesLogout(el.className) || | ||||
|           matchesLogout(el.innerText) || | ||||
|           containsLogoutAttribute(el) || | ||||
|           matchesTechnicalIndicators(el) | ||||
|         ) { | ||||
|           overrideLogout(el, logoutUrl); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // Initial scan | ||||
|     scanAndPatch(Array.from(document.querySelectorAll('*'))); | ||||
|  | ||||
|     // Watch for dynamic content | ||||
|     const observer = new MutationObserver(mutations => { | ||||
|       mutations.forEach(mutation => { | ||||
|         mutation.addedNodes.forEach(node => { | ||||
|           if (!(node instanceof Element)) return; | ||||
|           scanAndPatch([node, ...node.querySelectorAll('*')]); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|     observer.observe(document.body, { childList: true, subtree: true }); | ||||
|   } | ||||
|  | ||||
|   // Expose to global scope | ||||
|   global.initLogoutPatch = initLogoutPatch; | ||||
| })(window); | ||||
| @@ -0,0 +1,7 @@ | ||||
| document.addEventListener('DOMContentLoaded', function() { | ||||
|     initLogoutPatch( | ||||
|         '{{ OIDC.CLIENT.LOGOUT_URL }}', | ||||
|         '{{ WEB_PROTOCOL }}', | ||||
|         '{{ PRIMARY_DOMAIN }}' | ||||
|     ); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user