Files
Beszel/internal/site/public/sw.js
T
Tomas Dvorak 7727be166b feat(hub): implement native in-app container updates
Introduces the ability for registered users to trigger Beszel container updates directly from the web interface.

- Added `app_update` logic to the hub to pull the latest image from GHCR and recreate the container.
- Implemented `/api/beszel/update` and `/api/beszel/update/apply` endpoints.
- Added a new `AppUpdatePanel` in the settings UI to check for and apply updates.
- Added update notifications in the navbar and settings.
- Updated `docker-compose.yml` and `README.md` to include the required Docker socket mount for update functionality.
- Added a new public status page route that bypasses authentication.
- Refactored several TypeScript interfaces to replace `any` with `unknown` or specific types for better type safety.
- Updated localization files to support new update-related strings.
2026-04-30 14:38:13 +02:00

157 lines
3.7 KiB
JavaScript

// Beszel Service Worker
const CACHE_NAME = "beszel-v1"
const STATIC_ASSETS = ["/", "/index.html", "/manifest.json", "/favicon.ico", "/favicon.svg"]
// Install event - cache static assets
self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(STATIC_ASSETS)
})
.then(() => self.skipWaiting())
)
})
// Activate event - clean up old caches
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((cacheNames) => {
return Promise.all(cacheNames.filter((name) => name !== CACHE_NAME).map((name) => caches.delete(name)))
})
.then(() => self.clients.claim())
)
})
// Fetch event - serve from cache or network
self.addEventListener("fetch", (event) => {
const { request } = event
const url = new URL(request.url)
// Skip non-GET requests
if (request.method !== "GET") {
return
}
// Skip API requests
if (url.pathname.startsWith("/api/")) {
return
}
// Skip PocketBase API
if (url.pathname.startsWith("/_/")) {
return
}
event.respondWith(
caches
.match(request)
.then((cached) => {
if (cached) {
// Return cached version and update in background
fetch(request).then((response) => {
if (response.ok) {
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, response)
})
}
})
return cached
}
// Fetch from network
return fetch(request).then((response) => {
if (!response || response.status !== 200 || response.type !== "basic") {
return response
}
const responseToCache = response.clone()
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache)
})
return response
})
})
.catch(() => {
// Return offline page if available
return caches.match("/offline.html")
})
)
})
// Push notification event
self.addEventListener("push", (event) => {
if (!event.data) {
return
}
const data = event.data.json()
const options = {
body: data.body || "New notification",
icon: data.icon || "/favicon-192x192.png",
badge: data.badge || "/favicon-72x72.png",
tag: data.tag || "default",
requireInteraction: data.requireInteraction || false,
data: data.data || {},
actions: data.actions || [
{ action: "open", title: "Open" },
{ action: "close", title: "Dismiss" },
],
}
event.waitUntil(self.registration.showNotification(data.title || "Beszel Alert", options))
})
// Notification click event
self.addEventListener("notificationclick", (event) => {
event.notification.close()
const { data } = event.notification
const urlToOpen = data?.url || "/"
event.waitUntil(
clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
// Check if there's already a window open
for (const client of clientList) {
if (client.url === urlToOpen && "focus" in client) {
return client.focus()
}
}
// Open new window if not found
if (clients.openWindow) {
return clients.openWindow(urlToOpen)
}
})
)
})
// Background sync for offline support
self.addEventListener("sync", (event) => {
if (event.tag === "background-sync") {
event.waitUntil(doBackgroundSync())
}
})
function doBackgroundSync() {
// Retry any pending API requests stored in IndexedDB
// This is a placeholder - implement with actual pending request logic
console.log("Background sync executed")
}
// Periodic background sync (if supported)
self.addEventListener("periodicsync", (event) => {
if (event.tag === "update-check") {
event.waitUntil(checkForUpdates())
}
})
function checkForUpdates() {
// Check for new data and show notifications if needed
console.log("Periodic sync executed")
}