Compare commits
6 Commits
8873ffe9d3
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e5f0bbe8d | |||
| bb827f197d | |||
| 67fa2de818 | |||
| ead1846767 | |||
| 165d97a02f | |||
| 6fdf29f040 |
@@ -0,0 +1,51 @@
|
|||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# testing
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# editor
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# docker
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
docker-compose*.yml
|
||||||
|
|
||||||
|
# other
|
||||||
|
README.md
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Auf nuccy als ".env" neben docker-compose.prod.yml ablegen (NICHT committen).
|
||||||
|
# Nur Geheimnisse/Abweichungen; der Rest steht in docker-compose.prod.yml.
|
||||||
|
|
||||||
|
# InfluxDB Read-Token (Bucket strom)
|
||||||
|
INFLUX_TOKEN=
|
||||||
|
|
||||||
|
# Optional: abweichender Host-Port (Default 3000)
|
||||||
|
# STROM_PORT=3000
|
||||||
@@ -29,6 +29,7 @@ yarn-error.log*
|
|||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
!.env.local.example
|
!.env.local.example
|
||||||
|
!.env.production.example
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
# Multi-stage build for Next.js application (standalone output)
|
||||||
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM base AS deps
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Set build date as build argument
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ENV NEXT_PUBLIC_BUILD_DATE=${BUILD_DATE}
|
||||||
|
|
||||||
|
# Disable telemetry during build
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production image, copy all the files and run next
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
# Zeitzone Europe/Berlin: JS-Lokalzeit muss mit der Flux-Aggregation (Berlin)
|
||||||
|
# uebereinstimmen, sonst werden Tages-/Monatsgrenzen falsch zugeordnet.
|
||||||
|
RUN apk add --no-cache tzdata
|
||||||
|
ENV TZ=Europe/Berlin
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
# Copy necessary files
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -101,10 +101,48 @@ curl -s -XPOST "http://nuccy:8086/api/v2/query?org=citysensor" \
|
|||||||
schema.fieldKeys(bucket: "strom", predicate: (r) => r._measurement == "arbeit")'
|
schema.fieldKeys(bucket: "strom", predicate: (r) => r._measurement == "arbeit")'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Starten
|
## Starten (lokal)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev # http://localhost:3000
|
npm run dev # http://localhost:3000
|
||||||
npm run build # Produktionsbuild
|
npm run build # Produktionsbuild
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Docker / Deployment auf nuccy
|
||||||
|
|
||||||
|
Die App läuft als Container auf **nuccy** im externen Docker-Netzwerk
|
||||||
|
**`smarthome`** und erreicht die InfluxDB dort unter dem Host **`influxdb`**
|
||||||
|
(`http://influxdb:8086`).
|
||||||
|
|
||||||
|
### Image bauen & in die Registry pushen (vom Entwicklungsrechner)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy.sh # Tag latest
|
||||||
|
./deploy.sh v1.2.0 # eigener Tag
|
||||||
|
```
|
||||||
|
|
||||||
|
Baut für `linux/amd64` und pusht nach
|
||||||
|
`docker.citysensor.de/strom-next:<tag>`.
|
||||||
|
|
||||||
|
### Auf nuccy starten
|
||||||
|
|
||||||
|
`docker-compose.prod.yml` neben eine `.env` legen (aus
|
||||||
|
`.env.production.example`) und das **InfluxDB-Read-Token** dort eintragen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
INFLUX_TOKEN=...
|
||||||
|
# optional: STROM_PORT=3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Dann:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml pull
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Die App ist anschließend auf nuccy unter Port `3000` (bzw. `STROM_PORT`)
|
||||||
|
erreichbar. Alle InfluxDB-/Lade-Einstellungen sind in `docker-compose.prod.yml`
|
||||||
|
gesetzt; nur das Token kommt aus der `.env`.
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Deploy Script für strom-next
|
||||||
|
# Baut das Docker Image und lädt es zu docker.citysensor.de hoch
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Konfiguration
|
||||||
|
REGISTRY="docker.citysensor.de"
|
||||||
|
IMAGE_NAME="strom-next"
|
||||||
|
TAG="${1:-latest}" # Erster Parameter oder "latest"
|
||||||
|
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
||||||
|
|
||||||
|
# Build-Datum
|
||||||
|
BUILD_DATE=$(date +%d.%m.%Y)
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "strom-next Deploy Script"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Registry: ${REGISTRY}"
|
||||||
|
echo "Image: ${IMAGE_NAME}"
|
||||||
|
echo "Tag: ${TAG}"
|
||||||
|
echo "Build-Datum: ${BUILD_DATE}"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Login zur Registry (falls noch nicht eingeloggt)
|
||||||
|
echo ">>> Login zu ${REGISTRY}..."
|
||||||
|
docker login "${REGISTRY}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 2. Docker Image bauen (nur linux/amd64) - nutzt den Standard-Image-Store
|
||||||
|
echo ">>> Baue Docker Image (linux/amd64)..."
|
||||||
|
docker build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--build-arg BUILD_DATE="${BUILD_DATE}" \
|
||||||
|
-t "${FULL_IMAGE}" \
|
||||||
|
.
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 3. In die Registry pushen
|
||||||
|
echo ">>> Pushe ${FULL_IMAGE}..."
|
||||||
|
docker push "${FULL_IMAGE}"
|
||||||
|
|
||||||
|
echo ">>> Build und Push erfolgreich!"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "✓ Deploy erfolgreich abgeschlossen!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Auf nuccy ausführen (Verzeichnis mit docker-compose.prod.yml + .env):"
|
||||||
|
echo " docker compose -f docker-compose.prod.yml pull"
|
||||||
|
echo " docker compose -f docker-compose.prod.yml up -d"
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Docker Compose für nuccy
|
||||||
|
# Container im externen Docker-Netzwerk 'smarthome', erreicht InfluxDB als 'influxdb'.
|
||||||
|
# Geheimnisse (INFLUX_TOKEN) kommen aus einer .env-Datei neben dieser Compose-Datei.
|
||||||
|
services:
|
||||||
|
strom-app:
|
||||||
|
image: docker.citysensor.de/strom-next:latest
|
||||||
|
container_name: strom-next-app
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${STROM_PORT:-3000}:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
# Zeitzone (muss zur Flux-Aggregation Europe/Berlin passen)
|
||||||
|
- TZ=Europe/Berlin
|
||||||
|
# InfluxDB im smarthome-Netz
|
||||||
|
- INFLUX_URL=http://influxdb:8086
|
||||||
|
- INFLUX_TOKEN=${INFLUX_TOKEN}
|
||||||
|
- INFLUX_ORG=citysensor
|
||||||
|
- INFLUX_BUCKET=strom
|
||||||
|
# Rohdaten (24h) bzw. Rollup (7d/31d/365d)
|
||||||
|
- INFLUX_MEASUREMENT=vzlogger
|
||||||
|
- INFLUX_ROLLUP_MEASUREMENT=arbeit_hourly
|
||||||
|
- INFLUX_FIELD=arbeit
|
||||||
|
- INFLUX_UNIT_FACTOR=0.001
|
||||||
|
# E-Auto-Lade-Erkennung
|
||||||
|
- INFLUX_POWER_FIELD=leistung
|
||||||
|
- LADEN_THRESHOLD_KW=11
|
||||||
|
- LADEN_MIN_HOURS=2
|
||||||
|
- LADEN_MAX_GAP_MIN=10
|
||||||
|
networks:
|
||||||
|
- smarthome
|
||||||
|
|
||||||
|
networks:
|
||||||
|
smarthome:
|
||||||
|
external: true
|
||||||
+12
-1
@@ -20,7 +20,8 @@ const DAY_MS = 24 * HOUR_MS;
|
|||||||
* - 24h: ohne date -> rollierend die letzten 24h ab jetzt (1 Balken/Stunde)
|
* - 24h: ohne date -> rollierend die letzten 24h ab jetzt (1 Balken/Stunde)
|
||||||
* mit date -> dieser Kalendertag 00:00-24:00
|
* mit date -> dieser Kalendertag 00:00-24:00
|
||||||
* - 7d: 7 Tage endend am gewaehlten Tag (Default heute), 1 Balken/Stunde
|
* - 7d: 7 Tage endend am gewaehlten Tag (Default heute), 1 Balken/Stunde
|
||||||
* - 31d: kompletter gewaehlter Monat (Default aktueller Monat), 1 Balken/Tag
|
* - 31d: aktueller Monat (Default) -> rollierend die letzten 31 Tage endend heute;
|
||||||
|
* ein konkret gewaehlter (vergangener) Monat -> dieser Kalendermonat. 1 Balken/Tag
|
||||||
* - 365d: komplettes gewaehltes Jahr (Default aktuelles Jahr), 1 Balken/Tag
|
* - 365d: komplettes gewaehltes Jahr (Default aktuelles Jahr), 1 Balken/Tag
|
||||||
*/
|
*/
|
||||||
export function resolveRange(
|
export function resolveRange(
|
||||||
@@ -50,6 +51,16 @@ export function resolveRange(
|
|||||||
const { year, month } = params.month
|
const { year, month } = params.month
|
||||||
? parseMonth(params.month)
|
? parseMonth(params.month)
|
||||||
: { year: now.getFullYear(), month: now.getMonth() };
|
: { year: now.getFullYear(), month: now.getMonth() };
|
||||||
|
|
||||||
|
// Default/laufender Monat: rollierend die letzten 31 Tage endend heute
|
||||||
|
// (analog zu 24h). Tagesgrenzen lokal via Datums-Arithmetik -> DST-sicher.
|
||||||
|
if (year === now.getFullYear() && month === now.getMonth()) {
|
||||||
|
const stop = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); // Beginn morgen
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1 - 31);
|
||||||
|
return { start, stop, every: '1d', source: 'rollup' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Konkret gewaehlter (vergangener) Monat: kompletter Kalendermonat
|
||||||
const start = new Date(year, month, 1);
|
const start = new Date(year, month, 1);
|
||||||
const stop = new Date(year, month + 1, 1);
|
const stop = new Date(year, month + 1, 1);
|
||||||
return { start, stop, every: '1d', source: 'rollup' };
|
return { start, stop, every: '1d', source: 'rollup' };
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "strom-next",
|
"name": "strom-next",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"versiondate": "2026-06-28",
|
"versiondate": "2026-07-01",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user