diff --git a/countdown_patch.zip b/countdown_patch.zip
deleted file mode 100644
index c0c8682..0000000
Binary files a/countdown_patch.zip and /dev/null differ
diff --git a/pihole_patch (2)/proxy.conf.json b/pihole_patch (2)/proxy.conf.json
deleted file mode 100644
index 9353bd9..0000000
--- a/pihole_patch (2)/proxy.conf.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "/tasmota1": {
- "target": "http://10.0.0.31",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/tasmota1": ""
- }
- },
- "/tasmota2": {
- "target": "http://10.0.0.32",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/tasmota2": ""
- }
- },
- "/pihole": {
- "target": "http://10.0.0.60",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/pihole": ""
- }
- },
- "/api": {
- "target": "http://directus.neugasse.lan",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/api": ""
- },
- "headers": {
- "Authorization": "Bearer KGjpxZ3tesmhEXHS7C7JbKDyW6YJlgQT"
- }
- }
-}
diff --git a/pihole_patch (2)/src/app/app-routing.module.ts b/pihole_patch (2)/src/app/app-routing.module.ts
deleted file mode 100644
index c546569..0000000
--- a/pihole_patch (2)/src/app/app-routing.module.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-import { LinksComponent } from './links/links.component';
-import { TasmotaControlComponent } from './tasmota-control/tasmota-control.component';
-import { PiholeControlComponent } from './pihole-control/pihole-control.component';
-
-const routes: Routes = [
- { path: '', redirectTo: '/list-links', pathMatch: 'full'},
- { path: 'list-links', component:LinksComponent },
- { path: 'steckdosen', component:TasmotaControlComponent},
- { path: 'pihole', component:PiholeControlComponent}
-];
-
-@NgModule({
- imports: [RouterModule.forRoot(routes, {useHash: true})],
- exports: [RouterModule]
-})
-export class AppRoutingModule { }
diff --git a/pihole_patch (2)/src/app/app.component.html b/pihole_patch (2)/src/app/app.component.html
deleted file mode 100644
index b1b424b..0000000
--- a/pihole_patch (2)/src/app/app.component.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/pihole_patch (2)/src/app/app.module.ts b/pihole_patch (2)/src/app/app.module.ts
deleted file mode 100644
index 620ad38..0000000
--- a/pihole_patch (2)/src/app/app.module.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { NgModule } from '@angular/core';
-import { BrowserModule } from '@angular/platform-browser';
-
-import { AppRoutingModule } from './app-routing.module';
-import { AppComponent } from './app.component';
-import { HttpClientModule } from '@angular/common/http';
-import { LinksComponent } from './links/links.component';
-import * as _ from 'lodash';
-import { TasmotaControlComponent } from './tasmota-control/tasmota-control.component';
-import { PiholeControlComponent } from './pihole-control/pihole-control.component';
-
-
-@NgModule({
- declarations: [
- AppComponent,
- LinksComponent,
- TasmotaControlComponent,
- PiholeControlComponent
- ],
- imports: [
- BrowserModule,
- AppRoutingModule,
- HttpClientModule
- ],
- providers: [],
- bootstrap: [AppComponent]
-})
-export class AppModule { }
diff --git a/pihole_patch (2)/src/app/pihole-control/pihole-control.component.css b/pihole_patch (2)/src/app/pihole-control/pihole-control.component.css
deleted file mode 100644
index c3ea46a..0000000
--- a/pihole_patch (2)/src/app/pihole-control/pihole-control.component.css
+++ /dev/null
@@ -1,98 +0,0 @@
-/* src/app/pihole-control/pihole-control.component.css */
-.pihole-control {
- padding: 20px;
-}
-
-.status-row {
- margin: 15px 0;
- font-size: 1.1rem;
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 10px;
-}
-
-.status-label {
- font-weight: bold;
-}
-
-/* Status-Badge: farbcodiert */
-.status-badge {
- display: inline-block;
- padding: 6px 14px;
- border-radius: 14px;
- color: white;
- font-weight: bold;
-}
-
-/* Blocking aktiv -> rot */
-.status-badge.blocking {
- background-color: #d9534f;
-}
-
-/* kein Blocking -> grün */
-.status-badge.no-blocking {
- background-color: #28a745;
-}
-
-/* unbekannt -> grau */
-.status-badge.unknown {
- background-color: #888;
-}
-
-.timer {
- color: #555;
- font-style: italic;
-}
-
-.control-block {
- margin: 20px 0;
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
-}
-
-.control-block button {
- padding: 10px 20px;
- border-radius: 5px;
- border: none;
- cursor: pointer;
- font-size: 1rem;
-}
-
-.control-block button:disabled {
- opacity: 0.6;
- cursor: not-allowed;
-}
-
-/* Haupt-Schalter: zeigt die AKTION an.
- on (Blocking aktiv) -> Button schaltet AUS -> rötlich
- off (kein Blocking) -> Button schaltet EIN -> grünlich */
-.btn-toggle.on {
- background-color: #d9534f;
- color: white;
-}
-
-.btn-toggle.off {
- background-color: #28a745;
- color: white;
-}
-
-.btn-pause {
- background-color: #f0ad4e;
- color: white;
-}
-
-.btn-refresh {
- background-color: #e0e0e0;
- color: #333;
-}
-
-.info {
- color: #555;
-}
-
-.error {
- color: #d9534f;
- font-weight: bold;
-}
diff --git a/pihole_patch (2)/src/app/pihole-control/pihole-control.component.html b/pihole_patch (2)/src/app/pihole-control/pihole-control.component.html
deleted file mode 100644
index 17e4eb8..0000000
--- a/pihole_patch (2)/src/app/pihole-control/pihole-control.component.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
Pi-hole Steuerung
-
-
-
- Status:
-
-
- unbekannt
-
-
-
- Blocking aktiv
-
-
-
- kein Blocking
-
-
-
-
- (automatische Reaktivierung in {{ timerLabel }})
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Bitte warten …
-
{{ errorMessage }}
-
diff --git a/pihole_patch (2)/src/app/pihole-control/pihole-control.component.ts b/pihole_patch (2)/src/app/pihole-control/pihole-control.component.ts
deleted file mode 100644
index c3b79a1..0000000
--- a/pihole_patch (2)/src/app/pihole-control/pihole-control.component.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-// src/app/pihole-control/pihole-control.component.ts
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { PiholeService } from '../pihole.service';
-import { PiholeBlockingStatus } from '../pihole.interface';
-
-@Component({
- selector: 'app-pihole-control',
- templateUrl: './pihole-control.component.html',
- styleUrls: ['./pihole-control.component.css']
-})
-export class PiholeControlComponent implements OnInit, OnDestroy {
- // Aktueller Status: true = Blocking aktiv (rot), false = kein Blocking (grün)
- blockingEnabled: boolean | null = null;
-
- // Verbleibende Sekunden bis Auto-Revert (z.B. bei 30-Min-Pause), sonst null
- timer: number | null = null;
-
- loading = false;
- errorMessage: string | null = null;
-
- // Lokaler Countdown-Timer für die Anzeige der verbleibenden Pausenzeit
- private countdownHandle: any = null;
-
- constructor(private piholeService: PiholeService) {}
-
- ngOnInit(): void {
- this.refreshStatus();
- }
-
- ngOnDestroy(): void {
- this.clearCountdown();
- }
-
- /** Status vom Pi-hole abrufen. */
- refreshStatus(): void {
- this.loading = true;
- this.errorMessage = null;
- this.piholeService.getStatus().subscribe({
- next: (status) => this.applyStatus(status),
- error: (err) => this.handleError('Status konnte nicht abgerufen werden.', err)
- });
- }
-
- /** Blocking ein- bzw. ausschalten (Haupt-Schalter). */
- toggleBlocking(): void {
- // Wenn Status noch unbekannt, zuerst aktualisieren
- if (this.blockingEnabled === null) {
- this.refreshStatus();
- return;
- }
- this.loading = true;
- this.errorMessage = null;
- const action = this.blockingEnabled
- ? this.piholeService.disableBlocking()
- : this.piholeService.enableBlocking();
-
- action.subscribe({
- next: (status) => this.applyStatus(status),
- error: (err) => this.handleError('Schalten fehlgeschlagen.', err)
- });
- }
-
- /** Blocking für 30 Minuten pausieren (Auto-Revert durch Pi-hole). */
- disableFor30Minutes(): void {
- this.loading = true;
- this.errorMessage = null;
- this.piholeService.disableFor30Minutes().subscribe({
- next: (status) => this.applyStatus(status),
- error: (err) => this.handleError('30-Minuten-Pause fehlgeschlagen.', err)
- });
- }
-
- // --- intern ---------------------------------------------------------------
-
- private applyStatus(status: PiholeBlockingStatus): void {
- this.loading = false;
- this.blockingEnabled = status.blocking === 'enabled';
- this.timer = status.timer ?? null;
- this.startCountdown();
- }
-
- private handleError(message: string, err: any): void {
- this.loading = false;
- this.errorMessage = message;
- console.error(message, err);
- }
-
- // Lokaler 1s-Countdown nur für die Anzeige; nach Ablauf Status neu laden
- private startCountdown(): void {
- this.clearCountdown();
- if (this.timer && this.timer > 0) {
- this.countdownHandle = setInterval(() => {
- if (this.timer !== null && this.timer > 0) {
- this.timer--;
- if (this.timer <= 0) {
- this.clearCountdown();
- this.refreshStatus(); // Pi-hole hat vermutlich automatisch reaktiviert
- }
- }
- }, 1000);
- }
- }
-
- private clearCountdown(): void {
- if (this.countdownHandle) {
- clearInterval(this.countdownHandle);
- this.countdownHandle = null;
- }
- }
-
- /** mm:ss-Formatierung für die Timer-Anzeige. */
- get timerLabel(): string {
- if (!this.timer || this.timer <= 0) {
- return '';
- }
- const m = Math.floor(this.timer / 60);
- const s = this.timer % 60;
- return `${m}:${s.toString().padStart(2, '0')}`;
- }
-}
diff --git a/pihole_patch (2)/src/app/pihole.interface.ts b/pihole_patch (2)/src/app/pihole.interface.ts
deleted file mode 100644
index d969fbd..0000000
--- a/pihole_patch (2)/src/app/pihole.interface.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// src/app/pihole.interface.ts
-
-// Antwort von GET /api/dns/blocking
-export interface PiholeBlockingStatus {
- blocking: 'enabled' | 'disabled' | string; // FTL liefert "enabled"/"disabled"
- timer: number | null; // verbleibende Sekunden bis Auto-Revert, sonst null
- took?: number;
-}
-
-// Antwort von POST /api/auth
-export interface PiholeAuthResponse {
- session: {
- valid: boolean;
- totp?: boolean;
- sid: string | null;
- csrf?: string | null;
- validity: number; // Gültigkeit der SID in Sekunden
- message?: string | null;
- };
- took?: number;
-}
diff --git a/pihole_patch (2)/src/app/pihole.service.ts b/pihole_patch (2)/src/app/pihole.service.ts
deleted file mode 100644
index db3ddff..0000000
--- a/pihole_patch (2)/src/app/pihole.service.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-// src/app/pihole.service.ts
-import { Injectable } from '@angular/core';
-import { HttpClient, HttpHeaders } from '@angular/common/http';
-import { Observable, of, throwError } from 'rxjs';
-import { map, switchMap, catchError, tap } from 'rxjs/operators';
-import { environment } from '../environments/environment';
-import { PiholeBlockingStatus, PiholeAuthResponse } from './pihole.interface';
-
-@Injectable({
- providedIn: 'root'
-})
-export class PiholeService {
- // Basis-Pfad: im Dev über den Angular-Proxy (proxy.conf.json -> /pihole),
- // in Prod über den nginx-Reverse-Proxy (location /pihole/).
- // So bleibt das Pi-hole-Passwort serverseitig und landet nicht im JS-Bundle für Fremde.
- private apiBase = '/pihole/api';
-
- // Aktuelle Session-ID (SID) aus POST /api/auth. Wird bei 401 erneuert.
- private sid: string | null = null;
-
- constructor(private http: HttpClient) {}
-
- // --- Authentifizierung ----------------------------------------------------
-
- /**
- * Holt eine neue Session-ID per POST /api/auth.
- * Das Passwort/App-Passwort kommt aus environment (nur im internen LAN-Build).
- */
- private authenticate(): Observable {
- const body = { password: environment.piholePassword };
- return this.http
- .post(`${this.apiBase}/auth`, body, {
- headers: new HttpHeaders({ 'Content-Type': 'application/json' })
- })
- .pipe(
- map((res) => {
- if (!res?.session?.valid || !res.session.sid) {
- throw new Error('Pi-hole-Authentifizierung fehlgeschlagen (kein gültiges SID).');
- }
- return res.session.sid;
- }),
- tap((sid) => (this.sid = sid))
- );
- }
-
- /** Liefert eine gültige SID (vorhandene oder frisch geholte). */
- private ensureSid(): Observable {
- return this.sid ? of(this.sid) : this.authenticate();
- }
-
- /** HTTP-Header inkl. SID für authentifizierte Requests. */
- private authHeaders(sid: string): HttpHeaders {
- return new HttpHeaders({
- 'Content-Type': 'application/json',
- 'X-FTL-SID': sid
- });
- }
-
- /**
- * Führt einen authentifizierten Request aus und erneuert die SID
- * automatisch einmal, falls sie abgelaufen ist (401).
- */
- private withAuth(fn: (sid: string) => Observable): Observable {
- return this.ensureSid().pipe(
- switchMap((sid) => fn(sid)),
- catchError((err) => {
- if (err?.status === 401) {
- // SID abgelaufen -> verwerfen und einmalig neu anmelden
- this.sid = null;
- return this.authenticate().pipe(switchMap((sid) => fn(sid)));
- }
- return throwError(() => err);
- })
- );
- }
-
- // --- Blocking-Status ------------------------------------------------------
-
- /** Liest den aktuellen Blocking-Status: true = Blocking aktiv. */
- getStatus(): Observable {
- return this.withAuth((sid) =>
- this.http.get(`${this.apiBase}/dns/blocking`, {
- headers: this.authHeaders(sid)
- })
- );
- }
-
- /** Convenience: true wenn Blocking aktiv ist. */
- isBlockingEnabled(): Observable {
- return this.getStatus().pipe(map((s) => s.blocking === 'enabled'));
- }
-
- // --- Blocking schalten ----------------------------------------------------
-
- /**
- * Setzt den Blocking-Zustand.
- * @param enabled true = Blocking an, false = Blocking aus
- * @param timerSeconds optionaler Auto-Revert-Timer in Sekunden (null = dauerhaft)
- */
- setBlocking(enabled: boolean, timerSeconds: number | null = null): Observable {
- const body: { blocking: boolean; timer: number | null } = {
- blocking: enabled,
- timer: timerSeconds
- };
- return this.withAuth((sid) =>
- this.http.post(`${this.apiBase}/dns/blocking`, body, {
- headers: this.authHeaders(sid)
- })
- );
- }
-
- /** Blocking dauerhaft einschalten. */
- enableBlocking(): Observable {
- return this.setBlocking(true, null);
- }
-
- /** Blocking dauerhaft ausschalten. */
- disableBlocking(): Observable {
- return this.setBlocking(false, null);
- }
-
- /** Blocking für 30 Minuten deaktivieren (Auto-Revert). */
- disableFor30Minutes(): Observable {
- return this.setBlocking(false, 30 * 60);
- }
-}
diff --git a/pihole_patch (2)/src/environments/environment.prod.ts b/pihole_patch (2)/src/environments/environment.prod.ts
deleted file mode 100644
index 477c841..0000000
--- a/pihole_patch (2)/src/environments/environment.prod.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// environments/environment.prod.ts
-export const environment = {
- production: true,
- apiUrl: 'http://10.0.0.104',// Produktions-URL (leer wenn gleicher Server)
- // Pi-hole v6: App-Passwort (Settings > API > App password).
- // ACHTUNG: Im LAN-Build akzeptabel, aber Wert nicht öffentlich exponieren.
- piholePassword: 'BITTE_PIHOLE_APP_PASSWORT_EINTRAGEN'
-};
diff --git a/pihole_patch (2)/src/environments/environment.ts b/pihole_patch (2)/src/environments/environment.ts
deleted file mode 100644
index b466b9a..0000000
--- a/pihole_patch (2)/src/environments/environment.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// environments/environment.ts
-export const environment = {
- production: false,
- //apiUrl: 'http://localhost:4200' // Entwicklungs-URL
- apiUrl: 'http://10.0.0.104',
- // Pi-hole v6: App-Passwort (Settings > API > App password).
- // ACHTUNG: Im LAN-Build akzeptabel, aber Wert nicht öffentlich exponieren.
- piholePassword: 'BITTE_PIHOLE_APP_PASSWORT_EINTRAGEN'
-};
diff --git a/pihole_patch (2)/webserver-docker/nginx.conf b/pihole_patch (2)/webserver-docker/nginx.conf
deleted file mode 100644
index 8b00465..0000000
--- a/pihole_patch (2)/webserver-docker/nginx.conf
+++ /dev/null
@@ -1,54 +0,0 @@
-server {
- listen 80;
- server_name _;
-
- root /usr/share/nginx/html;
- index index.html;
-
- charset utf-8;
-
- gzip on;
- gzip_vary on;
- gzip_proxied any;
- gzip_comp_level 6;
- gzip_min_length 1024;
- gzip_types
- text/plain
- text/css
- text/javascript
- application/javascript
- application/json
- application/xml
- application/rss+xml
- image/svg+xml;
-
- # Pi-hole v6 REST API: /pihole/... wird auf den Pi-hole-Host weitergeleitet.
- # Der Browser spricht nur diesen gleichen Origin an (kein CORS/Mixed-Content).
- location /pihole/ {
- proxy_pass http://10.0.0.60/;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
-
- location / {
- try_files $uri $uri/ /index.html =404;
- }
-
- location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
- expires 30d;
- add_header Cache-Control "public, max-age=2592000, immutable";
- try_files $uri =404;
- }
-
- location = /index.html {
- add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
- expires -1;
- }
-
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- internal;
- }
-}
diff --git a/pihole_patch.zip b/pihole_patch.zip
deleted file mode 100644
index 630c3cb..0000000
Binary files a/pihole_patch.zip and /dev/null differ
diff --git a/pihole_patch/proxy.conf.json b/pihole_patch/proxy.conf.json
deleted file mode 100644
index 9353bd9..0000000
--- a/pihole_patch/proxy.conf.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "/tasmota1": {
- "target": "http://10.0.0.31",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/tasmota1": ""
- }
- },
- "/tasmota2": {
- "target": "http://10.0.0.32",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/tasmota2": ""
- }
- },
- "/pihole": {
- "target": "http://10.0.0.60",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/pihole": ""
- }
- },
- "/api": {
- "target": "http://directus.neugasse.lan",
- "secure": false,
- "changeOrigin": true,
- "pathRewrite": {
- "^/api": ""
- },
- "headers": {
- "Authorization": "Bearer KGjpxZ3tesmhEXHS7C7JbKDyW6YJlgQT"
- }
- }
-}
diff --git a/pihole_patch/src/app/app-routing.module.ts b/pihole_patch/src/app/app-routing.module.ts
deleted file mode 100644
index c546569..0000000
--- a/pihole_patch/src/app/app-routing.module.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-import { LinksComponent } from './links/links.component';
-import { TasmotaControlComponent } from './tasmota-control/tasmota-control.component';
-import { PiholeControlComponent } from './pihole-control/pihole-control.component';
-
-const routes: Routes = [
- { path: '', redirectTo: '/list-links', pathMatch: 'full'},
- { path: 'list-links', component:LinksComponent },
- { path: 'steckdosen', component:TasmotaControlComponent},
- { path: 'pihole', component:PiholeControlComponent}
-];
-
-@NgModule({
- imports: [RouterModule.forRoot(routes, {useHash: true})],
- exports: [RouterModule]
-})
-export class AppRoutingModule { }
diff --git a/pihole_patch/src/app/app.component.html b/pihole_patch/src/app/app.component.html
deleted file mode 100644
index b1b424b..0000000
--- a/pihole_patch/src/app/app.component.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/pihole_patch/src/app/app.module.ts b/pihole_patch/src/app/app.module.ts
deleted file mode 100644
index 620ad38..0000000
--- a/pihole_patch/src/app/app.module.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { NgModule } from '@angular/core';
-import { BrowserModule } from '@angular/platform-browser';
-
-import { AppRoutingModule } from './app-routing.module';
-import { AppComponent } from './app.component';
-import { HttpClientModule } from '@angular/common/http';
-import { LinksComponent } from './links/links.component';
-import * as _ from 'lodash';
-import { TasmotaControlComponent } from './tasmota-control/tasmota-control.component';
-import { PiholeControlComponent } from './pihole-control/pihole-control.component';
-
-
-@NgModule({
- declarations: [
- AppComponent,
- LinksComponent,
- TasmotaControlComponent,
- PiholeControlComponent
- ],
- imports: [
- BrowserModule,
- AppRoutingModule,
- HttpClientModule
- ],
- providers: [],
- bootstrap: [AppComponent]
-})
-export class AppModule { }
diff --git a/pihole_patch/src/app/pihole-control/pihole-control.component.css b/pihole_patch/src/app/pihole-control/pihole-control.component.css
deleted file mode 100644
index c3ea46a..0000000
--- a/pihole_patch/src/app/pihole-control/pihole-control.component.css
+++ /dev/null
@@ -1,98 +0,0 @@
-/* src/app/pihole-control/pihole-control.component.css */
-.pihole-control {
- padding: 20px;
-}
-
-.status-row {
- margin: 15px 0;
- font-size: 1.1rem;
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 10px;
-}
-
-.status-label {
- font-weight: bold;
-}
-
-/* Status-Badge: farbcodiert */
-.status-badge {
- display: inline-block;
- padding: 6px 14px;
- border-radius: 14px;
- color: white;
- font-weight: bold;
-}
-
-/* Blocking aktiv -> rot */
-.status-badge.blocking {
- background-color: #d9534f;
-}
-
-/* kein Blocking -> grün */
-.status-badge.no-blocking {
- background-color: #28a745;
-}
-
-/* unbekannt -> grau */
-.status-badge.unknown {
- background-color: #888;
-}
-
-.timer {
- color: #555;
- font-style: italic;
-}
-
-.control-block {
- margin: 20px 0;
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
-}
-
-.control-block button {
- padding: 10px 20px;
- border-radius: 5px;
- border: none;
- cursor: pointer;
- font-size: 1rem;
-}
-
-.control-block button:disabled {
- opacity: 0.6;
- cursor: not-allowed;
-}
-
-/* Haupt-Schalter: zeigt die AKTION an.
- on (Blocking aktiv) -> Button schaltet AUS -> rötlich
- off (kein Blocking) -> Button schaltet EIN -> grünlich */
-.btn-toggle.on {
- background-color: #d9534f;
- color: white;
-}
-
-.btn-toggle.off {
- background-color: #28a745;
- color: white;
-}
-
-.btn-pause {
- background-color: #f0ad4e;
- color: white;
-}
-
-.btn-refresh {
- background-color: #e0e0e0;
- color: #333;
-}
-
-.info {
- color: #555;
-}
-
-.error {
- color: #d9534f;
- font-weight: bold;
-}
diff --git a/pihole_patch/src/app/pihole-control/pihole-control.component.html b/pihole_patch/src/app/pihole-control/pihole-control.component.html
deleted file mode 100644
index 17e4eb8..0000000
--- a/pihole_patch/src/app/pihole-control/pihole-control.component.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
Pi-hole Steuerung
-
-
-
- Status:
-
-
- unbekannt
-
-
-
- Blocking aktiv
-
-
-
- kein Blocking
-
-
-
-
- (automatische Reaktivierung in {{ timerLabel }})
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Bitte warten …
-
{{ errorMessage }}
-
diff --git a/pihole_patch/src/app/pihole-control/pihole-control.component.ts b/pihole_patch/src/app/pihole-control/pihole-control.component.ts
deleted file mode 100644
index c3b79a1..0000000
--- a/pihole_patch/src/app/pihole-control/pihole-control.component.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-// src/app/pihole-control/pihole-control.component.ts
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { PiholeService } from '../pihole.service';
-import { PiholeBlockingStatus } from '../pihole.interface';
-
-@Component({
- selector: 'app-pihole-control',
- templateUrl: './pihole-control.component.html',
- styleUrls: ['./pihole-control.component.css']
-})
-export class PiholeControlComponent implements OnInit, OnDestroy {
- // Aktueller Status: true = Blocking aktiv (rot), false = kein Blocking (grün)
- blockingEnabled: boolean | null = null;
-
- // Verbleibende Sekunden bis Auto-Revert (z.B. bei 30-Min-Pause), sonst null
- timer: number | null = null;
-
- loading = false;
- errorMessage: string | null = null;
-
- // Lokaler Countdown-Timer für die Anzeige der verbleibenden Pausenzeit
- private countdownHandle: any = null;
-
- constructor(private piholeService: PiholeService) {}
-
- ngOnInit(): void {
- this.refreshStatus();
- }
-
- ngOnDestroy(): void {
- this.clearCountdown();
- }
-
- /** Status vom Pi-hole abrufen. */
- refreshStatus(): void {
- this.loading = true;
- this.errorMessage = null;
- this.piholeService.getStatus().subscribe({
- next: (status) => this.applyStatus(status),
- error: (err) => this.handleError('Status konnte nicht abgerufen werden.', err)
- });
- }
-
- /** Blocking ein- bzw. ausschalten (Haupt-Schalter). */
- toggleBlocking(): void {
- // Wenn Status noch unbekannt, zuerst aktualisieren
- if (this.blockingEnabled === null) {
- this.refreshStatus();
- return;
- }
- this.loading = true;
- this.errorMessage = null;
- const action = this.blockingEnabled
- ? this.piholeService.disableBlocking()
- : this.piholeService.enableBlocking();
-
- action.subscribe({
- next: (status) => this.applyStatus(status),
- error: (err) => this.handleError('Schalten fehlgeschlagen.', err)
- });
- }
-
- /** Blocking für 30 Minuten pausieren (Auto-Revert durch Pi-hole). */
- disableFor30Minutes(): void {
- this.loading = true;
- this.errorMessage = null;
- this.piholeService.disableFor30Minutes().subscribe({
- next: (status) => this.applyStatus(status),
- error: (err) => this.handleError('30-Minuten-Pause fehlgeschlagen.', err)
- });
- }
-
- // --- intern ---------------------------------------------------------------
-
- private applyStatus(status: PiholeBlockingStatus): void {
- this.loading = false;
- this.blockingEnabled = status.blocking === 'enabled';
- this.timer = status.timer ?? null;
- this.startCountdown();
- }
-
- private handleError(message: string, err: any): void {
- this.loading = false;
- this.errorMessage = message;
- console.error(message, err);
- }
-
- // Lokaler 1s-Countdown nur für die Anzeige; nach Ablauf Status neu laden
- private startCountdown(): void {
- this.clearCountdown();
- if (this.timer && this.timer > 0) {
- this.countdownHandle = setInterval(() => {
- if (this.timer !== null && this.timer > 0) {
- this.timer--;
- if (this.timer <= 0) {
- this.clearCountdown();
- this.refreshStatus(); // Pi-hole hat vermutlich automatisch reaktiviert
- }
- }
- }, 1000);
- }
- }
-
- private clearCountdown(): void {
- if (this.countdownHandle) {
- clearInterval(this.countdownHandle);
- this.countdownHandle = null;
- }
- }
-
- /** mm:ss-Formatierung für die Timer-Anzeige. */
- get timerLabel(): string {
- if (!this.timer || this.timer <= 0) {
- return '';
- }
- const m = Math.floor(this.timer / 60);
- const s = this.timer % 60;
- return `${m}:${s.toString().padStart(2, '0')}`;
- }
-}
diff --git a/pihole_patch/src/app/pihole.interface.ts b/pihole_patch/src/app/pihole.interface.ts
deleted file mode 100644
index d969fbd..0000000
--- a/pihole_patch/src/app/pihole.interface.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// src/app/pihole.interface.ts
-
-// Antwort von GET /api/dns/blocking
-export interface PiholeBlockingStatus {
- blocking: 'enabled' | 'disabled' | string; // FTL liefert "enabled"/"disabled"
- timer: number | null; // verbleibende Sekunden bis Auto-Revert, sonst null
- took?: number;
-}
-
-// Antwort von POST /api/auth
-export interface PiholeAuthResponse {
- session: {
- valid: boolean;
- totp?: boolean;
- sid: string | null;
- csrf?: string | null;
- validity: number; // Gültigkeit der SID in Sekunden
- message?: string | null;
- };
- took?: number;
-}
diff --git a/pihole_patch/src/app/pihole.service.ts b/pihole_patch/src/app/pihole.service.ts
deleted file mode 100644
index db3ddff..0000000
--- a/pihole_patch/src/app/pihole.service.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-// src/app/pihole.service.ts
-import { Injectable } from '@angular/core';
-import { HttpClient, HttpHeaders } from '@angular/common/http';
-import { Observable, of, throwError } from 'rxjs';
-import { map, switchMap, catchError, tap } from 'rxjs/operators';
-import { environment } from '../environments/environment';
-import { PiholeBlockingStatus, PiholeAuthResponse } from './pihole.interface';
-
-@Injectable({
- providedIn: 'root'
-})
-export class PiholeService {
- // Basis-Pfad: im Dev über den Angular-Proxy (proxy.conf.json -> /pihole),
- // in Prod über den nginx-Reverse-Proxy (location /pihole/).
- // So bleibt das Pi-hole-Passwort serverseitig und landet nicht im JS-Bundle für Fremde.
- private apiBase = '/pihole/api';
-
- // Aktuelle Session-ID (SID) aus POST /api/auth. Wird bei 401 erneuert.
- private sid: string | null = null;
-
- constructor(private http: HttpClient) {}
-
- // --- Authentifizierung ----------------------------------------------------
-
- /**
- * Holt eine neue Session-ID per POST /api/auth.
- * Das Passwort/App-Passwort kommt aus environment (nur im internen LAN-Build).
- */
- private authenticate(): Observable {
- const body = { password: environment.piholePassword };
- return this.http
- .post(`${this.apiBase}/auth`, body, {
- headers: new HttpHeaders({ 'Content-Type': 'application/json' })
- })
- .pipe(
- map((res) => {
- if (!res?.session?.valid || !res.session.sid) {
- throw new Error('Pi-hole-Authentifizierung fehlgeschlagen (kein gültiges SID).');
- }
- return res.session.sid;
- }),
- tap((sid) => (this.sid = sid))
- );
- }
-
- /** Liefert eine gültige SID (vorhandene oder frisch geholte). */
- private ensureSid(): Observable {
- return this.sid ? of(this.sid) : this.authenticate();
- }
-
- /** HTTP-Header inkl. SID für authentifizierte Requests. */
- private authHeaders(sid: string): HttpHeaders {
- return new HttpHeaders({
- 'Content-Type': 'application/json',
- 'X-FTL-SID': sid
- });
- }
-
- /**
- * Führt einen authentifizierten Request aus und erneuert die SID
- * automatisch einmal, falls sie abgelaufen ist (401).
- */
- private withAuth(fn: (sid: string) => Observable): Observable {
- return this.ensureSid().pipe(
- switchMap((sid) => fn(sid)),
- catchError((err) => {
- if (err?.status === 401) {
- // SID abgelaufen -> verwerfen und einmalig neu anmelden
- this.sid = null;
- return this.authenticate().pipe(switchMap((sid) => fn(sid)));
- }
- return throwError(() => err);
- })
- );
- }
-
- // --- Blocking-Status ------------------------------------------------------
-
- /** Liest den aktuellen Blocking-Status: true = Blocking aktiv. */
- getStatus(): Observable {
- return this.withAuth((sid) =>
- this.http.get(`${this.apiBase}/dns/blocking`, {
- headers: this.authHeaders(sid)
- })
- );
- }
-
- /** Convenience: true wenn Blocking aktiv ist. */
- isBlockingEnabled(): Observable {
- return this.getStatus().pipe(map((s) => s.blocking === 'enabled'));
- }
-
- // --- Blocking schalten ----------------------------------------------------
-
- /**
- * Setzt den Blocking-Zustand.
- * @param enabled true = Blocking an, false = Blocking aus
- * @param timerSeconds optionaler Auto-Revert-Timer in Sekunden (null = dauerhaft)
- */
- setBlocking(enabled: boolean, timerSeconds: number | null = null): Observable {
- const body: { blocking: boolean; timer: number | null } = {
- blocking: enabled,
- timer: timerSeconds
- };
- return this.withAuth((sid) =>
- this.http.post(`${this.apiBase}/dns/blocking`, body, {
- headers: this.authHeaders(sid)
- })
- );
- }
-
- /** Blocking dauerhaft einschalten. */
- enableBlocking(): Observable {
- return this.setBlocking(true, null);
- }
-
- /** Blocking dauerhaft ausschalten. */
- disableBlocking(): Observable {
- return this.setBlocking(false, null);
- }
-
- /** Blocking für 30 Minuten deaktivieren (Auto-Revert). */
- disableFor30Minutes(): Observable {
- return this.setBlocking(false, 30 * 60);
- }
-}
diff --git a/pihole_patch/src/environments/environment.prod.ts b/pihole_patch/src/environments/environment.prod.ts
deleted file mode 100644
index 477c841..0000000
--- a/pihole_patch/src/environments/environment.prod.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// environments/environment.prod.ts
-export const environment = {
- production: true,
- apiUrl: 'http://10.0.0.104',// Produktions-URL (leer wenn gleicher Server)
- // Pi-hole v6: App-Passwort (Settings > API > App password).
- // ACHTUNG: Im LAN-Build akzeptabel, aber Wert nicht öffentlich exponieren.
- piholePassword: 'BITTE_PIHOLE_APP_PASSWORT_EINTRAGEN'
-};
diff --git a/pihole_patch/src/environments/environment.ts b/pihole_patch/src/environments/environment.ts
deleted file mode 100644
index b466b9a..0000000
--- a/pihole_patch/src/environments/environment.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// environments/environment.ts
-export const environment = {
- production: false,
- //apiUrl: 'http://localhost:4200' // Entwicklungs-URL
- apiUrl: 'http://10.0.0.104',
- // Pi-hole v6: App-Passwort (Settings > API > App password).
- // ACHTUNG: Im LAN-Build akzeptabel, aber Wert nicht öffentlich exponieren.
- piholePassword: 'BITTE_PIHOLE_APP_PASSWORT_EINTRAGEN'
-};
diff --git a/pihole_patch/webserver-docker/nginx.conf b/pihole_patch/webserver-docker/nginx.conf
deleted file mode 100644
index 8b00465..0000000
--- a/pihole_patch/webserver-docker/nginx.conf
+++ /dev/null
@@ -1,54 +0,0 @@
-server {
- listen 80;
- server_name _;
-
- root /usr/share/nginx/html;
- index index.html;
-
- charset utf-8;
-
- gzip on;
- gzip_vary on;
- gzip_proxied any;
- gzip_comp_level 6;
- gzip_min_length 1024;
- gzip_types
- text/plain
- text/css
- text/javascript
- application/javascript
- application/json
- application/xml
- application/rss+xml
- image/svg+xml;
-
- # Pi-hole v6 REST API: /pihole/... wird auf den Pi-hole-Host weitergeleitet.
- # Der Browser spricht nur diesen gleichen Origin an (kein CORS/Mixed-Content).
- location /pihole/ {
- proxy_pass http://10.0.0.60/;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
-
- location / {
- try_files $uri $uri/ /index.html =404;
- }
-
- location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
- expires 30d;
- add_header Cache-Control "public, max-age=2592000, immutable";
- try_files $uri =404;
- }
-
- location = /index.html {
- add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
- expires -1;
- }
-
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- internal;
- }
-}