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) {
|
|
|
|
window.addEventListener('load', function() {
|
|
|
|
navigator.serviceWorker.register('{{ url_for("static", filename="service-worker.js") }}').then(function(registration) {
|
|
|
|
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
|
|
|
askPermission(registration);
|
|
|
|
}, function(err) {
|
|
|
|
console.log('ServiceWorker registration failed: ', err);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function askPermission(registration) {
|
|
|
|
Notification.requestPermission().then(function(result) {
|
|
|
|
if (result === 'granted') {
|
|
|
|
subscribeUser(registration);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function subscribeUser(registration) {
|
2025-07-17 16:22:56 +01:00
|
|
|
fetch('http://localhost:3001/vapid-public-key')
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {
|
|
|
|
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 15:41:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function saveSubscription(subscription) {
|
|
|
|
fetch('/save-subscription', {
|
|
|
|
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>
|