github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/deck/static/spyglass/spyglass.ts (about) 1 import {ProwJobState} from "../api/prow"; 2 import {createAbortProwJobIcon} from "../common/abort"; 3 import {createRerunProwJobIcon} from "../common/rerun"; 4 import {getParameterByName} from "../common/urls"; 5 import {isTransitMessage, serialiseHashes} from "./common"; 6 7 declare const src: string; 8 declare const lensArtifacts: {[index: string]: string[]}; 9 declare const lensIndexes: number[]; 10 declare const csrfToken: string; 11 declare const rerunCreatesJob: boolean; 12 declare const prowJob: string; 13 declare const prowJobName: string; 14 declare const prowJobState: ProwJobState; 15 16 // Loads views for this job 17 function loadLenses(): void { 18 const hashes = parseHash(); 19 for (const lensIndex of lensIndexes) { 20 const frame = document.querySelector<HTMLIFrameElement>(`#iframe-${lensIndex}`)!; 21 let url = urlForLensRequest(frame.dataset.lensName!, Number(frame.dataset.lensIndex!), 'iframe'); 22 url += `&topURL=${escape(location.href.split('#')[0])}&lensIndex=${lensIndex}`; 23 const hash = hashes[lensIndex]; 24 if (hash) { 25 url += hash; 26 } 27 frame.src = url; 28 } 29 } 30 31 function queryForLens(lens: string, index: number): string { 32 const data = { 33 artifacts: lensArtifacts[index], 34 index, 35 src, 36 }; 37 return `req=${encodeURIComponent(JSON.stringify(data))}`; 38 } 39 40 function urlForLensRequest(lens: string, index: number, request: string): string { 41 return `/spyglass/lens/${lens}/${request}?${queryForLens(lens, index)}`; 42 } 43 44 function frameForMessage(e: MessageEvent): HTMLIFrameElement { 45 for (const frame of Array.from(document.querySelectorAll('iframe'))) { 46 if (frame.contentWindow === e.source) { 47 return frame; 48 } 49 } 50 throw new Error("MessageEvent from unknown frame!?"); 51 } 52 53 function updateHash(index: number, hash: string): void { 54 const currentHash = parseHash(); 55 if (hash !== '') { 56 currentHash[index] = hash; 57 } else { 58 delete currentHash[index]; 59 } 60 location.hash = serialiseHashes(currentHash); 61 } 62 63 function parseHash(): {[index: string]: string} { 64 const parts = location.hash.substr(1).split(';'); 65 const result: { [index: string]: string } = {}; 66 for (const part of parts) { 67 if (part === '') { 68 continue; 69 } 70 const [index, hash] = part.split(':'); 71 result[index] = `#${ unescape(hash)}`; 72 } 73 return result; 74 } 75 76 function getLensRequestOptions(reqBody: string): RequestInit { 77 return {body: reqBody, method: 'POST', headers: {'X-CSRF-Token': csrfToken}, credentials: 'same-origin'}; 78 } 79 80 window.addEventListener('message', async (e) => { 81 const data = e.data; 82 if (isTransitMessage(data)) { 83 const {id, message} = data; 84 const frame = frameForMessage(e); 85 const lens = frame.dataset.lensName!; 86 const index = Number(frame.dataset.lensIndex!); 87 88 const respond = (response: string): void => { 89 frame.contentWindow.postMessage({id, message: {type: 'response', data: response}}, '*'); 90 }; 91 92 switch (message.type) { 93 case "contentUpdated": 94 frame.style.height = `${message.height}px`; 95 frame.style.visibility = 'visible'; 96 if (frame.dataset.hideTitle) { 97 frame.parentElement.parentElement.classList.add('hidden-title'); 98 } 99 document.querySelector<HTMLElement>(`#${lens}-loading`)!.style.display = 'none'; 100 respond(''); 101 break; 102 case "request": { 103 const req = await fetch(urlForLensRequest(lens, index, 'callback'), 104 getLensRequestOptions(message.data)); 105 respond(await req.text()); 106 break; 107 } 108 case "requestPage": { 109 const req = await fetch(urlForLensRequest(lens, index, 'rerender'), 110 getLensRequestOptions(message.data)); 111 respond(await req.text()); 112 break; 113 } 114 case "updatePage": { 115 const spinner = document.querySelector<HTMLElement>(`#${lens}-loading`)!; 116 frame.style.visibility = 'visible'; 117 spinner.style.display = 'block'; 118 const req = await fetch(urlForLensRequest(lens, index, 'rerender'), 119 getLensRequestOptions(message.data)); 120 respond(await req.text()); 121 break; 122 } 123 case "updateHash": { 124 updateHash(index, message.hash); 125 respond(''); 126 break; 127 } 128 case "showOffset": { 129 const container = document.getElementsByTagName('main')[0]!; 130 const containerOffset = {left: 0, top: 0}; 131 let parent: HTMLElement = frame; 132 // figure out our cumulative offset from the root container <main> by 133 // looping through our parents until we get to it or run out of parents. 134 while (parent) { 135 containerOffset.top += parent.offsetTop; 136 containerOffset.left += parent.offsetLeft; 137 if (parent.offsetParent instanceof HTMLElement && parent.offsetParent !== container) { 138 parent = parent.offsetParent; 139 } else { 140 break; 141 } 142 } 143 if (!parent) { 144 console.error("Couldn't find parent for frame!", container, frame); 145 } 146 container.scrollTop = containerOffset.top + message.top; 147 container.scrollLeft = containerOffset.left + message.left; 148 break; 149 } 150 default: 151 console.warn(`Unrecognised message type "${message.type}" from lens "${lens}":`, data); 152 break; 153 } 154 } 155 }); 156 157 window.addEventListener('hashchange', (e) => { 158 const hashes = parseHash(); 159 for (const index of Object.keys(hashes)) { 160 const iframe = document.querySelector<HTMLIFrameElement>(`#iframe-${index}`); 161 if (!iframe || !iframe.contentWindow) { 162 continue; 163 } 164 iframe.contentWindow.postMessage({type: 'hashUpdate', hash: hashes[index]}, '*'); 165 } 166 }); 167 168 // We can't use DOMContentLoaded here or we end up with a bunch of flickering. This appears to be MDL's fault. 169 window.addEventListener('load', () => { 170 loadLenses(); 171 handleRerunButton(); 172 handleAbortButton(); 173 }); 174 175 function handleRerunButton() { 176 // In case prowJob is unavailable, the rerun button shouldn't be shown 177 if (!prowJobName) { 178 return; 179 } 180 181 const rerunStatus = getParameterByName("rerun"); 182 const modal = document.getElementById('rerun')!; 183 const modalContent = document.querySelector('.modal-content')!; 184 185 const r = document.getElementById("header-title")!; 186 const c = document.createElement("div"); 187 c.appendChild(createRerunProwJobIcon(modal, modalContent, prowJobName, rerunCreatesJob, csrfToken)); 188 r.appendChild(c); 189 190 if (rerunStatus === "gh_redirect") { 191 modal.style.display = "block"; 192 modalContent.innerHTML = "Rerunning that job requires GitHub login. Now that you're logged in, try again"; 193 } 194 } 195 196 function handleAbortButton(): void { 197 // In case prowJob is unavailable, the abort button shouldn't be shown 198 if (!prowJobName) { 199 return; 200 } 201 202 const modal = document.getElementById('rerun')!; 203 const modalContent = document.querySelector('.modal-content')!; 204 205 const r = document.getElementById("header-title")!; 206 const c = document.createElement("div"); 207 c.appendChild(createAbortProwJobIcon(modal, modalContent, prowJob, prowJobState, prowJobName, csrfToken)); 208 r.appendChild(c); 209 }