feat(infrastructure): Add night-scheduler for automated Docker service shutdown
Implement dashboard-controlled night mode for automatic Docker service management. Services are stopped at a configurable time (default 22:00) and restarted in the morning (default 06:00). Features: - Python/FastAPI scheduler service (port 8096) - Admin dashboard API routes at /api/admin/night-mode - Toggle for enable/disable night mode - Time picker for shutdown and startup times - Manual start/stop buttons for immediate actions - Excluded services (night-scheduler, nginx always run) Files added: - night-scheduler/scheduler.py - Main scheduler with REST API - night-scheduler/Dockerfile - Container with Docker CLI - night-scheduler/requirements.txt - FastAPI, Uvicorn, Pydantic - night-scheduler/tests/test_scheduler.py - Unit tests - admin-v2/app/api/admin/night-mode/* - API proxy routes - .claude/rules/night-scheduler.md - Developer documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
50
admin-v2/app/api/admin/night-mode/execute/route.ts
Normal file
50
admin-v2/app/api/admin/night-mode/execute/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Night Mode Execute API Route
|
||||
*
|
||||
* POST - Sofortige Ausführung (start/stop)
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const NIGHT_SCHEDULER_URL = process.env.NIGHT_SCHEDULER_URL || 'http://night-scheduler:8096'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
if (!body.action || !['start', 'stop'].includes(body.action)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Aktion muss "start" oder "stop" sein' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const response = await fetch(`${NIGHT_SCHEDULER_URL}/api/night-mode/execute`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return NextResponse.json(
|
||||
{ error: `Night-Scheduler Fehler: ${error}` },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return NextResponse.json(data)
|
||||
} catch (error) {
|
||||
console.error('Night-Mode Execute API Error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Night-Scheduler nicht erreichbar',
|
||||
details: error instanceof Error ? error.message : 'Unbekannter Fehler',
|
||||
},
|
||||
{ status: 503 }
|
||||
)
|
||||
}
|
||||
}
|
||||
77
admin-v2/app/api/admin/night-mode/route.ts
Normal file
77
admin-v2/app/api/admin/night-mode/route.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Night Mode API Route
|
||||
*
|
||||
* Proxy für den night-scheduler Service (Port 8096)
|
||||
* GET - Status abrufen
|
||||
* POST - Konfiguration speichern
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const NIGHT_SCHEDULER_URL = process.env.NIGHT_SCHEDULER_URL || 'http://night-scheduler:8096'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const response = await fetch(`${NIGHT_SCHEDULER_URL}/api/night-mode`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
cache: 'no-store',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return NextResponse.json(
|
||||
{ error: `Night-Scheduler Fehler: ${error}` },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return NextResponse.json(data)
|
||||
} catch (error) {
|
||||
console.error('Night-Mode API Error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Night-Scheduler nicht erreichbar',
|
||||
details: error instanceof Error ? error.message : 'Unbekannter Fehler',
|
||||
},
|
||||
{ status: 503 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
const response = await fetch(`${NIGHT_SCHEDULER_URL}/api/night-mode`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return NextResponse.json(
|
||||
{ error: `Night-Scheduler Fehler: ${error}` },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return NextResponse.json(data)
|
||||
} catch (error) {
|
||||
console.error('Night-Mode API Error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Night-Scheduler nicht erreichbar',
|
||||
details: error instanceof Error ? error.message : 'Unbekannter Fehler',
|
||||
},
|
||||
{ status: 503 }
|
||||
)
|
||||
}
|
||||
}
|
||||
41
admin-v2/app/api/admin/night-mode/services/route.ts
Normal file
41
admin-v2/app/api/admin/night-mode/services/route.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Night Mode Services API Route
|
||||
*
|
||||
* GET - Liste aller Services abrufen
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
const NIGHT_SCHEDULER_URL = process.env.NIGHT_SCHEDULER_URL || 'http://night-scheduler:8096'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const response = await fetch(`${NIGHT_SCHEDULER_URL}/api/night-mode/services`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
cache: 'no-store',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return NextResponse.json(
|
||||
{ error: `Night-Scheduler Fehler: ${error}` },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return NextResponse.json(data)
|
||||
} catch (error) {
|
||||
console.error('Night-Mode Services API Error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Night-Scheduler nicht erreichbar',
|
||||
details: error instanceof Error ? error.message : 'Unbekannter Fehler',
|
||||
},
|
||||
{ status: 503 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user