0cb013a7fd
Moved the 'probe' link from the global templates array to individual stream status columns for improved clarity and accessibility. This change enhances the interface by contextualizing the 'probe' option, making it directly accessible alongside each stream's online status and info link, thereby streamlining the user experience and emphasizing the function's importance on a per-stream basis. This adjustment follows usability feedback indicating that users prefer immediate access to stream diagnostics.
169 lines
5.6 KiB
HTML
169 lines
5.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
|
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
<link rel="apple-touch-icon" href="https://alexxit.github.io/go2rtc/icons/apple-touch-icon-180x180.png" sizes="180x180">
|
|
<link rel="icon" href="https://alexxit.github.io/go2rtc/icons/favicon.ico">
|
|
<link rel="manifest" href="https://alexxit.github.io/go2rtc/manifest.json">
|
|
<title>go2rtc</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
table tbody td {
|
|
font-size: 13px;
|
|
}
|
|
|
|
label {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
padding: 5px;
|
|
}
|
|
|
|
.controls > label {
|
|
margin-left: 10px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<script src="main.js"></script>
|
|
<div class="info"></div>
|
|
<div class="controls">
|
|
<button>stream</button>
|
|
<label><input type="checkbox" name="webrtc" checked>webrtc</label>
|
|
<label><input type="checkbox" name="mse" checked>mse</label>
|
|
<label><input type="checkbox" name="hls" checked>hls</label>
|
|
<label><input type="checkbox" name="mjpeg" checked>mjpeg</label>
|
|
</div>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th><label><input id="selectall" type="checkbox">Name</label></th>
|
|
<th>Online</th>
|
|
<th>Commands</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="streams">
|
|
</tbody>
|
|
</table>
|
|
<script>
|
|
const templates = [
|
|
'<a href="stream.html?src={name}">stream</a>',
|
|
'<a href="links.html?src={name}">links</a>',
|
|
'<a href="#" data-name="{name}">delete</a>',
|
|
];
|
|
|
|
document.querySelector('.controls > button')
|
|
.addEventListener('click', () => {
|
|
const url = new URL('stream.html', location.href);
|
|
|
|
const streams = document.querySelectorAll('#streams input');
|
|
streams.forEach(i => {
|
|
if (i.checked) url.searchParams.append('src', i.name);
|
|
});
|
|
|
|
if (!url.searchParams.has('src')) return;
|
|
|
|
let mode = document.querySelectorAll('.controls input');
|
|
mode = Array.from(mode).filter(i => i.checked).map(i => i.name).join(',');
|
|
|
|
window.location.href = `${url}&mode=${mode}`;
|
|
});
|
|
|
|
const tbody = document.getElementById('streams');
|
|
tbody.addEventListener('click', async ev => {
|
|
if (ev.target.innerText !== 'delete') return;
|
|
|
|
ev.preventDefault();
|
|
|
|
const src = decodeURIComponent(ev.target.dataset.name);
|
|
|
|
const message = `Please type the name of the stream "${src}" to confirm its deletion from the configuration. This action is irreversible.`;
|
|
if (prompt(message) !== src) {
|
|
alert('Stream name does not match. Deletion cancelled.');
|
|
return;
|
|
}
|
|
|
|
const url = new URL('api/streams', location.href);
|
|
url.searchParams.set('src', src);
|
|
|
|
try {
|
|
await fetch(url, {method: 'DELETE'});
|
|
reload();
|
|
} catch (error) {
|
|
console.error('Failed to delete the stream:', error);
|
|
}
|
|
});
|
|
|
|
document.getElementById('selectall').addEventListener('change', ev => {
|
|
document.querySelectorAll('#streams input').forEach(el => {
|
|
el.checked = ev.target.checked;
|
|
});
|
|
});
|
|
|
|
function reload() {
|
|
const url = new URL('api/streams', location.href);
|
|
const checkboxStates = {};
|
|
tbody.querySelectorAll('input[type="checkbox"][name]').forEach(checkbox => {
|
|
checkboxStates[checkbox.name] = checkbox.checked;
|
|
});
|
|
fetch(url, {cache: 'no-cache'}).then(r => r.json()).then(data => {
|
|
const existingIds = Array.from(tbody.querySelectorAll('tr')).map(tr => tr.dataset['id']);
|
|
const fetchedIds = [];
|
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
const name = key.replace(/[<">]/g, ''); // sanitize
|
|
fetchedIds.push(name);
|
|
|
|
let tr = tbody.querySelector(`tr[data-id="${name}"]`);
|
|
const online = value && value.consumers ? value.consumers.length : 0;
|
|
const src = encodeURIComponent(name);
|
|
const links = templates.map(link => link.replace('{name}', src)).join(' ');
|
|
|
|
if (!tr) {
|
|
tr = document.createElement('tr');
|
|
tr.dataset['id'] = name;
|
|
tbody.appendChild(tr);
|
|
}
|
|
|
|
const isChecked = checkboxStates[name] ? 'checked' : '';
|
|
tr.innerHTML =
|
|
`<td><label><input type="checkbox" name="${name}" ${isChecked}>${name}</label></td>` +
|
|
`<td><a href="api/streams?src=${src}">${online} / info</a> / <a href="api/streams?src=${src}&video=all&audio=allµphone">probe</a></td>` +
|
|
`<td>${links}</td>`;
|
|
}
|
|
|
|
// Remove old rows
|
|
existingIds.forEach(id => {
|
|
if (!fetchedIds.includes(id)) {
|
|
const trToRemove = tbody.querySelector(`tr[data-id="${id}"]`);
|
|
tbody.removeChild(trToRemove);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Auto-reload
|
|
setInterval(reload, 1000);
|
|
|
|
const url = new URL('api', location.href);
|
|
fetch(url, {cache: 'no-cache'}).then(r => r.json()).then(data => {
|
|
const info = document.querySelector('.info');
|
|
info.innerText = `Version: ${data.version}, Config: ${data.config_path}`;
|
|
});
|
|
|
|
reload();
|
|
</script>
|
|
</body>
|
|
</html>
|