mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-24 15:06:38 +00:00
Add a button in the web interface to trigger pipeline dumps via websocket, replacing the need to manually send SIGUSR1 to the process. Also set up the pipeline-snapshot tracer with the proper websocket URL by default. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7999>
344 lines
10 KiB
JavaScript
344 lines
10 KiB
JavaScript
import { instance } from "/js/viz-standalone.mjs";
|
|
import Fuse from '/js/fuse.min.mjs'
|
|
|
|
let ws = null;
|
|
|
|
async function createOverlayElement(img, fname) {
|
|
let overlay = document.getElementById("overlay");
|
|
if (overlay || img.creating_svg) {
|
|
console.warn(`Overlay already exists`);
|
|
return;
|
|
}
|
|
|
|
const overlayDiv = document.createElement('div');
|
|
overlayDiv.id = "overlay";
|
|
overlayDiv.className = 'overlay';
|
|
|
|
document.getElementById('pipelines').appendChild(overlayDiv);
|
|
await generateSvg(img);
|
|
|
|
const closeButton = document.createElement('a');
|
|
closeButton.href = 'javascript:void(0)';
|
|
closeButton.className = 'closebtn';
|
|
closeButton.innerHTML = '×';
|
|
closeButton.onclick = (event) => {
|
|
removePipelineOverlay();
|
|
|
|
event.stopPropagation();
|
|
}
|
|
overlayDiv.appendChild(closeButton);
|
|
|
|
const contentDiv = document.createElement('div');
|
|
contentDiv.className = 'overlay-content';
|
|
|
|
const iframe = document.createElement('iframe');
|
|
iframe.loading = 'lazy';
|
|
iframe.src = '/overlay.html?svg=' + img.src + '&title=' + fname;
|
|
iframe.className = 'internalframe';
|
|
contentDiv.appendChild(iframe);
|
|
|
|
overlayDiv.appendChild(contentDiv);
|
|
|
|
return overlayDiv;
|
|
}
|
|
|
|
function dotId(dot_info) {
|
|
return `${dot_info.name}`;
|
|
}
|
|
|
|
async function generateSvg(img) {
|
|
if (img.src != "" || img.creating_svg) {
|
|
console.debug('Image already generated');
|
|
return;
|
|
}
|
|
|
|
img.creating_svg = true;
|
|
try {
|
|
let viz = await instance();
|
|
const svg = viz.renderSVGElement(img.dot_info.content);
|
|
img.src = URL.createObjectURL(new Blob([svg.outerHTML], { type: 'image/svg+xml' }));
|
|
img.creating_svg = false;
|
|
} catch (error) {
|
|
console.error(`Fail rendering SVG for '${img.dot_info.content}' failed: ${error}`, error);
|
|
}
|
|
}
|
|
|
|
async function createNewDotDiv(pipelines_div, dot_info) {
|
|
let path = dot_info.name;
|
|
let dirname = path.split('/');
|
|
let parent_div = pipelines_div;
|
|
if (dirname.length > 1) {
|
|
dirname = `/${dirname.slice(0, -1).join('/')}/`;
|
|
} else {
|
|
dirname = "/";
|
|
}
|
|
let div_id = `content-${dirname}`;
|
|
parent_div = document.getElementById(div_id);
|
|
|
|
if (!parent_div) {
|
|
parent_div = document.createElement("div");
|
|
parent_div.id = `dir-${dirname}`;
|
|
parent_div.className = "wrap-collabsible";
|
|
parent_div.innerHTML = `<input id="collapsible-${dirname}" class="toggle" type="checkbox">
|
|
<label for="collapsible-${dirname}" class="lbl-toggle">${dirname}</label>
|
|
<div class="collapsible-content" id="content-${dirname}">
|
|
</div>`;
|
|
|
|
if (pipelines_div.firstChild) {
|
|
pipelines_div.insertBefore(parent_div, pipelines_div.firstChild);
|
|
} else {
|
|
pipelines_div.appendChild(parent_div);
|
|
}
|
|
|
|
parent_div = document.getElementById(`content-${dirname}`);
|
|
}
|
|
|
|
|
|
let div = document.createElement("div");
|
|
div.id = dotId(dot_info);
|
|
div.className = "content-inner pipelineDiv";
|
|
div.setAttribute("data_score", "0");
|
|
div.setAttribute("creation_time", dot_info.creation_time);
|
|
|
|
let title = document.createElement("h2");
|
|
title.textContent = dot_info.name.replace(".dot", "");
|
|
|
|
let img = document.createElement("img");
|
|
img.alt = "image";
|
|
img.className = "preview";
|
|
img.loading = "lazy";
|
|
img.dot_info = dot_info;
|
|
const observer = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
console.debug(`Image ${div.id} is visible`);
|
|
generateSvg(img);
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, {
|
|
rootMargin: '100px'
|
|
});
|
|
observer.observe(img);
|
|
|
|
div.appendChild(title);
|
|
div.appendChild(img);
|
|
|
|
div.onclick = function () {
|
|
createOverlayElement(img, title.textContent).then(_ => {
|
|
setUrlVariable('pipeline', div.id);
|
|
});
|
|
}
|
|
|
|
|
|
if (parent_div.firstChild) {
|
|
parent_div.insertBefore(div, parent_div.firstChild);
|
|
} else {
|
|
parent_div.appendChild(div);
|
|
}
|
|
|
|
updateSearch();
|
|
updateFromUrl(false);
|
|
}
|
|
|
|
function unsetUrlVariable(key) {
|
|
let url = new URL(window.location.href);
|
|
let searchParams = new URLSearchParams(url.search);
|
|
searchParams.delete(key);
|
|
url.search = searchParams.toString();
|
|
window.history.pushState({}, '', url);
|
|
}
|
|
|
|
function setUrlVariable(key, value) {
|
|
let url = new URL(window.location.href);
|
|
let searchParams = new URLSearchParams(url.search);
|
|
searchParams.set(key, value);
|
|
url.search = searchParams.toString();
|
|
|
|
window.history.pushState({}, '', url);
|
|
}
|
|
|
|
export function updateFromUrl(noHistoryUpdate) {
|
|
if (window.location.search) {
|
|
const url = new URL(window.location.href);
|
|
const pipeline = url.searchParams.get('pipeline');
|
|
if (pipeline) {
|
|
console.log(`Creating overlay for ${pipeline}`);
|
|
let div = document.getElementById(pipeline);
|
|
if (!div) {
|
|
console.info(`Pipeline ${pipeline} not found`);
|
|
return;
|
|
}
|
|
let img = div.querySelector('img');
|
|
let title = div.querySelector('h2');
|
|
createOverlayElement(img, title.textContent).then(_ => {
|
|
console.debug(`Overlay created for ${pipeline}`);
|
|
});
|
|
}
|
|
} else {
|
|
removePipelineOverlay(noHistoryUpdate);
|
|
}
|
|
}
|
|
|
|
export function connectWs() {
|
|
console.assert(ws === null, "Websocket already exists");
|
|
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const host = window.location.hostname;
|
|
const port = window.location.port;
|
|
const wsUrl = `${protocol}//${host}:${port}/ws/`;
|
|
let pipelines_div = document.getElementById("pipelines");
|
|
|
|
ws = new WebSocket(wsUrl);
|
|
console.info(`Websocklet ${ws} created with ${wsUrl}`);
|
|
ws.onopen = () => {
|
|
let pipelines_div = document.getElementById("pipelines");
|
|
|
|
console.log(`WebSocket connected, removing all children from ${pipelines_div}`);
|
|
while (pipelines_div.firstChild) {
|
|
console.debug(`Removing ${pipelines_div.firstChild}`);
|
|
pipelines_div.removeChild(pipelines_div.firstChild);
|
|
}
|
|
};
|
|
|
|
ws.onmessage = function (event) {
|
|
console.debug(`Received message: ${event.data}`)
|
|
try {
|
|
const obj = JSON.parse(event.data);
|
|
if (obj.type == "NewDot") {
|
|
if (document.getElementById(dotId(obj))) {
|
|
console.warn(`Pipeline ${obj.name} already exists`);
|
|
return;
|
|
}
|
|
createNewDotDiv(pipelines_div, obj, ws).then(() => {
|
|
updateSearch();
|
|
});
|
|
} else if (obj.type == "DotRemoved") {
|
|
let dot_id = dotId(obj);
|
|
let dot_div = document.getElementById(dot_id);
|
|
if (dot_div) {
|
|
console.info(`Removing dot_div ${dot_id}`);
|
|
dot_div.remove();
|
|
} else {
|
|
console.error(`dot_div ${dot_id} not found`);
|
|
}
|
|
|
|
updateSearch();
|
|
} else {
|
|
console.warn(`Unknown message type: ${obj.type}`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`Error: ${e}`);
|
|
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
ws.onclose = (event) => {
|
|
console.log('WebSocket disconnected', event.reason);
|
|
ws.close();
|
|
ws = null;
|
|
setTimeout(connectWs, 10000);
|
|
};
|
|
|
|
}
|
|
|
|
function updateSearch() {
|
|
const input = document.getElementById('search');
|
|
const allDivs = document.querySelectorAll('.pipelineDiv');
|
|
|
|
if (document.querySelectorAll('.toggle').length == 1) {
|
|
// If the is only 1 folder, expand it
|
|
let toggle = document.querySelector('.toggle')
|
|
if (toggle && !toggle.auto_checked) {
|
|
toggle.checked = true;
|
|
toggle.auto_checked = true;
|
|
}
|
|
}
|
|
|
|
if (input.value === "") {
|
|
let divs = Array.from(allDivs).map(div => {
|
|
div.style.display = '';
|
|
div.setAttribute("data_score", "0");
|
|
|
|
return div;
|
|
});
|
|
|
|
divs.sort((a, b) => {
|
|
const scoreA = parseInt(a.getAttribute('creation_time'), 0);
|
|
const scoreB = parseInt(b.getAttribute('creation_time'), 0);
|
|
|
|
console.debug('Comparing', scoreA, scoreB, " = ", scoreB - scoreA);
|
|
return scoreB - scoreA;
|
|
});
|
|
|
|
divs.forEach(div => {
|
|
console.debug(`Moving ${div.id} in {div.parentNode}}`);
|
|
div.parentNode.appendChild(div);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
const options = {
|
|
includeScore: true,
|
|
threshold: 0.6,
|
|
keys: ['title']
|
|
};
|
|
|
|
|
|
const list = Array.from(allDivs).map(div => ({
|
|
id: div.getAttribute('id'), // Assuming each div has an ID
|
|
title: div.querySelector('h2').textContent
|
|
}));
|
|
|
|
const fuse = new Fuse(list, options);
|
|
const results = fuse.search(input.value);
|
|
|
|
allDivs.forEach(div => div.style.display = 'none');
|
|
let divs = results.map(result => {
|
|
let div = document.getElementById(result.item.id);
|
|
div.style.display = '';
|
|
div.setAttribute('data_score', result.score);
|
|
|
|
return div;
|
|
});
|
|
|
|
divs.sort((a, b) => {
|
|
const scoreA = parseFloat(a.getAttribute('data_score'), 0);
|
|
const scoreB = parseFloat(b.getAttribute('data_score'), 0);
|
|
|
|
return scoreA - scoreB; // For ascending order. Use (scoreB - scoreA) for descending.
|
|
});
|
|
|
|
divs.forEach(div => {
|
|
div.parentNode.appendChild(div);
|
|
});
|
|
}
|
|
|
|
export function connectSearch() {
|
|
const input = document.getElementById('search');
|
|
input.addEventListener('input', function () {
|
|
updateSearch();
|
|
|
|
});
|
|
}
|
|
|
|
export function removePipelineOverlay(noHistoryUpdate) {
|
|
let overlay = document.getElementById("overlay");
|
|
if (!overlay) {
|
|
return;
|
|
}
|
|
overlay.parentNode.removeChild(overlay);
|
|
if (!noHistoryUpdate) {
|
|
unsetUrlVariable('pipeline');
|
|
}
|
|
updateSearch();
|
|
}
|
|
|
|
export function dumpPipelines() {
|
|
if (ws) {
|
|
ws.send(JSON.stringify({ type: "Snapshot" }));
|
|
}
|
|
}
|
|
|