Refactor Docker setup and enhance PWA features with service worker, caching, and manifest updates
This commit is contained in:
16
Dockerfile
16
Dockerfile
@@ -1,5 +1,19 @@
|
||||
FROM nginx:alpine
|
||||
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
|
||||
|
||||
# services.xml will be mounted at runtime as a bind mount
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
services-homepage:
|
||||
image: nginx:alpine
|
||||
build: .
|
||||
container_name: services-homepage
|
||||
ports:
|
||||
- "8088:80"
|
||||
volumes:
|
||||
- ./index.html:/usr/share/nginx/html/index.html: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
|
||||
logging:
|
||||
driver: json-file
|
||||
|
||||
23
index.html
23
index.html
@@ -4,6 +4,16 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<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" />
|
||||
<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>
|
||||
@@ -53,6 +63,19 @@
|
||||
|
||||
<!-- Initialize features after DOM is loaded -->
|
||||
<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', () => {
|
||||
// Wait for services to be loaded
|
||||
const checkServicesLoaded = setInterval(() => {
|
||||
|
||||
BIN
logos/icon-192.png
Normal file
BIN
logos/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
8
logos/icon-192.svg
Normal file
8
logos/icon-192.svg
Normal 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
BIN
logos/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
39
manifest.json
Normal file
39
manifest.json
Normal 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
102
sw.js
Normal 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...');
|
||||
}
|
||||
Reference in New Issue
Block a user