Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
cff154c247
|
|||
|
038664ec94
|
|||
|
2bbf825cf7
|
|||
|
5e0159047c
|
|||
|
b23b624a86
|
|||
|
9c099e44af
|
|||
|
9c17a73605
|
@@ -1,35 +0,0 @@
|
||||
when:
|
||||
event: [tag]
|
||||
|
||||
steps:
|
||||
build-homekit-arm64:
|
||||
image: docker:27.2.0-dind
|
||||
privileged: true
|
||||
environment:
|
||||
REGISTRY:
|
||||
from_secret: local_registry
|
||||
USERNAME:
|
||||
from_secret: local_username
|
||||
PASSWORD:
|
||||
from_secret: local_password
|
||||
commands:
|
||||
- echo "Logging in to registry $REGISTRY as $USERNAME"
|
||||
- echo "$PASSWORD" | docker login "$REGISTRY" -u "$USERNAME" --password-stdin
|
||||
- docker version
|
||||
- echo "Enabling binfmt for multi-arch builds"
|
||||
- docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
- echo "Creating and bootstrapping buildx builder"
|
||||
- docker buildx create --use --name multiarch-builder || docker buildx use multiarch-builder
|
||||
- docker buildx inspect --bootstrap
|
||||
- export IMAGE_TAG="${FORGE_NAME}/${CI_REPO}/homekit:${CI_COMMIT_TAG}"
|
||||
- echo "Building ARM64 image: $IMAGE_TAG"
|
||||
- docker buildx build \
|
||||
--platform linux/arm64 \
|
||||
-f apps/homekit/Dockerfile \
|
||||
-t "$IMAGE_TAG" \
|
||||
--push .
|
||||
when:
|
||||
event: [tag]
|
||||
ref:
|
||||
exclude:
|
||||
- refs/tags/*-configchange
|
||||
@@ -7,6 +7,7 @@ matrix:
|
||||
- api
|
||||
- abstraction
|
||||
- rules
|
||||
- static
|
||||
|
||||
steps:
|
||||
build-${APP}:
|
||||
|
||||
@@ -11,6 +11,7 @@ matrix:
|
||||
- api
|
||||
- abstraction
|
||||
- rules
|
||||
- static
|
||||
|
||||
steps:
|
||||
deploy-${APP}:
|
||||
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
network_mode: host
|
||||
|
||||
environment:
|
||||
- HOMEKIT_NAME=Home Automation Bridge
|
||||
- HOMEKIT_NAME=Hottis Home Automation Bridge
|
||||
- HOMEKIT_PIN=031-45-154
|
||||
- HOMEKIT_PORT=51826
|
||||
|
||||
|
||||
15
apps/static/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
# Static assets Dockerfile (minimal webserver for /static only)
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
WORKDIR /usr/share/nginx/html
|
||||
|
||||
# Remove default nginx content
|
||||
RUN rm -rf ./*
|
||||
|
||||
# Copy only static assets from the UI project
|
||||
COPY apps/static/static/ ./
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
# Use default nginx config; caller can mount custom config if needed
|
||||
|
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 639 B After Width: | Height: | Size: 639 B |
|
Before Width: | Height: | Size: 827 B After Width: | Height: | Size: 827 B |
|
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 153 B After Width: | Height: | Size: 153 B |
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1018 B |
|
Before Width: | Height: | Size: 210 B After Width: | Height: | Size: 210 B |
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 346 B |
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 413 B |
|
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1018 B |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 519 B After Width: | Height: | Size: 519 B |
|
Before Width: | Height: | Size: 547 B After Width: | Height: | Size: 547 B |
|
Before Width: | Height: | Size: 641 B After Width: | Height: | Size: 641 B |
|
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 126 B After Width: | Height: | Size: 126 B |
|
Before Width: | Height: | Size: 808 B After Width: | Height: | Size: 808 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 808 B After Width: | Height: | Size: 808 B |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
1
apps/static/static/index.html
Normal file
@@ -0,0 +1 @@
|
||||
empty
|
||||
@@ -1,49 +1,41 @@
|
||||
# UI Service Dockerfile
|
||||
# FastAPI + Jinja2 + HTMX Dashboard
|
||||
# UI Service Dockerfile (Application only, without static files)
|
||||
|
||||
FROM python:3.14-alpine
|
||||
|
||||
# Prevent Python from writing .pyc files and enable unbuffered output
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
UI_PORT=8002 \
|
||||
API_BASE=http://api:8001 \
|
||||
BASE_PATH=""
|
||||
BASE_PATH="" \
|
||||
STATIC_BASE=http://static:8080
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 10001 -S app && \
|
||||
adduser -u 10001 -S app -G app
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apk add --no-cache \
|
||||
curl \
|
||||
gcc \
|
||||
musl-dev \
|
||||
linux-headers
|
||||
|
||||
# Install Python dependencies
|
||||
COPY apps/ui/requirements.txt /app/requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
# Copy only Python code and templates, but exclude static assets
|
||||
COPY apps/__init__.py /app/apps/__init__.py
|
||||
COPY apps/ui/ /app/apps/ui/
|
||||
COPY apps/ui/__init__.py /app/apps/ui/__init__.py
|
||||
COPY apps/ui/main.py /app/apps/ui/main.py
|
||||
COPY apps/ui/api_client.py /app/apps/ui/api_client.py
|
||||
COPY apps/ui/templates/ /app/apps/ui/templates/
|
||||
|
||||
# Change ownership to app user
|
||||
RUN chown -R app:app /app
|
||||
|
||||
# Switch to non-root user
|
||||
USER app
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:${UI_PORT}/health || exit 1
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8002
|
||||
|
||||
# Run application
|
||||
CMD ["python", "-m", "uvicorn", "apps.ui.main:app", "--host", "0.0.0.0", "--port", "8002"]
|
||||
|
||||
@@ -16,9 +16,11 @@ logger = logging.getLogger(__name__)
|
||||
# Read configuration from environment variables
|
||||
API_BASE = os.getenv("API_BASE", "http://localhost:8001")
|
||||
BASE_PATH = os.getenv("BASE_PATH", "") # e.g., "/ui" for reverse proxy
|
||||
STATIC_BASE = os.getenv("STATIC_BASE", "/static")
|
||||
|
||||
print(f"UI using API_BASE: {API_BASE}")
|
||||
print(f"UI using BASE_PATH: {BASE_PATH}")
|
||||
print(f"UI using STATIC_BASE: {STATIC_BASE}")
|
||||
|
||||
def api_url(path: str) -> str:
|
||||
"""Helper function to construct API URLs.
|
||||
@@ -43,6 +45,9 @@ app = FastAPI(
|
||||
templates_dir = Path(__file__).parent / "templates"
|
||||
templates = Jinja2Templates(directory=str(templates_dir))
|
||||
|
||||
# Make STATIC_BASE available in all templates
|
||||
templates.env.globals["STATIC_BASE"] = STATIC_BASE
|
||||
|
||||
# Setup static files
|
||||
static_dir = Path(__file__).parent / "static"
|
||||
static_dir.mkdir(exist_ok=True)
|
||||
@@ -98,7 +103,8 @@ async def health() -> JSONResponse:
|
||||
"status": "ok",
|
||||
"service": "ui",
|
||||
"api_base": API_BASE,
|
||||
"base_path": BASE_PATH
|
||||
"base_path": BASE_PATH,
|
||||
"static_base": STATIC_BASE,
|
||||
})
|
||||
|
||||
|
||||
@@ -127,7 +133,7 @@ async def rooms(request: Request) -> HTMLResponse:
|
||||
"""
|
||||
return templates.TemplateResponse("rooms.html", {
|
||||
"request": request,
|
||||
"api_base": API_BASE
|
||||
"api_base": API_BASE,
|
||||
})
|
||||
|
||||
|
||||
@@ -145,7 +151,7 @@ async def room_detail(request: Request, room_name: str) -> HTMLResponse:
|
||||
return templates.TemplateResponse("room.html", {
|
||||
"request": request,
|
||||
"api_base": API_BASE,
|
||||
"room_name": room_name
|
||||
"room_name": room_name,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
# Home Automation API Client
|
||||
|
||||
Wiederverwendbare JavaScript-API-Client-Bibliothek für das Home Automation UI.
|
||||
|
||||
## Installation
|
||||
|
||||
Füge die folgenden Script-Tags in deine HTML-Seiten ein:
|
||||
|
||||
```html
|
||||
<script src="/static/types.js"></script>
|
||||
<script src="/static/api-client.js"></script>
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Der API-Client nutzt `window.API_BASE`, das vom Backend gesetzt wird:
|
||||
|
||||
```javascript
|
||||
window.API_BASE = '{{ api_base }}'; // Jinja2 template
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Globale Instanz
|
||||
|
||||
Der API-Client erstellt automatisch eine globale Instanz `window.apiClient`:
|
||||
|
||||
```javascript
|
||||
// Layout abrufen
|
||||
const layout = await window.apiClient.getLayout();
|
||||
|
||||
// Geräte abrufen
|
||||
const devices = await window.apiClient.getDevices();
|
||||
|
||||
// Gerätestatus abrufen
|
||||
const state = await window.apiClient.getDeviceState('kitchen_light');
|
||||
|
||||
// Gerätesteuerung
|
||||
await window.apiClient.setDeviceState('kitchen_light', 'light', {
|
||||
power: true,
|
||||
brightness: 80
|
||||
});
|
||||
```
|
||||
|
||||
### Verfügbare Methoden
|
||||
|
||||
#### `getLayout(): Promise<Layout>`
|
||||
Lädt die Layout-Daten (Räume und ihre Geräte).
|
||||
|
||||
```javascript
|
||||
const layout = await window.apiClient.getLayout();
|
||||
// { rooms: [{name: "Küche", devices: ["kitchen_light", ...]}, ...] }
|
||||
```
|
||||
|
||||
#### `getDevices(): Promise<Device[]>`
|
||||
Lädt alle Geräte mit ihren Features.
|
||||
|
||||
```javascript
|
||||
const devices = await window.apiClient.getDevices();
|
||||
// [{device_id: "...", name: "...", type: "light", features: {...}}, ...]
|
||||
```
|
||||
|
||||
#### `getDeviceState(deviceId): Promise<DeviceState>`
|
||||
Lädt den aktuellen Status eines Geräts.
|
||||
|
||||
```javascript
|
||||
const state = await window.apiClient.getDeviceState('kitchen_light');
|
||||
// {power: true, brightness: 80, ...}
|
||||
```
|
||||
|
||||
#### `getAllStates(): Promise<Object>`
|
||||
Lädt alle Gerätestatus auf einmal.
|
||||
|
||||
```javascript
|
||||
const states = await window.apiClient.getAllStates();
|
||||
// {"kitchen_light": {power: true, ...}, "thermostat_1": {...}, ...}
|
||||
```
|
||||
|
||||
#### `setDeviceState(deviceId, type, payload): Promise<void>`
|
||||
Sendet einen Befehl an ein Gerät.
|
||||
|
||||
```javascript
|
||||
// Licht einschalten
|
||||
await window.apiClient.setDeviceState('kitchen_light', 'light', {
|
||||
power: true,
|
||||
brightness: 80
|
||||
});
|
||||
|
||||
// Thermostat einstellen
|
||||
await window.apiClient.setDeviceState('thermostat_1', 'thermostat', {
|
||||
target_temp: 22.5
|
||||
});
|
||||
|
||||
// Rollladen steuern
|
||||
await window.apiClient.setDeviceState('cover_1', 'cover', {
|
||||
position: 50
|
||||
});
|
||||
```
|
||||
|
||||
#### `getDeviceRoom(deviceId): Promise<{room: string}>`
|
||||
Ermittelt den Raum eines Geräts.
|
||||
|
||||
```javascript
|
||||
const { room } = await window.apiClient.getDeviceRoom('kitchen_light');
|
||||
// {room: "Küche"}
|
||||
```
|
||||
|
||||
#### `getScenes(): Promise<Scene[]>`
|
||||
Lädt alle verfügbaren Szenen.
|
||||
|
||||
```javascript
|
||||
const scenes = await window.apiClient.getScenes();
|
||||
```
|
||||
|
||||
#### `activateScene(sceneId): Promise<void>`
|
||||
Aktiviert eine Szene.
|
||||
|
||||
```javascript
|
||||
await window.apiClient.activateScene('evening');
|
||||
```
|
||||
|
||||
### Realtime-Updates (SSE)
|
||||
|
||||
#### `connectRealtime(onEvent, onError): EventSource`
|
||||
Verbindet sich mit dem SSE-Stream für Live-Updates.
|
||||
|
||||
```javascript
|
||||
window.apiClient.connectRealtime(
|
||||
(event) => {
|
||||
console.log('Update:', event.device_id, event.state);
|
||||
// event = {device_id: "...", type: "state", state: {...}}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Connection error:', error);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
#### `onDeviceUpdate(deviceId, callback): Function`
|
||||
Registriert einen Listener für spezifische Geräte-Updates.
|
||||
|
||||
```javascript
|
||||
// Für ein bestimmtes Gerät
|
||||
const unsubscribe = window.apiClient.onDeviceUpdate('kitchen_light', (event) => {
|
||||
console.log('Kitchen light changed:', event.state);
|
||||
updateUI(event.state);
|
||||
});
|
||||
|
||||
// Für alle Geräte
|
||||
const unsubscribeAll = window.apiClient.onDeviceUpdate(null, (event) => {
|
||||
console.log('Any device changed:', event.device_id, event.state);
|
||||
});
|
||||
|
||||
// Später: Listener entfernen
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
#### `disconnectRealtime(): void`
|
||||
Trennt die SSE-Verbindung und entfernt alle Listener.
|
||||
|
||||
```javascript
|
||||
window.apiClient.disconnectRealtime();
|
||||
```
|
||||
|
||||
### Helper-Methoden
|
||||
|
||||
#### `findDevice(devices, deviceId): Device|null`
|
||||
Findet ein Gerät in einem Array.
|
||||
|
||||
```javascript
|
||||
const devices = await window.apiClient.getDevices();
|
||||
const device = window.apiClient.findDevice(devices, 'kitchen_light');
|
||||
```
|
||||
|
||||
#### `findRoom(layout, roomName): Room|null`
|
||||
Findet einen Raum im Layout.
|
||||
|
||||
```javascript
|
||||
const layout = await window.apiClient.getLayout();
|
||||
const room = window.apiClient.findRoom(layout, 'Küche');
|
||||
```
|
||||
|
||||
#### `getDevicesForRoom(layout, devices, roomName): Device[]`
|
||||
Gibt alle Geräte eines Raums zurück.
|
||||
|
||||
```javascript
|
||||
const layout = await window.apiClient.getLayout();
|
||||
const devices = await window.apiClient.getDevices();
|
||||
const kitchenDevices = window.apiClient.getDevicesForRoom(layout, devices, 'Küche');
|
||||
```
|
||||
|
||||
#### `api(path): string`
|
||||
Konstruiert eine vollständige API-URL.
|
||||
|
||||
```javascript
|
||||
const url = window.apiClient.api('/devices');
|
||||
// "http://172.19.1.11:8001/devices"
|
||||
```
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
Die globale `api()` Funktion ist weiterhin verfügbar:
|
||||
|
||||
```javascript
|
||||
function api(url) {
|
||||
return window.apiClient.api(url);
|
||||
}
|
||||
```
|
||||
|
||||
## Typen (JSDoc)
|
||||
|
||||
Die Datei `types.js` enthält JSDoc-Definitionen für alle API-Typen:
|
||||
|
||||
- `Room` - Raum mit Geräten
|
||||
- `Layout` - Layout-Struktur
|
||||
- `Device` - Gerätedaten
|
||||
- `DeviceFeatures` - Geräte-Features
|
||||
- `DeviceState` - Gerätestatus (Light, Thermostat, Contact, etc.)
|
||||
- `RealtimeEvent` - SSE-Event-Format
|
||||
- `Scene` - Szenen-Definition
|
||||
- `*Payload` - Command-Payloads für verschiedene Gerätetypen
|
||||
|
||||
Diese ermöglichen IDE-Autocomplete und Type-Checking in modernen Editoren (VS Code, WebStorm).
|
||||
|
||||
## Beispiel: Vollständige Seite
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>My Page</title>
|
||||
<script src="/static/types.js"></script>
|
||||
<script src="/static/api-client.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status"></div>
|
||||
<button id="toggle">Toggle Light</button>
|
||||
|
||||
<script>
|
||||
window.API_BASE = 'http://172.19.1.11:8001';
|
||||
const deviceId = 'kitchen_light';
|
||||
|
||||
async function init() {
|
||||
// Load initial state
|
||||
const state = await window.apiClient.getDeviceState(deviceId);
|
||||
updateUI(state);
|
||||
|
||||
// Listen for updates
|
||||
window.apiClient.onDeviceUpdate(deviceId, (event) => {
|
||||
updateUI(event.state);
|
||||
});
|
||||
|
||||
// Connect to realtime
|
||||
window.apiClient.connectRealtime((event) => {
|
||||
console.log('Event:', event);
|
||||
});
|
||||
|
||||
// Handle button clicks
|
||||
document.getElementById('toggle').onclick = async () => {
|
||||
const currentState = await window.apiClient.getDeviceState(deviceId);
|
||||
await window.apiClient.setDeviceState(deviceId, 'light', {
|
||||
power: !currentState.power
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function updateUI(state) {
|
||||
document.getElementById('status').textContent =
|
||||
state.power ? 'ON' : 'OFF';
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Alle API-Methoden werfen Exceptions bei Fehlern:
|
||||
|
||||
```javascript
|
||||
try {
|
||||
const state = await window.apiClient.getDeviceState('invalid_id');
|
||||
} catch (error) {
|
||||
console.error('API error:', error);
|
||||
showErrorMessage(error.message);
|
||||
}
|
||||
```
|
||||
|
||||
## Auto-Reconnect
|
||||
|
||||
Der SSE-Client versucht automatisch, nach 5 Sekunden wieder zu verbinden, wenn die Verbindung abbricht.
|
||||
|
||||
## Verwendete Technologien
|
||||
|
||||
- **Fetch API** - Für HTTP-Requests
|
||||
- **EventSource** - Für Server-Sent Events
|
||||
- **JSDoc** - Für Type Definitions
|
||||
- **ES6+** - Modern JavaScript (Class, async/await, etc.)
|
||||
@@ -6,17 +6,17 @@
|
||||
<title>Home Automation</title>
|
||||
|
||||
<!-- Apple Touch Icon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/svg+xml" href="{{ STATIC_BASE }}/favicon.svg">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Dashboard">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<title>Gerät - Home Automation</title>
|
||||
|
||||
<!-- Apple Touch Icon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Gerät">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -346,8 +346,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Load API client AFTER API_BASE is set -->
|
||||
<script src="/static/types.js"></script>
|
||||
<script src="/static/api-client.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/types.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/api-client.js"></script>
|
||||
|
||||
<script>
|
||||
// Get device ID from URL
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
<title>Garage - Home Automation</title>
|
||||
|
||||
<!-- Apple Touch Icon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/garage-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/static/garage-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/static/garage-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/static/garage-icon-76x76.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/garage-icon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/garage-icon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ STATIC_BASE }}/garage-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{ STATIC_BASE }}/garage-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="{{ STATIC_BASE }}/garage-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="{{ STATIC_BASE }}/garage-icon-76x76.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_BASE }}/garage-icon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_BASE }}/garage-icon-16x16.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Garage">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -306,8 +306,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Load API client AFTER API_BASE is set -->
|
||||
<script src="/static/types.js"></script>
|
||||
<script src="/static/api-client.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/types.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/api-client.js"></script>
|
||||
|
||||
<script>
|
||||
// Device IDs for garage devices
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
<title>Home Automation</title>
|
||||
|
||||
<!-- Apple Touch Icon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/static/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/static/apple-touch-icon-76x76.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/apple-touch-icon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/apple-touch-icon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ STATIC_BASE }}/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{ STATIC_BASE }}/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="{{ STATIC_BASE }}/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="{{ STATIC_BASE }}/apple-touch-icon-76x76.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_BASE }}/apple-touch-icon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_BASE }}/apple-touch-icon-16x16.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Home Automation">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<title>{{ room_name }} - Home Automation</title>
|
||||
|
||||
<!-- Apple Touch Icon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_BASE }}/apple-touch-icon.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="{{ room_name }}">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -229,8 +229,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Load API client AFTER API_BASE is set -->
|
||||
<script src="/static/types.js"></script>
|
||||
<script src="/static/api-client.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/types.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/api-client.js"></script>
|
||||
|
||||
<script>
|
||||
// Get room name from URL
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
<title>Räume - Home Automation</title>
|
||||
|
||||
<!-- Apple Touch Icon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/static/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/static/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/static/apple-touch-icon-76x76.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/apple-touch-icon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/apple-touch-icon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ STATIC_BASE }}/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{{ STATIC_BASE }}/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="{{ STATIC_BASE }}/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="{{ STATIC_BASE }}/apple-touch-icon-76x76.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_BASE }}/apple-touch-icon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_BASE }}/apple-touch-icon-16x16.png">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Räume">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -163,8 +163,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Load API client AFTER API_BASE is set -->
|
||||
<script src="/static/types.js"></script>
|
||||
<script src="/static/api-client.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/types.js"></script>
|
||||
<script src="{{ STATIC_BASE }}/api-client.js"></script>
|
||||
|
||||
<script>
|
||||
// Room icon mapping
|
||||
|
||||
@@ -14,6 +14,7 @@ data:
|
||||
# UI specific environment variables
|
||||
UI_UI_PORT: "8002"
|
||||
UI_API_BASE: "https://homea2-api.hottis.de"
|
||||
UI_STATIC_BASE: "http://homea2-static.hottis.de"
|
||||
UI_BASE_PATH: "/"
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ spec:
|
||||
dnsNames:
|
||||
- homea2.hottis.de
|
||||
- homea2-api.hottis.de
|
||||
- homea2-static.hottis.de
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TLSOption
|
||||
@@ -60,6 +61,22 @@ spec:
|
||||
- name: api
|
||||
port: 80
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: static
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
secretName: homea2-cert
|
||||
routes:
|
||||
- match: Host(`homea2-static.hottis.de`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: static
|
||||
port: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
|
||||
61
deployment/static-deployment.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: static
|
||||
namespace: homea2
|
||||
labels:
|
||||
app: static
|
||||
component: home-automation
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: static
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: static
|
||||
component: home-automation
|
||||
spec:
|
||||
containers:
|
||||
- name: static
|
||||
image: %IMAGE%
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: static
|
||||
namespace: homea2
|
||||
labels:
|
||||
app: static
|
||||
component: home-automation
|
||||
spec:
|
||||
selector:
|
||||
app: static
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
name: http
|
||||
type: ClusterIP
|
||||
@@ -37,6 +37,11 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: home-automation-environment
|
||||
key: UI_API_BASE
|
||||
- name: STATIC_BASE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: home-automation-environment
|
||||
key: UI_STATIC_BASE
|
||||
- name: BASE_PATH
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
|
||||