2025-07-15 11:43:28 +01:00
<!-- templates/base.html -->
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > {% block title %}KTVManager{% endblock %}< / title >
< link rel = "stylesheet" href = "https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" >
< link rel = "icon" type = "image/png" href = "{{ url_for('static', filename='favicon-96x96.png') }}" sizes = "96x96" / >
< link rel = "icon" type = "image/svg+xml" href = "{{ url_for('static', filename='favicon.svg') }}" / >
< link rel = "shortcut icon" href = "{{ url_for('static', filename='favicon.ico') }}" / >
< link rel = "apple-touch-icon" sizes = "180x180" href = "{{ url_for('static', filename='apple-touch-icon.png') }}" / >
< meta name = "apple-mobile-web-app-title" content = "kTvManager" / >
< link rel = "manifest" href = "{{ url_for('static', filename='site.webmanifest') }}?v={{ version }}" / >
< link rel = "stylesheet" href = "{{ url_for('static', filename='styles.css') }}" / >
{% block head_content %}{% endblock %}
< / head >
< body >
<!-- Navbar -->
< nav class = "navbar navbar-expand-lg navbar-dark bg-dark" >
< a class = "navbar-brand" href = "/" > KTVManager< / a >
< button class = "navbar-toggler" type = "button" data-toggle = "collapse" data-target = "#navbarNav" aria-controls = "navbarNav" aria-expanded = "false" aria-label = "Toggle navigation" >
< span class = "navbar-toggler-icon" > < / span >
< / button >
< div class = "collapse navbar-collapse" id = "navbarNav" >
< ul class = "navbar-nav ml-auto" >
< li class = "nav-item" >
< a class = "nav-link" href = "/" > Home< / a >
< / li >
< li class = "nav-item" >
< a class = "nav-link" href = "/accounts" > Accounts< / a >
< / li >
< li class = "nav-item" >
< a class = "nav-link" href = "/urls" > URLs< / a >
< / li >
< / ul >
< / div >
< / nav >
{% block sub_nav %}{% endblock %}
<!-- Main Content -->
< main class = "container mt-5" >
{% block content %}{% endblock %}
< / main >
< footer class = "bg-dark text-white text-center py-3 mt-5" >
< p > Version: {{ version }}< / p >
< / footer >
2025-07-15 15:13:16 +01:00
< script src = "https://code.jquery.com/jquery-3.5.1.min.js" > < / script >
2025-07-15 11:43:28 +01:00
< script src = "https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" > < / script >
< script src = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" > < / script >
{% block scripts %}{% endblock %}
2025-07-17 10:32:06 +01:00
< script >
2025-07-17 15:41:16 +01:00
if ('serviceWorker' in navigator & & 'PushManager' in window) {
2025-07-17 17:08:11 +01:00
navigator.serviceWorker.register('{{ url_for("static", filename="service-worker.js") }}').then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
2025-07-17 18:13:10 +01:00
// Check if we should ask for permission
if (document.referrer.includes('/login') || (performance.navigation.type == performance.navigation.TYPE_RELOAD & & new URLSearchParams(window.location.search).has('loggedin'))) {
askPermission(registration);
}
2025-07-17 17:08:11 +01:00
}, function(err) {
console.log('ServiceWorker registration failed: ', err);
});
2025-07-17 15:41:16 +01:00
}
function askPermission(registration) {
Notification.requestPermission().then(function(result) {
if (result === 'granted') {
subscribeUser(registration);
}
});
}
function subscribeUser(registration) {
2025-07-17 16:50:08 +01:00
fetch('/vapid-public-key')
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch VAPID public key');
}
return response.json();
})
2025-07-17 16:22:56 +01:00
.then(data => {
2025-07-17 17:19:21 +01:00
console.log('Received VAPID public key:', data.public_key);
2025-07-17 16:22:56 +01:00
const applicationServerKey = urlB64ToUint8Array(data.public_key);
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
}).then(function(subscription) {
console.log('User is subscribed.');
saveSubscription(subscription);
}).catch(function(err) {
console.log('Failed to subscribe the user: ', err);
});
2025-07-17 16:43:07 +01:00
}).catch(function(err) {
2025-07-17 16:50:08 +01:00
console.error('Error during subscription process:', err);
2025-07-17 16:22:56 +01:00
});
2025-07-17 15:41:16 +01:00
}
function saveSubscription(subscription) {
2025-07-17 16:50:08 +01:00
fetch('/save-subscription', {
2025-07-17 15:41:16 +01:00
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic {{ session.auth_credentials }}'
},
body: JSON.stringify(subscription)
});
}
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length ; + + i ) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
2025-07-17 10:32:06 +01:00
}
< / script >
2025-07-15 11:43:28 +01:00
< / body >
< / html >