Refactor Docker setup and enhance PWA features with service worker, caching, and manifest updates

This commit is contained in:
MayaChat
2025-11-24 13:41:08 -05:00
parent 781346e757
commit 734c9dc70c
8 changed files with 188 additions and 9 deletions

View File

@@ -1,5 +1,19 @@
FROM nginx:alpine FROM nginx:alpine
LABEL maintainer="services-homepage" LABEL maintainer="services-homepage"
COPY . /usr/share/nginx/html
# Copy all static files into the image
COPY index.html /usr/share/nginx/html/
COPY styles.css /usr/share/nginx/html/
COPY manifest.json /usr/share/nginx/html/
COPY sw.js /usr/share/nginx/html/
COPY README.md /usr/share/nginx/html/
COPY FAQ.md /usr/share/nginx/html/
COPY logos/ /usr/share/nginx/html/logos/
COPY js/ /usr/share/nginx/html/js/
COPY nginx.conf /etc/nginx/nginx.conf
# Expose port 80
EXPOSE 80 EXPOSE 80
# services.xml will be mounted at runtime as a bind mount
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,19 +1,12 @@
version: '3.8' version: '3.8'
services: services:
services-homepage: services-homepage:
image: nginx:alpine build: .
container_name: services-homepage container_name: services-homepage
ports: ports:
- "8088:80" - "8088:80"
volumes: volumes:
- ./index.html:/usr/share/nginx/html/index.html:ro
- ./services.xml:/usr/share/nginx/html/services.xml:ro - ./services.xml:/usr/share/nginx/html/services.xml:ro
- ./styles.css:/usr/share/nginx/html/styles.css:ro
- ./logos:/usr/share/nginx/html/logos:ro
- ./js:/usr/share/nginx/html/js:ro
- ./README.md:/usr/share/nginx/html/README.md:ro
- ./FAQ.md:/usr/share/nginx/html/FAQ.md:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
restart: unless-stopped restart: unless-stopped
logging: logging:
driver: json-file driver: json-file

View File

@@ -4,6 +4,16 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Services Homepage</title> <title>Services Homepage</title>
<!-- PWA Meta Tags -->
<meta name="description" content="Quick access to your self-hosted services" />
<meta name="theme-color" content="#0f3460" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Services" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/logos/icon-192.png" />
<link rel="stylesheet" href="/styles.css" /> <link rel="stylesheet" href="/styles.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
@@ -53,6 +63,19 @@
<!-- Initialize features after DOM is loaded --> <!-- Initialize features after DOM is loaded -->
<script> <script>
// Register Service Worker for PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('Service Worker registered:', registration.scope);
})
.catch((error) => {
console.log('Service Worker registration failed:', error);
});
});
}
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
// Wait for services to be loaded // Wait for services to be loaded
const checkServicesLoaded = setInterval(() => { const checkServicesLoaded = setInterval(() => {

BIN
logos/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

8
logos/icon-192.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="192" height="192" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#0f3460"/>
<circle cx="96" cy="96" r="70" fill="#26fdd9" opacity="0.3"/>
<circle cx="96" cy="96" r="50" fill="#2bb2e6" opacity="0.5"/>
<path d="M 96 56 L 116 86 L 76 86 Z" fill="#fff"/>
<path d="M 96 136 L 76 106 L 116 106 Z" fill="#fff"/>
<circle cx="96" cy="96" r="8" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 406 B

BIN
logos/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

39
manifest.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "Services Homepage",
"short_name": "Services",
"description": "Quick access to your self-hosted services",
"start_url": "/",
"display": "standalone",
"background_color": "#001a2d",
"theme_color": "#0f3460",
"orientation": "any",
"icons": [
{
"src": "/logos/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/logos/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"categories": ["utilities", "productivity"],
"shortcuts": [
{
"name": "Search Services",
"short_name": "Search",
"description": "Search your services",
"url": "/?search=true",
"icons": [
{
"src": "/logos/icon-192.png",
"sizes": "192x192"
}
]
}
]
}

102
sw.js Normal file
View File

@@ -0,0 +1,102 @@
// Service Worker for Services Homepage PWA
const CACHE_NAME = 'services-homepage-v1';
const ASSETS_TO_CACHE = [
'/',
'/index.html',
'/styles.css',
'/manifest.json',
'/js/galaxy-background.js',
'/js/services-loader.js',
'/js/search.js',
'/js/keyboard-nav.js',
'/js/readme-loader.js',
'/js/drag-drop.js',
'/js/collapsible-groups.js',
'/js/themes.js',
'/js/export-import.js',
'/js/widgets.js',
'/FAQ.md',
'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js',
'https://cdn.jsdelivr.net/npm/marked/marked.min.js'
];
// Install event - cache assets
self.addEventListener('install', (event) => {
console.log('Service Worker installing...');
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Caching app assets');
return cache.addAll(ASSETS_TO_CACHE);
})
.catch((error) => {
console.error('Failed to cache assets:', error);
})
);
self.skipWaiting();
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('Service Worker activating...');
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});
// Fetch event - serve from cache, fallback to network
self.addEventListener('fetch', (event) => {
// Skip API calls and health checks - always go to network
if (event.request.url.includes('/api/') ||
event.request.url.includes('/healthcheck') ||
event.request.url.includes('services.xml')) {
event.respondWith(fetch(event.request));
return;
}
event.respondWith(
caches.match(event.request)
.then((response) => {
// Return cached version or fetch from network
return response || fetch(event.request).then((fetchResponse) => {
// Cache successful responses
if (fetchResponse && fetchResponse.status === 200) {
const responseClone = fetchResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
}
return fetchResponse;
});
})
.catch(() => {
// Offline fallback
if (event.request.destination === 'document') {
return caches.match('/index.html');
}
})
);
});
// Background sync for offline order saves (future enhancement)
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-order') {
event.waitUntil(syncOrderData());
}
});
async function syncOrderData() {
// Future: sync any pending order changes when back online
console.log('Syncing order data...');
}