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 @@ -
- - -
- - -
- -
- -
-
- Most of the stuff people worry about ain’t never gonna happen anyway. -
-
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; - } -}