diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000..a4d19e8
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,14 @@
+FROM node:18-alpine
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install --production
+
+COPY server.js ./server.js
+COPY data/apps.json ./data/apps.json
+
+ENV PORT=3000
+EXPOSE 3000
+
+CMD ["node", "server.js"]
diff --git a/backend/data/apps.json b/backend/data/apps.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/backend/data/apps.json
@@ -0,0 +1 @@
+[]
diff --git a/backend/k8s/admin-deployment.yaml b/backend/k8s/admin-deployment.yaml
new file mode 100644
index 0000000..43e12e1
--- /dev/null
+++ b/backend/k8s/admin-deployment.yaml
@@ -0,0 +1,54 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: admin-api
+ labels:
+ app: admin-api
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: admin-api
+ template:
+ metadata:
+ labels:
+ app: admin-api
+ spec:
+ containers:
+ - name: admin-api
+ image: admin-api:latest
+ imagePullPolicy: Never
+ ports:
+ - containerPort: 3000
+ env:
+ - name: PORT
+ value: "3000"
+ - name: ADMIN_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: admin-secrets
+ key: admin-token
+ - name: JWT_SECRET
+ valueFrom:
+ secretKeyRef:
+ name: admin-secrets
+ key: jwt-secret
+ resources:
+ requests:
+ memory: "128Mi"
+ cpu: "100m"
+ limits:
+ memory: "256Mi"
+ cpu: "200m"
+ livenessProbe:
+ httpGet:
+ path: /api/apps
+ port: 3000
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ readinessProbe:
+ httpGet:
+ path: /api/apps
+ port: 3000
+ initialDelaySeconds: 5
+ periodSeconds: 5
diff --git a/backend/k8s/admin-ingress.yaml b/backend/k8s/admin-ingress.yaml
new file mode 100644
index 0000000..1cf5026
--- /dev/null
+++ b/backend/k8s/admin-ingress.yaml
@@ -0,0 +1,18 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: admin-api
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ rules:
+ - host: admin.apps.local
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: admin-api
+ port:
+ number: 80
diff --git a/backend/k8s/admin-service.yaml b/backend/k8s/admin-service.yaml
new file mode 100644
index 0000000..ff57274
--- /dev/null
+++ b/backend/k8s/admin-service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: admin-api
+ labels:
+ app: admin-api
+spec:
+ type: ClusterIP
+ ports:
+ - port: 80
+ targetPort: 3000
+ protocol: TCP
+ name: http
+ selector:
+ app: admin-api
diff --git a/backend/package.json b/backend/package.json
new file mode 100644
index 0000000..5a55712
--- /dev/null
+++ b/backend/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "linux-app-store-admin",
+ "version": "1.0.0",
+ "description": "Admin panel API for Linux App Store",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "node --watch server.js"
+ },
+ "dependencies": {
+ "express": "^4.18.2",
+ "express-jwt": "^8.4.1",
+ "jsonwebtoken": "^9.0.2",
+ "cors": "^2.8.5"
+ }
+}
diff --git a/backend/server.js b/backend/server.js
new file mode 100644
index 0000000..82a81f0
--- /dev/null
+++ b/backend/server.js
@@ -0,0 +1,173 @@
+const express = require('express');
+const jwt = require('jsonwebtoken');
+const fs = require('fs');
+const path = require('path');
+
+const app = express();
+const PORT = process.env.PORT || 3000;
+const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-key';
+const APPS_FILE = path.join(__dirname, 'data', 'apps.json');
+
+app.use(express.json());
+app.use((req, res, next) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
+ if (req.method === 'OPTIONS') {
+ return res.status(200).end();
+ }
+ next();
+});
+
+// Health check
+app.get('/health', (req, res) => {
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
+});
+
+function verifyToken(req, res, next) {
+ const authHeader = req.headers.authorization;
+ if (!authHeader) {
+ return res.status(401).json({ error: 'Authorization header required' });
+ }
+
+ try {
+ const token = authHeader.split(' ')[1];
+ const decoded = jwt.verify(token, JWT_SECRET);
+ req.auth = decoded;
+ next();
+ } catch (err) {
+ return res.status(401).json({ error: 'Invalid or expired token' });
+ }
+}
+
+function readAppsFile() {
+ try {
+ if (fs.existsSync(APPS_FILE)) {
+ const data = fs.readFileSync(APPS_FILE, 'utf8');
+ return JSON.parse(data);
+ }
+ } catch (err) {
+ console.error('Error reading apps file:', err);
+ }
+ return [];
+}
+
+function writeAppsFile(apps) {
+ try {
+ const tempFile = `${APPS_FILE}.tmp`;
+ fs.writeFileSync(tempFile, JSON.stringify(apps, null, 2));
+ fs.renameSync(tempFile, APPS_FILE);
+ return true;
+ } catch (err) {
+ console.error('Error writing apps file:', err);
+ return false;
+ }
+}
+
+app.post('/api/admin/login', (req, res) => {
+ const { token } = req.body;
+ const adminToken = process.env.ADMIN_TOKEN;
+
+ if (!adminToken) {
+ return res.status(500).json({ error: 'ADMIN_TOKEN not configured' });
+ }
+
+ if (token === adminToken) {
+ const payload = {
+ admin: true,
+ iat: Math.floor(Date.now() / 1000),
+ exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24)
+ };
+ const secret = process.env.JWT_SECRET || 'fallback-secret-key';
+ const apiKey = jwt.sign(payload, secret);
+ return res.json({ apiKey });
+ }
+
+ return res.status(401).json({ error: 'Invalid token' });
+});
+
+app.get('/api/apps', (req, res) => {
+ const apps = readAppsFile();
+ return res.json(apps);
+});
+
+app.post('/api/admin/apps', verifyToken, (req, res) => {
+ if (!req.auth || !req.auth.admin) {
+ return res.status(403).json({ error: 'Admin access required' });
+ }
+ const { name, description, icon, colors, packages } = req.body;
+
+ if (!name || !description) {
+ return res.status(400).json({ error: 'Name and description are required' });
+ }
+
+ let apps = readAppsFile();
+ const newId = apps.length > 0 ? Math.max(...apps.map(a => a.id)) + 1 : 1;
+
+ const newApp = {
+ id: newId,
+ name,
+ description,
+ icon: icon || '',
+ colors: colors || { background: '', text: '' },
+ packages: packages || { apt: '', appstream: '', flatpak: '' }
+ };
+
+ apps.push(newApp);
+
+ if (writeAppsFile(apps)) {
+ return res.status(201).json(newApp);
+ }
+
+ return res.status(500).json({ error: 'Failed to save app' });
+});
+
+app.put('/api/admin/apps/:id', verifyToken, (req, res) => {
+ if (!req.auth || !req.auth.admin) {
+ return res.status(403).json({ error: 'Admin access required' });
+ }
+ const { id } = req.params;
+ const updates = req.body;
+
+ let apps = readAppsFile();
+ const appIndex = apps.findIndex(a => a.id === parseInt(id));
+
+ if (appIndex === -1) {
+ return res.status(404).json({ error: 'App not found' });
+ }
+
+ const updatedApp = { ...apps[appIndex], ...updates, id: parseInt(id) };
+ apps[appIndex] = updatedApp;
+
+ if (writeAppsFile(apps)) {
+ return res.json(updatedApp);
+ }
+
+ return res.status(500).json({ error: 'Failed to update app' });
+});
+
+app.delete('/api/admin/apps/:id', verifyToken, (req, res) => {
+ if (!req.auth || !req.auth.admin) {
+ return res.status(403).json({ error: 'Admin access required' });
+ }
+ const { id } = req.params;
+
+ let apps = readAppsFile();
+ const appIndex = apps.findIndex(a => a.id === parseInt(id));
+
+ if (appIndex === -1) {
+ return res.status(404).json({ error: 'App not found' });
+ }
+
+ apps.splice(appIndex, 1);
+
+ if (writeAppsFile(apps)) {
+ return res.json({ message: 'App deleted successfully' });
+ }
+
+ return res.status(500).json({ error: 'Failed to delete app' });
+});
+
+app.listen(PORT, () => {
+ console.log(`Admin API running on port ${PORT}`);
+});
diff --git a/deploy.sh b/deploy.sh
index 2a21f12..2827e13 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -2,17 +2,31 @@
set -e
REGISTRY="git.giaco.net"
-IMAGE_NAME="capitano/webapplinux"
+MAIN_IMAGE="capitano/webapplinux"
+ADMIN_IMAGE="capitano/webapplinux-admin"
TAG="latest"
-echo "Building image..."
-docker build -t $REGISTRY/$IMAGE_NAME:$TAG /home/capitano/kubernetes/infrastructure/webapplinux/
+echo "Building main image..."
+docker build -t $REGISTRY/$MAIN_IMAGE:$TAG /home/capitano/kubernetes/infrastructure/webapplinux/
-echo "Pushing to registry..."
-docker push $REGISTRY/$IMAGE_NAME:$TAG
+echo "Building admin API image..."
+docker build -t $REGISTRY/$ADMIN_IMAGE:$TAG /home/capitano/kubernetes/infrastructure/webapplinux/backend/
+
+echo "Pushing main image..."
+docker push $REGISTRY/$MAIN_IMAGE:$TAG
+
+echo "Pushing admin image..."
+docker push $REGISTRY/$ADMIN_IMAGE:$TAG
+
+echo "Creating admin secret..."
+kubectl create secret generic admin-secret -n linuxwebapp \
+ --from-literal=admin-token="${ADMIN_TOKEN:-admin123}" \
+ --from-literal=jwt-secret="${JWT_SECRET:-supersecretjwtkey}" \
+ --dry-run=client -o yaml | kubectl apply -f -
echo "Deploying to Kubernetes..."
kubectl apply -f k8s/namespace.yaml 2>/dev/null || true
kubectl apply -f k8s/
echo "Check status with: kubectl get pods -n linuxwebapp"
+echo "Admin panel at: http://admin.apps.local"
diff --git a/frontend/admin.html b/frontend/admin.html
new file mode 100644
index 0000000..8be801e
--- /dev/null
+++ b/frontend/admin.html
@@ -0,0 +1,111 @@
+
+
+
+
+
+ Admin - Linux App Store
+
+
+
+
+
+
+
+
+
+
Accesso Amministratore
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Gestione Applicazioni
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin.js b/frontend/admin.js
new file mode 100644
index 0000000..af47bdb
--- /dev/null
+++ b/frontend/admin.js
@@ -0,0 +1,244 @@
+const API_URL = 'http://localhost:3000';
+let authToken = localStorage.getItem('admin_token');
+
+document.addEventListener('DOMContentLoaded', () => {
+ checkAuth();
+ if (authToken) {
+ loadApps();
+ }
+});
+
+function checkAuth() {
+ const loginSection = document.getElementById('login-section');
+ const adminSection = document.getElementById('admin-section');
+ const userStatus = document.getElementById('user-status');
+
+ if (authToken) {
+ loginSection.classList.add('hidden');
+ adminSection.classList.remove('hidden');
+ userStatus.classList.remove('hidden');
+ userStatus.innerHTML = 'Admin';
+ }
+}
+
+async function login() {
+ const tokenInput = document.getElementById('admin-token');
+ const token = tokenInput.value;
+ const errorDiv = document.getElementById('error-message');
+
+ if (!token) {
+ showError('Inserisci il token admin');
+ return;
+ }
+
+ try {
+ const response = await fetch(`${API_URL}/api/admin/login`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ token })
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ authToken = data.apiKey;
+ localStorage.setItem('admin_token', authToken);
+ checkAuth();
+ loadApps();
+ tokenInput.value = '';
+ } else {
+ showError(data.error || 'Login fallito');
+ }
+ } catch (error) {
+ showError('Errore connessione API: ' + error.message);
+ }
+}
+
+function logout() {
+ localStorage.removeItem('admin_token');
+ authToken = null;
+ checkAuth();
+}
+
+async function loadApps() {
+ const container = document.getElementById('apps-list');
+ container.innerHTML = '
';
+
+ try {
+ const response = await fetch(`${API_URL}/api/apps`);
+ const data = await response.json();
+ renderApps(data.apps || data);
+ } catch (error) {
+ container.innerHTML = 'Errore caricamento app
';
+ }
+}
+
+function renderApps(apps) {
+ const container = document.getElementById('apps-list');
+
+ if (!apps || apps.length === 0) {
+ container.innerHTML = 'Nessuna applicazione. Aggiungine una!
';
+ return;
+ }
+
+ container.innerHTML = '';
+
+ apps.forEach(app => {
+ const appCard = document.createElement('div');
+ appCard.className = 'bg-gray-850 rounded-xl overflow-hidden shadow-lg border border-gray-700 hover:shadow-xl transition-shadow duration-300';
+ appCard.innerHTML = `
+
+
+
+
+
+
+
+
${app.name}
+ ID: ${app.id}
+
+
+
+
+
+
+
+
${app.description || ''}
+
+
+ APT: ${app.packages?.apt || 'N/A'}
+
+
+ AppStream: ${app.packages?.appstream || 'N/A'}
+
+
+ Flatpak: ${app.packages?.flatpak || 'N/A'}
+
+
+
+ `;
+ container.appendChild(appCard);
+ });
+}
+
+function showError(message) {
+ const errorDiv = document.getElementById('error-message');
+ errorDiv.textContent = message;
+ errorDiv.classList.remove('hidden');
+ setTimeout(() => errorDiv.classList.add('hidden'), 5000);
+}
+
+function showAddForm() {
+ document.getElementById('form-title').textContent = 'Aggiungi Applicazione';
+ document.getElementById('app-form').reset();
+ document.getElementById('app-id').value = '';
+ document.getElementById('app-id-input').value = '';
+ document.getElementById('app-name').value = '';
+ document.getElementById('app-description').value = '';
+ document.getElementById('app-icon').value = 'fa-package';
+ document.getElementById('app-colors').value = 'from-purple-500 to-indigo-600';
+ document.getElementById('app-apt').value = '';
+ document.getElementById('app-appstream').value = '';
+ document.getElementById('app-flatpak').value = '';
+ document.getElementById('app-form-modal').classList.remove('hidden');
+}
+
+function closeForm() {
+ document.getElementById('app-form-modal').classList.add('hidden');
+}
+
+async function saveApp(event) {
+ event.preventDefault();
+
+ const idInput = document.getElementById('app-id-input').value;
+ const name = document.getElementById('app-name').value;
+ const description = document.getElementById('app-description').value;
+ const icon = document.getElementById('app-icon').value;
+ const colors = document.getElementById('app-colors').value;
+ const apt = document.getElementById('app-apt').value;
+ const appstream = document.getElementById('app-appstream').value;
+ const flatpak = document.getElementById('app-flatpak').value;
+
+ const appData = {
+ id: idInput,
+ name,
+ description,
+ icon,
+ colors,
+ packages: { apt, appstream, flatpak }
+ };
+
+ const isEdit = document.getElementById('app-id').value;
+ const method = isEdit ? 'PUT' : 'POST';
+ const url = isEdit ? `${API_URL}/api/admin/apps/${document.getElementById('app-id').value}` : `${API_URL}/api/admin/apps`;
+
+ try {
+ const response = await fetch(url, {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${authToken}`
+ },
+ body: JSON.stringify(appData)
+ });
+
+ if (response.ok) {
+ closeForm();
+ loadApps();
+ } else {
+ const error = await response.json();
+ showError(error.error || 'Salvataggio fallito');
+ }
+ } catch (error) {
+ showError('Errore: ' + error.message);
+ }
+}
+
+async function editApp(id) {
+ const apps = await fetch(`${API_URL}/api/apps`).then(r => r.json());
+ const app = (apps.apps || apps).find(a => a.id === id);
+
+ if (!app) return;
+
+ document.getElementById('form-title').textContent = 'Modifica Applicazione';
+ document.getElementById('app-id').value = app.id;
+ document.getElementById('app-id-input').value = app.id;
+ document.getElementById('app-name').value = app.name;
+ document.getElementById('app-description').value = app.description;
+ document.getElementById('app-icon').value = app.icon || 'fa-package';
+ document.getElementById('app-colors').value = app.colors || 'from-purple-500 to-indigo-600';
+ document.getElementById('app-apt').value = app.packages?.apt || '';
+ document.getElementById('app-appstream').value = app.packages?.appstream || '';
+ document.getElementById('app-flatpak').value = app.packages?.flatpak || '';
+ document.getElementById('app-form-modal').classList.remove('hidden');
+}
+
+async function deleteApp(id) {
+ if (!confirm('Vuoi eliminare questa applicazione?')) return;
+
+ try {
+ const response = await fetch(`${API_URL}/api/admin/apps/${id}`, {
+ method: 'DELETE',
+ headers: { 'Authorization': `Bearer ${authToken}` }
+ });
+
+ if (response.ok) {
+ loadApps();
+ } else {
+ showError('Eliminazione fallita');
+ }
+ } catch (error) {
+ showError('Errore: ' + error.message);
+ }
+}
+
+// Close modal on outside click
+document.getElementById('app-form-modal').addEventListener('click', (e) => {
+ if (e.target.classList.contains('fixed')) {
+ closeForm();
+ }
+});
diff --git a/k8s/admin-deployment.yaml b/k8s/admin-deployment.yaml
new file mode 100644
index 0000000..21829f5
--- /dev/null
+++ b/k8s/admin-deployment.yaml
@@ -0,0 +1,53 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: admin-api
+ namespace: linuxwebapp
+ labels:
+ app: admin-api
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: admin-api
+ template:
+ metadata:
+ labels:
+ app: admin-api
+ spec:
+ containers:
+ - name: admin-api
+ image: git.giaco.net/capitano/webapplinux-admin:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 3000
+ resources:
+ requests:
+ memory: "64Mi"
+ cpu: "50m"
+ limits:
+ memory: "128Mi"
+ cpu: "100m"
+ env:
+ - name: ADMIN_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: admin-secret
+ key: admin-token
+ - name: JWT_SECRET
+ valueFrom:
+ secretKeyRef:
+ name: admin-secret
+ key: jwt-secret
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: 3000
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ readinessProbe:
+ httpGet:
+ path: /health
+ port: 3000
+ initialDelaySeconds: 5
+ periodSeconds: 5
diff --git a/k8s/admin-ingress.yaml b/k8s/admin-ingress.yaml
new file mode 100644
index 0000000..5406d0a
--- /dev/null
+++ b/k8s/admin-ingress.yaml
@@ -0,0 +1,22 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: admin-api-ingress
+ namespace: linuxwebapp
+ labels:
+ app: admin-api
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ ingressClassName: nginx
+ rules:
+ - host: admin.apps.local
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: admin-api-service
+ port:
+ number: 3000
diff --git a/k8s/admin-service.yaml b/k8s/admin-service.yaml
new file mode 100644
index 0000000..b6815ca
--- /dev/null
+++ b/k8s/admin-service.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: admin-api-service
+ namespace: linuxwebapp
+ labels:
+ app: admin-api
+spec:
+ type: ClusterIP
+ selector:
+ app: admin-api
+ ports:
+ - port: 3000
+ targetPort: 3000
+ protocol: TCP
+ name: admin-api
diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml
index 97703a4..200b91a 100644
--- a/k8s/deployment.yaml
+++ b/k8s/deployment.yaml
@@ -45,3 +45,38 @@ spec:
value: "apps.local"
- name: NGINX_PORT
value: "80"
+ - name: admin-api
+ image: git.giaco.net/capitano/webapplinux-admin:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 3000
+ resources:
+ requests:
+ memory: "64Mi"
+ cpu: "50m"
+ limits:
+ memory: "128Mi"
+ cpu: "100m"
+ env:
+ - name: ADMIN_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: admin-secret
+ key: admin-token
+ - name: JWT_SECRET
+ valueFrom:
+ secretKeyRef:
+ name: admin-secret
+ key: jwt-secret
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: 3000
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ readinessProbe:
+ httpGet:
+ path: /health
+ port: 3000
+ initialDelaySeconds: 5
+ periodSeconds: 5
diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml
index aa28344..16f2a5f 100644
--- a/k8s/ingress.yaml
+++ b/k8s/ingress.yaml
@@ -20,3 +20,27 @@ spec:
name: linux-app-store-service
port:
number: 80
+
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: admin-api-ingress
+ namespace: linuxwebapp
+ labels:
+ app: admin-api
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ ingressClassName: nginx
+ rules:
+ - host: admin.apps.local
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: admin-api-service
+ port:
+ number: 3000
diff --git a/k8s/service.yaml b/k8s/service.yaml
index 272d5da..802bded 100644
--- a/k8s/service.yaml
+++ b/k8s/service.yaml
@@ -14,3 +14,24 @@ spec:
targetPort: 80
protocol: TCP
name: http
+ - port: 3000
+ targetPort: 3000
+ protocol: TCP
+ name: admin-api
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: admin-api-service
+ namespace: linuxwebapp
+ labels:
+ app: admin-api
+spec:
+ type: ClusterIP
+ selector:
+ app: linux-app-store
+ ports:
+ - port: 3000
+ targetPort: 3000
+ protocol: TCP
+ name: admin-api
diff --git a/nginx.conf b/nginx.conf
index ef16f18..541a5d8 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -1,19 +1,38 @@
server {
listen 80;
- server_name apps.local;
+ server_name apps.local admin.apps.local;
root /usr/share/nginx/html;
- index index.html;
+ index index.html admin.html;
location / {
try_files $uri $uri/ /index.html;
}
+ location /admin {
+ alias /usr/share/nginx/html;
+ try_files $uri $uri/ /admin.html;
+ }
+
+ location /admin/ {
+ alias /usr/share/nginx/html;
+ try_files $uri $uri/ /admin.html;
+ }
+
location /apps.json {
add_header Content-Type application/json;
add_header Access-Control-Allow-Origin *;
}
+ location /api {
+ proxy_pass http://localhost:3000/api;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_cache_bypass $http_upgrade;
+ }
+
gzip on;
gzip_vary on;
gzip_min_length 1024;