Files
tvapp2/tvapp2/www/index.html

649 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<title><%= appName %> - v<%= appVersion %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex, nofollow">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
<link rel="stylesheet" href="css/tvapp2.min.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script src='https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js' integrity='sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq' crossorigin='anonymous'></script>
<script src=' https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js '></script>
<script src='js/tvapp2.min.js'></script>
</head>
<body>
<!-- Header -->
<div class="header">
<nav class="navbar sticky-top container">
<div class="brand">
<i data-bs-toggle="tooltip" title="v<%= appVersion %>" class="logo fa-sharp-duotone fa-regular fa-tv" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i>
<a target="_blank" data-bs-toggle="tooltip" title="View Github Repository" class="header-name" href="<%= appUrlGithub %>">TVApp2 for Docker</a>
</div>
<div class="social">
<i id="action-health" data-bs-toggle="tooltip" title="Health" class="heart logo health fa-duotone fa-solid fa-heart" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i>
<a href="javascript:runResync();"><i id="action-resync" data-bs-toggle="tooltip" title="Resync" class="restart fa-solid fa-rotate" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
<a target="_blank" href="<%= appUrlDocs %>"><i data-bs-toggle="tooltip" title="Documentation" class="logo fa-duotone fa-solid fa-book-open-cover" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
<a target="_blank" href="<%= appUrlGithub %>"><i data-bs-toggle="tooltip" title="Github" class="logo fa-logos fa-github" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
<a target="_blank" href="<%= appUrlDiscord %>"><i data-bs-toggle="tooltip" title="Discord" class="logo fa-logos fa-discord" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
</div>
</nav>
</div>
<!-- Header Notification: description -->
<div class="container">
<div class="introduction">
<div class="row">
<div class="col">
<div class="introduction-body">
<div class="desc">
This page displays your most recent copies of the <code><%= fileM3U %></code> playlist and <code><%= fileXML %></code>
EPG guide data. Right-click each file, select <span class="accent">Copy Link</span> and paste the URLs within an IPTV
app such as Jellyfin. The <code><%= fileXML %></code> and <code><%= fileGZP %></code> have identical guide data,
however the <code><%= fileGZP %></code> is compressed and will import into your IPTV application much faster.
<br />
<div class="badges">
<img src="https://img.shields.io/github/v/tag/TheBinaryNinja/tvapp2?logo=GitHub&label=Version&color=ba5225">
<img src="https://img.shields.io/github/downloads/TheBinaryNinja/tvapp2/total?logo=github&logoColor=FFFFFF&label=Downloads&color=376892">
<img src="https://img.shields.io/github/repo-size/TheBinaryNinja/tvapp2?logo=github&label=Size&color=59702a">
<img src="https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Fgithub.com%2Faetherinox%2Fbackage%2Fraw%2Findex%2FTheBinaryNinja%2Ftvapp2%2Ftvapp2.xml&query=%2Fxml%2Fdownloads&label=Pulls&logo=github&color=de1f5e">
<img src="https://img.shields.io/github/last-commit/TheBinaryNinja/tvapp2?logo=conventionalcommits&logoColor=FFFFFF&label=Last%20Commit&color=313131">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Header Fontawesome Icons -->
<div class="container main-container">
<table id="list" class="table table-dark table-striped">
<thead>
<tr class="d-none d-md-table-row">
<td class="icon cell-icon"></td>
<th class="file cell-file">
File Name
</th>
<th class="link cell-link">
Download
</th>
<th class="size cell-size">
Size
</th>
<th class="date cell-size">
Date
</th>
<th class="desc cell-desc">
Description
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="icon cell-icon">
<svg class="svg-inline--fa fa-file-lines fa-fw" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-lines" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" data-fa-i2svg="">
<path fill="currentColor" d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM112 256H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H272c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16z"></path>
</svg>
<!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> -->
</td>
<td class="file cell-file">
<a id="m3u-name" target="_blank" data-bs-toggle="tooltip" title="IPTV channel list"></a>
</td>
<td class="link cell-link"><a id="m3u-link" target="_blank"></a></td>
<td class="size cell-size"><span id="m3u-size"></span></td>
<td class="date cell-date"><span id="m3u-date"></span></td>
<td class="desc cell-desc">M3U playlist which lists all channels, their associated group, and logo.</td>
</tr>
<tr>
<td class="icon cell-icon">
<svg class="svg-inline--fa fa-file-xml fa-fw" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-xml" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" data-fa-i2svg="">
<path fill="currentColor" d="M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V304H176c-35.3 0-64 28.7-64 64V512H64c-35.3 0-64-28.7-64-64V64zm384 64H256V0L384 128zM192 368c0 7.3 2.2 14.4 6.2 20.4l9.8 14.7 9.8-14.7c4-6.1 6.2-13.2 6.2-20.4c0-8.8 7.2-16 16-16s16 7.2 16 16c0 13.6-4 26.9-11.6 38.2L227.2 432l17.2 25.8C252 469.1 256 482.4 256 496c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-7.3-2.2-14.4-6.2-20.4L208 460.8l-9.8 14.7c-4 6.1-6.2 13.2-6.2 20.4c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-13.6 4-26.9 11.6-38.2L188.8 432l-17.2-25.8C164 394.9 160 381.6 160 368c0-8.8 7.2-16 16-16s16 7.2 16 16zM448 496V368c0-8.8 7.2-16 16-16s16 7.2 16 16V480h16c8.8 0 16 7.2 16 16s-7.2 16-16 16H464c-8.8 0-16-7.2-16-16zM299.7 352.6c6.9-1.9 14.3 1 18 7.2L352 416.9l34.3-57.1c3.7-6.2 11.1-9.1 18-7.2s11.7 8.2 11.7 15.4V496c0 8.8-7.2 16-16 16s-16-7.2-16-16V425.8l-18.3 30.5c-2.9 4.8-8.1 7.8-13.7 7.8s-10.8-2.9-13.7-7.8L320 425.8V496c0 8.8-7.2 16-16 16s-16-7.2-16-16V368c0-7.2 4.8-13.5 11.7-15.4z"></path>
</svg>
<!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> -->
</td>
<td class="file cell-file">
<a id="xml-name" target="_blank" data-bs-toggle="tooltip" title="Uncompressed TV guide data"></a>
</td>
<td class="link cell-link"><a id="xml-link" target="_blank"></a></td>
<td class="size cell-size"><span id="xml-size"></span></td>
<td class="date cell-date"><span id="xml-date"></span></td>
<td class="desc cell-desc">XML / EPG guide data <code>(uncompressed)</code></td>
</tr>
<tr>
<td class="icon cell-icon">
<svg class="svg-inline--fa fa-file-zipper fa-fw" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-zipper" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" data-fa-i2svg="">
<path fill="currentColor" d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM96 48c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm0 64c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm0 64c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm-6.3 71.8c3.7-14 16.4-23.8 30.9-23.8h14.8c14.5 0 27.2 9.7 30.9 23.8l23.5 88.2c1.4 5.4 2.1 10.9 2.1 16.4c0 35.2-28.8 63.7-64 63.7s-64-28.5-64-63.7c0-5.5 .7-11.1 2.1-16.4l23.5-88.2zM112 336c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H112z"></path>
</svg>
<!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> -->
</td>
<td class="file cell-gzp">
<a id="gzp-name" target="_blank" data-bs-toggle="tooltip" title="Compressed TV guide data"></a>
</td>
<td class="link cell-link"><a id="gzp-link" target="_blank"></a></td>
<td class="size cell-size"><span id="gzp-size"></span></td>
<td class="date cell-date"><span id="gzp-date"></span></td>
<td class="desc cell-desc">XML / EPG guide data <code>(compressed)</code></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Footer -->
<footer class="footer">
<div class="container notifications" style="padding-bottom:20px;">
<div id="ntfy-restart" class="ntfy-parent indicator-success sticky-bottom"></div>
<div id="ntfy-firewall" class="ntfy-parent indicator-warning sticky-bottom"></div>
<div id="ntfy-localhost" class="ntfy-parent indicator-danger sticky-bottom"></div>
</div>
<div class="sub">
<div class="container">
<div class="col text-center text-muted text-small text-nowrap">
<small>Developed by BinaryNinja - <a data-bs-toggle="tooltip" title="v<%= appVersion %> <%= appRelease %> (<%= appGitHashShort %>)" href="<%= appUrlGithub %>"><%= appName %> (<%= appRelease %>)</a> v<%= appVersion %> <a target="_blank" data-bs-toggle="tooltip" title="View Github commit" href="<%= appUrlGithub %>/commit/<%= appGitHashLong %>"><%= appGitHashShort %></a></small><br />
<span class="footer-sub"><small>Uptime <a id="uptime" href="" data-bs-toggle="tooltip" title="<%= appUptimeLong %>"> <%= appUptimeShort %> </a> | Startup <a id="startup" data-bs-toggle="tooltip" title="Startup time" href=""><%= appStartup %>s</a> | OS <a id="os" href="" data-bs-toggle="tooltip" title="Server operating system" href=""><%= serverOs %></a></small></span>
</div>
</div>
</div>
</footer>
<!-- Toast Notifications -->
<!-- <button type="button" class="btn btn-primary" id="btnTestToasts">Show toast</button> -->
<div style="z-index: 9999;" class="toast position-fixed bottom-0 end-0 p-8 m-3" id="tvapp2Toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="true" data-bs-delay="10000">
<div class="toast-body">
<div class="d-flex gap-4">
<span><i class="fa-solid fa-circle-check fa-lg icon-success"></i></span>
<div class="d-flex flex-column flex-grow-1 gap-2">
<div class="d-flex align-items-center">
<span id="toast-title" class="fw-semibold">Toast Title</span>
<button type="button" class="btn-close btn-close-sm ms-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<span id="toast-message">Dismiss in 6 seconds</span>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="modalTvapp2" tabindex="-1" data-bs-backdrop="static" aria-labelledby="modalTvapp2Label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTvapp2Label">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" id="btn-secondary" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" id="btn-primary" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<script>
/*
this is test code. enable the "Show Toast" button and then uncomment this code.
document.getElementById("btnTestToasts").onclick = function()
{
var toastElList = [].slice.call(document.querySelectorAll('.toast'))
var toastList = toastElList.map(function(toastEl)
{
return new bootstrap.Toast(toastEl)
});
toastList.forEach(toast => toast.show());
console.log(toastList);
};
*/
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
const urlBase = window.location.origin;
const urlM3U = urlBase + '/playlist';
const urlXML = urlBase + '/epg';
const urlGZP = urlBase + '/gzip';
document.getElementById('m3u-name').textContent = '<%= fileM3U %>';
document.getElementById('m3u-name').href = urlM3U;
document.getElementById('m3u-link').textContent = urlM3U;
document.getElementById('m3u-link').href = urlM3U;
document.getElementById('m3u-size').textContent = '<%= sizeM3U %>';
document.getElementById('m3u-date').textContent = '<%= dateM3U %>';
document.getElementById('xml-name').textContent = '<%= fileXML %>';
document.getElementById('xml-name').href = urlXML;
document.getElementById('xml-link').textContent = urlXML;
document.getElementById('xml-link').href = urlXML;
document.getElementById('xml-size').textContent = '<%= sizeXML %>';
document.getElementById('xml-date').textContent = '<%= dateXML %>';
document.getElementById('gzp-name').textContent = '<%= fileGZP %>';
document.getElementById('gzp-name').href = urlGZP;
document.getElementById('gzp-link').textContent = urlGZP;
document.getElementById('gzp-link').href = urlGZP;
document.getElementById('gzp-size').textContent = '<%= sizeGZP %>';
document.getElementById('gzp-date').textContent = '<%= dateGZP %>';
</script>
<script>
/*
this is test code. enable the "Show Toast" button and then uncomment this code.
document.getElementById("btnTestToasts").onclick = function()
{
var toastElList = [].slice.call(document.querySelectorAll('.toast'))
var toastList = toastElList.map(function(toastEl)
{
return new bootstrap.Toast(toastEl)
});
toastList.forEach(toast => toast.show());
console.log(toastList);
};
*/
var tooltipList = [].slice.call( document.querySelectorAll( '[data-bs-toggle="tooltip"]' ) )
var tooltipList = tooltipList.map( function ( el )
{
return new bootstrap.Tooltip(el,
{
placement: "bottom",
trigger: "hover",
html: true
});
})
/*
Helper > Get Multiple Elements by ID
*/
function getElementsById( ids )
{
const idList = ids.split(" ");
let results = [], item;
for ( let i = 0; i < idList.length; i++ )
{
item = document.getElementById( idList[ i ] );
if (item)
{
results.push( item );
}
}
return( results );
}
/*
Document Ready
*/
$(function(){
$("[data-bs-toggle=tooltip]").tooltip({ placement: 'bottom'});
});
/*
Action > DOM Status
*/
document.addEventListener("DOMContentReady", function() {
$("#tvapp2Toast").toast();
});
/*
document.addEventListener("DOMContentLoaded", function() {
$('#tvapp2Toast').toast("show");
});
*/
/*
Notify > Localhost
*/
document.addEventListener( 'DOMContentLoaded', function()
{
const host = window.location.hostname;
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
if (host === 'localhost' || host === '127.0.0.1')
{
const msg = "<div class='ntfy-child'><span class='danger'>Danger</span> \
<span class='msg'> \
If accessing this page via 127.0.0.1 / localhost, proxying will not work on other devices. Load this page using \
your computer's IP address (e.g., 192.168.x.x) and port to access the playlist from other devices on your network. \
<br> \
Learn how to locate your IP address on <a href='https://youtube.com/watch?v=UAhDHXN2c6E' target = '_blank' > Windows</a> \
or <a href='https://youtube.com/watch?v=gaIYP4TZfHI' target = '_blank' > Linux</a>.\
</span></div>";
document.getElementById( 'ntfy-localhost' ).innerHTML = msg;
document.getElementById( 'ntfy-localhost' ).style.display = 'block';
} else {
document.getElementById( 'ntfy-localhost' ).style.display = 'none';
}
});
/*
Notify > Firewall
*/
document.addEventListener( 'DOMContentLoaded', function()
{
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
const msg = "<div class='ntfy-child'><span class='warning'>Warning</span> \
<span class='msg'> \
Port <strong> " + port + " </strong> must be open and allowed through your \
<a href='https://youtu.be/zOZWlTplrcA?si=nGXrHKU4sAQsy18e&t=18 target='_blank'>Windows</a> \
or \
<a href='https://youtu.be/7c_V_3nWWbA?si=Hkd_II9myn-AkNnS&t=12' target='_blank'>Linux</a> \
OS firewall settings. This action enables devices such as Firestick or Android to connect \
to the server and request the playlist through the proxy. \
</span></div>";
document.getElementById( 'ntfy-firewall' ).innerHTML = msg;
document.getElementById( 'ntfy-firewall' ).style.display = 'block';
});
/*
Notify > Restart / Resync
*/
document.addEventListener( 'DOMContentLoaded', function()
{
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
const msg = "<div class='ntfy-child'><span class='success'>Success</span> \
<span class='msg'> \
Your IPTV m3u channels and xml guide data has been successfully re-synced. \
Please refresh this window to see new data \
</span></div>";
document.getElementById( 'ntfy-restart' ).innerHTML = msg;
document.getElementById( 'ntfy-restart' ).style.display = 'none';
});
/*
Set initial health check sync time
first health check runs after 10 seconds
all future health checks run after <%= healthTimer %>
*/
let timerDelayMS = 10000;
let timerStartMS = Date.now(); // returns milliseconds
const timerHealthRun = '<%= healthTimer %>'; // time in milliseconds until health check ran AFTER initial run
const timerUptime = 1000;
/*
Action > Healthcheck
*/
function runHealthCheck()
{
const toastTypeClass = [];
toastTypeClass[ 'DEFAULT' ] = 'text-bg-primary';
toastTypeClass[ 'UNHEALTHY' ] = 'text-bg-warning';
toastTypeClass[ 'HEALTHY' ] = 'text-bg-success';
toastTypeClass[ 'ERROR' ] = 'text-bg-danger';
$.ajax(
{
url: 'api/health',
type: 'GET',
data: {
query: 'healthcheck',
silent: false
},
beforeSend: function( data )
{
console.log( 'Sending health check ...' )
},
success: function( data )
{
const status = data.message;
const code = data.code;
if ( status )
{
const toastClass = toastTypeClass[status.toUpperCase()];
const toastElm = document.getElementById('tvapp2Toast');
toastElm.classList.add(toastClass);
$('.toast #toast-title').html(`<%= appName %> is ${ status }`);
$('.toast #toast-message').html(`Health check returned ${ status } (${ code })`);
$('#tvapp2Toast').toast('show');
const elementsList = document.querySelectorAll( '#ntfy-firewall, #ntfy-localhost, #ntfy-restart' );
const elementsArray = [...elementsList];
elementsArray.forEach(element =>
{
element.style.transition = '1s';
element.style.opacity = '0';
element.style.visibility = 'hidden';
});
}
},
error: function( data )
{
const toastClass = toastTypeClass['ERROR'];
const toastElm = document.getElementById('tvapp2Toast');
toastElm.classList.add(toastClass);
$('.toast #toast-title').html(`Could not connect to health check api`);
$('.toast #toast-message').html(`Failed to communicate with health check api. Try restarting the docker container to restore connection.`);
$('#tvapp2Toast').toast('show');
}
}).always(function()
{
timerDelayMS = parseInt(timerHealthRun);
timerStartMS = Date.now();
setTimeout(function()
{
runHealthCheck();
}, parseInt(timerHealthRun));
}).responseText;
}
function runUptime()
{
const toastTypeClass = [];
toastTypeClass[ 'DEFAULT' ] = 'text-bg-primary';
toastTypeClass[ 'UNHEALTHY' ] = 'text-bg-warning';
toastTypeClass[ 'HEALTHY' ] = 'text-bg-success';
toastTypeClass[ 'ERROR' ] = 'text-bg-danger';
$.ajax(
{
url: 'api/health',
type: 'GET',
data: {
query: 'uptime',
silent: true
},
success: function( data )
{
const status = data.message;
const code = data.code;
const uptimeShort = data.uptimeShort;
const uptimeLong = data.uptimeLong;
if ( status )
{
$('a#uptime').text(`${ uptimeShort }`);
const tooltip = bootstrap.Tooltip.getInstance('#uptime') // Returns a Bootstrap tooltip instance
tooltip.setContent( { '.tooltip-inner': `App started ${ uptimeLong }` } )
}
},
error: function( data )
{
const toastClass = toastTypeClass['ERROR'];
const toastElm = document.getElementById('tvapp2Toast');
toastElm.classList.add(toastClass);
$('.toast #toast-title').html(`Could not get uptime from api`);
$('.toast #toast-message').html(`Failed to communicate with the api. Try restarting the docker container to restore connection.`);
$('#tvapp2Toast').toast('show');
}
}).always(function()
{
setTimeout(function()
{
runUptime();
}, parseInt(timerUptime));
}).responseText;
}
/*
Action > Do Resync
*/
function runResync()
{
$.ajax(
{
url: 'api/restart',
type: 'GET',
data: {
query: 'sync',
silent: false
},
beforeSend: function( data )
{
const dimmer = document.createElement('div');
dimmer.setAttribute('id', 'dimmer');
dimmer.style.visibility = 'visible';
dimmer.classList.add('dimmer-in');
document.getElementsByTagName('body')[0].appendChild(dimmer);
document.getElementById('ntfy-firewall').style.display = 'none';
document.getElementById('ntfy-localhost').style.display = 'none';
document.getElementById('ntfy-restart').style.display = 'none';
const iconResync = document.getElementsByClassName('fa-rotate');
iconResync[0].classList.remove('restart');
iconResync[0].classList.add('spin');
$('.modal-content .modal-body').html('<small>The M3U and EPG data will now be re-downloaded and synced with your TVApp2 container. Afterward, this page will be refreshed automatically.</small><br /><br /><small>Please wait...</small>')
$('.modal-content .modal-title').html('Resyncing Data')
$('#modalTvapp2').modal('show');
const modalBtnPrimary = document.querySelector('#btn-primary');
modalBtnPrimary.style.display = 'none';
modalBtnPrimary.style.visibility= 'hidden';
},
success: function( data )
{
/*
On successful restart, wait 1 second, remove dimmer, reload page in 5 seconds
*/
setTimeout( () =>
{
document.getElementById('ntfy-restart').style.display = 'block'
const dimmer = document.getElementById('dimmer');
dimmer.classList.remove('dimmer-in');
dimmer.classList.add('dimmer-out');
dimmer.remove();
setTimeout( function()
{
const iconResync = document.getElementsByClassName('fa-rotate'); // resync favicon
iconResync[0].classList.remove('spin'); // stop spinning
iconResync[0].classList.add('restart'); // normal spinner class
document.location.reload() // reload page
}, 5000 ); // how long until refresh page
}, 1000 ); // how long until dimmer is removed / reload page activated (also on delay)
}
});
}
/*
Health check > Show time remaining as tooltip
*/
function runTooltipCountdown( )
{
let timerHours, timerMins, timerRemainsLS;
function twoDigits( n )
{
return (n <= 9 ? "0" + n : n);
}
/*
Update Tooltip Countdown
MS = milliseconds
LS = long string (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time))
*/
function updateTooltipCountdown()
{
const timerElapsedMS = Date.now() - timerStartMS; // ( 2091 )
const timerRemainsMS = timerDelayMS - timerElapsedMS; // ( 7909 ) divide by 1000 for seconds
timerRemainsLS = new Date( timerRemainsMS ); // (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time))
timerHours = timerRemainsLS.getUTCHours(); // ( 0 )
timerMins = timerRemainsLS.getUTCMinutes(); // ( 9 )
const timeLeft = (timerHours ? timerHours + ':' + twoDigits( timerMins ) : timerMins) + ':' + twoDigits( timerRemainsLS.getUTCSeconds() );
jQuery(function($)
{
const tooltip = bootstrap.Tooltip.getInstance('#action-health') // Returns a Bootstrap tooltip instance
tooltip.setContent({ '.tooltip-inner': `Health check in ${ timeLeft }` })
});
const Heart = document.getElementsByClassName('fa-heart');
Heart[0].style.color = '#FFF';
setTimeout( function()
{
const Heart = document.getElementsByClassName('fa-heart');
Heart[0].style.color = '#FFF';
setTimeout( function()
{
Heart[0].style.color = '#FF6593';
}, timerRemainsLS.getUTCMilliseconds() + 100 );
}, timerRemainsLS.getUTCMilliseconds() + 500 );
setTimeout( function()
{
updateTooltipCountdown();
}, timerRemainsLS.getUTCMilliseconds() + 500 );
}
updateTooltipCountdown();
}
/*
Action > Healthcheck > Initialize
*/
setTimeout( function() { runHealthCheck(); }, timerDelayMS );
setTimeout( function() { runUptime(); }, 1000 );
/*
Action > Tooltip Resync Timers
*/
runTooltipCountdown( );
</script>
</body>
</html>