mirror of
https://github.com/Terranom674/Piwigo_Bratonien_Tools.git
synced 2026-09-20 18:03:16 +00:00
Compare commits
35 Commits
e8e845e039
...
b86e066e2a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b86e066e2a | ||
|
|
d995f6889b | ||
|
|
b6fc2879a0 | ||
|
|
9fa75f844a | ||
|
|
2f1d6cd832 | ||
|
|
fc6f39445e | ||
|
|
6a04b33f0c | ||
|
|
4194cf9e3b | ||
|
|
95a64fabc5 | ||
|
|
50feeccd45 | ||
|
|
d9d6968d5a | ||
|
|
4a6765f53e | ||
|
|
928c270319 | ||
|
|
812df537c3 | ||
|
|
2f8885f377 | ||
|
|
d9c9a88a66 | ||
|
|
53ab96e73b | ||
|
|
6d278c2d0d | ||
|
|
73a13a87d3 | ||
|
|
e9ed89555a | ||
|
|
e5a03b13bd | ||
|
|
98a4f49b26 | ||
|
|
b0f5c3d571 | ||
|
|
43d01d0450 | ||
|
|
38a8e76fee | ||
|
|
a38035fc92 | ||
|
|
39776d82a3 | ||
|
|
cce875c965 | ||
|
|
ae6707f3cd | ||
|
|
19abb98f23 | ||
|
|
d307f8437f | ||
|
|
97cb0b808f | ||
|
|
bfda6680f8 | ||
|
|
0f05760735 | ||
|
|
beff765612 |
@@ -1,122 +1,121 @@
|
|||||||
# Aktueller Entwicklungsstand
|
# Aktueller Entwicklungsstand
|
||||||
|
|
||||||
Stand: 17.08.2026
|
Stand: 18.08.2026
|
||||||
|
|
||||||
## Plugin
|
## Plugin
|
||||||
|
|
||||||
- Aktuelle Plugin-Version: **0.9.3.17**
|
- Aktuelle Plugin-Version: **0.9.4.2**
|
||||||
- Aktueller Entwicklungsblock: **NC Connector – Verbindungsverwaltung / laufende Optimierung**
|
- Aktueller Entwicklungsblock: **NC Connector – Endnutzer-Assistent und Konsolidierung**
|
||||||
- NC Connector ist Feature 10 und noch nicht vollständig abgeschlossen.
|
- `0.9.3.43` markiert den abgeschlossenen vorherigen Meilenstein.
|
||||||
- Solange dieser Optimierungsblock läuft, bleibt die Version im Bereich `0.9.3.x`.
|
- `0.9.4.x` ist der neue Entwicklungsblock.
|
||||||
|
|
||||||
## Aktueller GitHub-Stand
|
## Produktiver Stand
|
||||||
|
|
||||||
Der lokale NC Connector ist für den aktuellen Bratonien-Einsatz im bisherigen Login-Weg End-to-End funktionsfähig. Mit `0.9.3.17` beginnt der Umbau auf API-first für die Piwigo-Synchronisierung.
|
Der lokale NC Connector ist im aktuellen Bratonien-Einsatz End-to-End funktionsfähig:
|
||||||
|
|
||||||
Bereits erfolgreich umgesetzt und getestet:
|
- Nextcloud-Freigaben `folder` und `file` werden verarbeitet.
|
||||||
|
- Ordnerfreigaben werden als physische Piwigo-Alben synchronisiert.
|
||||||
|
- Einzeldateifreigaben werden als echte Piwigo-Orphans ohne Albumzuordnung registriert.
|
||||||
|
- Entfernte Freigaben werden aus Piwigo entfernt, Originaldateien bleiben erhalten.
|
||||||
|
- Neue physische Connector-Alben werden privat angelegt.
|
||||||
|
- API-first-Synchronisierung ist produktiv für Piwigo **16.4.0** freigegeben.
|
||||||
|
- Piwigo-Album- und Fotoinformationen werden nach dem direkten Sync aktualisiert.
|
||||||
|
- Der gemeinsame systemd-Timer verarbeitet aktive Verbindungen regelmäßig.
|
||||||
|
|
||||||
- Nextcloud-Freigaben vom Typ `folder` und `file` werden aus der Source-View gelesen.
|
## 0.9.4.1
|
||||||
- Ordnerfreigaben werden als Verzeichnisbaum im Shadow Tree gespiegelt.
|
|
||||||
- Einzeldateifreigaben werden als Symlink direkt im Galerie-Root angelegt und als echte Piwigo-Orphans ohne Albumzuordnung registriert.
|
|
||||||
- Entfernte Einzeldateifreigaben werden beim normalen Connector-Lauf aus Shadow Tree und Piwigo entfernt.
|
|
||||||
- Das Nextcloud-Original bleibt bei Löschvorgängen unangetastet.
|
|
||||||
- Connector-getriggerte neue physische Alben werden standardmäßig privat angelegt.
|
|
||||||
- Der gemeinsame systemd-Timer `bratonien-nc-connector.timer` läuft produktiv.
|
|
||||||
|
|
||||||
Neu in `0.9.3.17`:
|
Die Statusaktualisierung im Admin-UI wurde von Seiten-Reload auf gezielte DOM-Aktualisierung umgestellt. Der Poller aktualisiert nur noch **Letzter Lauf** und **Nächster Lauf**. Offene Dialoge, Details und der Assistent bleiben dadurch erhalten.
|
||||||
|
|
||||||
- die Piwigo-API ist als bevorzugter Synchronisierungsweg vorgesehen;
|
## 0.9.4.2
|
||||||
- ein erfolgreich geprüfter API-Key wird verschlüsselt in der Piwigo-Konfiguration gespeichert;
|
|
||||||
- produktive API-Synchronisierung ist ausdrücklich an die freigegebene Piwigo-Version **16.4.0** gebunden;
|
### Verbindungsassistent
|
||||||
- neuer Webservice `bratonien.nc.syncProductive` delegiert den freigegebenen produktiven Dateisync an Piwigos Core-Synchronisierung;
|
|
||||||
- `runtime/lib/piwigo-sync.php` versucht zuerst die API und fällt nur bei nicht nutzbarer API auf Benutzername/Passwort zurück;
|
Der Assistent wurde erneut aus Endnutzersicht konsolidiert:
|
||||||
- der bisherige Benutzername/Passwort-Zugang kann als verschlüsselter Fallback gespeichert oder gelöscht werden;
|
|
||||||
- ein Benutzername/Passwort-Fallback kann außerdem einmalig manuell ausgeführt werden, ohne die Zugangsdaten zu speichern;
|
1. Nextcloud-Adresse + Benutzer + Passwort.
|
||||||
- neue lokale Verbindungen werden intern als `api-first` angelegt; ein dauerhaft gespeicherter Login-Fallback ist technisch nicht mehr zwingend, sofern ein API-Zugang vorhanden ist.
|
2. Automatischer Web-Scan über HTTP/HTTPS und OCS.
|
||||||
|
3. Der Nutzer sieht zunächst keine komplette Datenbank-Technikmaske.
|
||||||
|
4. Für den Datenzugriff werden zuerst nur Lese-Benutzer und Passwort abgefragt.
|
||||||
|
5. Host = Nextcloud-Host, Port 5432 und Datenbank `nextcloud` werden nur als automatischer Prüfversuch verwendet und nicht als sicher erkannt dargestellt.
|
||||||
|
6. Scheitert dieser Versuch, fragt der Assistent gezielt die abweichende Datenbank-Adresse, Port und Datenbank ab.
|
||||||
|
7. Storage-Mounts werden nur abgefragt, wenn sie nicht sicher aus vorhandenen Zuordnungen übernommen werden können.
|
||||||
|
8. Danach folgen Showcase-Benutzer, Piwigo-API-Test/Skip und Fallback.
|
||||||
|
9. Erst der letzte Schritt legt die Verbindung dauerhaft an.
|
||||||
|
|
||||||
|
Fehler leeren die Wizard-Eingaben nicht. Geheimnisse bleiben während des Wizards serverseitig in der PHP-Sitzung und werden nicht im Browser-Web-Storage persistiert.
|
||||||
|
|
||||||
|
### Storage-Mappings
|
||||||
|
|
||||||
|
Das Storage-Format erlaubt jetzt einen leeren `source_prefix`. Damit kann ein kompletter Storage direkt auf einen lokalen Mount zeigen. Die frühere Fehlermeldung
|
||||||
|
|
||||||
|
`Storage-Zeilen muessen das Format storage_id | source_prefix | local_mount verwenden.`
|
||||||
|
|
||||||
|
tritt bei einem legitimen Root-Mapping nicht mehr auf.
|
||||||
|
|
||||||
|
### Native Aktivierung
|
||||||
|
|
||||||
|
`nc-connector-install.php` unterstützt jetzt auch API-only-Verbindungen ohne dauerhaft gespeicherten Login-Fallback. Ein Fallback ist nur nötig, wenn keine nutzbare API gespeichert ist.
|
||||||
|
|
||||||
|
### Dokumentation
|
||||||
|
|
||||||
|
README und CURRENT_STATUS wurden auf den aktuellen `0.9.4.x`-Stand gebracht und beschreiben jetzt den realen API-first-Sync, den Wizard, die Statusaktualisierung ohne Reload und die aktuelle Runtime-Architektur.
|
||||||
|
|
||||||
## Architektur NC Connector
|
## Architektur NC Connector
|
||||||
|
|
||||||
- Nextcloud bleibt die einzige dauerhafte Quelle der Originalbilder.
|
- Nextcloud ist Quelle der Originalbilder.
|
||||||
- Piwigo erhält nur die benötigte Verzeichnis-/Symlink-Struktur und erzeugt daraus seine Derivate und Cache-Dateien.
|
- Piwigo erhält einen Shadow Tree aus Verzeichnissen und Symlinks.
|
||||||
- Der Connector arbeitet mit einem PostgreSQL-Leser und den Views `piwigo_showcase_sources` und `piwigo_showcase_activity`.
|
- PostgreSQL-Reader und die Views `piwigo_showcase_sources` / `piwigo_showcase_activity` liefern Quellen und Aktivität.
|
||||||
- Laufzeitdaten liegen unter `/var/lib/bratonien-tools/nc-connector/...`.
|
- Runtime-Konfigurationen: `/etc/bratonien-tools/nc-connector/connection-*.conf`
|
||||||
- Verbindungs-Konfigurationen liegen unter `/etc/bratonien-tools/nc-connector/connection-*.conf`.
|
- State: `/var/lib/bratonien-tools/nc-connector/connection-ID`
|
||||||
- Der Shadow Tree enthält Verzeichnisse und Symlinks, keine Kopien der Originaldateien.
|
- Öffentlicher Admin-Status: Piwigo `_data/bratonien-tools/nc-connector-status/`
|
||||||
- Alle aktivierten Verbindungen werden über `runtime/run-all.sh` gemeinsam verarbeitet.
|
- Runner: `runtime/run-all.sh`
|
||||||
|
- Einzelverbindung: `runtime/sync.sh`
|
||||||
|
|
||||||
## Nextcloud-Freigabemodell
|
## Piwigo-Synchronisierung
|
||||||
|
|
||||||
Der Connector unterstützt komplette Ordner und einzelne Bilder.
|
Priorität:
|
||||||
|
|
||||||
- `folder` -> Verzeichnisstruktur spiegeln und über Piwigos Dateisynchronisierung als physische Albumstruktur einlesen;
|
|
||||||
- `file` -> Symlink direkt im Galerie-Root und anschließende Registrierung als Piwigo-Orphan.
|
|
||||||
|
|
||||||
Für einzelne Dateien wird kein künstlicher Unterordner erzeugt.
|
|
||||||
|
|
||||||
## Activity-Gate
|
|
||||||
|
|
||||||
Das Activity-Gate berücksichtigt neben dem Nextcloud-Aktivitätsstand auch die Signatur der aktuell sichtbaren Freigaben. Weiterhin vorhanden sind Quiet-Time, maximale Wartezeit, periodischer Full-Sync und verbindungsspezifischer State.
|
|
||||||
|
|
||||||
## Piwigo-Synchronisation
|
|
||||||
|
|
||||||
### API-first
|
|
||||||
|
|
||||||
Der normale Runtime-Lauf verwendet ab `0.9.3.17` folgende Priorität:
|
|
||||||
|
|
||||||
1. gespeicherter Piwigo-API-Key;
|
1. gespeicherter Piwigo-API-Key;
|
||||||
2. bei API-Fehler, fehlender Freigabe für die installierte Piwigo-Version oder nicht vorhandener API: gespeicherter Benutzername/Passwort-Fallback;
|
2. bei nicht nutzbarer API gespeicherter Benutzername/Passwort-Fallback;
|
||||||
3. ohne nutzbaren API- und Fallback-Zugang wird der Piwigo-Sync mit einer klaren Fehlermeldung abgebrochen.
|
3. ohne beide Wege Abbruch mit Fehlerstatus.
|
||||||
|
|
||||||
Nach einem Piwigo-Update wird die produktive API nicht automatisch für die neue Version freigeschaltet. Erst nach einem Kompatibilitätstest wird die Versionsfreigabe im Plugin angehoben. Bis dahin bleibt nur der Login-Fallback zulässig.
|
Produktive API-Methoden:
|
||||||
|
|
||||||
### Physische Alben
|
- `bratonien.nc.syncProductive`
|
||||||
|
- `bratonien.nc.syncOrphans`
|
||||||
|
|
||||||
`bratonien.nc.syncProductive` ist aktuell nur für Piwigo **16.4.0** freigegeben. Der Endpoint läuft als Administrator-Webservice und verwendet für den eigentlichen Dateisync Piwigos Core-Synchronisierung. Neue Connector-Alben werden dabei privat angelegt.
|
`bratonien.nc.syncProductive` ist auf Piwigo 16.4.0 versionsgebunden. Der direkte Sync verwendet Bratonien-eigene Logik auf Basis der Piwigo-Core-Funktionen, nicht das Rendern oder Fernsteuern einer Admin-Seite.
|
||||||
|
|
||||||
### Root-Dateien / Orphans
|
## Activity Gate und Shadow Tree
|
||||||
|
|
||||||
`bratonien.nc.syncOrphans` verarbeitet direkte Dateien im Galerie-Root separat. Neue Root-Dateien werden als Piwigo-Orphans ohne Albumzuordnung registriert. Entfernte Root-Dateien werden nur aus Piwigo entfernt; das Nextcloud-Original bleibt unberührt.
|
Das Activity Gate berücksichtigt:
|
||||||
|
|
||||||
## Erfolgreiche Live-Tests bis 0.9.3.16
|
- Aktivitätsstand;
|
||||||
|
- Share-Fingerprint;
|
||||||
|
- Quiet-Time;
|
||||||
|
- Max-Wartezeit;
|
||||||
|
- periodischen Full-Sync;
|
||||||
|
- Reparaturbedarf bei beschädigter lokaler Struktur.
|
||||||
|
|
||||||
Bestätigt wurden:
|
Der Shadow-Tree-Wechsel besitzt einen Rollback auf den vorherigen Baum, falls das Umschalten fehlschlägt.
|
||||||
|
|
||||||
- Ordnerfreigaben werden korrekt eingelesen;
|
## Sicherheit
|
||||||
- Einzeldateifreigaben werden als `file` erkannt und im Galerie-Root verlinkt;
|
|
||||||
- Orphan-Anlage und Orphan-Löschung funktionieren;
|
|
||||||
- physische neue Connector-Alben werden privat angelegt;
|
|
||||||
- der automatische Timer kann regulär laufen.
|
|
||||||
|
|
||||||
## Offener Test für 0.9.3.17
|
- Nextcloud-Originale werden nicht gelöscht.
|
||||||
|
- Connector-Zugangsdaten werden verschlüsselt gespeichert.
|
||||||
|
- Wizard-Secrets bleiben serverseitig.
|
||||||
|
- Storage-Pfade werden validiert.
|
||||||
|
- SQL-View-Namen werden validiert.
|
||||||
|
- Piwigo-API ist versionsgebunden.
|
||||||
|
- Mutierende Admin-Aktionen verwenden Post/Redirect/Get, sodass Browser-Reloads sie nicht erneut ausführen.
|
||||||
|
- Self-Updates sind an einen konkreten Commit und SHA-256 gebunden.
|
||||||
|
|
||||||
Vor Abschluss des API-first-Blocks muss noch live geprüft werden:
|
## Noch offen
|
||||||
|
|
||||||
1. Plugin-Update auf `0.9.3.17`;
|
- vollständiger Remote-Nextcloud-Adapter ohne direkten PostgreSQL-/Storage-Zugriff;
|
||||||
2. API-Key im NC Connector prüfen und speichern;
|
- weitere Bereinigung alter Legacy-Migrationshelfer, sobald sie nicht mehr benötigt werden;
|
||||||
3. produktiver Lauf verwendet `bratonien.nc.syncProductive` erfolgreich;
|
- weitere Endnutzer-Optimierung des Assistenten auf Basis realer Fehler- und Installationspfade.
|
||||||
4. Ordner- und Orphan-Sync bleiben unverändert korrekt;
|
|
||||||
5. gespeicherter Login-Fallback übernimmt bei absichtlich nicht nutzbarer API;
|
|
||||||
6. einmaliger Fallback funktioniert ohne Speicherung;
|
|
||||||
7. gelöschter Fallback wird vom Runtime-Lauf nicht mehr verwendet.
|
|
||||||
|
|
||||||
## Sicherheit / Betriebsmodell
|
|
||||||
|
|
||||||
- Nextcloud bleibt Eigentümer der Originaldateien.
|
|
||||||
- Der Connector löscht keine Originaldateien aus den Storage-Mounts.
|
|
||||||
- API- und Login-Zugangsdaten werden verschlüsselt gespeichert und nicht in das Repository geschrieben.
|
|
||||||
- Die API hat Vorrang, ist aber streng versionsgebunden.
|
|
||||||
- Benutzername/Passwort ist nur Fallback und kann dauerhaft, einmalig oder gar nicht verwendet werden.
|
|
||||||
- Neue Connector-Alben werden privat angelegt.
|
|
||||||
|
|
||||||
## Bekannte offene Punkte
|
|
||||||
|
|
||||||
Der NC Connector bleibt im Entwicklungsblock `0.9.3.x`. Nach erfolgreichem API-first-Livetest folgen insbesondere:
|
|
||||||
|
|
||||||
- Bereinigung und Vereinfachung der Verbindungsverwaltung im Admin-UI;
|
|
||||||
- Remote-Nextcloud-Adapter;
|
|
||||||
- Entscheidung über verbleibende Legacy-/Migrationshilfen;
|
|
||||||
- weitere Komfortfunktionen für Status, Diagnose und Administration.
|
|
||||||
|
|
||||||
## Repository-Fallback
|
## Repository-Fallback
|
||||||
|
|
||||||
Während der Entwicklung bleibt GitHub das führende Repository. Zusätzlich existiert auf dem privaten Gitea-System ein Pull-Mirror als Ausfall-/Fallback-Ebene.
|
GitHub bleibt das führende Repository. Das private Gitea-System dient weiterhin als Mirror/Fallback.
|
||||||
|
|||||||
391
README.md
391
README.md
@@ -1,303 +1,228 @@
|
|||||||
# Piwigo Bratonien Tools
|
# Piwigo Bratonien Tools
|
||||||
|
|
||||||
Modular aufgebautes Piwigo-Plugin mit erweiterten Werkzeugen fuer Administration, Bildverarbeitung, Nextcloud-Anbindung und Frontend-Funktionen.
|
Modulares Piwigo-Plugin für Administration, Bildverarbeitung, geschützte Freigaben, Fotoauswahl und die Anbindung von Nextcloud an Piwigo.
|
||||||
|
|
||||||
Das Projekt ist aus der Bratonien-Piwigo-Installation entstanden, wird aber bewusst so entwickelt, dass die einzelnen Funktionen moeglichst neutral und auch ausserhalb dieser Installation nutzbar bleiben.
|
Aktuelle Plugin-Version: **0.9.4.2**
|
||||||
|
|
||||||
Aktuelle Plugin-Version: **0.9.3.16**
|
## Grundprinzip
|
||||||
|
|
||||||
## Funktionsumfang
|
Bratonien Tools erweitert Piwigo um Funktionen, die in der Bratonien-Installation benötigt werden, hält die einzelnen Module aber möglichst unabhängig voneinander.
|
||||||
|
|
||||||
### NC Connector
|
Für die Administration gilt: Der normale Nutzer sieht Aufgaben, Entscheidungen und Ergebnisse. Technische Interna werden automatisch ermittelt oder bleiben hinter ausdrücklich technischen Einstellungen verborgen.
|
||||||
|
|
||||||
Der NC Connector verwaltet Nextcloud-Quellen fuer Piwigo und fuehrt den regelmaessigen Abgleich mit einer Plugin-eigenen Sync-Runtime aus.
|
## NC Connector
|
||||||
|
|
||||||
Aktuell vorhanden:
|
Der NC Connector synchronisiert ausdrücklich freigegebene Nextcloud-Inhalte mit Piwigo.
|
||||||
|
|
||||||
- mehrere Connector-Verbindungen im eigenen Datenmodell
|
### Datenmodell
|
||||||
- lokale Verbindungen direkt in Bratonien Tools anlegen
|
|
||||||
- verschluesselte Speicherung von PostgreSQL- und Piwigo-Sync-Zugangsdaten
|
|
||||||
- lokaler Nextcloud-Adapter ueber PostgreSQL-Reader und explizite Storage-Mounts
|
|
||||||
- Verifikation von PostgreSQL-Verbindung, Source-View, Activity-View und Storage-Mounts
|
|
||||||
- native Aktivierung einer verifizierten Verbindung ohne vorherige Legacy-Installation
|
|
||||||
- eigener State pro Verbindung unter `/var/lib/bratonien-tools/nc-connector/connection-ID`
|
|
||||||
- gemeinsame Runtime-Konfiguration unter `/etc/bratonien-tools/nc-connector/`
|
|
||||||
- Multi-Connection-Runner unter `runtime/run-all.sh`
|
|
||||||
- gemeinsame Zeitsteuerung ueber `bratonien-nc-connector.timer`
|
|
||||||
- Verbindungen koennen kontrolliert deaktiviert und danach geloescht werden
|
|
||||||
- Anzeige von Timer-Status, letztem Lauf, letztem Ergebnis und naechstem geplanten Lauf
|
|
||||||
- Plugin-eigene Sync-Runtime unter `runtime/`
|
|
||||||
- Activity-Gate mit Quiet-Time, Max-Wartezeit, Share-Fingerprint und periodischem Full-Sync
|
|
||||||
- Shadow Tree fuer Piwigo ohne Kopie der Originaldateien
|
|
||||||
- Unterstuetzung von Nextcloud-Freigaben fuer komplette Ordner und einzelne Dateien
|
|
||||||
- Ordnerfreigaben werden als physische Piwigo-Alben synchronisiert
|
|
||||||
- neu importierte Connector-Alben werden direkt als privat angelegt
|
|
||||||
- einzelne Dateifreigaben werden direkt im Galerie-Root verlinkt und als echte Piwigo-Orphans registriert
|
|
||||||
- entfernte Einzeldateifreigaben werden beim naechsten Lauf wieder aus Piwigo entfernt, ohne das Original zu loeschen
|
|
||||||
- Originalbilder bleiben an ihrer Nextcloud-/Storage-Quelle
|
|
||||||
- lokale Piwigo-Derivate und Caches bleiben davon getrennt und koennen jederzeit neu erzeugt werden
|
|
||||||
|
|
||||||
Die Runtime baut aus den freigegebenen Nextcloud-Quellen einen Piwigo-kompatiblen Shadow Tree. Dateien werden dabei nicht in eine zweite permanente Originalbibliothek kopiert, sondern ueber Symlinks auf die vorhandenen Storage-Mounts referenziert.
|
- Nextcloud bleibt die Quelle der Originaldateien.
|
||||||
|
- Originalbilder werden nicht in eine zweite dauerhafte Bibliothek kopiert.
|
||||||
|
- Ein Shadow Tree erzeugt die für Piwigo benötigte Verzeichnisstruktur mit Symlinks.
|
||||||
|
- Ordnerfreigaben werden als physische Piwigo-Alben synchronisiert.
|
||||||
|
- Einzeldateifreigaben werden im Galerie-Root verlinkt und als echte Piwigo-Orphans ohne Albumzuordnung registriert.
|
||||||
|
- Entfernte Freigaben werden aus Shadow Tree und Piwigo entfernt, das Nextcloud-Original bleibt erhalten.
|
||||||
|
- Neu importierte physische Connector-Alben werden privat angelegt.
|
||||||
|
|
||||||
#### Freigabemodell
|
### Verbindungsassistent
|
||||||
|
|
||||||
Der Connector unterscheidet zwei Quelltypen:
|
Neue Verbindungen werden bevorzugt mit einem Endnutzer-Assistenten angelegt.
|
||||||
|
|
||||||
- `folder` - komplette Ordnerfreigabe; der Verzeichnisbaum wird gespiegelt und ueber Piwigos normale Dateisynchronisierung als Albumstruktur eingelesen
|
Ablauf:
|
||||||
- `file` - einzelne Dateifreigabe; die Datei wird direkt als Symlink im Galerie-Root angelegt und separat als Piwigo-Orphan ohne Albumzuordnung registriert
|
|
||||||
|
|
||||||
Fuer Einzeldateien wird kein kuenstlicher Unterordner erzeugt. Entfernt Nextcloud eine solche Freigabe, entfernt der Connector den Root-Symlink und anschliessend nur den zugehoerigen Piwigo-Datenbankeintrag. Das Original bleibt unangetastet.
|
1. Nextcloud-Adresse, Benutzer und Passwort eingeben.
|
||||||
|
2. Der Assistent prüft HTTP/HTTPS, Nextcloud-Status und Anmeldung.
|
||||||
|
3. Vorhandene passende Connector-Daten werden automatisch wiederverwendet, wenn sie sicher zugeordnet werden können.
|
||||||
|
4. Für den Nextcloud-Datenzugriff wird zunächst nur der eingerichtete Lese-Benutzer mit Passwort abgefragt. Der übliche PostgreSQL-Weg wird automatisch geprüft.
|
||||||
|
5. Nur wenn diese Prüfung scheitert, werden abweichende Datenbank-Adresse, Port und Datenbankname gezielt abgefragt.
|
||||||
|
6. Nicht automatisch zuordenbare Storage-Mounts werden einzeln bestätigt.
|
||||||
|
7. Danach wird der Nextcloud-Benutzer gewählt, dessen Freigaben verwendet werden sollen. Empfohlen wird ein eigener `showcase`-Benutzer.
|
||||||
|
8. Piwigo-API-Zugang testen oder bewusst überspringen.
|
||||||
|
9. Optionaler bzw. bei übersprungener API verpflichtender Login-Fallback.
|
||||||
|
10. Erst der Abschluss legt die Verbindung dauerhaft an.
|
||||||
|
|
||||||
#### Neuinstallation
|
Wizard-Geheimnisse werden während der Einrichtung serverseitig in der Sitzung gehalten. Ein fehlgeschlagener Test leert die Eingaben nicht. Ein API-Test verändert die dauerhafte API-Konfiguration erst beim erfolgreichen Abschluss des Assistenten.
|
||||||
|
|
||||||
Eine frische Piwigo-Installation benoetigt keinen vorher vorhandenen `piwigo-sync` mehr.
|
Die vollständige technische Maske bleibt weiterhin über **Ohne Assistent anlegen** sowie bei bestehenden Verbindungen unter **Technische Einstellungen** erreichbar.
|
||||||
|
|
||||||
Der vorgesehene Ablauf ist:
|
### Lokaler Adapter
|
||||||
|
|
||||||
1. lokale Nextcloud-Verbindung in Bratonien Tools anlegen
|
Der derzeit produktive Adapter verwendet:
|
||||||
2. PostgreSQL, Views und Storage-Mounts pruefen
|
|
||||||
3. die angezeigte Root-Aktivierung ausfuehren
|
|
||||||
4. der Installer legt Runtime-Konfiguration, Secrets, State-Verzeichnis sowie systemd-Service und -Timer an
|
|
||||||
5. vor der Aktivierung wird ein echter Lauf mit der Plugin-Runtime getestet
|
|
||||||
|
|
||||||
Der gemeinsame Service verarbeitet alle aktivierten `connection-*.conf`-Dateien nacheinander. Dadurch koennen mehrere Verbindungen mit einer gemeinsamen Zeitsteuerung betrieben werden.
|
- PostgreSQL-Reader mit minimalen Leserechten;
|
||||||
|
- Source-View `piwigo_showcase_sources`;
|
||||||
|
- Activity-View `piwigo_showcase_activity`;
|
||||||
|
- explizite Storage-Zuordnungen auf bereits vorhandene lokale Mounts;
|
||||||
|
- Plugin-eigene Runtime und getrennten State pro Verbindung.
|
||||||
|
|
||||||
#### Migration bestehender Installationen
|
Ein Storage-Mapping besteht aus `storage_id`, einem optionalen `source_prefix` und dem lokalen Mount. Ein leerer Prefix ist zulässig und bedeutet, dass der Mount direkt dem Storage-Root entspricht.
|
||||||
|
|
||||||
Fuer bestehende Installationen, die zuvor den separaten `piwigo-sync` aus `Proxmox_Scripts` verwendet haben, existiert weiterhin ein kontrollierter Migrationsweg:
|
### Activity Gate
|
||||||
|
|
||||||
- bestehende Verbindung einmalig importieren
|
Der regelmäßige Lauf berücksichtigt:
|
||||||
- Connector-Kopie unabhaengig verifizieren
|
|
||||||
- kontrollierten Cutover vorbereiten
|
|
||||||
- Connector-Timer aktivieren und Legacy-Timer deaktivieren
|
|
||||||
- Runtime vom bisherigen `/opt/piwigo-sync` auf die Plugin-eigene Runtime umstellen
|
|
||||||
- Legacy-Bestand entfernen
|
|
||||||
- den verbliebenen Laufzeit-State anschliessend mit `nc-connector-normalize.php` in die native Struktur unter `/var/lib/bratonien-tools/nc-connector/` ueberfuehren
|
|
||||||
|
|
||||||
Nach abgeschlossener Migration werden `/opt/piwigo-sync`, `/etc/piwigo-sync`, `piwigo-sync.service` und `piwigo-sync.timer` nicht mehr benoetigt.
|
- Nextcloud-Aktivität;
|
||||||
|
- Signatur der sichtbaren Freigaben;
|
||||||
|
- Quiet-Time;
|
||||||
|
- maximale Wartezeit;
|
||||||
|
- periodischen Full-Sync;
|
||||||
|
- notwendigen Reparaturlauf bei beschädigtem Shadow Tree oder fehlenden Piwigo-Alben.
|
||||||
|
|
||||||
### Bildcache
|
### Piwigo-Synchronisierung
|
||||||
|
|
||||||
- Piwigo-Bildcache gezielt leeren
|
Der produktive Lauf ist API-first:
|
||||||
- vorhandene Bildderivate neu erzeugen
|
|
||||||
- Cache-Aufbau als Worker-Prozess starten und abbrechen
|
|
||||||
- Worker-Einstellungen verwalten
|
|
||||||
- Originalbilder bleiben unangetastet
|
|
||||||
|
|
||||||
### Wasserzeichenverwaltung
|
1. `bratonien.nc.syncProductive` für physische Alben und Bilder;
|
||||||
|
2. `bratonien.nc.syncOrphans` für Root-Dateien;
|
||||||
|
3. Benutzername/Passwort nur als optionaler Fallback.
|
||||||
|
|
||||||
Erweiterte Wasserzeichenverwaltung als Alternative zur einfachen Piwigo-Standardkonfiguration.
|
Die produktive direkte API-Synchronisierung ist aktuell ausdrücklich für **Piwigo 16.4.0** freigegeben. Bei einer anderen Piwigo-Version wird sie nicht stillschweigend als kompatibel angenommen.
|
||||||
|
|
||||||
Unter anderem vorhanden:
|
Der direkte produktive Sync führt außerdem die für den normalen Piwigo-Import relevanten Nacharbeiten aus, darunter Albuminformationen, globale Ränge, Bildinformationen, Pfade und Cache-Invalidierung. Er ruft dafür keine Admin-Seite fernsteuernd auf.
|
||||||
|
|
||||||
- eigene Wasserzeichendateien
|
### Runtime
|
||||||
- Wasserzeichenprofile
|
|
||||||
- Standardprofil
|
|
||||||
- albumbezogene Regeln
|
|
||||||
- getrennte Einstellungen fuer Positionierung und Verarbeitung
|
|
||||||
- eigener Runtime-Filter fuer Piwigo-Bildderivate
|
|
||||||
- Aktivierung und Deaktivierung der erweiterten Wasserzeichen-Engine
|
|
||||||
|
|
||||||
Beim Aktivieren wird die bisherige Piwigo-Wasserzeichenkonfiguration gesichert und nicht dauerhaft ueberschrieben. Beim Deaktivieren kann sie wiederhergestellt werden.
|
Aktive Verbindungen werden über einen gemeinsamen systemd-Timer verarbeitet:
|
||||||
|
|
||||||
### Bilddateien & Pfade
|
- `bratonien-nc-connector.timer`
|
||||||
|
- `bratonien-nc-connector.service`
|
||||||
|
- `runtime/run-all.sh`
|
||||||
|
- `runtime/sync.sh`
|
||||||
|
|
||||||
Verwaltung eigener Bilddateien fuer die Piwigo-Installation.
|
Verbindungsspezifische Konfigurationen liegen unter `/etc/bratonien-tools/nc-connector/`, State-Daten unter `/var/lib/bratonien-tools/nc-connector/connection-ID`.
|
||||||
|
|
||||||
- Upload nach `local/bratonien/assets/`
|
Der Shadow-Tree-Austausch besitzt einen Rollback: Scheitert der Wechsel auf den neuen Baum, wird der vorherige Galeriebaum wiederhergestellt.
|
||||||
- Vorschau vorhandener Dateien
|
|
||||||
- Anzeige von Dateiname, Pfad, Abmessungen und Dateigroesse
|
|
||||||
- Dateien loeschen
|
|
||||||
- konfigurierbare PHP-Uploadgrenzen ueber `.user.ini`
|
|
||||||
- Validierung von `upload_max_filesize` und `post_max_size`
|
|
||||||
|
|
||||||
### Oeffentliche Bildauswahl fuer Batch Downloader
|
### Statusanzeige
|
||||||
|
|
||||||
Erweitert Albumseiten um die Moeglichkeit, einzelne Bilder fuer einen Download auszuwaehlen.
|
Die Administration zeigt letzten und nächsten Lauf. Der Status wird regelmäßig im Hintergrund abgefragt. Dafür wird die Admin-Seite **nicht** neu geladen; offene Dialoge, Assistenten und aufgeklappte Bereiche bleiben erhalten.
|
||||||
|
|
||||||
- Auswahlmodus direkt in der Albumansicht
|
### Bestehende Verbindungen
|
||||||
- einzelne Bilder markieren
|
|
||||||
- alle Bilder auswaehlen oder Auswahl aufheben
|
|
||||||
- nur die ausgewaehlten Bilder an Batch Downloader uebergeben
|
|
||||||
- vorhandene Piwigo- und Batch-Downloader-Berechtigungen bleiben wirksam
|
|
||||||
- getrennte Freigabe fuer Gaeste, registrierte Benutzer und Gruppen
|
|
||||||
|
|
||||||
**Abhaengigkeit:** Das Piwigo-Plugin Batch Downloader muss installiert und aktiv sein.
|
Bestehende Verbindungen können:
|
||||||
|
|
||||||
Die ZIP-Erstellung wird nicht dupliziert. Bratonien Tools uebergibt lediglich die berechtigten Bild-IDs an Batch Downloader.
|
- umbenannt;
|
||||||
|
- geprüft;
|
||||||
|
- technisch bearbeitet, solange sie nicht aktiv sind;
|
||||||
|
- deaktiviert und anschließend gelöscht werden.
|
||||||
|
|
||||||
### Fortlaufende Bildtitel in der Stapelverarbeitung
|
Das Löschen einer Connector-Verbindung entfernt keine Nextcloud-Originale und keine Piwigo-Bilder. Erreichbare Connector-eigene Status-/State-Daten der Verbindung werden bereinigt.
|
||||||
|
|
||||||
Erweitert Piwigos globale Stapelverarbeitung um die Aktion **Fortlaufende Bildtitel**.
|
### Legacy-Migration
|
||||||
|
|
||||||
- arbeitet ausschliesslich mit der aktuellen Piwigo-Auswahl
|
Für ältere Installationen mit dem früheren separaten `piwigo-sync` existieren weiterhin Migrations-/Cutover-Helfer. Neue Installationen sollen den nativen Connector-Weg verwenden.
|
||||||
- frei waehlbares Titelpraefix
|
|
||||||
- frei waehlbare Startnummer
|
|
||||||
- einstellbare Stellenzahl mit fuehrenden Nullen
|
|
||||||
- direkte Vorschau des resultierenden Titelformats
|
|
||||||
- wahlweise alle ausgewaehlten Titel ueberschreiben oder nur leere beziehungsweise typische Kamera-/Importtitel ersetzen
|
|
||||||
- Reihenfolge nach Dateiname, Aufnahmedatum oder aktueller Albumreihenfolge
|
|
||||||
- vorhandene individuelle Titel koennen gezielt geschuetzt werden
|
|
||||||
- physische Dateinamen werden nicht veraendert
|
|
||||||
|
|
||||||
Beispiel: `Samt 2026 - 001`, `Samt 2026 - 002`, `Samt 2026 - 003`.
|
## Bildcache
|
||||||
|
|
||||||
Die Albumreihenfolge steht nur zur Verfuegung, wenn die Stapelverarbeitung auf genau ein Album ohne rekursive Unteralben gefiltert ist.
|
- Piwigo-Bildderivate gezielt leeren;
|
||||||
|
- vorhandene Bildgrößen neu erzeugen;
|
||||||
|
- Cache-Aufbau als Worker-Prozess starten und abbrechen;
|
||||||
|
- Worker-Zahl automatisch oder manuell konfigurieren;
|
||||||
|
- Originalbilder bleiben unangetastet.
|
||||||
|
|
||||||
### Albumzugriff verwalten
|
## Wasserzeichenverwaltung
|
||||||
|
|
||||||
In der Administrationsoberflaeche koennen Alben direkt zwischen **oeffentlich** und **privat** umgeschaltet werden.
|
- eigene Wasserzeichendateien;
|
||||||
|
- Wasserzeichenprofile;
|
||||||
|
- globale Regeln für öffentliche/private Alben;
|
||||||
|
- Album-Ausnahmen und Vererbung;
|
||||||
|
- Position, Transparenz und Skalierung;
|
||||||
|
- eigener Runtime-Filter für Piwigo-Derivate;
|
||||||
|
- bisherige Piwigo-Wasserzeichenkonfiguration wird beim Aktivieren gesichert.
|
||||||
|
|
||||||
- Albumliste mit Suchfeld
|
## Bilddateien und Pfade
|
||||||
- paginierte Darstellung
|
|
||||||
- Umschalten des Zugriffs direkt aus Bratonien Tools
|
|
||||||
- Verwendung nativer Piwigo-Icons fuer den Zugriffsstatus
|
|
||||||
- beim Sperren eines Albums behaelt der aktuell handelnde Benutzer automatisch direkten Zugriff
|
|
||||||
|
|
||||||
Der Schutz vor versehentlichem Selbstaussperren greift auch dann, wenn ein Album ueber Piwigos eigene Album-Zugriffsverwaltung von oeffentlich auf privat gesetzt wird.
|
- Upload nach `local/bratonien/assets/`;
|
||||||
|
- Vorschau, Abmessungen und Dateigröße;
|
||||||
|
- Löschen verwalteter Assets;
|
||||||
|
- konfigurierbare PHP-Uploadgrenzen über `.user.ini`, sofern die Serverkonfiguration dies zulässt.
|
||||||
|
|
||||||
### Geschuetzte Albumfreigaben
|
## Fotoauswahl und Batch Downloader
|
||||||
|
|
||||||
Private Alben koennen direkt mit Bratonien Tools geteilt werden. ShareAlbum oder ein anderes Freigabe-Plugin ist dafuer nicht erforderlich.
|
Bratonien Tools erweitert Albumseiten um eine öffentliche bzw. berechtigungsabhängige Bildauswahl und übergibt nur die ausgewählten Bild-IDs an das Piwigo-Plugin Batch Downloader.
|
||||||
|
|
||||||
- eigener individueller Freigabelink pro Freigabe
|
**Abhängigkeit:** Batch Downloader muss installiert und aktiv sein.
|
||||||
- Passwort **optional**; ohne Passwort reicht der nicht erratbare Link
|
|
||||||
- Passwoerter werden nur als Hash gespeichert
|
|
||||||
- integrierter Generator fuer sichere Passwoerter
|
|
||||||
- erzeugte Passwoerter koennen angezeigt und kopiert werden
|
|
||||||
- optionales Ablaufdatum
|
|
||||||
- optionaler Freigabe-Tag, um mehrere Freigaben desselben Albums auseinanderzuhalten
|
|
||||||
- eigener technischer Piwigo-Benutzer pro Freigabe
|
|
||||||
- der technische Benutzer erhaelt nur Zugriff auf das freigegebene private Album
|
|
||||||
- aktive Freigabelinks werden in der Administration angezeigt und koennen direkt kopiert werden
|
|
||||||
- bei aelteren, nicht rekonstruierbaren Links kann ein neuer Link erzeugt werden; der bisherige Link wird dabei ungueltig
|
|
||||||
- Freigaben koennen einzeln widerrufen werden
|
|
||||||
- beim Widerruf wird der technische Benutzer samt Sitzungen und Albumrecht entfernt
|
|
||||||
- wird das Album geloescht, werden zugehoerige Freigaben automatisch bereinigt
|
|
||||||
|
|
||||||
Freigabe-Tokens werden nicht im Klartext gespeichert. Fuer aktuelle Freigaben kann der Link deterministisch aus dem technischen Freigabebenutzer, dem Album und einem lokal gespeicherten Secret rekonstruiert werden; in der Datenbank liegt nur der Hash des Tokens.
|
## Fortlaufende Bildtitel
|
||||||
|
|
||||||
### Erweiterte Bildnavigation
|
Die globale Piwigo-Stapelverarbeitung erhält die Aktion **Fortlaufende Bildtitel** mit Präfix, Startnummer, Stellenzahl, Sortierung und Schutz vorhandener individueller Titel. Physische Dateinamen werden nicht verändert.
|
||||||
|
|
||||||
Auf der Bilddetailseite werden die vorhandenen Piwigo-Image-Maps weiterverwendet und responsiv neu aufgeteilt.
|
## Albumzugriff
|
||||||
|
|
||||||
Die Bildflaeche besteht aus vier Navigationszonen:
|
Alben können in Bratonien Tools zwischen öffentlich und privat umgeschaltet werden. Beim Sperren wird verhindert, dass sich der handelnde Benutzer versehentlich selbst aussperrt.
|
||||||
|
|
||||||
- links: vorheriges Bild
|
## Geschützte Albumfreigaben
|
||||||
- rechts: naechstes Bild
|
|
||||||
- oben mittig: zurueck zur Vorschau beziehungsweise Albumansicht
|
|
||||||
- Mitte: PhotoSwipe / Vollbildansicht
|
|
||||||
|
|
||||||
Die Zonen werden anhand der aktuell dargestellten Bildgroesse berechnet und bei Groessenaenderungen neu gesetzt.
|
Private Alben können über eigene Freigabelinks geteilt werden:
|
||||||
|
|
||||||
Fuer die sichtbaren Hover-Elemente werden neutrale CSS-Klassen bereitgestellt. Das Plugin enthaelt bewusst kein Bratonien-spezifisches Farbschema. Individuelles Branding kann ueber Theme- oder Custom-CSS erfolgen.
|
- optionales Passwort;
|
||||||
|
- optionales Ablaufdatum;
|
||||||
|
- eigener technischer Piwigo-Benutzer pro Freigabe;
|
||||||
|
- minimaler Albumzugriff;
|
||||||
|
- Hash-Speicherung von Passwörtern und Tokens;
|
||||||
|
- Widerruf und automatische Bereinigung beim Löschen eines Albums.
|
||||||
|
|
||||||
### Selbstaktualisierung
|
## Erweiterte Bildnavigation
|
||||||
|
|
||||||
Bratonien Tools kann den aktuellen Stand des GitHub-Repositories pruefen und sich aus der Administration heraus aktualisieren.
|
Die Piwigo-Bilddetailseite erhält responsive Navigationszonen für vorheriges Bild, nächstes Bild, Rückkehr zur Übersicht und PhotoSwipe/Vollbild.
|
||||||
|
|
||||||
- Versionspruefung gegen `main.inc.php` im GitHub-Repository
|
## Selbstaktualisierung
|
||||||
- Update-Pruefung wird kurzzeitig zwischengespeichert
|
|
||||||
- Updates duerfen nur vom Piwigo-Webmaster ausgefuehrt werden
|
Der integrierte Updater:
|
||||||
- Download des aktuellen `main`-Branches als ZIP
|
|
||||||
- Pruefung auf `ZipArchive` und Schreibrechte des Plugin-Verzeichnisses
|
- liest den Zielstand aus GitHub;
|
||||||
- detailliertere Downloadfehler
|
- bindet ein Update an einen konkreten Commit statt an einen während des Updates beweglichen Branch-Stand;
|
||||||
- Status wird nach Update-Pruefungen und Aktionen neu eingelesen
|
- prüft Version und SHA-256 des Zielstands;
|
||||||
|
- erstellt vor dem Austausch ein Backup;
|
||||||
|
- nutzt Post/Redirect/Get, damit ein Browser-Reload keine schreibende Aktion erneut ausführt;
|
||||||
|
- verlangt Webmaster-Rechte, `ZipArchive` und ausreichende Schreibrechte.
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
|
|
||||||
Das Plugin ist modular aufgebaut. Administrative Werkzeuge werden getrennt implementiert und ueber eine zentrale Registry eingebunden. Frontend-, Runtime- und Piwigo-Integrationen liegen ebenfalls in eigenen Modulen.
|
Wichtige Dateien und Verzeichnisse:
|
||||||
|
|
||||||
Wichtige Bestandteile:
|
- `main.inc.php` – Plugin-Einstieg und Runtime-Hooks
|
||||||
|
- `admin.php` – zentraler Admin-Controller mit Post/Redirect/Get
|
||||||
- `main.inc.php` - Plugin-Einstieg, Runtime-Module und Admin-Menue
|
- `include/tool_registry.inc.php` – Registry administrativer Aktionen
|
||||||
- `admin.php` - zentraler Admin-Controller
|
- `include/nc_connector.inc.php` – Datenmodell und gemeinsame Connector-Funktionen
|
||||||
- `include/tool_registry.inc.php` - Registry der administrativen Aktionen
|
- `include/nc_connector_wizard.inc.php` – Endnutzer-Assistent und Connection-Bearbeitung
|
||||||
- `include/nc_connector.inc.php` - Connector-Datenmodell, Legacy-Import und gemeinsame Low-Level-Funktionen
|
- `include/nc_connector_manage.inc.php` – Credential-Format, Storage-Mappings, Verifikation und Löschen
|
||||||
- `include/nc_connector_manage.inc.php` - native Connection-Verwaltung und Verifikation
|
- `include/nc_connector_create_api.inc.php` – API-first-Verbindungserstellung
|
||||||
- `include/nc_connector_takeover.inc.php` - kontrollierte Legacy-Uebergabe
|
- `include/nc_connector_piwigo_api.inc.php` – API-Zugang und Fallback-Verwaltung
|
||||||
- `include/nc_connector_system.inc.php` - Timer- und Laufzeitstatus ueber alle aktiven Verbindungen
|
- `include/nc_connector_system.inc.php` – Timer- und Laufzeitstatus
|
||||||
- `include/nc_connector_ws.inc.php` - read-only Paritaets-/Synchronisationspruefung ueber den Piwigo-Webservice
|
- `include/nc_productive_ws.inc.php` – produktiver Piwigo-Dateisync
|
||||||
- `include/nc_orphan_ws.inc.php` - produktive Synchronisation einzelner Root-Dateien als Piwigo-Orphans
|
- `include/nc_orphan_ws.inc.php` – Orphan-Synchronisierung
|
||||||
- `runtime/sync.sh` - Plugin-eigene Sync-Runtime einer Verbindung
|
- `runtime/lib/activity_gate.py` – Activity Gate
|
||||||
- `runtime/run-all.sh` - gemeinsamer Runner fuer alle installierten Verbindungen
|
- `runtime/lib/build_manifest.py` – Auflösung der Nextcloud-Freigaben auf Storage-Mounts
|
||||||
- `runtime/lib/activity_gate.py` - Activity-Gate, Share-Fingerprint und zeitgesteuerte Reconciliation
|
- `runtime/lib/shadow_tree.py` – atomarer Shadow Tree mit Rollback
|
||||||
- `runtime/lib/build_manifest.py` - Aufloesung von Ordner- und Dateifreigaben auf konfigurierte Storage-Mounts
|
- `runtime/lib/piwigo-sync.php` – API-first-Piwigo-Sync mit Fallback
|
||||||
- `runtime/lib/shadow_tree.py` - Aufbau des Piwigo-kompatiblen Shadow Trees ohne Kopieren der Originale
|
- `runtime/run-all.sh` – Multi-Connection-Runner
|
||||||
- `runtime/lib/piwigo-db-check.php` - Konsistenzpruefung vorhandener Piwigo-Alben
|
- `runtime/sync.sh` – Ablauf einer Verbindung
|
||||||
- `runtime/lib/piwigo-db-sync.pl` - Ausloesen der Piwigo-Dateisynchronisierung und anschliessender Orphan-Abgleich
|
- `nc-connector-install.php` – native Root-Aktivierung
|
||||||
- `nc-connector-install.php` - native Aktivierung einer verifizierten Verbindung
|
- `nc-connector-disable.php` – Deaktivierung
|
||||||
- `nc-connector-disable.php` - kontrollierte Deaktivierung einer aktiven Verbindung
|
- `nc-connector-normalize.php` – Normalisierung älterer aktiver Verbindungen
|
||||||
- `nc-connector-normalize.php` - Ueberfuehrung einer migrierten aktiven Verbindung in den nativen State- und Multi-Connection-Aufbau
|
- `nc-connector-*-cleanup/switch/cutover` – Legacy-Migrationshelfer
|
||||||
- `nc-connector-migrate.php` - einmaliger Migrationshelfer fuer bestehende Legacy-Installationen
|
- `include/self_update.inc.php` – Self-Updater
|
||||||
- `nc-connector-cutover-v2.php` - kontrollierter Legacy-Cutover
|
- `include/album_shares.inc.php` – geschützte Albumfreigaben
|
||||||
- `nc-connector-runtime-switch.php` - Umstellung einer aktiven Legacy-Verbindung auf die Plugin-eigene Runtime
|
- `include/public_selection.inc.php` – Fotoauswahl
|
||||||
- `nc-connector-legacy-cleanup.php` - kontrollierte Entfernung alter Sync-Scripts und systemd-Units
|
- `include/batch_titles.inc.php` – fortlaufende Titel
|
||||||
- `include/public_selection.inc.php` - oeffentliche Fotoauswahl und Batch-Downloader-Anbindung
|
- `include/watermark_*.inc.php` – Wasserzeichen-Engine
|
||||||
- `include/picture_navigation.inc.php` - Einbindung der erweiterten Bildnavigation
|
- `tools/` – administrative Einzelwerkzeuge
|
||||||
- `include/batch_titles.inc.php` - fortlaufende Titelvergabe in Piwigos Stapelverarbeitung
|
- `template/`, `js/`, `css/` – Oberfläche
|
||||||
- `include/album_lock.inc.php` - Laden und Umschalten des Album-Zugriffsstatus
|
|
||||||
- `include/album_shares.inc.php` - Albumfreigaben, Freigabetokens und Schutz vor Selbstaussperren bei privaten Alben
|
|
||||||
- `include/cache_worker_settings.inc.php` - Einstellungen fuer den Cache-Worker
|
|
||||||
- `include/dependencies.inc.php` - Abhaengigkeitspruefungen
|
|
||||||
- `include/watermark_*.inc.php` - Wasserzeichen-Engine und Runtime
|
|
||||||
- `include/self_update.inc.php` - Versionspruefung und Selbstaktualisierung
|
|
||||||
- `tools/album_rules.inc.php` - albumbezogene Regeln
|
|
||||||
- `tools/asset_manager.inc.php` - Verwaltung eigener Bilddateien
|
|
||||||
- `tools/image_cache.inc.php` - Cache-Verwaltung
|
|
||||||
- `tools/watermark*.inc.php` - administrative Wasserzeichenmodule
|
|
||||||
- `main-cache-build.php` - Cache-Aufbau im Worker-Prozess
|
|
||||||
- `main-cache-status.php` - Statusschnittstelle fuer den Cache-Worker
|
|
||||||
- `js/` - Frontend- und Admin-JavaScript
|
|
||||||
- `css/` - strukturelles Plugin-CSS
|
|
||||||
- `template/` - Admin- und Frontend-Templates
|
|
||||||
- `maintain.class.php` - Piwigo-Lifecycle
|
|
||||||
|
|
||||||
## Neues administratives Tool hinzufuegen
|
|
||||||
|
|
||||||
1. Neue Implementierung unter `tools/` anlegen.
|
|
||||||
2. Datei in `include/tool_registry.inc.php` laden.
|
|
||||||
3. Handler in `bratonien_tools_get_tools()` registrieren.
|
|
||||||
4. Benoetigte Darstellung in der gemeinsamen Administrationsoberflaeche ergaenzen.
|
|
||||||
|
|
||||||
Funktionen sollten moeglichst eigenstaendig bleiben und keine Bratonien-spezifische Gestaltung voraussetzen.
|
|
||||||
|
|
||||||
## Sicherheit
|
## Sicherheit
|
||||||
|
|
||||||
Administrative Aktionen pruefen Piwigo-Berechtigungen und verwenden fuer schreibende Aktionen Piwigos CSRF-Schutz.
|
- administrative Schreibaktionen verwenden Piwigos CSRF-Schutz;
|
||||||
|
- Connector-Zugangsdaten werden verschlüsselt gespeichert;
|
||||||
Weitere Schutzmechanismen sind funktionsabhaengig, unter anderem:
|
- Wizard-Geheimnisse werden nicht in Browser-Web-Storage persistiert;
|
||||||
|
- Storage-Mounts werden explizit zugeordnet und validiert;
|
||||||
- verschluesselte Speicherung der Connector-Zugangsdaten mit einem lokalen Connector-Secret
|
- SQL-View-Namen werden vor Verwendung validiert;
|
||||||
- PostgreSQL-Reader mit minimalen Leserechten fuer lokale Nextcloud-Verbindungen
|
- gefährliche technische Pfade sind nicht Teil des normalen Wizard-Happy-Paths;
|
||||||
- explizite Storage-Zuordnungen statt automatischer Freigabe beliebiger Dateipfade
|
- produktive Piwigo-API ist versionsgebunden;
|
||||||
- Verifikation von Datenbank, Views und Mounts vor einer Aktivierung
|
- Originalbilder werden vom Connector nicht gelöscht;
|
||||||
- Runtime-Test vor der nativen Aktivierung
|
- Update-Pakete werden an Commit und Hash gebunden;
|
||||||
- Root-only-Hilfsprogramme fuer systemd-, Secret- und State-Aenderungen
|
- Root-Helfer prüfen CLI-/Root-Ausführung, bevor sie Systemdienste oder `/etc`-/`/var/lib`-Daten verändern.
|
||||||
- getrennte State-Verzeichnisse pro Connector-Verbindung
|
|
||||||
- Originalbilder werden vom NC Connector nicht in eine zweite permanente Bibliothek kopiert
|
|
||||||
- Validierung von Cache- und Dateipfaden
|
|
||||||
- Filterung ausgewaehlter Bild-IDs gegen die aktuell berechtigte Bildmenge
|
|
||||||
- Nutzung der von Piwigos Stapelverarbeitung validierten Auswahl fuer die fortlaufende Titelvergabe
|
|
||||||
- gehashte Passwoerter und gehashte Freigabetokens fuer Albumfreigaben
|
|
||||||
- nicht erratbare Freigabelinks auf Basis eines lokal erzeugten Secrets
|
|
||||||
- eigene technische Benutzer mit minimalem Albumzugriff fuer Freigaben
|
|
||||||
- automatische Bereinigung widerrufener und geloeschter Albumfreigaben
|
|
||||||
- automatischer Erhalt des eigenen Zugriffs beim Umschalten eines Albums auf privat
|
|
||||||
- Connector-importierte physische Alben werden standardmaessig privat angelegt
|
|
||||||
- Beibehaltung bestehender Piwigo- und Plugin-Berechtigungen
|
|
||||||
- kontrollierte Uploadziele und Uploadgrenzen
|
|
||||||
- kein Zugriff auf Originalbilder beim Leeren des Bildcaches
|
|
||||||
- Webmaster-Pruefung, Schreibbarkeitspruefung und kontrolliertes temporaeres Arbeitsverzeichnis bei Selbstupdates
|
|
||||||
|
|
||||||
## Styling und Anpassung
|
|
||||||
|
|
||||||
Bratonien Tools soll Funktion und Gestaltung voneinander trennen. Plugin-eigene Styles dienen daher nur der technischen Darstellung und Positionierung.
|
|
||||||
|
|
||||||
Farben, Schatten, Hover-Effekte und individuelles Branding sollten ueber das aktive Piwigo-Theme oder Custom CSS umgesetzt werden.
|
|
||||||
|
|
||||||
## Entwicklungsstand
|
## Entwicklungsstand
|
||||||
|
|
||||||
Das Plugin befindet sich weiterhin in aktiver Entwicklung. Mit Version **0.9.3.16** ist der lokale NC-Connector fuer den aktuellen Bratonien-Einsatz End-to-End funktionsfaehig: Ordner- und Einzeldateifreigaben werden erkannt, der Shadow Tree wird automatisch gepflegt, Piwigo wird synchronisiert, Einzeldateien werden als Orphans verwaltet und der regelmaessige Lauf erfolgt ueber den gemeinsamen systemd-Timer.
|
`0.9.4.x` ist der aktuelle Entwicklungsblock. Der lokale NC Connector ist End-to-End produktiv im Einsatz. Der Verbindungsassistent und die Verwaltung werden weiter aus Endnutzersicht konsolidiert. Ein vollständig entfernter Nextcloud-Adapter ohne direkten PostgreSQL-/Storage-Zugriff ist noch nicht umgesetzt.
|
||||||
|
|
||||||
Als naechster groesserer Connector-Ausbauschritt bleibt der Remote-Nextcloud-Adapter offen.
|
|
||||||
|
|||||||
40
admin.php
40
admin.php
@@ -15,6 +15,32 @@ $errors = array();
|
|||||||
$self_update_override = null;
|
$self_update_override = null;
|
||||||
$nc_piwigo_api_test = null;
|
$nc_piwigo_api_test = null;
|
||||||
|
|
||||||
|
// Post/Redirect/Get: never leave a mutating Bratonien Tools action as the
|
||||||
|
// browser's current request. Otherwise a normal reload can submit the same
|
||||||
|
// action again (for example an update, delete or create action).
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && !empty($_SESSION['bratonien_tools_flash']) && is_array($_SESSION['bratonien_tools_flash']))
|
||||||
|
{
|
||||||
|
$flash = $_SESSION['bratonien_tools_flash'];
|
||||||
|
unset($_SESSION['bratonien_tools_flash']);
|
||||||
|
|
||||||
|
if (!empty($flash['messages']) && is_array($flash['messages']))
|
||||||
|
{
|
||||||
|
$messages = array_values($flash['messages']);
|
||||||
|
}
|
||||||
|
if (!empty($flash['errors']) && is_array($flash['errors']))
|
||||||
|
{
|
||||||
|
$errors = array_values($flash['errors']);
|
||||||
|
}
|
||||||
|
if (!empty($flash['self_update']) && is_array($flash['self_update']))
|
||||||
|
{
|
||||||
|
$self_update_override = $flash['self_update'];
|
||||||
|
}
|
||||||
|
if (!empty($flash['nc_piwigo_api_test']) && is_array($flash['nc_piwigo_api_test']))
|
||||||
|
{
|
||||||
|
$nc_piwigo_api_test = $flash['nc_piwigo_api_test'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$_SERVER['REQUEST_METHOD'] === 'POST'
|
$_SERVER['REQUEST_METHOD'] === 'POST'
|
||||||
&& empty($_POST)
|
&& empty($_POST)
|
||||||
@@ -59,6 +85,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bratonien_tool']))
|
|||||||
$errors[] = $e->getMessage();
|
$errors[] = $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$_SESSION['bratonien_tools_flash'] = array(
|
||||||
|
'messages' => $messages,
|
||||||
|
'errors' => $errors,
|
||||||
|
'self_update' => $self_update_override,
|
||||||
|
'nc_piwigo_api_test' => $nc_piwigo_api_test,
|
||||||
|
);
|
||||||
|
|
||||||
|
$redirect_url = get_root_url().'admin.php?page=plugin-'.BRATONIEN_TOOLS_ID;
|
||||||
|
if (!headers_sent())
|
||||||
|
{
|
||||||
|
header('Location: '.$redirect_url, true, 303);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$watermark = bratonien_tools_get_watermark_data();
|
$watermark = bratonien_tools_get_watermark_data();
|
||||||
|
|||||||
@@ -16,13 +16,9 @@ function bratonien_tools_nc_connector_create_local_api_first()
|
|||||||
'nc_activity_view' => 'Activity-View',
|
'nc_activity_view' => 'Activity-View',
|
||||||
'nc_gallery_root' => 'Galerie-Pfad',
|
'nc_gallery_root' => 'Galerie-Pfad',
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($required as $field => $label)
|
foreach ($required as $field => $label)
|
||||||
{
|
{
|
||||||
if (trim((string)($_POST[$field] ?? '')) === '')
|
if (trim((string)($_POST[$field] ?? '')) === '') throw new RuntimeException($label.' fehlt.');
|
||||||
{
|
|
||||||
throw new RuntimeException($label.' fehlt.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$fallback_user = trim((string)($_POST['nc_piwigo_user'] ?? ''));
|
$fallback_user = trim((string)($_POST['nc_piwigo_user'] ?? ''));
|
||||||
@@ -33,102 +29,67 @@ function bratonien_tools_nc_connector_create_local_api_first()
|
|||||||
}
|
}
|
||||||
|
|
||||||
$api = bratonien_tools_nc_api_credentials();
|
$api = bratonien_tools_nc_api_credentials();
|
||||||
if (($api['key_id'] ?? '') === '' && $fallback_user === '')
|
$validated_pending_api = (string)($_POST['nc_api_validated'] ?? '') === '1';
|
||||||
|
if (($api['key_id'] ?? '') === '' && $fallback_user === '' && !$validated_pending_api)
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Es ist weder ein gespeicherter Piwigo-API-Zugang noch ein Benutzername/Passwort-Fallback vorhanden.');
|
throw new RuntimeException('Es ist weder ein geprüfter Piwigo-API-Zugang noch ein Benutzername/Passwort-Fallback vorhanden.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$port = (int)($_POST['nc_port'] ?? 5432);
|
$port = (int)($_POST['nc_port'] ?? 5432);
|
||||||
if ($port < 1 || $port > 65535)
|
if ($port < 1 || $port > 65535) throw new RuntimeException('Ungueltiger PostgreSQL-Port.');
|
||||||
{
|
|
||||||
throw new RuntimeException('Ungueltiger PostgreSQL-Port.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$gallery_root = rtrim(trim((string)$_POST['nc_gallery_root']), '/');
|
$gallery_root = rtrim(trim((string)$_POST['nc_gallery_root']), '/');
|
||||||
if (strpos($gallery_root, './') === 0)
|
if (strpos($gallery_root, './') === 0)
|
||||||
{
|
{
|
||||||
$piwigo_root = realpath(PHPWG_ROOT_PATH);
|
$piwigo_root = realpath(PHPWG_ROOT_PATH);
|
||||||
if ($piwigo_root === false)
|
if ($piwigo_root === false) throw new RuntimeException('Piwigo-Stammverzeichnis konnte nicht aufgelöst werden.');
|
||||||
{
|
|
||||||
throw new RuntimeException('Piwigo-Stammverzeichnis konnte nicht aufgelöst werden.');
|
|
||||||
}
|
|
||||||
$gallery_root = rtrim($piwigo_root, '/').'/'.ltrim(substr($gallery_root, 2), '/');
|
$gallery_root = rtrim($piwigo_root, '/').'/'.ltrim(substr($gallery_root, 2), '/');
|
||||||
}
|
}
|
||||||
if ($gallery_root === '' || $gallery_root[0] !== '/')
|
if ($gallery_root === '' || $gallery_root[0] !== '/') throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
|
||||||
{
|
|
||||||
throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config = array(
|
$config = array(
|
||||||
'host' => trim((string)$_POST['nc_host']),
|
'host'=>trim((string)$_POST['nc_host']),
|
||||||
'port' => (string)$port,
|
'port'=>(string)$port,
|
||||||
'database' => trim((string)$_POST['nc_database']),
|
'database'=>trim((string)$_POST['nc_database']),
|
||||||
'user' => trim((string)$_POST['nc_user']),
|
'user'=>trim((string)$_POST['nc_user']),
|
||||||
'source_view' => trim((string)$_POST['nc_source_view']),
|
'source_view'=>trim((string)$_POST['nc_source_view']),
|
||||||
'activity_view' => trim((string)$_POST['nc_activity_view']),
|
'activity_view'=>trim((string)$_POST['nc_activity_view']),
|
||||||
'gallery_root' => $gallery_root,
|
'gallery_root'=>$gallery_root,
|
||||||
'state_dir' => '',
|
'state_dir'=>'','status_file'=>'',
|
||||||
'status_file' => '',
|
'quiet_seconds'=>max(0,(int)($_POST['nc_quiet_seconds'] ?? 120)),
|
||||||
'quiet_seconds' => max(0, (int)($_POST['nc_quiet_seconds'] ?? 120)),
|
'max_wait_seconds'=>max(60,(int)($_POST['nc_max_wait_seconds'] ?? 900)),
|
||||||
'max_wait_seconds' => max(60, (int)($_POST['nc_max_wait_seconds'] ?? 900)),
|
'full_sync_seconds'=>max(300,(int)($_POST['nc_full_sync_seconds'] ?? 86400)),
|
||||||
'full_sync_seconds' => max(300, (int)($_POST['nc_full_sync_seconds'] ?? 86400)),
|
'storages'=>bratonien_tools_nc_connector_parse_storages($_POST['nc_storages'] ?? ''),
|
||||||
'storages' => bratonien_tools_nc_connector_parse_storages($_POST['nc_storages'] ?? ''),
|
'origin'=>'native','piwigo_auth'=>'api-first',
|
||||||
'origin' => 'native',
|
|
||||||
'piwigo_auth' => 'api-first',
|
|
||||||
);
|
);
|
||||||
|
foreach (array('nextcloud_url'=>'nc_nextcloud_url','showcase_user'=>'nc_showcase_user','nextcloud_access_user'=>'nc_access_user','nextcloud_product'=>'nc_product','nextcloud_version'=>'nc_version') as $config_key=>$post_key)
|
||||||
foreach (array(
|
|
||||||
'nextcloud_url' => 'nc_nextcloud_url',
|
|
||||||
'showcase_user' => 'nc_showcase_user',
|
|
||||||
'nextcloud_access_user' => 'nc_access_user',
|
|
||||||
'nextcloud_product' => 'nc_product',
|
|
||||||
'nextcloud_version' => 'nc_version',
|
|
||||||
) as $config_key => $post_key)
|
|
||||||
{
|
{
|
||||||
$value = trim((string)($_POST[$post_key] ?? ''));
|
$value=trim((string)($_POST[$post_key] ?? '')); if($value!=='') $config[$config_key]=$value;
|
||||||
if ($value !== '')
|
|
||||||
{
|
|
||||||
$config[$config_key] = $value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bratonien_tools_nc_connector_view_name($config['source_view']);
|
bratonien_tools_nc_connector_view_name($config['source_view']);
|
||||||
bratonien_tools_nc_connector_view_name($config['activity_view']);
|
bratonien_tools_nc_connector_view_name($config['activity_view']);
|
||||||
|
|
||||||
$table = bratonien_tools_nc_connector_table();
|
$table=bratonien_tools_nc_connector_table(); bratonien_tools_nc_connector_ensure_table(); $now=date('Y-m-d H:i:s');
|
||||||
bratonien_tools_nc_connector_ensure_table();
|
$connection_key='local-'.bin2hex(random_bytes(12));
|
||||||
$now = date('Y-m-d H:i:s');
|
$secret_blob=bratonien_tools_nc_connector_encrypt_credentials((string)$_POST['nc_db_password'],$fallback_user,$fallback_password);
|
||||||
$connection_key = 'local-'.bin2hex(random_bytes(12));
|
$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||||
$secret_blob = bratonien_tools_nc_connector_encrypt_credentials(
|
if(!is_string($config_json)) throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.');
|
||||||
(string)$_POST['nc_db_password'],
|
|
||||||
$fallback_user,
|
pwg_query("INSERT INTO `$table` (connection_key, name, adapter, enabled, takeover_state, config_json, secret_blob, created, updated) VALUES ('".pwg_db_real_escape_string($connection_key)."','".pwg_db_real_escape_string(trim((string)$_POST['nc_name']))."','local',0,'disabled','".pwg_db_real_escape_string($config_json)."','".pwg_db_real_escape_string($secret_blob)."','".pwg_db_real_escape_string($now)."','".pwg_db_real_escape_string($now)."')");
|
||||||
$fallback_password
|
$id=(int)pwg_db_insert_id();
|
||||||
);
|
if($id<1) throw new RuntimeException('Die Connector-Verbindung konnte nicht eindeutig angelegt werden.');
|
||||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$config['state_dir']='/var/lib/bratonien-tools/nc-connector/connection-'.$id;
|
||||||
if (!is_string($config_json))
|
$config['status_file']=$config['state_dir'].'/connector-status.json';
|
||||||
|
$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||||
|
if(!is_string($config_json))
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.');
|
pwg_query("DELETE FROM `$table` WHERE id=".$id." LIMIT 1");
|
||||||
|
throw new RuntimeException('Connector-Konfiguration konnte nach dem Anlegen nicht serialisiert werden.');
|
||||||
}
|
}
|
||||||
|
|
||||||
pwg_query("INSERT INTO `$table`
|
|
||||||
(connection_key, name, adapter, enabled, takeover_state, config_json, secret_blob, created, updated)
|
|
||||||
VALUES ('".pwg_db_real_escape_string($connection_key)."',
|
|
||||||
'".pwg_db_real_escape_string(trim((string)$_POST['nc_name']))."',
|
|
||||||
'local', 0, 'disabled',
|
|
||||||
'".pwg_db_real_escape_string($config_json)."',
|
|
||||||
'".pwg_db_real_escape_string($secret_blob)."',
|
|
||||||
'".pwg_db_real_escape_string($now)."',
|
|
||||||
'".pwg_db_real_escape_string($now)."')");
|
|
||||||
|
|
||||||
$id = (int)pwg_db_insert_id();
|
|
||||||
$config['state_dir'] = '/var/lib/bratonien-tools/nc-connector/connection-'.$id;
|
|
||||||
$config['status_file'] = $config['state_dir'].'/connector-status.json';
|
|
||||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
pwg_query("UPDATE `$table` SET config_json='".pwg_db_real_escape_string($config_json)."' WHERE id=".$id);
|
pwg_query("UPDATE `$table` SET config_json='".pwg_db_real_escape_string($config_json)."' WHERE id=".$id);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'message' => $fallback_user === ''
|
'connection_id'=>$id,
|
||||||
? 'Nextcloud-Verbindung wurde API-first ohne dauerhaft gespeicherten Benutzername/Passwort-Fallback angelegt. Bitte technisch pruefen und danach im LXC aktivieren.'
|
'message'=>$fallback_user==='' ? 'Nextcloud-Verbindung wurde API-first ohne dauerhaft gespeicherten Benutzername/Passwort-Fallback angelegt. Bitte technisch pruefen und danach im LXC aktivieren.' : 'Nextcloud-Verbindung wurde API-first mit verschluesseltem Benutzername/Passwort-Fallback angelegt. Bitte technisch pruefen und danach im LXC aktivieren.'
|
||||||
: 'Nextcloud-Verbindung wurde API-first mit verschluesseltem Benutzername/Passwort-Fallback angelegt. Bitte technisch pruefen und danach im LXC aktivieren.'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,304 +7,101 @@ if (!defined('PHPWG_ROOT_PATH'))
|
|||||||
function bratonien_tools_nc_connector_credentials_from_blob($blob)
|
function bratonien_tools_nc_connector_credentials_from_blob($blob)
|
||||||
{
|
{
|
||||||
$plain = bratonien_tools_nc_connector_decrypt_secret($blob);
|
$plain = bratonien_tools_nc_connector_decrypt_secret($blob);
|
||||||
if ($plain === '')
|
if ($plain === '') return array('db_password'=>'', 'piwigo_user'=>'', 'piwigo_password'=>'');
|
||||||
{
|
|
||||||
return array('db_password'=>'', 'piwigo_user'=>'', 'piwigo_password'=>'');
|
|
||||||
}
|
|
||||||
|
|
||||||
$decoded = json_decode($plain, true);
|
$decoded = json_decode($plain, true);
|
||||||
if (is_array($decoded) && isset($decoded['db_password']))
|
if (is_array($decoded) && isset($decoded['db_password']))
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'db_password' => (string)($decoded['db_password'] ?? ''),
|
'db_password'=>(string)($decoded['db_password'] ?? ''),
|
||||||
'piwigo_user' => (string)($decoded['piwigo_user'] ?? ''),
|
'piwigo_user'=>(string)($decoded['piwigo_user'] ?? ''),
|
||||||
'piwigo_password' => (string)($decoded['piwigo_password'] ?? ''),
|
'piwigo_password'=>(string)($decoded['piwigo_password'] ?? ''),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backwards compatibility for migrated 0.14.x connections.
|
|
||||||
return array('db_password'=>$plain, 'piwigo_user'=>'', 'piwigo_password'=>'');
|
return array('db_password'=>$plain, 'piwigo_user'=>'', 'piwigo_password'=>'');
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_connector_encrypt_credentials($db_password, $piwigo_user, $piwigo_password)
|
function bratonien_tools_nc_connector_encrypt_credentials($db_password, $piwigo_user, $piwigo_password)
|
||||||
{
|
{
|
||||||
$payload = json_encode(array(
|
$payload=json_encode(array('v'=>1,'db_password'=>(string)$db_password,'piwigo_user'=>(string)$piwigo_user,'piwigo_password'=>(string)$piwigo_password),JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||||
'v' => 1,
|
if(!is_string($payload)) throw new RuntimeException('Connector-Zugangsdaten konnten nicht serialisiert werden.');
|
||||||
'db_password' => (string)$db_password,
|
|
||||||
'piwigo_user' => (string)$piwigo_user,
|
|
||||||
'piwigo_password' => (string)$piwigo_password,
|
|
||||||
), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
|
|
||||||
if (!is_string($payload))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Connector-Zugangsdaten konnten nicht serialisiert werden.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return bratonien_tools_nc_connector_encrypt_secret($payload);
|
return bratonien_tools_nc_connector_encrypt_secret($payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_connector_parse_storages($raw)
|
function bratonien_tools_nc_connector_parse_storages($raw)
|
||||||
{
|
{
|
||||||
$storages = array();
|
$storages=array();
|
||||||
$lines = preg_split('/\r\n|\r|\n/', trim((string)$raw));
|
$lines=preg_split('/\r\n|\r|\n/',trim((string)$raw));
|
||||||
foreach ($lines as $line)
|
foreach($lines as $line)
|
||||||
{
|
{
|
||||||
$line = trim($line);
|
if(trim($line)==='') continue;
|
||||||
if ($line === '')
|
$parts=array_map('trim',explode('|',$line,3));
|
||||||
|
if(count($parts)!==3 || $parts[0]==='' || $parts[2]==='')
|
||||||
{
|
{
|
||||||
continue;
|
throw new RuntimeException('Eine Speicherzuordnung ist unvollständig. Benötigt werden Storage-ID und lokaler Speicherpfad.');
|
||||||
}
|
}
|
||||||
$parts = array_map('trim', explode('|', $line));
|
if($parts[2][0]!=='/') throw new RuntimeException('Der lokale Speicherpfad muss ein absoluter Pfad sein.');
|
||||||
if (count($parts) !== 3 || $parts[0] === '' || $parts[1] === '' || $parts[2] === '')
|
$storages[]=array(
|
||||||
{
|
'storage_id'=>$parts[0],
|
||||||
throw new RuntimeException('Storage-Zeilen muessen das Format storage_id | source_prefix | local_mount verwenden.');
|
'source_prefix'=>trim($parts[1],'/'),
|
||||||
}
|
'local_mount'=>rtrim($parts[2],'/'),
|
||||||
if ($parts[2][0] !== '/')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Der lokale Storage-Mount muss ein absoluter Pfad sein.');
|
|
||||||
}
|
|
||||||
$storages[] = array(
|
|
||||||
'storage_id' => $parts[0],
|
|
||||||
'source_prefix' => $parts[1],
|
|
||||||
'local_mount' => rtrim($parts[2], '/'),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!$storages)
|
if(!$storages) throw new RuntimeException('Mindestens ein Speicherort muss zugeordnet werden.');
|
||||||
{
|
|
||||||
throw new RuntimeException('Mindestens ein Storage muss konfiguriert werden.');
|
|
||||||
}
|
|
||||||
return $storages;
|
return $storages;
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_connector_create_local()
|
function bratonien_tools_nc_connector_create_local()
|
||||||
{
|
{
|
||||||
$required = array(
|
$required=array('nc_name'=>'Name','nc_host'=>'PostgreSQL-Host','nc_database'=>'Datenbank','nc_user'=>'Reader-Benutzer','nc_db_password'=>'Reader-Passwort','nc_source_view'=>'Source-View','nc_activity_view'=>'Activity-View','nc_gallery_root'=>'Galerie-Pfad','nc_piwigo_user'=>'Piwigo-Benutzer','nc_piwigo_password'=>'Piwigo-Passwort');
|
||||||
'nc_name' => 'Name',
|
foreach($required as $field=>$label)if(trim((string)($_POST[$field]??''))==='')throw new RuntimeException($label.' fehlt.');
|
||||||
'nc_host' => 'PostgreSQL-Host',
|
$port=(int)($_POST['nc_port']??5432);if($port<1||$port>65535)throw new RuntimeException('Ungueltiger PostgreSQL-Port.');
|
||||||
'nc_database' => 'Datenbank',
|
$gallery_root=rtrim(trim((string)$_POST['nc_gallery_root']),'/');if($gallery_root===''||$gallery_root[0]!=='/')throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
|
||||||
'nc_user' => 'Reader-Benutzer',
|
$config=array('host'=>trim((string)$_POST['nc_host']),'port'=>(string)$port,'database'=>trim((string)$_POST['nc_database']),'user'=>trim((string)$_POST['nc_user']),'source_view'=>trim((string)$_POST['nc_source_view']),'activity_view'=>trim((string)$_POST['nc_activity_view']),'gallery_root'=>$gallery_root,'state_dir'=>'','status_file'=>'','quiet_seconds'=>max(0,(int)($_POST['nc_quiet_seconds']??120)),'max_wait_seconds'=>max(60,(int)($_POST['nc_max_wait_seconds']??900)),'full_sync_seconds'=>max(300,(int)($_POST['nc_full_sync_seconds']??86400)),'storages'=>bratonien_tools_nc_connector_parse_storages($_POST['nc_storages']??''),'origin'=>'native');
|
||||||
'nc_db_password' => 'Reader-Passwort',
|
bratonien_tools_nc_connector_view_name($config['source_view']);bratonien_tools_nc_connector_view_name($config['activity_view']);
|
||||||
'nc_source_view' => 'Source-View',
|
$table=bratonien_tools_nc_connector_table();bratonien_tools_nc_connector_ensure_table();$now=date('Y-m-d H:i:s');$connection_key='local-'.bin2hex(random_bytes(12));$secret_blob=bratonien_tools_nc_connector_encrypt_credentials((string)$_POST['nc_db_password'],trim((string)$_POST['nc_piwigo_user']),(string)$_POST['nc_piwigo_password']);$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);if(!is_string($config_json))throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.');
|
||||||
'nc_activity_view' => 'Activity-View',
|
pwg_query("INSERT INTO `$table` (connection_key,name,adapter,enabled,takeover_state,config_json,secret_blob,created,updated) VALUES ('".pwg_db_real_escape_string($connection_key)."','".pwg_db_real_escape_string(trim((string)$_POST['nc_name']))."','local',0,'disabled','".pwg_db_real_escape_string($config_json)."','".pwg_db_real_escape_string($secret_blob)."','".pwg_db_real_escape_string($now)."','".pwg_db_real_escape_string($now)."')");
|
||||||
'nc_gallery_root' => 'Galerie-Pfad',
|
$id=(int)pwg_db_insert_id();$config['state_dir']='/var/lib/bratonien-tools/nc-connector/connection-'.$id;$config['status_file']=$config['state_dir'].'/connector-status.json';$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);pwg_query("UPDATE `$table` SET config_json='".pwg_db_real_escape_string($config_json)."' WHERE id=".$id);
|
||||||
'nc_piwigo_user' => 'Piwigo-Benutzer',
|
|
||||||
'nc_piwigo_password' => 'Piwigo-Passwort',
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($required as $field => $label)
|
|
||||||
{
|
|
||||||
if (trim((string)($_POST[$field] ?? '')) === '')
|
|
||||||
{
|
|
||||||
throw new RuntimeException($label.' fehlt.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$port = (int)($_POST['nc_port'] ?? 5432);
|
|
||||||
if ($port < 1 || $port > 65535)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Ungueltiger PostgreSQL-Port.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$gallery_root = rtrim(trim((string)$_POST['nc_gallery_root']), '/');
|
|
||||||
if ($gallery_root === '' || $gallery_root[0] !== '/')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config = array(
|
|
||||||
'host' => trim((string)$_POST['nc_host']),
|
|
||||||
'port' => (string)$port,
|
|
||||||
'database' => trim((string)$_POST['nc_database']),
|
|
||||||
'user' => trim((string)$_POST['nc_user']),
|
|
||||||
'source_view' => trim((string)$_POST['nc_source_view']),
|
|
||||||
'activity_view' => trim((string)$_POST['nc_activity_view']),
|
|
||||||
'gallery_root' => $gallery_root,
|
|
||||||
'state_dir' => '',
|
|
||||||
'status_file' => '',
|
|
||||||
'quiet_seconds' => max(0, (int)($_POST['nc_quiet_seconds'] ?? 120)),
|
|
||||||
'max_wait_seconds' => max(60, (int)($_POST['nc_max_wait_seconds'] ?? 900)),
|
|
||||||
'full_sync_seconds' => max(300, (int)($_POST['nc_full_sync_seconds'] ?? 86400)),
|
|
||||||
'storages' => bratonien_tools_nc_connector_parse_storages($_POST['nc_storages'] ?? ''),
|
|
||||||
'origin' => 'native',
|
|
||||||
);
|
|
||||||
|
|
||||||
bratonien_tools_nc_connector_view_name($config['source_view']);
|
|
||||||
bratonien_tools_nc_connector_view_name($config['activity_view']);
|
|
||||||
|
|
||||||
$table = bratonien_tools_nc_connector_table();
|
|
||||||
bratonien_tools_nc_connector_ensure_table();
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
$connection_key = 'local-'.bin2hex(random_bytes(12));
|
|
||||||
$secret_blob = bratonien_tools_nc_connector_encrypt_credentials(
|
|
||||||
(string)$_POST['nc_db_password'],
|
|
||||||
trim((string)$_POST['nc_piwigo_user']),
|
|
||||||
(string)$_POST['nc_piwigo_password']
|
|
||||||
);
|
|
||||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
if (!is_string($config_json))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.');
|
|
||||||
}
|
|
||||||
|
|
||||||
pwg_query("INSERT INTO `$table`
|
|
||||||
(connection_key, name, adapter, enabled, takeover_state, config_json, secret_blob, created, updated)
|
|
||||||
VALUES ('".pwg_db_real_escape_string($connection_key)."',
|
|
||||||
'".pwg_db_real_escape_string(trim((string)$_POST['nc_name']))."',
|
|
||||||
'local', 0, 'disabled',
|
|
||||||
'".pwg_db_real_escape_string($config_json)."',
|
|
||||||
'".pwg_db_real_escape_string($secret_blob)."',
|
|
||||||
'".pwg_db_real_escape_string($now)."',
|
|
||||||
'".pwg_db_real_escape_string($now)."')");
|
|
||||||
|
|
||||||
$id = (int)pwg_db_insert_id();
|
|
||||||
$config['state_dir'] = '/var/lib/bratonien-tools/nc-connector/connection-'.$id;
|
|
||||||
$config['status_file'] = $config['state_dir'].'/connector-status.json';
|
|
||||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
pwg_query("UPDATE `$table` SET config_json='".pwg_db_real_escape_string($config_json)."' WHERE id=".$id);
|
|
||||||
|
|
||||||
return array('message'=>'Nextcloud-Verbindung wurde angelegt. Bitte zuerst technisch pruefen und danach im LXC aktivieren.');
|
return array('message'=>'Nextcloud-Verbindung wurde angelegt. Bitte zuerst technisch pruefen und danach im LXC aktivieren.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_connector_verify_managed()
|
function bratonien_tools_nc_connector_verify_managed()
|
||||||
{
|
{
|
||||||
$id = isset($_POST['connection_id']) ? (int)$_POST['connection_id'] : 0;
|
$id=isset($_POST['connection_id'])?(int)$_POST['connection_id']:0;$connection=bratonien_tools_nc_connector_connection($id,true);if(!$connection)throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');if((string)$connection['adapter']!=='local')throw new RuntimeException('Diese Verifikation ist derzeit nur fuer lokale Connector-Verbindungen verfuegbar.');
|
||||||
$connection = bratonien_tools_nc_connector_connection($id, true);
|
$config=$connection['config'];foreach(array('host','port','database','user','source_view','activity_view') as $key)if(trim((string)($config[$key]??''))==='')throw new RuntimeException('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.');
|
||||||
if (!$connection)
|
$credentials=bratonien_tools_nc_connector_credentials_from_blob($connection['secret_blob']??'');if($credentials['db_password']==='')throw new RuntimeException('Fuer diese Verbindung ist kein Datenbankpasswort gespeichert.');
|
||||||
|
$checks=array();$ok=true;$source_count=null;
|
||||||
|
try{bratonien_tools_nc_connector_psql($config,$credentials['db_password'],'SELECT 1');$checks[]=array('name'=>'PostgreSQL-Verbindung','ok'=>true,'detail'=>'Reader-Anmeldung erfolgreich');}catch(Throwable $e){$checks[]=array('name'=>'PostgreSQL-Verbindung','ok'=>false,'detail'=>$e->getMessage());$ok=false;}
|
||||||
|
if($ok)
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
|
try{$source_view=bratonien_tools_nc_connector_view_name($config['source_view']);$value=bratonien_tools_nc_connector_psql($config,$credentials['db_password'],'SELECT COUNT(*) FROM '.$source_view);if(!preg_match('/^\d+$/',$value))throw new RuntimeException('Source-View lieferte keinen gueltigen Zaehler.');$source_count=(int)$value;$checks[]=array('name'=>'Source-View','ok'=>true,'detail'=>$source_count.' Quelle(n) lesbar');}catch(Throwable $e){$checks[]=array('name'=>'Source-View','ok'=>false,'detail'=>$e->getMessage());$ok=false;}
|
||||||
}
|
try{$activity_view=bratonien_tools_nc_connector_view_name($config['activity_view']);bratonien_tools_nc_connector_psql($config,$credentials['db_password'],'SELECT 1 FROM '.$activity_view.' LIMIT 1');$checks[]=array('name'=>'Activity-View','ok'=>true,'detail'=>'View lesbar');}catch(Throwable $e){$checks[]=array('name'=>'Activity-View','ok'=>false,'detail'=>$e->getMessage());$ok=false;}
|
||||||
if ((string)$connection['adapter'] !== 'local')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Diese Verifikation ist derzeit nur fuer lokale Connector-Verbindungen verfuegbar.');
|
|
||||||
}
|
}
|
||||||
|
$storages=isset($config['storages'])&&is_array($config['storages'])?$config['storages']:array();$mount_points=bratonien_tools_nc_connector_mount_points();
|
||||||
|
if(!$storages){$checks[]=array('name'=>'Storage-Mounts','ok'=>false,'detail'=>'Keine Storage-Zuordnung gespeichert');$ok=false;}
|
||||||
|
else foreach($storages as $index=>$storage){$path=rtrim((string)($storage['local_mount']??''),'/');$exists=$path!==''&&is_dir($path);$readable=$exists&&is_readable($path);$mounted=$path!==''&&isset($mount_points[$path]);$storage_ok=$exists&&$readable&&$mounted;$storage_id=(string)($storage['storage_id']??('#'.($index+1)));$detail=$storage_id.' -> '.($path!==''?$path:'(kein Pfad)');if(!$exists)$detail.=' (Pfad fehlt)';elseif(!$readable)$detail.=' (nicht lesbar)';elseif(!$mounted)$detail.=' (kein aktiver Mount)';else$detail.=' (bereit)';$checks[]=array('name'=>'Storage '.($index+1),'ok'=>$storage_ok,'detail'=>$detail);if(!$storage_ok)$ok=false;}
|
||||||
|
$config['verification']=array('checked_at'=>date('Y-m-d H:i:s'),'ok'=>$ok,'source_count'=>$source_count,'checks'=>$checks);$table=bratonien_tools_nc_connector_table();$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);if(!is_string($config_json))throw new RuntimeException('Verifikationsergebnis konnte nicht gespeichert werden.');$state=$ok?'verified':((string)$connection['takeover_state']==='disabled'?'disabled':'imported');$now=date('Y-m-d H:i:s');pwg_query("UPDATE `$table` SET takeover_state='".pwg_db_real_escape_string($state)."', enabled=0, config_json='".pwg_db_real_escape_string($config_json)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id);
|
||||||
|
if(!$ok)throw new RuntimeException('Connector-Verifikation ist fehlgeschlagen. Die Verbindung bleibt deaktiviert; Details stehen im NC Connector.');return array('message'=>'Connector-Verbindung wurde erfolgreich verifiziert. PostgreSQL, Views und Storage-Mounts sind erreichbar.');
|
||||||
|
}
|
||||||
|
|
||||||
$config = $connection['config'];
|
function bratonien_tools_nc_connector_remove_tree($path)
|
||||||
foreach (array('host','port','database','user','source_view','activity_view') as $key)
|
{
|
||||||
{
|
$path=rtrim((string)$path,'/');if($path===''||$path==='/'||!file_exists($path))return true;
|
||||||
if (trim((string)($config[$key] ?? '')) === '')
|
if(is_link($path)||is_file($path))return @unlink($path);
|
||||||
{
|
$items=@scandir($path);if(!is_array($items))return false;
|
||||||
throw new RuntimeException('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.');
|
foreach($items as $item){if($item==='.'||$item==='..')continue;if(!bratonien_tools_nc_connector_remove_tree($path.'/'.$item))return false;}
|
||||||
}
|
return @rmdir($path);
|
||||||
}
|
|
||||||
|
|
||||||
$credentials = bratonien_tools_nc_connector_credentials_from_blob($connection['secret_blob'] ?? '');
|
|
||||||
if ($credentials['db_password'] === '')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Fuer diese Verbindung ist kein Datenbankpasswort gespeichert.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$checks = array();
|
|
||||||
$ok = true;
|
|
||||||
$source_count = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bratonien_tools_nc_connector_psql($config, $credentials['db_password'], 'SELECT 1');
|
|
||||||
$checks[] = array('name'=>'PostgreSQL-Verbindung', 'ok'=>true, 'detail'=>'Reader-Anmeldung erfolgreich');
|
|
||||||
}
|
|
||||||
catch (Throwable $e)
|
|
||||||
{
|
|
||||||
$checks[] = array('name'=>'PostgreSQL-Verbindung', 'ok'=>false, 'detail'=>$e->getMessage());
|
|
||||||
$ok = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($ok)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$source_view = bratonien_tools_nc_connector_view_name($config['source_view']);
|
|
||||||
$value = bratonien_tools_nc_connector_psql($config, $credentials['db_password'], 'SELECT COUNT(*) FROM '.$source_view);
|
|
||||||
if (!preg_match('/^\d+$/', $value))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Source-View lieferte keinen gueltigen Zaehler.');
|
|
||||||
}
|
|
||||||
$source_count = (int)$value;
|
|
||||||
$checks[] = array('name'=>'Source-View', 'ok'=>true, 'detail'=>$source_count.' Quelle(n) lesbar');
|
|
||||||
}
|
|
||||||
catch (Throwable $e)
|
|
||||||
{
|
|
||||||
$checks[] = array('name'=>'Source-View', 'ok'=>false, 'detail'=>$e->getMessage());
|
|
||||||
$ok = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$activity_view = bratonien_tools_nc_connector_view_name($config['activity_view']);
|
|
||||||
bratonien_tools_nc_connector_psql($config, $credentials['db_password'], 'SELECT 1 FROM '.$activity_view.' LIMIT 1');
|
|
||||||
$checks[] = array('name'=>'Activity-View', 'ok'=>true, 'detail'=>'View lesbar');
|
|
||||||
}
|
|
||||||
catch (Throwable $e)
|
|
||||||
{
|
|
||||||
$checks[] = array('name'=>'Activity-View', 'ok'=>false, 'detail'=>$e->getMessage());
|
|
||||||
$ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$storages = isset($config['storages']) && is_array($config['storages']) ? $config['storages'] : array();
|
|
||||||
$mount_points = bratonien_tools_nc_connector_mount_points();
|
|
||||||
if (!$storages)
|
|
||||||
{
|
|
||||||
$checks[] = array('name'=>'Storage-Mounts', 'ok'=>false, 'detail'=>'Keine Storage-Zuordnung gespeichert');
|
|
||||||
$ok = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach ($storages as $index => $storage)
|
|
||||||
{
|
|
||||||
$path = rtrim((string)($storage['local_mount'] ?? ''), '/');
|
|
||||||
$exists = $path !== '' && is_dir($path);
|
|
||||||
$readable = $exists && is_readable($path);
|
|
||||||
$mounted = $path !== '' && isset($mount_points[$path]);
|
|
||||||
$storage_ok = $exists && $readable && $mounted;
|
|
||||||
$storage_id = (string)($storage['storage_id'] ?? ('#'.($index + 1)));
|
|
||||||
$detail = $storage_id.' -> '.($path !== '' ? $path : '(kein Pfad)');
|
|
||||||
if (!$exists) $detail .= ' (Pfad fehlt)';
|
|
||||||
elseif (!$readable) $detail .= ' (nicht lesbar)';
|
|
||||||
elseif (!$mounted) $detail .= ' (kein aktiver Mount)';
|
|
||||||
else $detail .= ' (bereit)';
|
|
||||||
$checks[] = array('name'=>'Storage '.($index + 1), 'ok'=>$storage_ok, 'detail'=>$detail);
|
|
||||||
if (!$storage_ok) $ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$config['verification'] = array(
|
|
||||||
'checked_at' => date('Y-m-d H:i:s'),
|
|
||||||
'ok' => $ok,
|
|
||||||
'source_count' => $source_count,
|
|
||||||
'checks' => $checks,
|
|
||||||
);
|
|
||||||
$table = bratonien_tools_nc_connector_table();
|
|
||||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
if (!is_string($config_json))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Verifikationsergebnis konnte nicht gespeichert werden.');
|
|
||||||
}
|
|
||||||
$state = $ok ? 'verified' : ((string)$connection['takeover_state'] === 'disabled' ? 'disabled' : 'imported');
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
pwg_query("UPDATE `$table` SET takeover_state='".pwg_db_real_escape_string($state)."', enabled=0, config_json='".pwg_db_real_escape_string($config_json)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id);
|
|
||||||
|
|
||||||
if (!$ok)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Connector-Verifikation ist fehlgeschlagen. Die Verbindung bleibt deaktiviert; Details stehen im NC Connector.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return array('message'=>'Connector-Verbindung wurde erfolgreich verifiziert. PostgreSQL, Views und Storage-Mounts sind erreichbar.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_connector_delete()
|
function bratonien_tools_nc_connector_delete()
|
||||||
{
|
{
|
||||||
$id = (int)($_POST['connection_id'] ?? 0);
|
$id=(int)($_POST['connection_id']??0);$connection=bratonien_tools_nc_connector_connection($id,false);if(!$connection)throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');if(!empty($connection['enabled'])||(string)$connection['takeover_state']==='active')throw new RuntimeException('Eine aktive Verbindung kann nicht geloescht werden. Sie muss zuerst deaktiviert werden.');
|
||||||
$connection = bratonien_tools_nc_connector_connection($id, false);
|
$config=is_array($connection['config'])?$connection['config']:array();
|
||||||
if (!$connection)
|
$public_status=rtrim(PHPWG_ROOT_PATH,'/').'/_data/bratonien-tools/nc-connector-status/connection-'.$id.'.json';if(is_file($public_status))@unlink($public_status);
|
||||||
|
$state_dir=rtrim((string)($config['state_dir']??''),'/');
|
||||||
|
if($state_dir!==''&&strpos($state_dir,'/var/lib/bratonien-tools/nc-connector/connection-'.$id)===0&&file_exists($state_dir))
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
|
bratonien_tools_nc_connector_remove_tree($state_dir);
|
||||||
}
|
}
|
||||||
if (!empty($connection['enabled']) || (string)$connection['takeover_state'] === 'active')
|
$table=bratonien_tools_nc_connector_table();pwg_query("DELETE FROM `$table` WHERE id=".$id." LIMIT 1");
|
||||||
{
|
return array('message'=>'Connector-Verbindung und erreichbare Connector-eigene Statusdaten wurden geloescht. Nextcloud- und Piwigo-Bilder blieben unverändert.');
|
||||||
throw new RuntimeException('Eine aktive Verbindung kann nicht geloescht werden. Sie muss zuerst deaktiviert werden.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$table = bratonien_tools_nc_connector_table();
|
|
||||||
pwg_query("DELETE FROM `$table` WHERE id=".$id." LIMIT 1");
|
|
||||||
return array('message'=>'Connector-Verbindung wurde geloescht.');
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,689 +6,372 @@ if (!defined('PHPWG_ROOT_PATH'))
|
|||||||
|
|
||||||
function bratonien_tools_nc_wizard_state()
|
function bratonien_tools_nc_wizard_state()
|
||||||
{
|
{
|
||||||
$state = isset($_SESSION['bratonien_nc_wizard']) && is_array($_SESSION['bratonien_nc_wizard'])
|
$state = isset($_SESSION['bratonien_nc_wizard']) && is_array($_SESSION['bratonien_nc_wizard']) ? $_SESSION['bratonien_nc_wizard'] : array();
|
||||||
? $_SESSION['bratonien_nc_wizard']
|
|
||||||
: array();
|
|
||||||
|
|
||||||
return array_merge(array(
|
return array_merge(array(
|
||||||
'step' => 1,
|
'step'=>1,'scan_ok'=>false,'base_url'=>'','host_input'=>'','username'=>'','display_name'=>'','version'=>'','product'=>'Nextcloud',
|
||||||
'scan_ok' => false,
|
'users'=>array(),'can_list_users'=>false,'showcase_user'=>'','connection_name'=>'','scan_message'=>'',
|
||||||
'base_url' => '',
|
'technical_stage'=>'reader_credentials','technical_source'=>'Noch nicht geprüft','technical_error'=>'','technical_complete'=>false,
|
||||||
'host_input' => '',
|
'db_host'=>'','db_port'=>'5432','db_database'=>'nextcloud','db_user'=>'','db_password_set'=>false,
|
||||||
'username' => '',
|
'source_view'=>'piwigo_showcase_sources','activity_view'=>'piwigo_showcase_activity','gallery_root'=>'','storages'=>array(),'storage_candidates'=>array(),
|
||||||
'display_name' => '',
|
'api_status'=>'pending','api_username'=>'','api_error'=>'','_password'=>'','_db_password'=>'','_api_key_id'=>'','_api_key_secret'=>'',
|
||||||
'version' => '',
|
'_fallback_user'=>'','_fallback_password'=>'',
|
||||||
'product' => 'Nextcloud',
|
|
||||||
'users' => array(),
|
|
||||||
'can_list_users' => false,
|
|
||||||
'showcase_user' => '',
|
|
||||||
'connection_name' => '',
|
|
||||||
'scan_message' => '',
|
|
||||||
'technical_source' => '',
|
|
||||||
'technical_complete' => false,
|
|
||||||
'db_host' => '',
|
|
||||||
'db_port' => '5432',
|
|
||||||
'db_database' => 'nextcloud',
|
|
||||||
'db_user' => '',
|
|
||||||
'db_password_set' => false,
|
|
||||||
'source_view' => 'piwigo_showcase_sources',
|
|
||||||
'activity_view' => 'piwigo_showcase_activity',
|
|
||||||
'gallery_root' => '',
|
|
||||||
'storages' => array(),
|
|
||||||
'storage_candidates' => array(),
|
|
||||||
'api_status' => 'pending',
|
|
||||||
'api_username' => '',
|
|
||||||
'api_error' => '',
|
|
||||||
), $state);
|
), $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_store(array $state)
|
function bratonien_tools_nc_wizard_store(array $state) { $_SESSION['bratonien_nc_wizard'] = $state; }
|
||||||
{
|
function bratonien_tools_nc_wizard_reset() { unset($_SESSION['bratonien_nc_wizard']); return array('message'=>'Verbindungsassistent wurde zurückgesetzt.'); }
|
||||||
$_SESSION['bratonien_nc_wizard'] = $state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_reset()
|
|
||||||
{
|
|
||||||
unset($_SESSION['bratonien_nc_wizard']);
|
|
||||||
return array('message'=>'Verbindungsassistent wurde zurückgesetzt.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_normalize_url($host)
|
function bratonien_tools_nc_wizard_normalize_url($host)
|
||||||
{
|
{
|
||||||
$host = trim((string)$host);
|
$host=trim((string)$host);
|
||||||
if ($host === '')
|
if($host==='') throw new RuntimeException('Nextcloud-Adresse fehlt.');
|
||||||
{
|
if(!preg_match('#^https?://#i',$host)) $host='https://'.$host;
|
||||||
throw new RuntimeException('Nextcloud-Host fehlt.');
|
$parts=parse_url($host);
|
||||||
}
|
if(!is_array($parts)||empty($parts['host'])) throw new RuntimeException('Die Nextcloud-Adresse ist ungültig.');
|
||||||
|
$scheme=strtolower((string)($parts['scheme']??'https'));
|
||||||
if (!preg_match('#^https?://#i', $host))
|
if(!in_array($scheme,array('http','https'),true)) throw new RuntimeException('Nextcloud muss per HTTP oder HTTPS erreichbar sein.');
|
||||||
{
|
$url=$scheme.'://'.$parts['host'];
|
||||||
$host = 'https://'.$host;
|
if(!empty($parts['port'])) $url.=':'.(int)$parts['port'];
|
||||||
}
|
if(!empty($parts['path'])&&$parts['path']!=='/') $url.='/'.trim($parts['path'],'/');
|
||||||
|
return rtrim($url,'/');
|
||||||
$parts = parse_url($host);
|
|
||||||
if (!is_array($parts) || empty($parts['host']))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Nextcloud-Host ist ungültig.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$scheme = strtolower((string)($parts['scheme'] ?? 'https'));
|
|
||||||
if (!in_array($scheme, array('http','https'), true))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Nextcloud muss per HTTP oder HTTPS erreichbar sein.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = $scheme.'://'.$parts['host'];
|
|
||||||
if (!empty($parts['port']))
|
|
||||||
{
|
|
||||||
$url .= ':'.(int)$parts['port'];
|
|
||||||
}
|
|
||||||
if (!empty($parts['path']) && $parts['path'] !== '/')
|
|
||||||
{
|
|
||||||
$url .= '/'.trim($parts['path'], '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return rtrim($url, '/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_http($url, $username = '', $password = '', array $headers = array())
|
function bratonien_tools_nc_wizard_candidate_urls($host)
|
||||||
{
|
{
|
||||||
if (!function_exists('curl_init'))
|
$host=trim((string)$host);
|
||||||
{
|
if($host==='') throw new RuntimeException('Bitte die Adresse der Nextcloud angeben.');
|
||||||
throw new RuntimeException('cURL ist in PHP nicht verfügbar. Der Nextcloud-Scan kann nicht ausgeführt werden.');
|
if(preg_match('#^https?://#i',$host)) return array(bratonien_tools_nc_wizard_normalize_url($host));
|
||||||
}
|
return array(bratonien_tools_nc_wizard_normalize_url('https://'.$host),bratonien_tools_nc_wizard_normalize_url('http://'.$host));
|
||||||
|
}
|
||||||
|
|
||||||
$ch = curl_init($url);
|
function bratonien_tools_nc_wizard_http($url,$username='',$password='',array $headers=array())
|
||||||
$http_headers = array_merge(array('Accept: application/json'), $headers);
|
{
|
||||||
$options = array(
|
if(!function_exists('curl_init')) throw new RuntimeException('Der Server kann Nextcloud derzeit nicht per HTTP prüfen.');
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
$ch=curl_init($url);
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
$options=array(
|
||||||
CURLOPT_MAXREDIRS => 3,
|
CURLOPT_RETURNTRANSFER=>true,CURLOPT_FOLLOWLOCATION=>true,CURLOPT_MAXREDIRS=>3,CURLOPT_CONNECTTIMEOUT=>8,CURLOPT_TIMEOUT=>15,
|
||||||
CURLOPT_CONNECTTIMEOUT => 8,
|
CURLOPT_HTTPHEADER=>array_merge(array('Accept: application/json'),$headers),
|
||||||
CURLOPT_TIMEOUT => 15,
|
CURLOPT_USERAGENT=>'Bratonien-Tools-NC-Wizard/'.(function_exists('bratonien_tools_current_version')?bratonien_tools_current_version():'dev'),
|
||||||
CURLOPT_HTTPHEADER => $http_headers,
|
|
||||||
CURLOPT_USERAGENT => 'Bratonien-Tools-NC-Wizard/'.(function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : 'dev'),
|
|
||||||
);
|
);
|
||||||
if ($username !== '')
|
if($username!==''){$options[CURLOPT_HTTPAUTH]=CURLAUTH_BASIC;$options[CURLOPT_USERPWD]=$username.':'.$password;}
|
||||||
{
|
curl_setopt_array($ch,$options);
|
||||||
$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
|
$body=curl_exec($ch);$errno=curl_errno($ch);$status=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);
|
||||||
$options[CURLOPT_USERPWD] = $username.':'.$password;
|
if($body===false||$errno!==0) throw new RuntimeException('Verbindung fehlgeschlagen.');
|
||||||
}
|
return array('status'=>$status,'body'=>(string)$body);
|
||||||
curl_setopt_array($ch, $options);
|
|
||||||
|
|
||||||
$body = curl_exec($ch);
|
|
||||||
$errno = curl_errno($ch);
|
|
||||||
$error = curl_error($ch);
|
|
||||||
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if ($body === false || $errno !== 0)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Nextcloud konnte nicht erreicht werden: '.$error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array('status'=>$status, 'body'=>(string)$body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_ocs_data($body)
|
function bratonien_tools_nc_wizard_ocs_data($body)
|
||||||
{
|
{
|
||||||
$decoded = json_decode((string)$body, true);
|
$decoded=json_decode((string)$body,true);
|
||||||
if (!is_array($decoded))
|
if(!is_array($decoded)||!isset($decoded['ocs'])||!is_array($decoded['ocs'])) throw new RuntimeException('Nextcloud hat keine gültige Antwort geliefert.');
|
||||||
|
$meta=isset($decoded['ocs']['meta'])&&is_array($decoded['ocs']['meta'])?$decoded['ocs']['meta']:array();
|
||||||
|
$status=strtolower(trim((string)($meta['status']??'')));$status_code=isset($meta['statuscode'])?(int)$meta['statuscode']:0;
|
||||||
|
if($status!=='ok'&&!in_array($status_code,array(100,200),true))
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Nextcloud hat keine gültige JSON-Antwort geliefert.');
|
$message=trim((string)($meta['message']??''));
|
||||||
}
|
if($message===''||strtolower($message)==='ok') $message='Nextcloud hat die Anfrage abgelehnt.';
|
||||||
|
|
||||||
if (isset($decoded['ocs']['meta']['statuscode']) && (int)$decoded['ocs']['meta']['statuscode'] !== 100)
|
|
||||||
{
|
|
||||||
$message = (string)($decoded['ocs']['meta']['message'] ?? 'Nextcloud hat die Anfrage abgelehnt.');
|
|
||||||
throw new RuntimeException($message);
|
throw new RuntimeException($message);
|
||||||
}
|
}
|
||||||
|
return isset($decoded['ocs']['data'])&&is_array($decoded['ocs']['data'])?$decoded['ocs']['data']:array();
|
||||||
return isset($decoded['ocs']['data']) && is_array($decoded['ocs']['data']) ? $decoded['ocs']['data'] : array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_fill_profile(array &$state)
|
function bratonien_tools_nc_wizard_fill_profile(array &$state)
|
||||||
{
|
{
|
||||||
$url_host = strtolower((string)parse_url($state['base_url'], PHP_URL_HOST));
|
$url_host=strtolower((string)parse_url($state['base_url'],PHP_URL_HOST));
|
||||||
$state['db_host'] = $url_host;
|
$state['db_host']='';
|
||||||
$state['db_port'] = '5432';
|
$state['db_port']='5432';
|
||||||
$state['db_database'] = 'nextcloud';
|
$state['db_database']='nextcloud';
|
||||||
$state['source_view'] = 'piwigo_showcase_sources';
|
$state['source_view']='piwigo_showcase_sources';
|
||||||
$state['activity_view'] = 'piwigo_showcase_activity';
|
$state['activity_view']='piwigo_showcase_activity';
|
||||||
$state['gallery_root'] = rtrim(PHPWG_ROOT_PATH, '/').'/galleries/nextcloud';
|
$state['gallery_root']=rtrim(PHPWG_ROOT_PATH,'/').'/galleries/nextcloud';
|
||||||
$state['technical_source'] = 'Standardwerte vorbereitet';
|
$state['technical_stage']='reader_credentials';
|
||||||
|
$state['technical_source']='Noch nicht geprüft';
|
||||||
|
$state['technical_error']='';
|
||||||
|
$state['technical_complete']=false;
|
||||||
|
|
||||||
foreach (bratonien_tools_nc_connector_connections() as $candidate)
|
foreach(bratonien_tools_nc_connector_connections() as $candidate)
|
||||||
{
|
{
|
||||||
$config = isset($candidate['config']) && is_array($candidate['config']) ? $candidate['config'] : array();
|
$config=isset($candidate['config'])&&is_array($candidate['config'])?$candidate['config']:array();
|
||||||
$candidate_url = trim((string)($config['nextcloud_url'] ?? ''));
|
$candidate_url=trim((string)($config['nextcloud_url']??''));
|
||||||
$candidate_host = strtolower(trim((string)($config['host'] ?? '')));
|
$match=false;
|
||||||
$match = false;
|
if($candidate_url!=='')
|
||||||
|
{
|
||||||
|
try{$match=bratonien_tools_nc_wizard_normalize_url($candidate_url)===$state['base_url'];}catch(Throwable $ignored){$match=false;}
|
||||||
|
}
|
||||||
|
if(!$match) continue;
|
||||||
|
|
||||||
if ($candidate_url !== '')
|
$full=bratonien_tools_nc_connector_connection((int)$candidate['id'],true);
|
||||||
{
|
if(!$full) continue;
|
||||||
try
|
$credentials=bratonien_tools_nc_connector_credentials_from_blob($full['secret_blob']??'');
|
||||||
{
|
$state['db_host']=(string)($config['host']??'');
|
||||||
$match = bratonien_tools_nc_wizard_normalize_url($candidate_url) === $state['base_url'];
|
$state['db_port']=(string)($config['port']??'5432');
|
||||||
}
|
$state['db_database']=(string)($config['database']??'nextcloud');
|
||||||
catch (Throwable $ignored)
|
$state['db_user']=(string)($config['user']??'');
|
||||||
{
|
$state['_db_password']=(string)($credentials['db_password']??'');
|
||||||
$match = false;
|
$state['db_password_set']=$state['_db_password']!=='';
|
||||||
}
|
$state['source_view']=(string)($config['source_view']??$state['source_view']);
|
||||||
}
|
$state['activity_view']=(string)($config['activity_view']??$state['activity_view']);
|
||||||
if (!$match && $candidate_host !== '' && $candidate_host === $url_host)
|
$state['gallery_root']=(string)($config['gallery_root']??$state['gallery_root']);
|
||||||
{
|
$state['storages']=isset($config['storages'])&&is_array($config['storages'])?$config['storages']:array();
|
||||||
$match = true;
|
$state['storage_candidates']=$state['storages'];
|
||||||
}
|
$state['technical_complete']=$state['db_host']!==''&&$state['db_user']!==''&&$state['db_password_set']&&!empty($state['storages']);
|
||||||
if (!$match)
|
$state['technical_stage']=$state['technical_complete']?'ready':'reader_credentials';
|
||||||
{
|
$state['technical_source']='Passende vorhandene Verbindung übernommen';
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$full = bratonien_tools_nc_connector_connection((int)$candidate['id'], true);
|
|
||||||
if (!$full)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$credentials = bratonien_tools_nc_connector_credentials_from_blob($full['secret_blob'] ?? '');
|
|
||||||
|
|
||||||
$state['db_host'] = (string)($config['host'] ?? $state['db_host']);
|
|
||||||
$state['db_port'] = (string)($config['port'] ?? $state['db_port']);
|
|
||||||
$state['db_database'] = (string)($config['database'] ?? $state['db_database']);
|
|
||||||
$state['db_user'] = (string)($config['user'] ?? '');
|
|
||||||
$state['_db_password'] = (string)($credentials['db_password'] ?? '');
|
|
||||||
$state['db_password_set'] = $state['_db_password'] !== '';
|
|
||||||
$state['source_view'] = (string)($config['source_view'] ?? $state['source_view']);
|
|
||||||
$state['activity_view'] = (string)($config['activity_view'] ?? $state['activity_view']);
|
|
||||||
$state['gallery_root'] = (string)($config['gallery_root'] ?? $state['gallery_root']);
|
|
||||||
$state['storages'] = isset($config['storages']) && is_array($config['storages']) ? $config['storages'] : array();
|
|
||||||
$state['technical_source'] = 'Passende vorhandene Connector-Konfiguration erkannt';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$state['technical_complete'] = $state['db_host'] !== ''
|
if($state['db_host']==='') $state['db_host']=$url_host;
|
||||||
&& $state['db_port'] !== ''
|
|
||||||
&& $state['db_database'] !== ''
|
|
||||||
&& $state['db_user'] !== ''
|
|
||||||
&& !empty($state['db_password_set'])
|
|
||||||
&& $state['source_view'] !== ''
|
|
||||||
&& $state['activity_view'] !== ''
|
|
||||||
&& $state['gallery_root'] !== ''
|
|
||||||
&& !empty($state['storages']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_scan()
|
function bratonien_tools_nc_wizard_scan()
|
||||||
{
|
{
|
||||||
$host_input = trim((string)($_POST['nc_wizard_host'] ?? ''));
|
$state=bratonien_tools_nc_wizard_state();
|
||||||
$username = trim((string)($_POST['nc_wizard_user'] ?? ''));
|
$host_input=trim((string)($_POST['nc_wizard_host']??$state['host_input']));
|
||||||
$password = (string)($_POST['nc_wizard_password'] ?? '');
|
$username=trim((string)($_POST['nc_wizard_user']??$state['username']));
|
||||||
if ($username === '' || $password === '')
|
$password=array_key_exists('nc_wizard_password',$_POST)?(string)$_POST['nc_wizard_password']:(string)$state['_password'];
|
||||||
{
|
$state['step']=1;$state['host_input']=$host_input;$state['username']=$username;$state['_password']=$password;bratonien_tools_nc_wizard_store($state);
|
||||||
throw new RuntimeException('Nextcloud-Benutzer und Passwort werden für den Scan benötigt.');
|
if($username===''||$password==='') throw new RuntimeException('Nextcloud-Benutzer und Passwort werden für den Scan benötigt.');
|
||||||
}
|
|
||||||
|
|
||||||
$base_url = bratonien_tools_nc_wizard_normalize_url($host_input);
|
$base_url='';$status_data=null;
|
||||||
$status_response = bratonien_tools_nc_wizard_http($base_url.'/status.php');
|
foreach(bratonien_tools_nc_wizard_candidate_urls($host_input) as $candidate_url)
|
||||||
if ($status_response['status'] < 200 || $status_response['status'] >= 300)
|
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Unter diesem Host wurde keine erreichbare Nextcloud-Instanz erkannt (HTTP '.$status_response['status'].').');
|
try
|
||||||
}
|
{
|
||||||
$status_data = json_decode($status_response['body'], true);
|
$response=bratonien_tools_nc_wizard_http($candidate_url.'/status.php');
|
||||||
if (!is_array($status_data) || empty($status_data['installed']))
|
if($response['status']<200||$response['status']>=300) continue;
|
||||||
{
|
$candidate_status=json_decode($response['body'],true);
|
||||||
throw new RuntimeException('Der Host antwortet, wurde aber nicht als installierte Nextcloud-Instanz erkannt.');
|
if(!is_array($candidate_status)||empty($candidate_status['installed'])) continue;
|
||||||
|
$base_url=$candidate_url;$status_data=$candidate_status;break;
|
||||||
|
}
|
||||||
|
catch(Throwable $ignored){}
|
||||||
}
|
}
|
||||||
|
if($base_url===''||!is_array($status_data)) throw new RuntimeException('Unter dieser Adresse konnte keine Nextcloud erreicht werden. HTTP und HTTPS wurden automatisch geprüft.');
|
||||||
|
|
||||||
$user_response = bratonien_tools_nc_wizard_http(
|
$user_response=bratonien_tools_nc_wizard_http($base_url.'/ocs/v2.php/cloud/user?format=json',$username,$password,array('OCS-APIRequest: true'));
|
||||||
$base_url.'/ocs/v2.php/cloud/user?format=json',
|
if($user_response['status']===401||$user_response['status']===403) throw new RuntimeException('Nextcloud hat Benutzername oder Passwort abgelehnt.');
|
||||||
$username,
|
if($user_response['status']<200||$user_response['status']>=300) throw new RuntimeException('Nextcloud ist erreichbar, aber die Anmeldung konnte nicht geprüft werden.');
|
||||||
$password,
|
$user_data=bratonien_tools_nc_wizard_ocs_data($user_response['body']);
|
||||||
array('OCS-APIRequest: true')
|
|
||||||
);
|
|
||||||
if ($user_response['status'] === 401 || $user_response['status'] === 403)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Nextcloud hat Benutzername oder Passwort abgelehnt.');
|
|
||||||
}
|
|
||||||
if ($user_response['status'] < 200 || $user_response['status'] >= 300)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Nextcloud-Benutzerprüfung ist fehlgeschlagen (HTTP '.$user_response['status'].').');
|
|
||||||
}
|
|
||||||
$user_data = bratonien_tools_nc_wizard_ocs_data($user_response['body']);
|
|
||||||
|
|
||||||
$users = array();
|
$users=array();$can_list_users=false;
|
||||||
$can_list_users = false;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$users_response = bratonien_tools_nc_wizard_http(
|
$users_response=bratonien_tools_nc_wizard_http($base_url.'/ocs/v2.php/cloud/users?format=json',$username,$password,array('OCS-APIRequest: true'));
|
||||||
$base_url.'/ocs/v2.php/cloud/users?format=json',
|
if($users_response['status']>=200&&$users_response['status']<300)
|
||||||
$username,
|
|
||||||
$password,
|
|
||||||
array('OCS-APIRequest: true')
|
|
||||||
);
|
|
||||||
if ($users_response['status'] >= 200 && $users_response['status'] < 300)
|
|
||||||
{
|
{
|
||||||
$users_data = bratonien_tools_nc_wizard_ocs_data($users_response['body']);
|
$users_data=bratonien_tools_nc_wizard_ocs_data($users_response['body']);
|
||||||
if (isset($users_data['users']) && is_array($users_data['users']))
|
if(isset($users_data['users'])&&is_array($users_data['users']))
|
||||||
{
|
{
|
||||||
$users = array_values(array_filter(array_map('strval', $users_data['users'])));
|
$users=array_values(array_filter(array_map('strval',$users_data['users'])));sort($users,SORT_NATURAL|SORT_FLAG_CASE);$can_list_users=true;
|
||||||
sort($users, SORT_NATURAL | SORT_FLAG_CASE);
|
|
||||||
$can_list_users = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable $ignored)
|
catch(Throwable $ignored){}
|
||||||
|
|
||||||
|
$url_host=(string)parse_url($base_url,PHP_URL_HOST);
|
||||||
|
$state=array_merge($state,array(
|
||||||
|
'step'=>2,'scan_ok'=>true,'base_url'=>$base_url,'host_input'=>$host_input,
|
||||||
|
'username'=>(string)($user_data['id']??$username),'display_name'=>(string)($user_data['display-name']??$user_data['displayname']??''),
|
||||||
|
'version'=>(string)($status_data['versionstring']??$status_data['version']??''),'product'=>(string)($status_data['productname']??'Nextcloud'),
|
||||||
|
'users'=>$users,'can_list_users'=>$can_list_users,'showcase_user'=>'','connection_name'=>$url_host!==''?$url_host:'Nextcloud',
|
||||||
|
'scan_message'=>'Nextcloud wurde erkannt und der Benutzerzugriff wurde bestätigt.','_password'=>$password,'api_status'=>'pending','api_username'=>'','api_error'=>''
|
||||||
|
));
|
||||||
|
bratonien_tools_nc_wizard_fill_profile($state);bratonien_tools_nc_wizard_store($state);
|
||||||
|
return array('message'=>'Nextcloud wurde gefunden und der Zugang bestätigt.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function bratonien_tools_nc_wizard_finish_data_access(array &$state)
|
||||||
|
{
|
||||||
|
$config=array('host'=>$state['db_host'],'port'=>$state['db_port'],'database'=>$state['db_database'],'user'=>$state['db_user']);
|
||||||
|
$password=(string)$state['_db_password'];
|
||||||
|
bratonien_tools_nc_connector_psql($config,$password,'SELECT 1');
|
||||||
|
|
||||||
|
$source_view=bratonien_tools_nc_connector_view_name($state['source_view']);
|
||||||
|
$activity_view=bratonien_tools_nc_connector_view_name($state['activity_view']);
|
||||||
|
bratonien_tools_nc_connector_psql($config,$password,'SELECT COUNT(*) FROM '.$source_view);
|
||||||
|
bratonien_tools_nc_connector_psql($config,$password,'SELECT 1 FROM '.$activity_view.' LIMIT 1');
|
||||||
|
|
||||||
|
$rows=bratonien_tools_nc_connector_psql($config,$password,"SELECT DISTINCT storage_id::text || E'\\t' || COALESCE(source_path, '') FROM ".$source_view." ORDER BY 1");
|
||||||
|
$candidates=array();
|
||||||
|
foreach(preg_split('/\r\n|\r|\n/',trim($rows)) as $line)
|
||||||
{
|
{
|
||||||
$users = array();
|
if($line==='') continue;
|
||||||
$can_list_users = false;
|
$parts=explode("\t",$line,2);
|
||||||
|
if(count($parts)!==2) continue;
|
||||||
|
$storage_id=trim($parts[0]);$source_path=trim($parts[1],'/');
|
||||||
|
if($storage_id==='') continue;
|
||||||
|
$prefix=$source_path;
|
||||||
|
if(strpos($source_path,'/')!==false) $prefix=substr($source_path,0,strpos($source_path,'/'));
|
||||||
|
if(!isset($candidates[$storage_id])) $candidates[$storage_id]=array('storage_id'=>$storage_id,'source_prefix'=>$prefix,'local_mount'=>'');
|
||||||
}
|
}
|
||||||
|
if(!$candidates) throw new RuntimeException('Die Verbindung zur Datenquelle funktioniert, aber es wurden noch keine freigegebenen Speicherorte gefunden.');
|
||||||
|
|
||||||
$url_host = (string)parse_url($base_url, PHP_URL_HOST);
|
$known=array();
|
||||||
$state = array(
|
foreach(bratonien_tools_nc_connector_connections() as $connection)
|
||||||
'step' => 2,
|
{
|
||||||
'scan_ok' => true,
|
foreach(($connection['config']['storages']??array()) as $storage)
|
||||||
'base_url' => $base_url,
|
{
|
||||||
'host_input' => $host_input,
|
$key=(string)($storage['storage_id']??'').'|'.trim((string)($storage['source_prefix']??''),'/');
|
||||||
'username' => (string)($user_data['id'] ?? $username),
|
if($key!=='|') $known[$key]=(string)($storage['local_mount']??'');
|
||||||
'display_name' => (string)($user_data['display-name'] ?? $user_data['displayname'] ?? ''),
|
}
|
||||||
'version' => (string)($status_data['versionstring'] ?? $status_data['version'] ?? ''),
|
}
|
||||||
'product' => (string)($status_data['productname'] ?? 'Nextcloud'),
|
foreach($candidates as &$candidate)
|
||||||
'users' => $users,
|
{
|
||||||
'can_list_users' => $can_list_users,
|
$key=$candidate['storage_id'].'|'.trim($candidate['source_prefix'],'/');
|
||||||
'showcase_user' => '',
|
if(!empty($known[$key])&&is_dir($known[$key])&&is_readable($known[$key])) $candidate['local_mount']=$known[$key];
|
||||||
'connection_name' => $url_host !== '' ? $url_host : 'Nextcloud',
|
}
|
||||||
'scan_message' => 'Nextcloud wurde erkannt und der Benutzerzugriff wurde bestätigt.',
|
unset($candidate);
|
||||||
'_password' => $password,
|
|
||||||
'api_status' => 'pending',
|
|
||||||
'api_username' => '',
|
|
||||||
'api_error' => '',
|
|
||||||
);
|
|
||||||
bratonien_tools_nc_wizard_fill_profile($state);
|
|
||||||
bratonien_tools_nc_wizard_store($state);
|
|
||||||
|
|
||||||
return array('message'=>'Nextcloud-Scan erfolgreich. Erkannte Werte wurden übernommen; fehlende Angaben werden jetzt gezielt abgefragt.');
|
$state['storage_candidates']=array_values($candidates);
|
||||||
|
$state['storages']=array_values(array_filter($state['storage_candidates'],function($storage){return trim((string)($storage['local_mount']??''))!=='';}));
|
||||||
|
$state['technical_error']='';
|
||||||
|
$state['technical_source']='Datenzugriff erfolgreich geprüft';
|
||||||
|
$state['technical_complete']=count($state['storages'])===count($state['storage_candidates']);
|
||||||
|
$state['technical_stage']=$state['technical_complete']?'ready':'mounts';
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_save_technical()
|
function bratonien_tools_nc_wizard_save_technical()
|
||||||
{
|
{
|
||||||
$state = bratonien_tools_nc_wizard_state();
|
$state=bratonien_tools_nc_wizard_state();
|
||||||
if (empty($state['scan_ok']))
|
if(empty($state['scan_ok'])) throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
|
||||||
|
|
||||||
|
$state['connection_name']=trim((string)($_POST['nc_wizard_connection_name']??$state['connection_name']));
|
||||||
|
$state['db_user']=trim((string)($_POST['nc_wizard_db_user']??$state['db_user']));
|
||||||
|
$db_password=array_key_exists('nc_wizard_db_password',$_POST)?(string)$_POST['nc_wizard_db_password']:(string)$state['_db_password'];
|
||||||
|
if($db_password!==''){$state['_db_password']=$db_password;$state['db_password_set']=true;}
|
||||||
|
|
||||||
|
$advanced=isset($_POST['nc_wizard_db_host'])||$state['technical_stage']==='database_details';
|
||||||
|
if($advanced)
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
|
$state['db_host']=trim((string)($_POST['nc_wizard_db_host']??$state['db_host']));
|
||||||
|
$state['db_port']=(string)max(1,min(65535,(int)($_POST['nc_wizard_db_port']??$state['db_port'])));
|
||||||
|
$state['db_database']=trim((string)($_POST['nc_wizard_db_database']??$state['db_database']));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$state['db_host']=(string)parse_url($state['base_url'],PHP_URL_HOST);
|
||||||
|
$state['db_port']='5432';
|
||||||
|
$state['db_database']='nextcloud';
|
||||||
}
|
}
|
||||||
|
|
||||||
$state['connection_name'] = trim((string)($_POST['nc_wizard_connection_name'] ?? $state['connection_name']));
|
|
||||||
$state['db_host'] = trim((string)($_POST['nc_wizard_db_host'] ?? $state['db_host']));
|
|
||||||
$state['db_port'] = (string)max(1, min(65535, (int)($_POST['nc_wizard_db_port'] ?? $state['db_port'])));
|
|
||||||
$state['db_database'] = trim((string)($_POST['nc_wizard_db_database'] ?? $state['db_database']));
|
|
||||||
$state['db_user'] = trim((string)($_POST['nc_wizard_db_user'] ?? $state['db_user']));
|
|
||||||
$db_password = (string)($_POST['nc_wizard_db_password'] ?? '');
|
|
||||||
if ($db_password !== '')
|
|
||||||
{
|
|
||||||
$state['_db_password'] = $db_password;
|
|
||||||
$state['db_password_set'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($state['connection_name'] === '' || $state['db_host'] === '' || $state['db_database'] === '' || $state['db_user'] === '' || empty($state['db_password_set']))
|
|
||||||
{
|
|
||||||
bratonien_tools_nc_wizard_store($state);
|
|
||||||
throw new RuntimeException('Die noch fehlenden Datenbankangaben müssen vollständig angegeben werden.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config = array(
|
|
||||||
'host'=>$state['db_host'],
|
|
||||||
'port'=>$state['db_port'],
|
|
||||||
'database'=>$state['db_database'],
|
|
||||||
'user'=>$state['db_user'],
|
|
||||||
);
|
|
||||||
$password = (string)($state['_db_password'] ?? '');
|
|
||||||
bratonien_tools_nc_connector_psql($config, $password, 'SELECT 1');
|
|
||||||
|
|
||||||
$source_view = bratonien_tools_nc_connector_view_name($state['source_view']);
|
|
||||||
$activity_view = bratonien_tools_nc_connector_view_name($state['activity_view']);
|
|
||||||
bratonien_tools_nc_connector_psql($config, $password, 'SELECT COUNT(*) FROM '.$source_view);
|
|
||||||
bratonien_tools_nc_connector_psql($config, $password, 'SELECT 1 FROM '.$activity_view.' LIMIT 1');
|
|
||||||
|
|
||||||
$rows = bratonien_tools_nc_connector_psql(
|
|
||||||
$config,
|
|
||||||
$password,
|
|
||||||
"SELECT DISTINCT storage_id::text || E'\\t' || source_path FROM ".$source_view." ORDER BY 1"
|
|
||||||
);
|
|
||||||
$candidates = array();
|
|
||||||
foreach (preg_split('/\r\n|\r|\n/', trim($rows)) as $line)
|
|
||||||
{
|
|
||||||
if ($line === '') continue;
|
|
||||||
$parts = explode("\t", $line, 2);
|
|
||||||
if (count($parts) !== 2) continue;
|
|
||||||
$storage_id = trim($parts[0]);
|
|
||||||
$source_path = trim($parts[1], '/');
|
|
||||||
$prefix = $source_path;
|
|
||||||
if (strpos($source_path, '/') !== false)
|
|
||||||
{
|
|
||||||
$prefix = substr($source_path, 0, strpos($source_path, '/'));
|
|
||||||
}
|
|
||||||
if ($storage_id === '') continue;
|
|
||||||
if (!isset($candidates[$storage_id]))
|
|
||||||
{
|
|
||||||
$candidates[$storage_id] = array('storage_id'=>$storage_id, 'source_prefix'=>$prefix, 'local_mount'=>'');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$candidates)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Die Source-View ist erreichbar, liefert aber keine Storage-Zuordnung.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$known = array();
|
|
||||||
foreach (bratonien_tools_nc_connector_connections() as $connection)
|
|
||||||
{
|
|
||||||
foreach (($connection['config']['storages'] ?? array()) as $storage)
|
|
||||||
{
|
|
||||||
$key = (string)($storage['storage_id'] ?? '').'|'.(string)($storage['source_prefix'] ?? '');
|
|
||||||
if ($key !== '|') $known[$key] = (string)($storage['local_mount'] ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($candidates as &$candidate)
|
|
||||||
{
|
|
||||||
$key = $candidate['storage_id'].'|'.$candidate['source_prefix'];
|
|
||||||
if (!empty($known[$key]))
|
|
||||||
{
|
|
||||||
$candidate['local_mount'] = $known[$key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unset($candidate);
|
|
||||||
|
|
||||||
$state['storage_candidates'] = array_values($candidates);
|
|
||||||
$state['storages'] = array_values(array_filter($state['storage_candidates'], function($storage)
|
|
||||||
{
|
|
||||||
return trim((string)($storage['local_mount'] ?? '')) !== '';
|
|
||||||
}));
|
|
||||||
$state['technical_source'] = 'Datenbank und Views erfolgreich geprüft';
|
|
||||||
$state['technical_complete'] = count($state['storages']) === count($state['storage_candidates']);
|
|
||||||
bratonien_tools_nc_wizard_store($state);
|
bratonien_tools_nc_wizard_store($state);
|
||||||
|
if($state['db_user']===''||empty($state['db_password_set'])) throw new RuntimeException('Für den Datenzugriff werden Lese-Benutzer und Passwort benötigt.');
|
||||||
return array('message'=>$state['technical_complete']
|
if($state['db_host']===''||$state['db_database']==='') throw new RuntimeException('Die Adresse der Datenbank ist noch unvollständig.');
|
||||||
? 'Technische Verbindung wurde automatisch vervollständigt.'
|
|
||||||
: 'Datenbank und Views wurden erkannt. Für nicht erkannte Storages wird nur noch der lokale Mount abgefragt.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_save_mounts()
|
|
||||||
{
|
|
||||||
$state = bratonien_tools_nc_wizard_state();
|
|
||||||
$candidates = isset($state['storage_candidates']) && is_array($state['storage_candidates']) ? $state['storage_candidates'] : array();
|
|
||||||
$mounts = isset($_POST['nc_wizard_storage_mount']) && is_array($_POST['nc_wizard_storage_mount']) ? $_POST['nc_wizard_storage_mount'] : array();
|
|
||||||
if (!$candidates)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Es wurden noch keine Storages erkannt.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$storages = array();
|
|
||||||
foreach ($candidates as $index => $candidate)
|
|
||||||
{
|
|
||||||
$mount = trim((string)($candidate['local_mount'] ?? ''));
|
|
||||||
if ($mount === '')
|
|
||||||
{
|
|
||||||
$mount = trim((string)($mounts[$index] ?? ''));
|
|
||||||
}
|
|
||||||
if ($mount === '' || $mount[0] !== '/')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Für Storage '.(string)$candidate['storage_id'].' fehlt ein absoluter lokaler Mount-Pfad.');
|
|
||||||
}
|
|
||||||
$mount = rtrim($mount, '/');
|
|
||||||
if (!is_dir($mount) || !is_readable($mount))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Storage-Mount '.$mount.' ist nicht vorhanden oder nicht lesbar.');
|
|
||||||
}
|
|
||||||
$candidate['local_mount'] = $mount;
|
|
||||||
$storages[] = $candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
$state['storages'] = $storages;
|
|
||||||
$state['storage_candidates'] = $storages;
|
|
||||||
$state['technical_complete'] = true;
|
|
||||||
bratonien_tools_nc_wizard_store($state);
|
|
||||||
return array('message'=>'Storage-Zuordnung ist vollständig.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_select_user()
|
|
||||||
{
|
|
||||||
$state = bratonien_tools_nc_wizard_state();
|
|
||||||
if (empty($state['scan_ok']))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
|
|
||||||
}
|
|
||||||
if (empty($state['technical_complete']))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Die automatische technische Erkennung ist noch nicht vollständig. Bitte nur die noch fehlenden Angaben ergänzen.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$showcase_user = trim((string)($_POST['nc_wizard_showcase_user'] ?? ''));
|
|
||||||
if ($showcase_user === '')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Bitte einen Nextcloud-Benutzer für die Showcase-Freigaben auswählen.');
|
|
||||||
}
|
|
||||||
$connection_name = trim((string)($_POST['nc_wizard_connection_name'] ?? $state['connection_name']));
|
|
||||||
if ($connection_name === '')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Bitte einen Namen für die Verbindung angeben.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$state['connection_name'] = $connection_name;
|
|
||||||
$state['showcase_user'] = $showcase_user;
|
|
||||||
$state['step'] = 3;
|
|
||||||
$state['api_error'] = '';
|
|
||||||
bratonien_tools_nc_wizard_store($state);
|
|
||||||
|
|
||||||
return array('message'=>'Showcase-Benutzer übernommen. Jetzt folgt der Piwigo-API-Zugang.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_api_test()
|
|
||||||
{
|
|
||||||
$state = bratonien_tools_nc_wizard_state();
|
|
||||||
if ((int)$state['step'] !== 3)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Der API-Test ist in diesem Assistentenschritt nicht verfügbar.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$key_id = trim((string)($_POST['nc_wizard_api_key_id'] ?? ''));
|
|
||||||
$secret = trim((string)($_POST['nc_wizard_api_key_secret'] ?? ''));
|
|
||||||
if ($key_id === '' && $secret === '')
|
|
||||||
{
|
|
||||||
$stored = bratonien_tools_nc_api_credentials();
|
|
||||||
$key_id = trim((string)($stored['key_id'] ?? ''));
|
|
||||||
$secret = trim((string)($stored['key_secret'] ?? ''));
|
|
||||||
}
|
|
||||||
if ($key_id === '' || $secret === '')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('API-Schlüssel-ID und API-Geheimnis fehlen.');
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$status = bratonien_tools_nc_connector_piwigo_api_request($key_id, $secret, 'pwg.session.getStatus');
|
bratonien_tools_nc_wizard_finish_data_access($state);
|
||||||
$user_status = strtolower((string)($status['status'] ?? ''));
|
|
||||||
if (!in_array($user_status, array('admin','webmaster'), true))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Der API-Key gehört keinem Piwigo-Administrator/Webmaster.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$method_result = bratonien_tools_nc_connector_piwigo_api_request($key_id, $secret, 'reflection.getMethodList');
|
|
||||||
$method_map = array();
|
|
||||||
bratonien_tools_nc_connector_collect_method_names($method_result, $method_map);
|
|
||||||
$missing = array_values(array_diff(array('bratonien.nc.syncProductive','bratonien.nc.syncOrphans'), array_keys($method_map)));
|
|
||||||
if ($missing)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Benötigte Bratonien-Sync-Methoden fehlen: '.implode(', ', $missing).'.');
|
|
||||||
}
|
|
||||||
|
|
||||||
bratonien_tools_nc_api_credentials_store($key_id, $secret);
|
|
||||||
$state['api_status'] = 'ok';
|
|
||||||
$state['api_username'] = (string)($status['username'] ?? $status['user'] ?? '');
|
|
||||||
$state['api_error'] = '';
|
|
||||||
$state['step'] = 4;
|
|
||||||
bratonien_tools_nc_wizard_store($state);
|
bratonien_tools_nc_wizard_store($state);
|
||||||
return array('message'=>'Piwigo-API erfolgreich geprüft. Zum Abschluss kann jetzt der Fallback festgelegt werden.');
|
return array('message'=>$state['technical_complete']?'Datenzugriff wurde erfolgreich geprüft.':'Datenzugriff funktioniert. Ein Speicherort muss noch bestätigt werden.');
|
||||||
}
|
}
|
||||||
catch (Throwable $e)
|
catch(Throwable $e)
|
||||||
{
|
{
|
||||||
$state['api_status'] = 'error';
|
$state['technical_error']=$e->getMessage();
|
||||||
$state['api_error'] = $e->getMessage();
|
if(!$advanced)
|
||||||
|
{
|
||||||
|
$state['technical_stage']='database_details';
|
||||||
|
bratonien_tools_nc_wizard_store($state);
|
||||||
|
throw new RuntimeException('Der übliche Datenbankzugang konnte nicht automatisch bestätigt werden. Bitte die Datenbank-Adresse prüfen.');
|
||||||
|
}
|
||||||
bratonien_tools_nc_wizard_store($state);
|
bratonien_tools_nc_wizard_store($state);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bratonien_tools_nc_wizard_save_mounts()
|
||||||
|
{
|
||||||
|
$state=bratonien_tools_nc_wizard_state();
|
||||||
|
$candidates=is_array($state['storage_candidates'])?$state['storage_candidates']:array();
|
||||||
|
$mounts=isset($_POST['nc_wizard_storage_mount'])&&is_array($_POST['nc_wizard_storage_mount'])?$_POST['nc_wizard_storage_mount']:array();
|
||||||
|
if(!$candidates) throw new RuntimeException('Es wurden noch keine Speicherorte erkannt.');
|
||||||
|
$storages=array();
|
||||||
|
foreach($candidates as $index=>$candidate)
|
||||||
|
{
|
||||||
|
$mount=trim((string)($candidate['local_mount']??''));
|
||||||
|
if($mount==='') $mount=trim((string)($mounts[$index]??''));
|
||||||
|
if($mount===''||$mount[0]!=='/') throw new RuntimeException('Für einen Speicherort fehlt ein gültiger lokaler Pfad.');
|
||||||
|
$mount=rtrim($mount,'/');
|
||||||
|
if(!is_dir($mount)||!is_readable($mount)) throw new RuntimeException('Der angegebene Speicherort ist nicht vorhanden oder nicht lesbar: '.$mount);
|
||||||
|
$candidate['local_mount']=$mount;$storages[]=$candidate;
|
||||||
|
}
|
||||||
|
$state['storages']=$storages;$state['storage_candidates']=$storages;$state['technical_complete']=true;$state['technical_stage']='ready';$state['technical_error']='';bratonien_tools_nc_wizard_store($state);
|
||||||
|
return array('message'=>'Speicherzuordnung wurde erfolgreich geprüft.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function bratonien_tools_nc_wizard_select_user()
|
||||||
|
{
|
||||||
|
$state=bratonien_tools_nc_wizard_state();
|
||||||
|
if(empty($state['scan_ok'])) throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
|
||||||
|
if(empty($state['technical_complete'])) throw new RuntimeException('Die Verbindung ist noch nicht vollständig geprüft.');
|
||||||
|
$showcase_user=trim((string)($_POST['nc_wizard_showcase_user']??''));
|
||||||
|
$connection_name=trim((string)($_POST['nc_wizard_connection_name']??$state['connection_name']));
|
||||||
|
if($showcase_user==='') throw new RuntimeException('Bitte den Benutzer auswählen, der die Showcase-Freigaben bereitstellt.');
|
||||||
|
if($connection_name==='') throw new RuntimeException('Bitte einen Namen für die Verbindung angeben.');
|
||||||
|
$state['connection_name']=$connection_name;$state['showcase_user']=$showcase_user;$state['step']=3;$state['api_error']='';bratonien_tools_nc_wizard_store($state);
|
||||||
|
return array('message'=>'Showcase-Benutzer übernommen. Jetzt folgt der Piwigo-API-Zugang.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function bratonien_tools_nc_wizard_api_test()
|
||||||
|
{
|
||||||
|
$state=bratonien_tools_nc_wizard_state();
|
||||||
|
if((int)$state['step']!==3) throw new RuntimeException('Der API-Test ist in diesem Assistentenschritt nicht verfügbar.');
|
||||||
|
$posted_id=trim((string)($_POST['nc_wizard_api_key_id']??''));$posted_secret=trim((string)($_POST['nc_wizard_api_key_secret']??''));
|
||||||
|
if($posted_id!=='')$state['_api_key_id']=$posted_id;if($posted_secret!=='')$state['_api_key_secret']=$posted_secret;bratonien_tools_nc_wizard_store($state);
|
||||||
|
$key_id=trim((string)$state['_api_key_id']);$secret=trim((string)$state['_api_key_secret']);
|
||||||
|
if($key_id===''&&$secret===''){$stored=bratonien_tools_nc_api_credentials();$key_id=trim((string)($stored['key_id']??''));$secret=trim((string)($stored['key_secret']??''));}
|
||||||
|
if($key_id===''||$secret==='') throw new RuntimeException('API-Schlüssel-ID und API-Geheimnis fehlen.');
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$status=bratonien_tools_nc_connector_piwigo_api_request($key_id,$secret,'pwg.session.getStatus');
|
||||||
|
$user_status=strtolower((string)($status['status']??''));
|
||||||
|
if(!in_array($user_status,array('admin','webmaster'),true)) throw new RuntimeException('Der API-Key gehört keinem Piwigo-Administrator/Webmaster.');
|
||||||
|
$method_result=bratonien_tools_nc_connector_piwigo_api_request($key_id,$secret,'reflection.getMethodList');$method_map=array();bratonien_tools_nc_connector_collect_method_names($method_result,$method_map);
|
||||||
|
$missing=array_values(array_diff(array('bratonien.nc.syncProductive','bratonien.nc.syncOrphans'),array_keys($method_map)));
|
||||||
|
if($missing) throw new RuntimeException('Benötigte Bratonien-Sync-Methoden fehlen: '.implode(', ',$missing).'.');
|
||||||
|
$state['_api_key_id']=$key_id;$state['_api_key_secret']=$secret;$state['api_status']='ok';$state['api_username']=(string)($status['username']??$status['user']??'');$state['api_error']='';$state['step']=4;bratonien_tools_nc_wizard_store($state);
|
||||||
|
return array('message'=>'Piwigo-API erfolgreich geprüft. Gespeichert wird sie erst beim Abschluss des Assistenten.');
|
||||||
|
}
|
||||||
|
catch(Throwable $e){$state['api_status']='error';$state['api_error']=$e->getMessage();bratonien_tools_nc_wizard_store($state);throw $e;}
|
||||||
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_api_skip()
|
function bratonien_tools_nc_wizard_api_skip()
|
||||||
{
|
{
|
||||||
$state = bratonien_tools_nc_wizard_state();
|
$state=bratonien_tools_nc_wizard_state();if((int)$state['step']!==3)throw new RuntimeException('Die API kann in diesem Assistentenschritt nicht übersprungen werden.');
|
||||||
if ((int)$state['step'] !== 3)
|
$state['api_status']='skipped';$state['api_error']='';$state['step']=4;bratonien_tools_nc_wizard_store($state);
|
||||||
{
|
|
||||||
throw new RuntimeException('Die API kann in diesem Assistentenschritt nicht übersprungen werden.');
|
|
||||||
}
|
|
||||||
$state['api_status'] = 'skipped';
|
|
||||||
$state['api_error'] = '';
|
|
||||||
$state['step'] = 4;
|
|
||||||
bratonien_tools_nc_wizard_store($state);
|
|
||||||
return array('message'=>'Piwigo-API wurde übersprungen. Für diese Verbindung ist deshalb ein Fallback-Zugang erforderlich.');
|
return array('message'=>'Piwigo-API wurde übersprungen. Für diese Verbindung ist deshalb ein Fallback-Zugang erforderlich.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_wizard_finish()
|
function bratonien_tools_nc_wizard_finish()
|
||||||
{
|
{
|
||||||
$state = bratonien_tools_nc_wizard_state();
|
$state=bratonien_tools_nc_wizard_state();
|
||||||
if ((int)$state['step'] !== 4 || empty($state['technical_complete']) || trim((string)$state['showcase_user']) === '')
|
if((int)$state['step']!==4||empty($state['technical_complete'])||trim((string)$state['showcase_user'])==='') throw new RuntimeException('Der Assistent ist noch nicht vollständig.');
|
||||||
{
|
if(array_key_exists('nc_wizard_fallback_user',$_POST)) $state['_fallback_user']=trim((string)$_POST['nc_wizard_fallback_user']);
|
||||||
throw new RuntimeException('Der Assistent ist noch nicht vollständig.');
|
if(array_key_exists('nc_wizard_fallback_password',$_POST)) $state['_fallback_password']=(string)$_POST['nc_wizard_fallback_password'];
|
||||||
}
|
bratonien_tools_nc_wizard_store($state);
|
||||||
|
$fallback_user=trim((string)$state['_fallback_user']);$fallback_password=(string)$state['_fallback_password'];
|
||||||
|
if(($fallback_user==='')!==($fallback_password==='')) throw new RuntimeException('Fallback-Benutzer und Fallback-Passwort müssen entweder beide angegeben oder beide leer gelassen werden.');
|
||||||
|
if($state['api_status']!=='ok'&&$fallback_user==='') throw new RuntimeException('Da die Piwigo-API übersprungen wurde, ist ein Fallback-Zugang erforderlich.');
|
||||||
|
|
||||||
$fallback_user = trim((string)($_POST['nc_wizard_fallback_user'] ?? ''));
|
$storage_lines=array();
|
||||||
$fallback_password = (string)($_POST['nc_wizard_fallback_password'] ?? '');
|
foreach($state['storages'] as $storage)
|
||||||
if (($fallback_user === '') !== ($fallback_password === ''))
|
|
||||||
{
|
{
|
||||||
throw new RuntimeException('Fallback-Benutzer und Fallback-Passwort müssen entweder beide angegeben oder beide leer gelassen werden.');
|
$storage_lines[]=(string)$storage['storage_id'].' | '.trim((string)$storage['source_prefix'],'/').' | '.(string)$storage['local_mount'];
|
||||||
}
|
}
|
||||||
if ($state['api_status'] !== 'ok' && $fallback_user === '')
|
$_POST['nc_name']=(string)$state['connection_name'];$_POST['nc_host']=(string)$state['db_host'];$_POST['nc_port']=(string)$state['db_port'];$_POST['nc_database']=(string)$state['db_database'];$_POST['nc_user']=(string)$state['db_user'];$_POST['nc_db_password']=(string)$state['_db_password'];$_POST['nc_source_view']=(string)$state['source_view'];$_POST['nc_activity_view']=(string)$state['activity_view'];$_POST['nc_gallery_root']=(string)$state['gallery_root'];$_POST['nc_storages']=implode("\n",$storage_lines);$_POST['nc_quiet_seconds']='120';$_POST['nc_max_wait_seconds']='900';$_POST['nc_full_sync_seconds']='86400';$_POST['nc_piwigo_user']=$fallback_user;$_POST['nc_piwigo_password']=$fallback_password;$_POST['nc_nextcloud_url']=(string)$state['base_url'];$_POST['nc_showcase_user']=(string)$state['showcase_user'];$_POST['nc_access_user']=(string)$state['username'];$_POST['nc_product']=(string)$state['product'];$_POST['nc_version']=(string)$state['version'];$_POST['nc_api_validated']=$state['api_status']==='ok'?'1':'0';
|
||||||
{
|
$result=bratonien_tools_nc_connector_create_local_api_first();$connection_id=(int)($result['connection_id']??0);
|
||||||
throw new RuntimeException('Da die Piwigo-API übersprungen wurde, ist ein Fallback-Zugang erforderlich.');
|
try{if($state['api_status']==='ok')bratonien_tools_nc_api_credentials_store((string)$state['_api_key_id'],(string)$state['_api_key_secret']);}
|
||||||
}
|
catch(Throwable $e){if($connection_id>0){$table=bratonien_tools_nc_connector_table();pwg_query("DELETE FROM `$table` WHERE id=".$connection_id." LIMIT 1");}throw new RuntimeException('Die Verbindung wurde nicht übernommen, weil der geprüfte API-Zugang nicht gespeichert werden konnte: '.$e->getMessage());}
|
||||||
|
unset($_SESSION['bratonien_nc_wizard']);unset($result['connection_id']);$result['message']='Verbindung wurde vollständig durch den Assistenten angelegt. '.$result['message'];return $result;
|
||||||
$storage_lines = array();
|
|
||||||
foreach ($state['storages'] as $storage)
|
|
||||||
{
|
|
||||||
$storage_lines[] = (string)$storage['storage_id'].' | '.(string)$storage['source_prefix'].' | '.(string)$storage['local_mount'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$_POST['nc_name'] = (string)$state['connection_name'];
|
|
||||||
$_POST['nc_host'] = (string)$state['db_host'];
|
|
||||||
$_POST['nc_port'] = (string)$state['db_port'];
|
|
||||||
$_POST['nc_database'] = (string)$state['db_database'];
|
|
||||||
$_POST['nc_user'] = (string)$state['db_user'];
|
|
||||||
$_POST['nc_db_password'] = (string)($state['_db_password'] ?? '');
|
|
||||||
$_POST['nc_source_view'] = (string)$state['source_view'];
|
|
||||||
$_POST['nc_activity_view'] = (string)$state['activity_view'];
|
|
||||||
$_POST['nc_gallery_root'] = (string)$state['gallery_root'];
|
|
||||||
$_POST['nc_storages'] = implode("\n", $storage_lines);
|
|
||||||
$_POST['nc_quiet_seconds'] = '120';
|
|
||||||
$_POST['nc_max_wait_seconds'] = '900';
|
|
||||||
$_POST['nc_full_sync_seconds'] = '86400';
|
|
||||||
$_POST['nc_piwigo_user'] = $fallback_user;
|
|
||||||
$_POST['nc_piwigo_password'] = $fallback_password;
|
|
||||||
$_POST['nc_nextcloud_url'] = (string)$state['base_url'];
|
|
||||||
$_POST['nc_showcase_user'] = (string)$state['showcase_user'];
|
|
||||||
$_POST['nc_access_user'] = (string)$state['username'];
|
|
||||||
$_POST['nc_product'] = (string)$state['product'];
|
|
||||||
$_POST['nc_version'] = (string)$state['version'];
|
|
||||||
|
|
||||||
$result = bratonien_tools_nc_connector_create_local_api_first();
|
|
||||||
unset($_SESSION['bratonien_nc_wizard']);
|
|
||||||
$result['message'] = 'Verbindung wurde vollständig durch den Assistenten angelegt. '.$result['message'];
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_connector_update_name()
|
function bratonien_tools_nc_connector_update_name()
|
||||||
{
|
{
|
||||||
$id = (int)($_POST['connection_id'] ?? 0);
|
$id=(int)($_POST['connection_id']??0);$name=trim((string)($_POST['connection_name']??''));if($id<1||$name==='')throw new RuntimeException('Verbindung oder Name fehlt.');$connection=bratonien_tools_nc_connector_connection($id,false);if(!$connection)throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');$table=bratonien_tools_nc_connector_table();$now=date('Y-m-d H:i:s');pwg_query("UPDATE `$table` SET name='".pwg_db_real_escape_string($name)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id." LIMIT 1");return array('message'=>'Verbindungsname wurde aktualisiert.');
|
||||||
$name = trim((string)($_POST['connection_name'] ?? ''));
|
|
||||||
if ($id < 1 || $name === '')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Verbindung oder Name fehlt.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$connection = bratonien_tools_nc_connector_connection($id, false);
|
|
||||||
if (!$connection)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$table = bratonien_tools_nc_connector_table();
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
pwg_query("UPDATE `$table` SET name='".pwg_db_real_escape_string($name)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id." LIMIT 1");
|
|
||||||
|
|
||||||
return array('message'=>'Verbindungsname wurde aktualisiert.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bratonien_tools_nc_connector_update_technical()
|
function bratonien_tools_nc_connector_update_technical()
|
||||||
{
|
{
|
||||||
$id = (int)($_POST['connection_id'] ?? 0);
|
$id=(int)($_POST['connection_id']??0);$connection=bratonien_tools_nc_connector_connection($id,true);if(!$connection)throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');if(!empty($connection['enabled'])||(string)$connection['takeover_state']==='active')throw new RuntimeException('Technische Einstellungen einer aktiven Verbindung können nicht geändert werden. Bitte zuerst deaktivieren.');
|
||||||
$connection = bratonien_tools_nc_connector_connection($id, true);
|
$config=$connection['config'];$port=(int)($_POST['nc_port']??($config['port']??5432));if($port<1||$port>65535)throw new RuntimeException('Ungültiger PostgreSQL-Port.');$gallery_root=rtrim(trim((string)($_POST['nc_gallery_root']??($config['gallery_root']??''))),'/');if($gallery_root===''||$gallery_root[0]!=='/')throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
|
||||||
if (!$connection)
|
$config['host']=trim((string)($_POST['nc_host']??($config['host']??'')));$config['port']=(string)$port;$config['database']=trim((string)($_POST['nc_database']??($config['database']??'')));$config['user']=trim((string)($_POST['nc_user']??($config['user']??'')));$config['source_view']=trim((string)($_POST['nc_source_view']??($config['source_view']??'')));$config['activity_view']=trim((string)($_POST['nc_activity_view']??($config['activity_view']??'')));$config['gallery_root']=$gallery_root;$config['quiet_seconds']=max(0,(int)($_POST['nc_quiet_seconds']??($config['quiet_seconds']??120)));$config['max_wait_seconds']=max(60,(int)($_POST['nc_max_wait_seconds']??($config['max_wait_seconds']??900)));$config['full_sync_seconds']=max(300,(int)($_POST['nc_full_sync_seconds']??($config['full_sync_seconds']??86400)));$config['storages']=bratonien_tools_nc_connector_parse_storages($_POST['nc_storages']??'');unset($config['verification']);bratonien_tools_nc_connector_view_name($config['source_view']);bratonien_tools_nc_connector_view_name($config['activity_view']);
|
||||||
{
|
$credentials=bratonien_tools_nc_connector_credentials_from_blob($connection['secret_blob']??'');$db_password=(string)($_POST['nc_db_password']??'');if($db_password==='')$db_password=$credentials['db_password'];if($db_password==='')throw new RuntimeException('Datenbankpasswort fehlt.');$secret_blob=bratonien_tools_nc_connector_encrypt_credentials($db_password,$credentials['piwigo_user'],$credentials['piwigo_password']);$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);if(!is_string($config_json))throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.');$table=bratonien_tools_nc_connector_table();$now=date('Y-m-d H:i:s');pwg_query("UPDATE `$table` SET takeover_state='disabled', enabled=0, config_json='".pwg_db_real_escape_string($config_json)."', secret_blob='".pwg_db_real_escape_string($secret_blob)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id." LIMIT 1");return array('message'=>'Technische Verbindungseinstellungen wurden gespeichert. Die Verbindung muss erneut geprüft werden.');
|
||||||
throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
|
|
||||||
}
|
|
||||||
if (!empty($connection['enabled']) || (string)$connection['takeover_state'] === 'active')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Technische Einstellungen einer aktiven Verbindung können nicht geändert werden. Bitte zuerst deaktivieren.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config = $connection['config'];
|
|
||||||
$port = (int)($_POST['nc_port'] ?? ($config['port'] ?? 5432));
|
|
||||||
if ($port < 1 || $port > 65535)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Ungültiger PostgreSQL-Port.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$gallery_root = rtrim(trim((string)($_POST['nc_gallery_root'] ?? ($config['gallery_root'] ?? ''))), '/');
|
|
||||||
if ($gallery_root === '' || $gallery_root[0] !== '/')
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config['host'] = trim((string)($_POST['nc_host'] ?? ($config['host'] ?? '')));
|
|
||||||
$config['port'] = (string)$port;
|
|
||||||
$config['database'] = trim((string)($_POST['nc_database'] ?? ($config['database'] ?? '')));
|
|
||||||
$config['user'] = trim((string)($_POST['nc_user'] ?? ($config['user'] ?? '')));
|
|
||||||
$config['source_view'] = trim((string)($_POST['nc_source_view'] ?? ($config['source_view'] ?? '')));
|
|
||||||
$config['activity_view'] = trim((string)($_POST['nc_activity_view'] ?? ($config['activity_view'] ?? '')));
|
|
||||||
$config['gallery_root'] = $gallery_root;
|
|
||||||
$config['quiet_seconds'] = max(0, (int)($_POST['nc_quiet_seconds'] ?? ($config['quiet_seconds'] ?? 120)));
|
|
||||||
$config['max_wait_seconds'] = max(60, (int)($_POST['nc_max_wait_seconds'] ?? ($config['max_wait_seconds'] ?? 900)));
|
|
||||||
$config['full_sync_seconds'] = max(300, (int)($_POST['nc_full_sync_seconds'] ?? ($config['full_sync_seconds'] ?? 86400)));
|
|
||||||
$config['storages'] = bratonien_tools_nc_connector_parse_storages($_POST['nc_storages'] ?? '');
|
|
||||||
unset($config['verification']);
|
|
||||||
|
|
||||||
bratonien_tools_nc_connector_view_name($config['source_view']);
|
|
||||||
bratonien_tools_nc_connector_view_name($config['activity_view']);
|
|
||||||
|
|
||||||
$credentials = bratonien_tools_nc_connector_credentials_from_blob($connection['secret_blob'] ?? '');
|
|
||||||
$db_password = (string)($_POST['nc_db_password'] ?? '');
|
|
||||||
if ($db_password === '')
|
|
||||||
{
|
|
||||||
$db_password = $credentials['db_password'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$secret_blob = bratonien_tools_nc_connector_encrypt_credentials(
|
|
||||||
$db_password,
|
|
||||||
$credentials['piwigo_user'],
|
|
||||||
$credentials['piwigo_password']
|
|
||||||
);
|
|
||||||
|
|
||||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
if (!is_string($config_json))
|
|
||||||
{
|
|
||||||
throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$table = bratonien_tools_nc_connector_table();
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
pwg_query("UPDATE `$table` SET takeover_state='disabled', enabled=0, config_json='".pwg_db_real_escape_string($config_json)."', secret_blob='".pwg_db_real_escape_string($secret_blob)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id." LIMIT 1");
|
|
||||||
|
|
||||||
return array('message'=>'Technische Verbindungseinstellungen wurden gespeichert. Die Verbindung muss erneut geprüft werden.');
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
Plugin Name: Bratonien Tools
|
Plugin Name: Bratonien Tools
|
||||||
Version: 0.9.3.36
|
Version: 0.9.4.2
|
||||||
Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation.
|
Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation.
|
||||||
Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools
|
Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools
|
||||||
Author: Bratonien
|
Author: Bratonien
|
||||||
|
|||||||
@@ -34,31 +34,18 @@ function fail($message)
|
|||||||
|
|
||||||
function readKeyValueFile($path)
|
function readKeyValueFile($path)
|
||||||
{
|
{
|
||||||
if (!is_readable($path))
|
if (!is_readable($path)) fail('Konfiguration nicht lesbar: '.$path);
|
||||||
{
|
|
||||||
fail('Konfiguration nicht lesbar: '.$path);
|
|
||||||
}
|
|
||||||
$result = array();
|
$result = array();
|
||||||
foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line)
|
foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line)
|
||||||
{
|
{
|
||||||
$line = trim($line);
|
$line = trim($line);
|
||||||
if ($line === '' || $line[0] === '#')
|
if ($line === '' || $line[0] === '#') continue;
|
||||||
{
|
if (!preg_match('/^([A-Z0-9_]+)=(.*)$/', $line, $matches)) continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!preg_match('/^([A-Z0-9_]+)=(.*)$/', $line, $matches))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$value = trim($matches[2]);
|
$value = trim($matches[2]);
|
||||||
if (strlen($value) >= 2)
|
if (strlen($value) >= 2)
|
||||||
{
|
{
|
||||||
$first = $value[0];
|
$first = $value[0]; $last = $value[strlen($value)-1];
|
||||||
$last = $value[strlen($value)-1];
|
if (($first === '"' && $last === '"') || ($first === "'" && $last === "'")) $value = substr($value, 1, -1);
|
||||||
if (($first === '"' && $last === '"') || ($first === "'" && $last === "'"))
|
|
||||||
{
|
|
||||||
$value = substr($value, 1, -1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$result[$matches[1]] = $value;
|
$result[$matches[1]] = $value;
|
||||||
}
|
}
|
||||||
@@ -67,49 +54,42 @@ function readKeyValueFile($path)
|
|||||||
|
|
||||||
function runCommand(array $command, $allowFailure = false)
|
function runCommand(array $command, $allowFailure = false)
|
||||||
{
|
{
|
||||||
$spec = array(
|
$spec = array(0=>array('file','/dev/null','r'),1=>array('pipe','w'),2=>array('pipe','w'));
|
||||||
0 => array('file', '/dev/null', 'r'),
|
|
||||||
1 => array('pipe', 'w'),
|
|
||||||
2 => array('pipe', 'w'),
|
|
||||||
);
|
|
||||||
$process = proc_open($command, $spec, $pipes);
|
$process = proc_open($command, $spec, $pipes);
|
||||||
if (!is_resource($process))
|
if (!is_resource($process)) fail('Prozess konnte nicht gestartet werden: '.implode(' ', $command));
|
||||||
{
|
$stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]);
|
||||||
fail('Prozess konnte nicht gestartet werden: '.implode(' ', $command));
|
fclose($pipes[1]); fclose($pipes[2]);
|
||||||
}
|
|
||||||
$stdout = stream_get_contents($pipes[1]);
|
|
||||||
$stderr = stream_get_contents($pipes[2]);
|
|
||||||
fclose($pipes[1]);
|
|
||||||
fclose($pipes[2]);
|
|
||||||
$exit = proc_close($process);
|
$exit = proc_close($process);
|
||||||
if ($exit !== 0 && !$allowFailure)
|
if ($exit !== 0 && !$allowFailure)
|
||||||
{
|
{
|
||||||
$detail = trim($stderr) !== '' ? trim($stderr) : trim($stdout);
|
$detail = trim($stderr) !== '' ? trim($stderr) : trim($stdout);
|
||||||
fail('Befehl fehlgeschlagen ('.$exit.'): '.implode(' ', $command).($detail !== '' ? "\n".$detail : ''));
|
fail('Befehl fehlgeschlagen ('.$exit.'): '.implode(' ', $command).($detail !== '' ? "\n".$detail : ''));
|
||||||
}
|
}
|
||||||
return array('exit'=>$exit, 'stdout'=>(string)$stdout, 'stderr'=>(string)$stderr);
|
return array('exit'=>$exit,'stdout'=>(string)$stdout,'stderr'=>(string)$stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptConnectorSecret($blob, $hexKey)
|
function decryptConnectorSecret($blob, $hexKey)
|
||||||
{
|
{
|
||||||
if (!preg_match('/^[a-f0-9]{64}$/', $hexKey))
|
if (!preg_match('/^[a-f0-9]{64}$/', $hexKey)) fail('Connector-Schluessel ist ungueltig.');
|
||||||
{
|
|
||||||
fail('Connector-Schluessel ist ungueltig.');
|
|
||||||
}
|
|
||||||
$outer = base64_decode(trim((string)$blob), true);
|
$outer = base64_decode(trim((string)$blob), true);
|
||||||
$payload = is_string($outer) ? json_decode($outer, true) : null;
|
$payload = is_string($outer) ? json_decode($outer, true) : null;
|
||||||
if (!is_array($payload) || (int)($payload['v'] ?? 0) !== 1)
|
if (!is_array($payload) || (int)($payload['v'] ?? 0) !== 1) fail('Connector-Zugangsdaten haben ein unbekanntes Format.');
|
||||||
{
|
|
||||||
fail('Connector-Zugangsdaten haben ein unbekanntes Format.');
|
|
||||||
}
|
|
||||||
$iv = base64_decode((string)($payload['iv'] ?? ''), true);
|
$iv = base64_decode((string)($payload['iv'] ?? ''), true);
|
||||||
$tag = base64_decode((string)($payload['tag'] ?? ''), true);
|
$tag = base64_decode((string)($payload['tag'] ?? ''), true);
|
||||||
$cipher = base64_decode((string)($payload['data'] ?? ''), true);
|
$cipher = base64_decode((string)($payload['data'] ?? ''), true);
|
||||||
$plain = openssl_decrypt($cipher, 'aes-256-gcm', hex2bin($hexKey), OPENSSL_RAW_DATA, $iv, $tag);
|
$plain = openssl_decrypt($cipher, 'aes-256-gcm', hex2bin($hexKey), OPENSSL_RAW_DATA, $iv, $tag);
|
||||||
if ($plain === false || $plain === '')
|
if ($plain === false || $plain === '') fail('Connector-Zugangsdaten konnten nicht entschluesselt werden.');
|
||||||
|
|
||||||
|
$decoded = json_decode($plain, true);
|
||||||
|
if (is_array($decoded) && array_key_exists('db_password', $decoded))
|
||||||
{
|
{
|
||||||
fail('Connector-Zugangsdaten konnten nicht entschluesselt werden.');
|
$password = (string)$decoded['db_password'];
|
||||||
|
if ($password === '') fail('Datenbankpasswort fehlt in den Connector-Zugangsdaten.');
|
||||||
|
return $password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backward compatibility for older imported connections that stored only
|
||||||
|
// the database password as plaintext inside the encrypted envelope.
|
||||||
return (string)$plain;
|
return (string)$plain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,16 +101,10 @@ function sqlEscape(mysqli $db, $value)
|
|||||||
function saveTakeoverResult(mysqli $db, $table, $connectionId, array $config, $state, $enabled)
|
function saveTakeoverResult(mysqli $db, $table, $connectionId, array $config, $state, $enabled)
|
||||||
{
|
{
|
||||||
$json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
if (!is_string($json))
|
if (!is_string($json)) fail('Connector-Status konnte nicht serialisiert werden.');
|
||||||
{
|
|
||||||
fail('Connector-Status konnte nicht serialisiert werden.');
|
|
||||||
}
|
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
$sql = "UPDATE `".$table."` SET takeover_state='".sqlEscape($db, $state)."', enabled=".($enabled ? 1 : 0).", config_json='".sqlEscape($db, $json)."', updated='".sqlEscape($db, $now)."' WHERE id=".(int)$connectionId;
|
$sql = "UPDATE `".$table."` SET takeover_state='".sqlEscape($db, $state)."', enabled=".($enabled ? 1 : 0).", config_json='".sqlEscape($db, $json)."', updated='".sqlEscape($db, $now)."' WHERE id=".(int)$connectionId;
|
||||||
if (!$db->query($sql))
|
if (!$db->query($sql)) fail('Connector-Status konnte nicht gespeichert werden: '.$db->error);
|
||||||
{
|
|
||||||
fail('Connector-Status konnte nicht gespeichert werden: '.$db->error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$legacyTimerWasEnabled = false;
|
$legacyTimerWasEnabled = false;
|
||||||
@@ -141,84 +115,43 @@ $config = array();
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!is_readable($dbConfig))
|
if (!is_readable($dbConfig)) fail('Piwigo-Datenbankkonfiguration nicht lesbar: '.$dbConfig);
|
||||||
{
|
if (!is_executable($legacyRuntime)) fail('Bestehende Sync-Runtime fehlt: '.$legacyRuntime);
|
||||||
fail('Piwigo-Datenbankkonfiguration nicht lesbar: '.$dbConfig);
|
|
||||||
}
|
|
||||||
if (!is_executable($legacyRuntime))
|
|
||||||
{
|
|
||||||
fail('Bestehende Sync-Runtime fehlt: '.$legacyRuntime);
|
|
||||||
}
|
|
||||||
|
|
||||||
$conf = array();
|
$conf = array(); $prefixeTable = 'piwigo_'; require $dbConfig;
|
||||||
$prefixeTable = 'piwigo_';
|
foreach (array('db_host','db_user','db_password','db_base') as $key) if (!isset($conf[$key])) fail('Piwigo-Datenbankkonfiguration enthaelt '.$key.' nicht.');
|
||||||
require $dbConfig;
|
|
||||||
foreach (array('db_host','db_user','db_password','db_base') as $key)
|
|
||||||
{
|
|
||||||
if (!isset($conf[$key]))
|
|
||||||
{
|
|
||||||
fail('Piwigo-Datenbankkonfiguration enthaelt '.$key.' nicht.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$db = new mysqli($conf['db_host'], $conf['db_user'], $conf['db_password'], $conf['db_base']);
|
$db = new mysqli($conf['db_host'], $conf['db_user'], $conf['db_password'], $conf['db_base']);
|
||||||
if ($db->connect_errno)
|
if ($db->connect_errno) fail('Piwigo-Datenbank nicht erreichbar: '.$db->connect_error);
|
||||||
{
|
|
||||||
fail('Piwigo-Datenbank nicht erreichbar: '.$db->connect_error);
|
|
||||||
}
|
|
||||||
$db->set_charset('utf8mb4');
|
$db->set_charset('utf8mb4');
|
||||||
|
|
||||||
$table = $prefixeTable.'bratonien_tools_nc_connections';
|
$table = $prefixeTable.'bratonien_tools_nc_connections';
|
||||||
$result = $db->query("SELECT id, takeover_state, enabled, config_json, secret_blob FROM `".$table."` WHERE id=".$connectionId." LIMIT 1");
|
$result = $db->query("SELECT id, takeover_state, enabled, config_json, secret_blob FROM `".$table."` WHERE id=".$connectionId." LIMIT 1");
|
||||||
if (!$result || !$result->num_rows)
|
if (!$result || !$result->num_rows) fail('Connector-Verbindung #'.$connectionId.' wurde nicht gefunden.');
|
||||||
{
|
|
||||||
fail('Connector-Verbindung #'.$connectionId.' wurde nicht gefunden.');
|
|
||||||
}
|
|
||||||
$row = $result->fetch_assoc();
|
$row = $result->fetch_assoc();
|
||||||
if ((string)$row['takeover_state'] !== 'ready' || (int)$row['enabled'] !== 0)
|
if ((string)$row['takeover_state'] !== 'ready' || (int)$row['enabled'] !== 0) fail('Connector-Verbindung muss im Zustand ready und deaktiviert sein.');
|
||||||
{
|
|
||||||
fail('Connector-Verbindung muss im Zustand ready und deaktiviert sein.');
|
|
||||||
}
|
|
||||||
$config = json_decode((string)$row['config_json'], true);
|
$config = json_decode((string)$row['config_json'], true);
|
||||||
if (!is_array($config) || empty($config['verification']['ok']))
|
if (!is_array($config) || empty($config['verification']['ok'])) fail('Connector-Verbindung besitzt keine erfolgreiche Verifikation.');
|
||||||
{
|
|
||||||
fail('Connector-Verbindung besitzt keine erfolgreiche Verifikation.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$keyResult = $db->query("SELECT value FROM `".$prefixeTable."config` WHERE param='bratonien_nc_connector_secret' LIMIT 1");
|
$keyResult = $db->query("SELECT value FROM `".$prefixeTable."config` WHERE param='bratonien_nc_connector_secret' LIMIT 1");
|
||||||
if (!$keyResult || !$keyResult->num_rows)
|
if (!$keyResult || !$keyResult->num_rows) fail('Connector-Schluessel wurde in Piwigo nicht gefunden.');
|
||||||
{
|
|
||||||
fail('Connector-Schluessel wurde in Piwigo nicht gefunden.');
|
|
||||||
}
|
|
||||||
$keyRow = $keyResult->fetch_assoc();
|
$keyRow = $keyResult->fetch_assoc();
|
||||||
$dbPassword = decryptConnectorSecret($row['secret_blob'], (string)$keyRow['value']);
|
$dbPassword = decryptConnectorSecret($row['secret_blob'], (string)$keyRow['value']);
|
||||||
|
|
||||||
$legacy = readKeyValueFile($legacyConfig);
|
$legacy = readKeyValueFile($legacyConfig);
|
||||||
$piwigoUser = trim((string)($legacy['PIWIGO_SYNC_USER'] ?? ''));
|
$piwigoUser = trim((string)($legacy['PIWIGO_SYNC_USER'] ?? ''));
|
||||||
$piwigoPasswordFile = trim((string)($legacy['PIWIGO_SYNC_PASSWORD_FILE'] ?? '/etc/piwigo-sync/piwigo-password'));
|
$piwigoPasswordFile = trim((string)($legacy['PIWIGO_SYNC_PASSWORD_FILE'] ?? '/etc/piwigo-sync/piwigo-password'));
|
||||||
if ($piwigoUser === '' || !is_readable($piwigoPasswordFile))
|
if ($piwigoUser === '' || !is_readable($piwigoPasswordFile)) fail('Legacy-Piwigo-Sync-Zugangsdaten konnten fuer den einmaligen Cutover nicht gelesen werden.');
|
||||||
{
|
|
||||||
fail('Legacy-Piwigo-Sync-Zugangsdaten konnten fuer den einmaligen Cutover nicht gelesen werden.');
|
|
||||||
}
|
|
||||||
$piwigoPassword = trim((string)file_get_contents($piwigoPasswordFile));
|
$piwigoPassword = trim((string)file_get_contents($piwigoPasswordFile));
|
||||||
if ($piwigoPassword === '')
|
if ($piwigoPassword === '') fail('Legacy-Piwigo-Sync-Passwort ist leer.');
|
||||||
{
|
|
||||||
fail('Legacy-Piwigo-Sync-Passwort ist leer.');
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (array('host','port','database','user','source_view','gallery_root','state_dir') as $key)
|
foreach (array('host','port','database','user','source_view','gallery_root','state_dir') as $key)
|
||||||
{
|
{
|
||||||
if (!isset($config[$key]) || trim((string)$config[$key]) === '')
|
if (!isset($config[$key]) || trim((string)$config[$key]) === '') fail('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.');
|
||||||
{
|
|
||||||
fail('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$runtimeDir = '/etc/bratonien-tools/nc-connector';
|
$runtimeDir = '/etc/bratonien-tools/nc-connector';
|
||||||
if (!is_dir($runtimeDir) && !mkdir($runtimeDir, 0700, true))
|
if (!is_dir($runtimeDir) && !mkdir($runtimeDir, 0700, true)) fail('Connector-Laufzeitverzeichnis konnte nicht angelegt werden.');
|
||||||
{
|
|
||||||
fail('Connector-Laufzeitverzeichnis konnte nicht angelegt werden.');
|
|
||||||
}
|
|
||||||
chmod($runtimeDir, 0700);
|
chmod($runtimeDir, 0700);
|
||||||
|
|
||||||
$base = $runtimeDir.'/connection-'.$connectionId;
|
$base = $runtimeDir.'/connection-'.$connectionId;
|
||||||
@@ -230,16 +163,14 @@ try
|
|||||||
|
|
||||||
file_put_contents($dbPasswordPath, $dbPassword."\n", LOCK_EX);
|
file_put_contents($dbPasswordPath, $dbPassword."\n", LOCK_EX);
|
||||||
file_put_contents($piwigoPasswordPath, $piwigoPassword."\n", LOCK_EX);
|
file_put_contents($piwigoPasswordPath, $piwigoPassword."\n", LOCK_EX);
|
||||||
chmod($dbPasswordPath, 0600);
|
chmod($dbPasswordPath, 0600); chmod($piwigoPasswordPath, 0600);
|
||||||
chmod($piwigoPasswordPath, 0600);
|
|
||||||
|
|
||||||
$storageLines = array('# storage_id<TAB>source_prefix<TAB>local_mount');
|
$storageLines = array('# storage_id<TAB>source_prefix<TAB>local_mount');
|
||||||
foreach ((array)($config['storages'] ?? array()) as $storage)
|
foreach ((array)($config['storages'] ?? array()) as $storage)
|
||||||
{
|
{
|
||||||
$storageLines[] = (string)($storage['storage_id'] ?? '')."\t".(string)($storage['source_prefix'] ?? '')."\t".(string)($storage['local_mount'] ?? '');
|
$storageLines[] = (string)($storage['storage_id'] ?? '')."\t".trim((string)($storage['source_prefix'] ?? ''), '/')."\t".(string)($storage['local_mount'] ?? '');
|
||||||
}
|
}
|
||||||
file_put_contents($storagePath, implode("\n", $storageLines)."\n", LOCK_EX);
|
file_put_contents($storagePath, implode("\n", $storageLines)."\n", LOCK_EX); chmod($storagePath, 0600);
|
||||||
chmod($storagePath, 0600);
|
|
||||||
|
|
||||||
$piwigoRootConfigured = isset($legacy['PIWIGO_ROOT']) && trim((string)$legacy['PIWIGO_ROOT']) !== '' ? trim((string)$legacy['PIWIGO_ROOT']) : $piwigoRoot;
|
$piwigoRootConfigured = isset($legacy['PIWIGO_ROOT']) && trim((string)$legacy['PIWIGO_ROOT']) !== '' ? trim((string)$legacy['PIWIGO_ROOT']) : $piwigoRoot;
|
||||||
$lines = array(
|
$lines = array(
|
||||||
@@ -261,8 +192,7 @@ try
|
|||||||
'PIWIGO_SYNC_USER='.$piwigoUser,
|
'PIWIGO_SYNC_USER='.$piwigoUser,
|
||||||
'PIWIGO_SYNC_PASSWORD_FILE='.$piwigoPasswordPath,
|
'PIWIGO_SYNC_PASSWORD_FILE='.$piwigoPasswordPath,
|
||||||
);
|
);
|
||||||
file_put_contents($configPath, implode("\n", $lines)."\n", LOCK_EX);
|
file_put_contents($configPath, implode("\n", $lines)."\n", LOCK_EX); chmod($configPath, 0600);
|
||||||
chmod($configPath, 0600);
|
|
||||||
|
|
||||||
$enabledCheck = runCommand(array('systemctl', 'is-enabled', $legacyTimer), true);
|
$enabledCheck = runCommand(array('systemctl', 'is-enabled', $legacyTimer), true);
|
||||||
$legacyTimerWasEnabled = $enabledCheck['exit'] === 0;
|
$legacyTimerWasEnabled = $enabledCheck['exit'] === 0;
|
||||||
@@ -274,14 +204,8 @@ try
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
$active = runCommand(array('systemctl', 'is-active', '--quiet', 'piwigo-sync.service'), true);
|
$active = runCommand(array('systemctl', 'is-active', '--quiet', 'piwigo-sync.service'), true);
|
||||||
if ($active['exit'] !== 0)
|
if ($active['exit'] !== 0) break;
|
||||||
{
|
if (time() >= $deadline) fail('Ein laufender Legacy-Sync wurde nach 120 Sekunden nicht beendet.');
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (time() >= $deadline)
|
|
||||||
{
|
|
||||||
fail('Ein laufender Legacy-Sync wurde nach 120 Sekunden nicht beendet.');
|
|
||||||
}
|
|
||||||
sleep(2);
|
sleep(2);
|
||||||
}
|
}
|
||||||
while (true);
|
while (true);
|
||||||
@@ -295,9 +219,7 @@ try
|
|||||||
fail('Erster Connector-Lauf ist technisch fehlgeschlagen'.($detail !== '' ? ': '.$detail : '.'));
|
fail('Erster Connector-Lauf ist technisch fehlgeschlagen'.($detail !== '' ? ': '.$detail : '.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$runResult = 'no_changes';
|
$runResult = 'no_changes'; $statusState = ''; $statusMessage = '';
|
||||||
$statusState = '';
|
|
||||||
$statusMessage = '';
|
|
||||||
if (is_readable($statusPath))
|
if (is_readable($statusPath))
|
||||||
{
|
{
|
||||||
$status = json_decode((string)file_get_contents($statusPath), true);
|
$status = json_decode((string)file_get_contents($statusPath), true);
|
||||||
@@ -306,16 +228,9 @@ try
|
|||||||
$statusState = trim((string)($status['state'] ?? ''));
|
$statusState = trim((string)($status['state'] ?? ''));
|
||||||
$statusMessage = trim((string)($status['message'] ?? ''));
|
$statusMessage = trim((string)($status['message'] ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
$legacyNoChangeMessage = 'Synchronisierung fehlgeschlagen; bestehende Galerie blieb unverändert';
|
$legacyNoChangeMessage = 'Synchronisierung fehlgeschlagen; bestehende Galerie blieb unverändert';
|
||||||
if ($statusState === 'error' && $statusMessage !== $legacyNoChangeMessage)
|
if ($statusState === 'error' && $statusMessage !== $legacyNoChangeMessage) fail('Erster Connector-Lauf meldete einen technischen Fehler'.($statusMessage !== '' ? ': '.$statusMessage : '.'));
|
||||||
{
|
if ($statusState === 'ok') $runResult = 'changed';
|
||||||
fail('Erster Connector-Lauf meldete einen technischen Fehler'.($statusMessage !== '' ? ': '.$statusMessage : '.'));
|
|
||||||
}
|
|
||||||
if ($statusState === 'ok')
|
|
||||||
{
|
|
||||||
$runResult = 'changed';
|
|
||||||
}
|
|
||||||
elseif ($statusState === 'error' && $statusMessage === $legacyNoChangeMessage)
|
elseif ($statusState === 'error' && $statusMessage === $legacyNoChangeMessage)
|
||||||
{
|
{
|
||||||
$runResult = 'no_changes';
|
$runResult = 'no_changes';
|
||||||
@@ -331,28 +246,18 @@ try
|
|||||||
$timerPath = '/etc/systemd/system/'.$newTimer;
|
$timerPath = '/etc/systemd/system/'.$newTimer;
|
||||||
$service = "[Unit]\nDescription=Bratonien NC Connector Sync\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=oneshot\nEnvironment=PIWIGO_CONFIG=".$configPath."\nExecStart=".$legacyRuntime."\n\n";
|
$service = "[Unit]\nDescription=Bratonien NC Connector Sync\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=oneshot\nEnvironment=PIWIGO_CONFIG=".$configPath."\nExecStart=".$legacyRuntime."\n\n";
|
||||||
$timer = "[Unit]\nDescription=Bratonien NC Connector regelmaessig pruefen\n\n[Timer]\nOnBootSec=3min\nOnUnitActiveSec=1min\nRandomizedDelaySec=15s\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n";
|
$timer = "[Unit]\nDescription=Bratonien NC Connector regelmaessig pruefen\n\n[Timer]\nOnBootSec=3min\nOnUnitActiveSec=1min\nRandomizedDelaySec=15s\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n";
|
||||||
file_put_contents($servicePath, $service, LOCK_EX);
|
file_put_contents($servicePath, $service, LOCK_EX); file_put_contents($timerPath, $timer, LOCK_EX); chmod($servicePath,0644); chmod($timerPath,0644); $newTimerInstalled=true;
|
||||||
file_put_contents($timerPath, $timer, LOCK_EX);
|
|
||||||
chmod($servicePath, 0644);
|
|
||||||
chmod($timerPath, 0644);
|
|
||||||
$newTimerInstalled = true;
|
|
||||||
|
|
||||||
runCommand(array('systemctl', 'daemon-reload'));
|
runCommand(array('systemctl','daemon-reload'));
|
||||||
runCommand(array('systemctl', 'disable', $legacyTimer), true);
|
runCommand(array('systemctl','disable',$legacyTimer),true);
|
||||||
runCommand(array('systemctl', 'enable', '--now', $newTimer));
|
runCommand(array('systemctl','enable','--now',$newTimer));
|
||||||
|
|
||||||
$config['takeover']['cutover_at'] = date('Y-m-d H:i:s');
|
$config['takeover']['cutover_at']=date('Y-m-d H:i:s');
|
||||||
$config['takeover']['legacy_timer_disabled'] = true;
|
$config['takeover']['legacy_timer_disabled']=true;
|
||||||
$config['takeover']['connector_timer'] = $newTimer;
|
$config['takeover']['connector_timer']=$newTimer;
|
||||||
$config['takeover']['runtime'] = 'legacy-runtime-transition';
|
$config['takeover']['runtime']='legacy-runtime-transition';
|
||||||
$config['takeover']['first_run'] = array(
|
$config['takeover']['first_run']=array('state'=>'success','result'=>$runResult,'status_state'=>$statusState,'status_message'=>$statusMessage,'checked_at'=>date('Y-m-d H:i:s'));
|
||||||
'state' => 'success',
|
saveTakeoverResult($db,$table,$connectionId,$config,'active',true);
|
||||||
'result' => $runResult,
|
|
||||||
'status_state' => $statusState,
|
|
||||||
'status_message' => $statusMessage,
|
|
||||||
'checked_at' => date('Y-m-d H:i:s'),
|
|
||||||
);
|
|
||||||
saveTakeoverResult($db, $table, $connectionId, $config, 'active', true);
|
|
||||||
|
|
||||||
echo "Cutover erfolgreich.\n";
|
echo "Cutover erfolgreich.\n";
|
||||||
echo "Erster Connector-Lauf: ".$runResult."\n";
|
echo "Erster Connector-Lauf: ".$runResult."\n";
|
||||||
@@ -365,32 +270,14 @@ catch (Throwable $e)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$config['takeover']['first_run'] = array(
|
$config['takeover']['first_run']=array('state'=>'error','result'=>'error','checked_at'=>date('Y-m-d H:i:s'),'message'=>substr($e->getMessage(),0,500));
|
||||||
'state' => 'error',
|
saveTakeoverResult($db,$table,$connectionId,$config,'ready',false);
|
||||||
'result' => 'error',
|
|
||||||
'checked_at' => date('Y-m-d H:i:s'),
|
|
||||||
'message' => substr($e->getMessage(), 0, 500),
|
|
||||||
);
|
|
||||||
saveTakeoverResult($db, $table, $connectionId, $config, 'ready', false);
|
|
||||||
}
|
|
||||||
catch (Throwable $ignored)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
catch (Throwable $ignored){}
|
||||||
}
|
}
|
||||||
|
if ($newTimerInstalled) runCommand(array('systemctl','disable','--now',$newTimer),true);
|
||||||
if ($newTimerInstalled)
|
if ($legacyTimerWasEnabled) runCommand(array('systemctl','enable','--now',$legacyTimer),true);
|
||||||
{
|
else runCommand(array('systemctl','start',$legacyTimer),true);
|
||||||
runCommand(array('systemctl', 'disable', '--now', $newTimer), true);
|
|
||||||
}
|
|
||||||
if ($legacyTimerWasEnabled)
|
|
||||||
{
|
|
||||||
runCommand(array('systemctl', 'enable', '--now', $legacyTimer), true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
runCommand(array('systemctl', 'start', $legacyTimer), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fwrite(STDERR, "Cutover fehlgeschlagen: ".$e->getMessage()."\n");
|
fwrite(STDERR, "Cutover fehlgeschlagen: ".$e->getMessage()."\n");
|
||||||
fwrite(STDERR, "Legacy-Timer wurde wieder aktiviert/gestartet.\n");
|
fwrite(STDERR, "Legacy-Timer wurde wieder aktiviert/gestartet.\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|||||||
@@ -25,82 +25,57 @@ $stateRoot = '/var/lib/bratonien-tools/nc-connector';
|
|||||||
$servicePath = '/etc/systemd/system/bratonien-nc-connector.service';
|
$servicePath = '/etc/systemd/system/bratonien-nc-connector.service';
|
||||||
$timerPath = '/etc/systemd/system/bratonien-nc-connector.timer';
|
$timerPath = '/etc/systemd/system/bratonien-nc-connector.timer';
|
||||||
|
|
||||||
function fail_install($message)
|
function fail_install($message) { throw new RuntimeException($message); }
|
||||||
{
|
|
||||||
throw new RuntimeException($message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_install(array $command, $allowFailure = false)
|
function run_install(array $command, $allowFailure = false)
|
||||||
{
|
{
|
||||||
$spec = array(
|
$spec = array(0=>array('file','/dev/null','r'),1=>array('pipe','w'),2=>array('pipe','w'));
|
||||||
0 => array('file', '/dev/null', 'r'),
|
|
||||||
1 => array('pipe', 'w'),
|
|
||||||
2 => array('pipe', 'w'),
|
|
||||||
);
|
|
||||||
$process = proc_open($command, $spec, $pipes);
|
$process = proc_open($command, $spec, $pipes);
|
||||||
if (!is_resource($process))
|
if (!is_resource($process)) fail_install('Prozess konnte nicht gestartet werden: '.implode(' ', $command));
|
||||||
{
|
|
||||||
fail_install('Prozess konnte nicht gestartet werden: '.implode(' ', $command));
|
|
||||||
}
|
|
||||||
$stdout = stream_get_contents($pipes[1]);
|
$stdout = stream_get_contents($pipes[1]);
|
||||||
$stderr = stream_get_contents($pipes[2]);
|
$stderr = stream_get_contents($pipes[2]);
|
||||||
fclose($pipes[1]);
|
fclose($pipes[1]); fclose($pipes[2]);
|
||||||
fclose($pipes[2]);
|
|
||||||
$exit = proc_close($process);
|
$exit = proc_close($process);
|
||||||
if ($exit !== 0 && !$allowFailure)
|
if ($exit !== 0 && !$allowFailure)
|
||||||
{
|
{
|
||||||
$detail = trim($stderr) !== '' ? trim($stderr) : trim($stdout);
|
$detail = trim($stderr) !== '' ? trim($stderr) : trim($stdout);
|
||||||
fail_install('Befehl fehlgeschlagen ('.$exit.'): '.implode(' ', $command).($detail !== '' ? "\n".$detail : ''));
|
fail_install('Befehl fehlgeschlagen ('.$exit.'): '.implode(' ', $command).($detail !== '' ? "\n".$detail : ''));
|
||||||
}
|
}
|
||||||
return array('exit'=>$exit, 'stdout'=>(string)$stdout, 'stderr'=>(string)$stderr);
|
return array('exit'=>$exit,'stdout'=>(string)$stdout,'stderr'=>(string)$stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrypt_install_credentials($blob, $hexKey)
|
function decrypt_install_credentials($blob, $hexKey)
|
||||||
{
|
{
|
||||||
if (!preg_match('/^[a-f0-9]{64}$/', $hexKey))
|
if (!preg_match('/^[a-f0-9]{64}$/', $hexKey)) fail_install('Connector-Schluessel ist ungueltig.');
|
||||||
{
|
|
||||||
fail_install('Connector-Schluessel ist ungueltig.');
|
|
||||||
}
|
|
||||||
$outer = base64_decode(trim((string)$blob), true);
|
$outer = base64_decode(trim((string)$blob), true);
|
||||||
$payload = is_string($outer) ? json_decode($outer, true) : null;
|
$payload = is_string($outer) ? json_decode($outer, true) : null;
|
||||||
if (!is_array($payload) || (int)($payload['v'] ?? 0) !== 1)
|
if (!is_array($payload) || (int)($payload['v'] ?? 0) !== 1) fail_install('Connector-Zugangsdaten haben ein unbekanntes Format.');
|
||||||
{
|
|
||||||
fail_install('Connector-Zugangsdaten haben ein unbekanntes Format.');
|
|
||||||
}
|
|
||||||
$iv = base64_decode((string)($payload['iv'] ?? ''), true);
|
$iv = base64_decode((string)($payload['iv'] ?? ''), true);
|
||||||
$tag = base64_decode((string)($payload['tag'] ?? ''), true);
|
$tag = base64_decode((string)($payload['tag'] ?? ''), true);
|
||||||
$cipher = base64_decode((string)($payload['data'] ?? ''), true);
|
$cipher = base64_decode((string)($payload['data'] ?? ''), true);
|
||||||
$plain = openssl_decrypt($cipher, 'aes-256-gcm', hex2bin($hexKey), OPENSSL_RAW_DATA, $iv, $tag);
|
$plain = openssl_decrypt($cipher, 'aes-256-gcm', hex2bin($hexKey), OPENSSL_RAW_DATA, $iv, $tag);
|
||||||
if ($plain === false || $plain === '')
|
if ($plain === false || $plain === '') fail_install('Connector-Zugangsdaten konnten nicht entschluesselt werden.');
|
||||||
{
|
|
||||||
fail_install('Connector-Zugangsdaten konnten nicht entschluesselt werden.');
|
|
||||||
}
|
|
||||||
$decoded = json_decode($plain, true);
|
$decoded = json_decode($plain, true);
|
||||||
if (!is_array($decoded) || empty($decoded['db_password']) || empty($decoded['piwigo_user']) || empty($decoded['piwigo_password']))
|
if (!is_array($decoded))
|
||||||
{
|
{
|
||||||
fail_install('Fuer eine native Aktivierung muessen Datenbank- und Piwigo-Zugangsdaten vollstaendig gespeichert sein.');
|
// Backward compatibility: old connections stored only the DB password.
|
||||||
|
return array('db_password'=>$plain,'piwigo_user'=>'','piwigo_password'=>'');
|
||||||
}
|
}
|
||||||
return $decoded;
|
if (empty($decoded['db_password'])) fail_install('Datenbankpasswort fehlt in den Connector-Zugangsdaten.');
|
||||||
|
return array(
|
||||||
|
'db_password'=>(string)$decoded['db_password'],
|
||||||
|
'piwigo_user'=>(string)($decoded['piwigo_user'] ?? ''),
|
||||||
|
'piwigo_password'=>(string)($decoded['piwigo_password'] ?? ''),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sql_install(mysqli $db, $value)
|
function sql_install(mysqli $db, $value) { return $db->real_escape_string((string)$value); }
|
||||||
{
|
|
||||||
return $db->real_escape_string((string)$value);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!is_readable($dbConfig))
|
if (!is_readable($dbConfig)) fail_install('Piwigo-Datenbankkonfiguration nicht lesbar: '.$dbConfig);
|
||||||
{
|
$conf = array(); $prefixeTable = 'piwigo_'; require $dbConfig;
|
||||||
fail_install('Piwigo-Datenbankkonfiguration nicht lesbar: '.$dbConfig);
|
foreach (array('db_host','db_user','db_password','db_base') as $key) if (!isset($conf[$key])) fail_install('Piwigo-Datenbankkonfiguration enthaelt '.$key.' nicht.');
|
||||||
}
|
|
||||||
$conf = array();
|
|
||||||
$prefixeTable = 'piwigo_';
|
|
||||||
require $dbConfig;
|
|
||||||
foreach (array('db_host','db_user','db_password','db_base') as $key)
|
|
||||||
{
|
|
||||||
if (!isset($conf[$key])) fail_install('Piwigo-Datenbankkonfiguration enthaelt '.$key.' nicht.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$db = new mysqli($conf['db_host'], $conf['db_user'], $conf['db_password'], $conf['db_base']);
|
$db = new mysqli($conf['db_host'], $conf['db_user'], $conf['db_password'], $conf['db_base']);
|
||||||
if ($db->connect_errno) fail_install('Piwigo-Datenbank nicht erreichbar: '.$db->connect_error);
|
if ($db->connect_errno) fail_install('Piwigo-Datenbank nicht erreichbar: '.$db->connect_error);
|
||||||
@@ -111,29 +86,27 @@ try
|
|||||||
if (!$result || !$result->num_rows) fail_install('Connector-Verbindung #'.$id.' wurde nicht gefunden.');
|
if (!$result || !$result->num_rows) fail_install('Connector-Verbindung #'.$id.' wurde nicht gefunden.');
|
||||||
$row = $result->fetch_assoc();
|
$row = $result->fetch_assoc();
|
||||||
if ((string)$row['adapter'] !== 'local') fail_install('Native Aktivierung ist derzeit nur fuer lokale Verbindungen verfuegbar.');
|
if ((string)$row['adapter'] !== 'local') fail_install('Native Aktivierung ist derzeit nur fuer lokale Verbindungen verfuegbar.');
|
||||||
if ((string)$row['takeover_state'] !== 'verified' || (int)$row['enabled'] !== 0)
|
if ((string)$row['takeover_state'] !== 'verified' || (int)$row['enabled'] !== 0) fail_install('Die Verbindung muss zuerst erfolgreich verifiziert und deaktiviert sein.');
|
||||||
{
|
|
||||||
fail_install('Die Verbindung muss zuerst erfolgreich verifiziert und deaktiviert sein.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config = json_decode((string)$row['config_json'], true);
|
$config = json_decode((string)$row['config_json'], true);
|
||||||
if (!is_array($config) || empty($config['verification']['ok'])) fail_install('Erfolgreiche Verifikation fehlt.');
|
if (!is_array($config) || empty($config['verification']['ok'])) fail_install('Erfolgreiche Verifikation fehlt.');
|
||||||
foreach (array('host','port','database','user','source_view','activity_view','gallery_root') as $key)
|
foreach (array('host','port','database','user','source_view','activity_view','gallery_root') as $key) if (trim((string)($config[$key] ?? '')) === '') fail_install('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.');
|
||||||
{
|
|
||||||
if (trim((string)($config[$key] ?? '')) === '') fail_install('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$keyResult = $db->query("SELECT value FROM `".$prefixeTable."config` WHERE param='bratonien_nc_connector_secret' LIMIT 1");
|
$keyResult = $db->query("SELECT value FROM `".$prefixeTable."config` WHERE param='bratonien_nc_connector_secret' LIMIT 1");
|
||||||
if (!$keyResult || !$keyResult->num_rows) fail_install('Connector-Schluessel wurde in Piwigo nicht gefunden.');
|
if (!$keyResult || !$keyResult->num_rows) fail_install('Connector-Schluessel wurde in Piwigo nicht gefunden.');
|
||||||
$keyRow = $keyResult->fetch_assoc();
|
$credentials = decrypt_install_credentials((string)$row['secret_blob'], (string)$keyResult->fetch_assoc()['value']);
|
||||||
$credentials = decrypt_install_credentials((string)$row['secret_blob'], (string)$keyRow['value']);
|
|
||||||
|
$apiAvailable = false;
|
||||||
|
$apiResult = $db->query("SELECT value FROM `".$prefixeTable."config` WHERE param='bratonien_nc_piwigo_api' LIMIT 1");
|
||||||
|
if ($apiResult && $apiResult->num_rows) $apiAvailable = trim((string)$apiResult->fetch_assoc()['value']) !== '';
|
||||||
|
$fallbackAvailable = $credentials['piwigo_user'] !== '' && $credentials['piwigo_password'] !== '';
|
||||||
|
if (!$apiAvailable && !$fallbackAvailable) fail_install('Weder Piwigo-API noch Fallback-Zugang sind gespeichert.');
|
||||||
|
|
||||||
$stateDir = $stateRoot.'/connection-'.$id;
|
$stateDir = $stateRoot.'/connection-'.$id;
|
||||||
$statusFile = $stateDir.'/connector-status.json';
|
$statusFile = $stateDir.'/connector-status.json';
|
||||||
if (!is_dir($configDir) && !mkdir($configDir, 0700, true)) fail_install('Konfigurationsverzeichnis konnte nicht angelegt werden.');
|
if (!is_dir($configDir) && !mkdir($configDir, 0700, true)) fail_install('Konfigurationsverzeichnis konnte nicht angelegt werden.');
|
||||||
if (!is_dir($stateDir) && !mkdir($stateDir, 0750, true)) fail_install('State-Verzeichnis konnte nicht angelegt werden.');
|
if (!is_dir($stateDir) && !mkdir($stateDir, 0750, true)) fail_install('State-Verzeichnis konnte nicht angelegt werden.');
|
||||||
chmod($configDir, 0700);
|
chmod($configDir, 0700); chmod($stateDir, 0750);
|
||||||
chmod($stateDir, 0750);
|
|
||||||
|
|
||||||
$base = $configDir.'/connection-'.$id;
|
$base = $configDir.'/connection-'.$id;
|
||||||
$dbPasswordPath = $base.'.db-password';
|
$dbPasswordPath = $base.'.db-password';
|
||||||
@@ -141,19 +114,23 @@ try
|
|||||||
$storagePath = $base.'.storages.tsv';
|
$storagePath = $base.'.storages.tsv';
|
||||||
$configPath = $base.'.conf';
|
$configPath = $base.'.conf';
|
||||||
|
|
||||||
file_put_contents($dbPasswordPath, (string)$credentials['db_password']."\n", LOCK_EX);
|
file_put_contents($dbPasswordPath, (string)$credentials['db_password']."\n", LOCK_EX); chmod($dbPasswordPath, 0600);
|
||||||
file_put_contents($piwigoPasswordPath, (string)$credentials['piwigo_password']."\n", LOCK_EX);
|
if ($fallbackAvailable)
|
||||||
chmod($dbPasswordPath, 0600);
|
{
|
||||||
chmod($piwigoPasswordPath, 0600);
|
file_put_contents($piwigoPasswordPath, (string)$credentials['piwigo_password']."\n", LOCK_EX); chmod($piwigoPasswordPath, 0600);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@unlink($piwigoPasswordPath);
|
||||||
|
}
|
||||||
|
|
||||||
$storageLines = array('# storage_id<TAB>source_prefix<TAB>local_mount');
|
$storageLines = array('# storage_id<TAB>source_prefix<TAB>local_mount');
|
||||||
foreach ((array)($config['storages'] ?? array()) as $storage)
|
foreach ((array)($config['storages'] ?? array()) as $storage)
|
||||||
{
|
{
|
||||||
$storageLines[] = (string)($storage['storage_id'] ?? '')."\t".(string)($storage['source_prefix'] ?? '')."\t".(string)($storage['local_mount'] ?? '');
|
$storageLines[] = (string)($storage['storage_id'] ?? '')."\t".trim((string)($storage['source_prefix'] ?? ''), '/')."\t".(string)($storage['local_mount'] ?? '');
|
||||||
}
|
}
|
||||||
if (count($storageLines) === 1) fail_install('Keine Storage-Zuordnungen gespeichert.');
|
if (count($storageLines) === 1) fail_install('Keine Storage-Zuordnungen gespeichert.');
|
||||||
file_put_contents($storagePath, implode("\n", $storageLines)."\n", LOCK_EX);
|
file_put_contents($storagePath, implode("\n", $storageLines)."\n", LOCK_EX); chmod($storagePath, 0600);
|
||||||
chmod($storagePath, 0600);
|
|
||||||
|
|
||||||
$lines = array(
|
$lines = array(
|
||||||
'PIWIGO_ROOT='.$piwigoRoot,
|
'PIWIGO_ROOT='.$piwigoRoot,
|
||||||
@@ -172,44 +149,32 @@ try
|
|||||||
'MAX_WAIT_SECONDS='.(int)($config['max_wait_seconds'] ?? 900),
|
'MAX_WAIT_SECONDS='.(int)($config['max_wait_seconds'] ?? 900),
|
||||||
'FULL_SYNC_SECONDS='.(int)($config['full_sync_seconds'] ?? 86400),
|
'FULL_SYNC_SECONDS='.(int)($config['full_sync_seconds'] ?? 86400),
|
||||||
'PIWIGO_SYNC_ENABLED=1',
|
'PIWIGO_SYNC_ENABLED=1',
|
||||||
'PIWIGO_SYNC_USER='.(string)$credentials['piwigo_user'],
|
|
||||||
'PIWIGO_SYNC_PASSWORD_FILE='.$piwigoPasswordPath,
|
|
||||||
);
|
);
|
||||||
file_put_contents($configPath, implode("\n", $lines)."\n", LOCK_EX);
|
if ($fallbackAvailable)
|
||||||
chmod($configPath, 0600);
|
{
|
||||||
|
$lines[] = 'PIWIGO_SYNC_USER='.(string)$credentials['piwigo_user'];
|
||||||
|
$lines[] = 'PIWIGO_SYNC_PASSWORD_FILE='.$piwigoPasswordPath;
|
||||||
|
}
|
||||||
|
file_put_contents($configPath, implode("\n", $lines)."\n", LOCK_EX); chmod($configPath, 0600);
|
||||||
|
|
||||||
echo "Teste Verbindung mit Plugin-Runtime...\n";
|
echo "Teste Verbindung mit Plugin-Runtime...\n";
|
||||||
$test = run_install(array('env', 'PIWIGO_CONFIG='.$configPath, 'bash', $pluginRoot.'/runtime/sync.sh'), true);
|
$test = run_install(array('env', 'PIWIGO_CONFIG='.$configPath, 'bash', $pluginRoot.'/runtime/sync.sh'), true);
|
||||||
if ($test['exit'] !== 0)
|
if ($test['exit'] !== 0)
|
||||||
{
|
{
|
||||||
@unlink($configPath);
|
@unlink($configPath); @unlink($storagePath); @unlink($dbPasswordPath); @unlink($piwigoPasswordPath);
|
||||||
@unlink($storagePath);
|
|
||||||
@unlink($dbPasswordPath);
|
|
||||||
@unlink($piwigoPasswordPath);
|
|
||||||
$detail = trim($test['stderr']) !== '' ? trim($test['stderr']) : trim($test['stdout']);
|
$detail = trim($test['stderr']) !== '' ? trim($test['stderr']) : trim($test['stdout']);
|
||||||
fail_install('Runtime-Test fehlgeschlagen'.($detail !== '' ? ': '.$detail : '.'));
|
fail_install('Runtime-Test fehlgeschlagen'.($detail !== '' ? ': '.$detail : '.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$service = "[Unit]\nDescription=Bratonien NC Connector Sync\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=oneshot\nExecStart=/usr/bin/env bash ".$pluginRoot."/runtime/run-all.sh\n\n";
|
$service = "[Unit]\nDescription=Bratonien NC Connector Sync\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=oneshot\nExecStart=/usr/bin/env bash ".$pluginRoot."/runtime/run-all.sh\n\n";
|
||||||
$timer = "[Unit]\nDescription=Bratonien NC Connector regelmaessig pruefen\n\n[Timer]\nOnBootSec=3min\nOnUnitActiveSec=1min\nRandomizedDelaySec=15s\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n";
|
$timer = "[Unit]\nDescription=Bratonien NC Connector regelmaessig pruefen\n\n[Timer]\nOnBootSec=3min\nOnUnitActiveSec=1min\nRandomizedDelaySec=15s\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n";
|
||||||
file_put_contents($servicePath, $service, LOCK_EX);
|
file_put_contents($servicePath, $service, LOCK_EX); file_put_contents($timerPath, $timer, LOCK_EX); chmod($servicePath,0644); chmod($timerPath,0644);
|
||||||
file_put_contents($timerPath, $timer, LOCK_EX);
|
|
||||||
chmod($servicePath, 0644);
|
|
||||||
chmod($timerPath, 0644);
|
|
||||||
|
|
||||||
$config['state_dir'] = $stateDir;
|
$config['state_dir']=$stateDir;$config['status_file']=$statusFile;$config['runtime']=array('mode'=>'plugin-runtime','config'=>$configPath);
|
||||||
$config['status_file'] = $statusFile;
|
$json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);$now=date('Y-m-d H:i:s');
|
||||||
$config['runtime'] = array('mode'=>'plugin-runtime', 'config'=>$configPath);
|
if (!$db->query("UPDATE `".$table."` SET enabled=1, takeover_state='active', config_json='".sql_install($db,$json)."', updated='".sql_install($db,$now)."' WHERE id=".$id)) fail_install('Connector-Status konnte nicht gespeichert werden: '.$db->error);
|
||||||
$json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
if (!$db->query("UPDATE `".$table."` SET enabled=1, takeover_state='active', config_json='".sql_install($db, $json)."', updated='".sql_install($db, $now)."' WHERE id=".$id))
|
|
||||||
{
|
|
||||||
fail_install('Connector-Status konnte nicht gespeichert werden: '.$db->error);
|
|
||||||
}
|
|
||||||
|
|
||||||
run_install(array('systemctl', 'daemon-reload'));
|
|
||||||
run_install(array('systemctl', 'enable', '--now', 'bratonien-nc-connector.timer'));
|
|
||||||
|
|
||||||
|
run_install(array('systemctl','daemon-reload')); run_install(array('systemctl','enable','--now','bratonien-nc-connector.timer'));
|
||||||
echo "Aktivierung erfolgreich.\n";
|
echo "Aktivierung erfolgreich.\n";
|
||||||
echo "Verbindung #".$id." verwendet jetzt den nativen Bratonien-Tools-State unter ".$stateDir.".\n";
|
echo "Verbindung #".$id." verwendet jetzt den nativen Bratonien-Tools-State unter ".$stateDir.".\n";
|
||||||
echo "Der gemeinsame Connector-Timer verarbeitet alle installierten connection-*.conf Dateien.\n";
|
echo "Der gemeinsame Connector-Timer verarbeitet alle installierten connection-*.conf Dateien.\n";
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ $latest = array(
|
|||||||
'state'=>'idle',
|
'state'=>'idle',
|
||||||
'message'=>'Noch kein Connector-Status verfügbar.',
|
'message'=>'Noch kein Connector-Status verfügbar.',
|
||||||
'timestamp'=>0,
|
'timestamp'=>0,
|
||||||
|
'last_run_label'=>'Nicht verfügbar',
|
||||||
|
'next_run_timestamp'=>0,
|
||||||
|
'next_run_label'=>'Nicht verfügbar',
|
||||||
'auth_mode'=>'',
|
'auth_mode'=>'',
|
||||||
'api'=>array('state'=>'not_run','message'=>''),
|
'api'=>array('state'=>'not_run','message'=>''),
|
||||||
'fallback'=>array('state'=>'not_run','message'=>''),
|
'fallback'=>array('state'=>'not_run','message'=>''),
|
||||||
@@ -56,4 +59,21 @@ if (is_dir($dir))
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((int)$latest['timestamp'] > 0)
|
||||||
|
{
|
||||||
|
$latest['last_run_label'] = date('d.m.Y H:i:s', (int)$latest['timestamp']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$system_file = BRATONIEN_TOOLS_PATH.'include/nc_connector_system.inc.php';
|
||||||
|
if (is_readable($system_file))
|
||||||
|
{
|
||||||
|
include_once($system_file);
|
||||||
|
if (function_exists('bratonien_tools_nc_connector_system_status'))
|
||||||
|
{
|
||||||
|
$system = bratonien_tools_nc_connector_system_status(array());
|
||||||
|
$latest['next_run_timestamp'] = (int)($system['next_run_timestamp'] ?? 0);
|
||||||
|
$latest['next_run_label'] = (string)($system['next_run_label'] ?? 'Nicht verfügbar');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode($latest, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
echo json_encode($latest, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
|||||||
@@ -7,109 +7,71 @@ import argparse
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
IDENTIFIER = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_view(name: str) -> str:
|
||||||
|
value = str(name).strip()
|
||||||
|
if not IDENTIFIER.fullmatch(value):
|
||||||
|
raise ValueError(f"invalid SQL view name: {value!r}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def load(path: Path) -> dict[str, object]:
|
def load(path: Path) -> dict[str, object]:
|
||||||
defaults: dict[str, object] = {
|
defaults: dict[str, object] = {"processed":0,"observed":0,"pending_since":0,"last_change":0,"last_full":0,"source_signature":""}
|
||||||
"processed": 0,
|
|
||||||
"observed": 0,
|
|
||||||
"pending_since": 0,
|
|
||||||
"last_change": 0,
|
|
||||||
"last_full": 0,
|
|
||||||
"source_signature": "",
|
|
||||||
}
|
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return defaults
|
return defaults
|
||||||
data = json.loads(path.read_text(encoding="utf-8"))
|
data=json.loads(path.read_text(encoding="utf-8"))
|
||||||
return {
|
return {"processed":int(data.get("processed",0)),"observed":int(data.get("observed",0)),"pending_since":int(data.get("pending_since",0)),"last_change":int(data.get("last_change",0)),"last_full":int(data.get("last_full",0)),"source_signature":str(data.get("source_signature",""))}
|
||||||
"processed": int(data.get("processed", 0)),
|
|
||||||
"observed": int(data.get("observed", 0)),
|
|
||||||
"pending_since": int(data.get("pending_since", 0)),
|
|
||||||
"last_change": int(data.get("last_change", 0)),
|
|
||||||
"last_full": int(data.get("last_full", 0)),
|
|
||||||
"source_signature": str(data.get("source_signature", "")),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def save(path: Path, state: dict[str, object]) -> None:
|
def save(path: Path, state: dict[str, object]) -> None:
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True,exist_ok=True)
|
||||||
fd, name = tempfile.mkstemp(dir=path.parent)
|
fd,name=tempfile.mkstemp(dir=path.parent)
|
||||||
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
with os.fdopen(fd,"w",encoding="utf-8") as handle:
|
||||||
json.dump(state, handle, sort_keys=True)
|
json.dump(state,handle,sort_keys=True);handle.write("\n")
|
||||||
handle.write("\n")
|
|
||||||
Path(name).replace(path)
|
Path(name).replace(path)
|
||||||
|
|
||||||
|
|
||||||
def query(args: argparse.Namespace, sql: str) -> str:
|
def query(args: argparse.Namespace, sql: str) -> str:
|
||||||
env = os.environ.copy()
|
env=os.environ.copy();env["PGPASSWORD"]=args.password_file.read_text(encoding="utf-8").strip()
|
||||||
env["PGPASSWORD"] = args.password_file.read_text(encoding="utf-8").strip()
|
command=["psql","-XAt","-h",args.host,"-p",str(args.port),"-U",args.user,"-d",args.database,"-v","ON_ERROR_STOP=1","-c",sql]
|
||||||
command = ["psql", "-XAt", "-h", args.host, "-p", str(args.port), "-U", args.user, "-d", args.database, "-c", sql]
|
return subprocess.run(command,env=env,check=True,text=True,capture_output=True).stdout
|
||||||
return subprocess.run(command, env=env, check=True, text=True, capture_output=True).stdout
|
|
||||||
|
|
||||||
|
|
||||||
def latest(args: argparse.Namespace) -> int:
|
def latest(args: argparse.Namespace) -> int:
|
||||||
return int(query(args, f"SELECT COALESCE(MAX(activity_id), 0) FROM {args.view}").strip())
|
return int(query(args,f"SELECT COALESCE(MAX(activity_id), 0) FROM {validate_view(args.view)}").strip())
|
||||||
|
|
||||||
|
|
||||||
def source_signature(args: argparse.Namespace) -> str:
|
def source_signature(args: argparse.Namespace) -> str:
|
||||||
if not args.source_view:
|
if not args.source_view:return ""
|
||||||
return ""
|
payload=query(args,f"SELECT share_id, display_name, storage_id, source_path FROM {validate_view(args.source_view)} ORDER BY share_id")
|
||||||
payload = query(args, f"SELECT share_id, display_name, storage_id, source_path FROM {args.source_view} ORDER BY share_id")
|
|
||||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser()
|
parser=argparse.ArgumentParser();parser.add_argument("action",choices=("check","commit"));parser.add_argument("--state",required=True,type=Path);parser.add_argument("--host",required=True);parser.add_argument("--port",type=int,default=5432);parser.add_argument("--database",required=True);parser.add_argument("--user",required=True);parser.add_argument("--password-file",required=True,type=Path);parser.add_argument("--view",default="piwigo_showcase_activity");parser.add_argument("--source-view",default="");parser.add_argument("--quiet",type=int,default=120);parser.add_argument("--max-wait",type=int,default=900);parser.add_argument("--full-after",type=int,default=86400);args=parser.parse_args()
|
||||||
parser.add_argument("action", choices=("check", "commit"))
|
|
||||||
parser.add_argument("--state", required=True, type=Path)
|
|
||||||
parser.add_argument("--host", required=True)
|
|
||||||
parser.add_argument("--port", type=int, default=5432)
|
|
||||||
parser.add_argument("--database", required=True)
|
|
||||||
parser.add_argument("--user", required=True)
|
|
||||||
parser.add_argument("--password-file", required=True, type=Path)
|
|
||||||
parser.add_argument("--view", default="piwigo_showcase_activity")
|
|
||||||
parser.add_argument("--source-view", default="")
|
|
||||||
parser.add_argument("--quiet", type=int, default=120)
|
|
||||||
parser.add_argument("--max-wait", type=int, default=900)
|
|
||||||
parser.add_argument("--full-after", type=int, default=86400)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = load(args.state)
|
validate_view(args.view)
|
||||||
now = int(time.time())
|
if args.source_view:validate_view(args.source_view)
|
||||||
current = latest(args)
|
state=load(args.state);now=int(time.time());current=latest(args);current_source=source_signature(args)
|
||||||
current_source = source_signature(args)
|
if args.action=="commit":state.update(processed=current,observed=current,pending_since=0,last_change=0,last_full=now,source_signature=current_source);save(args.state,state);return 0
|
||||||
|
if not int(state["last_full"]):return 0
|
||||||
if args.action == "commit":
|
if current_source and current_source!=str(state["source_signature"]):return 0
|
||||||
state.update(processed=current, observed=current, pending_since=0, last_change=0, last_full=now, source_signature=current_source)
|
if current>int(state["observed"]):state["observed"]=current;state["last_change"]=now;state["pending_since"]=int(state["pending_since"]) or now;save(args.state,state)
|
||||||
save(args.state, state)
|
if now-int(state["last_full"])>=args.full_after:return 0
|
||||||
return 0
|
if int(state["observed"])<=int(state["processed"]):return 3
|
||||||
|
if now-int(state["last_change"])>=args.quiet or now-int(state["pending_since"])>=args.max_wait:return 0
|
||||||
if not int(state["last_full"]):
|
|
||||||
return 0
|
|
||||||
if current_source and current_source != str(state["source_signature"]):
|
|
||||||
return 0
|
|
||||||
if current > int(state["observed"]):
|
|
||||||
state["observed"] = current
|
|
||||||
state["last_change"] = now
|
|
||||||
state["pending_since"] = int(state["pending_since"]) or now
|
|
||||||
save(args.state, state)
|
|
||||||
if now - int(state["last_full"]) >= args.full_after:
|
|
||||||
return 0
|
|
||||||
if int(state["observed"]) <= int(state["processed"]):
|
|
||||||
return 3
|
|
||||||
if now - int(state["last_change"]) >= args.quiet or now - int(state["pending_since"]) >= args.max_wait:
|
|
||||||
return 0
|
|
||||||
return 3
|
return 3
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
print(f"activity-gate: {error}", file=sys.stderr)
|
print(f"activity-gate: {error}",file=sys.stderr);return 1
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__=="__main__":raise SystemExit(main())
|
||||||
raise SystemExit(main())
|
|
||||||
|
|||||||
@@ -7,174 +7,109 @@ import argparse
|
|||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
|
|
||||||
|
IDENTIFIER = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?$")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_view(name: str) -> str:
|
||||||
|
value = str(name).strip()
|
||||||
|
if not IDENTIFIER.fullmatch(value):
|
||||||
|
raise ValueError(f"invalid SQL view name: {value!r}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def read_config(path: Path) -> dict[str, tuple[str, Path]]:
|
def read_config(path: Path) -> dict[str, tuple[str, Path]]:
|
||||||
result: dict[str, tuple[str, Path]] = {}
|
result: dict[str, tuple[str, Path]] = {}
|
||||||
with path.open(encoding="utf-8") as handle:
|
with path.open(encoding="utf-8") as handle:
|
||||||
for number, line in enumerate(handle, 1):
|
for number, line in enumerate(handle, 1):
|
||||||
line = line.rstrip("\n")
|
line=line.rstrip("\n")
|
||||||
if not line or line.startswith("#"):
|
if not line or line.startswith("#"):continue
|
||||||
continue
|
fields=line.split("\t")
|
||||||
fields = line.split("\t")
|
if len(fields)!=3:raise ValueError(f"{path}:{number}: expected storage_id, source_prefix and local_mount")
|
||||||
if len(fields) != 3:
|
storage_id,prefix,mount=fields
|
||||||
raise ValueError(f"{path}:{number}: expected storage_id, source_prefix and local_mount")
|
if not storage_id.strip():raise ValueError(f"{path}:{number}: storage_id is empty")
|
||||||
storage_id, prefix, mount = fields
|
result[storage_id]=(prefix.strip("/"),Path(mount))
|
||||||
result[storage_id] = (prefix.strip("/"), Path(mount))
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def run_query(args: argparse.Namespace, env: dict[str, str], sql: str) -> subprocess.CompletedProcess[str]:
|
def run_query(args: argparse.Namespace, env: dict[str,str], sql: str) -> subprocess.CompletedProcess[str]:
|
||||||
command = [
|
command=["psql","-X","-A","-F","\t","-t","-v","ON_ERROR_STOP=1","-h",args.host,"-p",str(args.port),"-U",args.user,"-d",args.database,"-c",sql]
|
||||||
"psql", "-X", "-A", "-F", "\t", "-t",
|
return subprocess.run(command,env=env,check=False,text=True,capture_output=True)
|
||||||
"-h", args.host, "-p", str(args.port), "-U", args.user, "-d", args.database,
|
|
||||||
"-c", sql,
|
|
||||||
]
|
|
||||||
return subprocess.run(command, env=env, check=False, text=True, capture_output=True)
|
|
||||||
|
|
||||||
|
|
||||||
def query_rows(args: argparse.Namespace) -> list[list[str]]:
|
def query_rows(args: argparse.Namespace) -> list[list[str]]:
|
||||||
password = args.password_file.read_text(encoding="utf-8").strip()
|
password=args.password_file.read_text(encoding="utf-8").strip();env=os.environ.copy();env["PGPASSWORD"]=password;view=validate_view(args.view)
|
||||||
env = os.environ.copy()
|
modern_sql=f"SELECT share_id, item_type, display_name, storage_id, source_path FROM {view} ORDER BY share_id"
|
||||||
env["PGPASSWORD"] = password
|
completed=run_query(args,env,modern_sql)
|
||||||
|
if completed.returncode==0:return list(csv.reader(completed.stdout.splitlines(),delimiter="\t"))
|
||||||
modern_sql = f"SELECT share_id, item_type, display_name, storage_id, source_path FROM {args.view} ORDER BY share_id"
|
if "item_type" not in completed.stderr or "does not exist" not in completed.stderr:raise RuntimeError(completed.stderr.strip() or "Nextcloud source view query failed")
|
||||||
completed = run_query(args, env, modern_sql)
|
legacy_sql=f"SELECT share_id, display_name, storage_id, source_path FROM {view} ORDER BY share_id"
|
||||||
if completed.returncode == 0:
|
completed=run_query(args,env,legacy_sql)
|
||||||
return list(csv.reader(completed.stdout.splitlines(), delimiter="\t"))
|
if completed.returncode!=0:raise RuntimeError(completed.stderr.strip() or "legacy Nextcloud source view query failed")
|
||||||
|
rows=[]
|
||||||
# Existing installations may still expose the original four-column view.
|
for row in csv.reader(completed.stdout.splitlines(),delimiter="\t"):
|
||||||
# Keep them usable and derive folder/file from the resolved source path.
|
if len(row)==4:
|
||||||
if "item_type" not in completed.stderr or "does not exist" not in completed.stderr:
|
share_id,display_name,storage_id,source_path=row;rows.append([share_id,"",display_name,storage_id,source_path])
|
||||||
raise RuntimeError(completed.stderr.strip() or "Nextcloud source view query failed")
|
else:rows.append(row)
|
||||||
|
|
||||||
legacy_sql = f"SELECT share_id, display_name, storage_id, source_path FROM {args.view} ORDER BY share_id"
|
|
||||||
completed = run_query(args, env, legacy_sql)
|
|
||||||
if completed.returncode != 0:
|
|
||||||
raise RuntimeError(completed.stderr.strip() or "legacy Nextcloud source view query failed")
|
|
||||||
|
|
||||||
rows: list[list[str]] = []
|
|
||||||
for row in csv.reader(completed.stdout.splitlines(), delimiter="\t"):
|
|
||||||
if len(row) == 4:
|
|
||||||
share_id, display_name, storage_id, source_path = row
|
|
||||||
rows.append([share_id, "", display_name, storage_id, source_path])
|
|
||||||
else:
|
|
||||||
rows.append(row)
|
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def contained_join(root: Path, relative: str) -> Path:
|
def contained_join(root: Path, relative: str) -> Path:
|
||||||
parts = PurePosixPath(relative).parts
|
parts=PurePosixPath(relative).parts
|
||||||
if relative.startswith("/") or ".." in parts:
|
if relative.startswith("/") or ".." in parts:raise ValueError(f"unsafe source path: {relative}")
|
||||||
raise ValueError(f"unsafe source path: {relative}")
|
|
||||||
return root.joinpath(*parts)
|
return root.joinpath(*parts)
|
||||||
|
|
||||||
|
|
||||||
def build(args: argparse.Namespace) -> dict[str, object]:
|
def build(args: argparse.Namespace) -> dict[str,object]:
|
||||||
adapters = read_config(args.storage_config)
|
validate_view(args.view);adapters=read_config(args.storage_config);rows=query_rows(args)
|
||||||
rows = query_rows(args)
|
if not rows and not args.allow_empty:raise RuntimeError("Nextcloud returned no Showcase shares; refusing an empty manifest")
|
||||||
if not rows and not args.allow_empty:
|
manifest=[];errors=[];folder_count=0;file_count=0
|
||||||
raise RuntimeError("Nextcloud returned no Showcase shares; refusing an empty manifest")
|
|
||||||
|
|
||||||
manifest: list[str] = []
|
|
||||||
errors: list[str] = []
|
|
||||||
folder_count = 0
|
|
||||||
file_count = 0
|
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if len(row) != 5:
|
if len(row)!=5:errors.append(f"invalid database row with {len(row)} columns");continue
|
||||||
errors.append(f"invalid database row with {len(row)} columns")
|
share_id,item_type,display_name,storage_id,source_path=row;item_type=item_type.strip().lower()
|
||||||
continue
|
if item_type and item_type not in {"folder","file"}:errors.append(f"share {share_id}: unsupported item_type {item_type!r}");continue
|
||||||
|
adapter=adapters.get(storage_id)
|
||||||
share_id, item_type, display_name, storage_id, source_path = row
|
if not adapter:errors.append(f"share {share_id}: unknown storage {storage_id}");continue
|
||||||
item_type = item_type.strip().lower()
|
prefix,mount=adapter;relative=source_path.strip("/")
|
||||||
if item_type and item_type not in {"folder", "file"}:
|
|
||||||
errors.append(f"share {share_id}: unsupported item_type {item_type!r}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
adapter = adapters.get(storage_id)
|
|
||||||
if not adapter:
|
|
||||||
errors.append(f"share {share_id}: unknown storage {storage_id}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
prefix, mount = adapter
|
|
||||||
relative = source_path.strip("/")
|
|
||||||
if prefix:
|
if prefix:
|
||||||
expected = prefix + "/"
|
expected=prefix+"/"
|
||||||
if relative != prefix and not relative.startswith(expected):
|
if relative!=prefix and not relative.startswith(expected):errors.append(f"share {share_id}: path does not match configured prefix");continue
|
||||||
errors.append(f"share {share_id}: path does not match configured prefix")
|
relative=relative[len(prefix):].lstrip("/")
|
||||||
continue
|
source=contained_join(mount,relative)
|
||||||
relative = relative[len(prefix):].lstrip("/")
|
if not mount.is_mount():errors.append(f"share {share_id}: storage mount unavailable: {mount}");continue
|
||||||
|
|
||||||
source = contained_join(mount, relative)
|
|
||||||
if not mount.is_mount():
|
|
||||||
errors.append(f"share {share_id}: storage mount unavailable: {mount}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not item_type:
|
if not item_type:
|
||||||
if source.is_dir():
|
if source.is_dir():item_type="folder"
|
||||||
item_type = "folder"
|
elif source.is_file():item_type="file"
|
||||||
elif source.is_file():
|
else:errors.append(f"share {share_id}: source unavailable: {source}");continue
|
||||||
item_type = "file"
|
if item_type=="folder":
|
||||||
else:
|
if not source.is_dir():errors.append(f"share {share_id}: source directory unavailable: {source}");continue
|
||||||
errors.append(f"share {share_id}: source unavailable: {source}")
|
folder_count+=1
|
||||||
continue
|
|
||||||
|
|
||||||
if item_type == "folder":
|
|
||||||
if not source.is_dir():
|
|
||||||
errors.append(f"share {share_id}: source directory unavailable: {source}")
|
|
||||||
continue
|
|
||||||
folder_count += 1
|
|
||||||
else:
|
else:
|
||||||
if not source.is_file():
|
if not source.is_file():errors.append(f"share {share_id}: source file unavailable: {source}");continue
|
||||||
errors.append(f"share {share_id}: source file unavailable: {source}")
|
file_count+=1
|
||||||
continue
|
if "\t" in display_name or "\n" in display_name or "\r" in display_name:errors.append(f"share {share_id}: display name contains unsupported control characters");continue
|
||||||
file_count += 1
|
source_text=str(source)
|
||||||
|
if "\t" in source_text or "\n" in source_text or "\r" in source_text:errors.append(f"share {share_id}: source path contains unsupported control characters");continue
|
||||||
manifest.append(f"{share_id}\t{item_type}\t{display_name.lstrip('/')}\t{source}")
|
manifest.append(f"{share_id}\t{item_type}\t{display_name.lstrip('/')}\t{source}")
|
||||||
|
if errors:raise RuntimeError("; ".join(errors))
|
||||||
if errors:
|
if len(manifest)!=len(rows):raise RuntimeError("not all Showcase shares could be resolved")
|
||||||
raise RuntimeError("; ".join(errors))
|
args.output.parent.mkdir(parents=True,exist_ok=True)
|
||||||
if len(manifest) != len(rows):
|
with tempfile.NamedTemporaryFile("w",encoding="utf-8",dir=args.output.parent,delete=False) as handle:
|
||||||
raise RuntimeError("not all Showcase shares could be resolved")
|
handle.write("\n".join(manifest)+("\n" if manifest else ""));temporary=Path(handle.name)
|
||||||
|
|
||||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=args.output.parent, delete=False) as handle:
|
|
||||||
handle.write("\n".join(manifest) + ("\n" if manifest else ""))
|
|
||||||
temporary = Path(handle.name)
|
|
||||||
temporary.replace(args.output)
|
temporary.replace(args.output)
|
||||||
|
return {"shares":len(manifest),"folders":folder_count,"files":file_count,"manifest":str(args.output)}
|
||||||
return {
|
|
||||||
"shares": len(manifest),
|
|
||||||
"folders": folder_count,
|
|
||||||
"files": file_count,
|
|
||||||
"manifest": str(args.output),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser()
|
parser=argparse.ArgumentParser();parser.add_argument("--host",required=True);parser.add_argument("--port",type=int,default=5432);parser.add_argument("--database",required=True);parser.add_argument("--user",required=True);parser.add_argument("--password-file",required=True,type=Path);parser.add_argument("--view",default="piwigo_showcase_sources");parser.add_argument("--storage-config",required=True,type=Path);parser.add_argument("--output",required=True,type=Path);parser.add_argument("--allow-empty",action="store_true");args=parser.parse_args()
|
||||||
parser.add_argument("--host", required=True)
|
try:print(json.dumps(build(args),ensure_ascii=False))
|
||||||
parser.add_argument("--port", type=int, default=5432)
|
except Exception as error:print(f"manifest: {error}",file=sys.stderr);return 1
|
||||||
parser.add_argument("--database", required=True)
|
|
||||||
parser.add_argument("--user", required=True)
|
|
||||||
parser.add_argument("--password-file", required=True, type=Path)
|
|
||||||
parser.add_argument("--view", default="piwigo_showcase_sources")
|
|
||||||
parser.add_argument("--storage-config", required=True, type=Path)
|
|
||||||
parser.add_argument("--output", required=True, type=Path)
|
|
||||||
parser.add_argument("--allow-empty", action="store_true")
|
|
||||||
args = parser.parse_args()
|
|
||||||
try:
|
|
||||||
print(json.dumps(build(args), ensure_ascii=False))
|
|
||||||
except Exception as error:
|
|
||||||
print(f"manifest: {error}", file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__=="__main__":raise SystemExit(main())
|
||||||
raise SystemExit(main())
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function http_request($url, array $fields, array $headers = array(), $cookie_fil
|
|||||||
CURLOPT_CONNECTTIMEOUT => 10,
|
CURLOPT_CONNECTTIMEOUT => 10,
|
||||||
CURLOPT_TIMEOUT => 900,
|
CURLOPT_TIMEOUT => 900,
|
||||||
CURLOPT_FOLLOWLOCATION => false,
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
CURLOPT_USERAGENT => 'Bratonien-NC-Connector/0.9.3.26',
|
CURLOPT_USERAGENT => 'Bratonien-NC-Connector',
|
||||||
);
|
);
|
||||||
if ($headers)
|
if ($headers)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,12 +56,7 @@ def load_manifest(path: Path) -> list[dict[str, str]]:
|
|||||||
share_id, item_type, display_name, source_path = fields
|
share_id, item_type, display_name, source_path = fields
|
||||||
if item_type not in {"folder", "file"}:
|
if item_type not in {"folder", "file"}:
|
||||||
raise ValueError(f"{path}:{line_number}: unsupported item_type {item_type!r}")
|
raise ValueError(f"{path}:{line_number}: unsupported item_type {item_type!r}")
|
||||||
entries.append({
|
entries.append({"share_id": share_id, "item_type": item_type, "display_name": display_name, "source_path": source_path})
|
||||||
"share_id": share_id,
|
|
||||||
"item_type": item_type,
|
|
||||||
"display_name": display_name,
|
|
||||||
"source_path": source_path,
|
|
||||||
})
|
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
||||||
@@ -87,8 +82,7 @@ def preferred_target(source_key: str, raw_name: str, parent_target: Path, old_ma
|
|||||||
def mirror_directory(source: Path, target: Path, source_key: str, target_key: Path, old_map: dict[str, str], new_map: dict[str, str]) -> None:
|
def mirror_directory(source: Path, target: Path, source_key: str, target_key: Path, old_map: dict[str, str], new_map: dict[str, str]) -> None:
|
||||||
target.mkdir(parents=True, exist_ok=True)
|
target.mkdir(parents=True, exist_ok=True)
|
||||||
used: set[str] = set()
|
used: set[str] = set()
|
||||||
children = sorted(source.iterdir(), key=lambda item: (item.name.casefold(), item.name))
|
for child in sorted(source.iterdir(), key=lambda item: (item.name.casefold(), item.name)):
|
||||||
for child in children:
|
|
||||||
if child.is_symlink():
|
if child.is_symlink():
|
||||||
continue
|
continue
|
||||||
child_source_key = f"{source_key}/{child.name}"
|
child_source_key = f"{source_key}/{child.name}"
|
||||||
@@ -103,51 +97,68 @@ def mirror_directory(source: Path, target: Path, source_key: str, target_key: Pa
|
|||||||
child_target.symlink_to(child.resolve())
|
child_target.symlink_to(child.resolve())
|
||||||
|
|
||||||
|
|
||||||
|
def remove_tree(path: Path) -> None:
|
||||||
|
if path.is_symlink() or path.is_file():
|
||||||
|
path.unlink(missing_ok=True)
|
||||||
|
elif path.exists():
|
||||||
|
shutil.rmtree(path)
|
||||||
|
|
||||||
|
|
||||||
def build(manifest: Path, destination: Path, state_file: Path) -> None:
|
def build(manifest: Path, destination: Path, state_file: Path) -> None:
|
||||||
entries = load_manifest(manifest)
|
entries = load_manifest(manifest)
|
||||||
old_map = load_map(state_file)
|
old_map = load_map(state_file)
|
||||||
new_map: dict[str, str] = {}
|
new_map: dict[str, str] = {}
|
||||||
staging = destination.with_name(f".{destination.name}.next")
|
staging = destination.with_name(f".{destination.name}.next")
|
||||||
previous = destination.with_name(f".{destination.name}.previous")
|
previous = destination.with_name(f".{destination.name}.previous")
|
||||||
|
state_staging = state_file.with_suffix(state_file.suffix + ".next")
|
||||||
|
|
||||||
if staging.exists():
|
remove_tree(staging)
|
||||||
shutil.rmtree(staging)
|
remove_tree(previous)
|
||||||
|
if state_staging.exists():
|
||||||
|
state_staging.unlink()
|
||||||
staging.mkdir(parents=True)
|
staging.mkdir(parents=True)
|
||||||
|
|
||||||
used_roots: set[str] = set()
|
try:
|
||||||
for entry in sorted(entries, key=lambda item: (item["display_name"].casefold(), item["share_id"])):
|
used_roots: set[str] = set()
|
||||||
source = Path(entry["source_path"])
|
for entry in sorted(entries, key=lambda item: (item["display_name"].casefold(), item["share_id"])):
|
||||||
source_key = f"share:{entry['share_id']}"
|
source = Path(entry["source_path"])
|
||||||
preferred = preferred_target(source_key, entry["display_name"], Path("."), old_map)
|
source_key = f"share:{entry['share_id']}"
|
||||||
is_file_share = entry["item_type"] == "file"
|
preferred = preferred_target(source_key, entry["display_name"], Path("."), old_map)
|
||||||
root_name = unique_name(preferred, used_roots, is_file_share)
|
is_file_share = entry["item_type"] == "file"
|
||||||
root_key = Path(root_name)
|
root_name = unique_name(preferred, used_roots, is_file_share)
|
||||||
new_map[source_key] = root_key.as_posix()
|
root_key = Path(root_name)
|
||||||
|
new_map[source_key] = root_key.as_posix()
|
||||||
|
if is_file_share:
|
||||||
|
if not source.is_file():
|
||||||
|
raise FileNotFoundError(f"source is not a readable file: {source}")
|
||||||
|
(staging / root_name).symlink_to(source.resolve())
|
||||||
|
else:
|
||||||
|
if not source.is_dir():
|
||||||
|
raise FileNotFoundError(f"source is not a readable directory: {source}")
|
||||||
|
mirror_directory(source, staging / root_name, source_key, root_key, old_map, new_map)
|
||||||
|
|
||||||
if is_file_share:
|
state_staging.parent.mkdir(parents=True, exist_ok=True)
|
||||||
if not source.is_file():
|
with state_staging.open("w", encoding="utf-8") as handle:
|
||||||
raise FileNotFoundError(f"source is not a readable file: {source}")
|
json.dump(new_map, handle, ensure_ascii=False, indent=2, sort_keys=True)
|
||||||
(staging / root_name).symlink_to(source.resolve())
|
handle.write("\n")
|
||||||
continue
|
|
||||||
|
|
||||||
if not source.is_dir():
|
had_destination = destination.exists()
|
||||||
raise FileNotFoundError(f"source is not a readable directory: {source}")
|
if had_destination:
|
||||||
mirror_directory(source, staging / root_name, source_key, root_key, old_map, new_map)
|
destination.rename(previous)
|
||||||
|
try:
|
||||||
state_staging = state_file.with_suffix(state_file.suffix + ".next")
|
staging.rename(destination)
|
||||||
state_staging.parent.mkdir(parents=True, exist_ok=True)
|
state_staging.replace(state_file)
|
||||||
with state_staging.open("w", encoding="utf-8") as handle:
|
except Exception:
|
||||||
json.dump(new_map, handle, ensure_ascii=False, indent=2, sort_keys=True)
|
remove_tree(destination)
|
||||||
handle.write("\n")
|
if had_destination and previous.exists():
|
||||||
|
previous.rename(destination)
|
||||||
if previous.exists():
|
raise
|
||||||
shutil.rmtree(previous)
|
remove_tree(previous)
|
||||||
if destination.exists():
|
except Exception:
|
||||||
destination.rename(previous)
|
remove_tree(staging)
|
||||||
staging.rename(destination)
|
if state_staging.exists():
|
||||||
state_staging.replace(state_file)
|
state_staging.unlink()
|
||||||
if previous.exists():
|
raise
|
||||||
shutil.rmtree(previous)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
|||||||
@@ -11,9 +11,7 @@
|
|||||||
.bratonien-card { margin-left:0; margin-right:0; }
|
.bratonien-card { margin-left:0; margin-right:0; }
|
||||||
@media (max-width:760px) { .bratonien-tabs{gap:5px}.bratonien-tab{border-bottom:1px solid rgba(255,255,255,.16);border-radius:4px;margin-bottom:0;padding:9px 11px} }
|
@media (max-width:760px) { .bratonien-tabs{gap:5px}.bratonien-tab{border-bottom:1px solid rgba(255,255,255,.16);border-radius:4px;margin-bottom:0;padding:9px 11px} }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="bratonien-tabs-anchor"></div>
|
<div id="bratonien-tabs-anchor"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -27,114 +25,56 @@
|
|||||||
{ id:'wartung', label:'Wartung / Cache' },
|
{ id:'wartung', label:'Wartung / Cache' },
|
||||||
{ id:'system', label:'System & Updates' }
|
{ id:'system', label:'System & Updates' }
|
||||||
];
|
];
|
||||||
var panels = [];
|
var panels=[];
|
||||||
definitions.forEach(function (d) {
|
definitions.forEach(function(d){
|
||||||
var ids=d.sections||[d.id], elements=[];
|
var ids=d.sections||[d.id],elements=[];
|
||||||
ids.forEach(function(id){var el=document.getElementById(id);if(el){el.classList.add('bratonien-tab-panel');elements.push(el);}});
|
ids.forEach(function(id){var el=document.getElementById(id);if(el){el.classList.add('bratonien-tab-panel');elements.push(el);}});
|
||||||
if (!elements.length) return;
|
if(elements.length)panels.push({definition:d,elements:elements});
|
||||||
panels.push({definition:d,elements:elements});
|
|
||||||
});
|
|
||||||
if (!panels.length) return;
|
|
||||||
|
|
||||||
var anchor = document.getElementById('bratonien-tabs-anchor');
|
|
||||||
if (!anchor) return;
|
|
||||||
var tabs = document.createElement('div');
|
|
||||||
tabs.className = 'bratonien-tabs';
|
|
||||||
tabs.setAttribute('role','tablist');
|
|
||||||
tabs.setAttribute('aria-label','Bratonien Tools Bereiche');
|
|
||||||
|
|
||||||
panels.forEach(function (item) {
|
|
||||||
var b = document.createElement('button');
|
|
||||||
b.type='button';
|
|
||||||
b.className='bratonien-tab';
|
|
||||||
b.id='bratonien-tab-'+item.definition.id;
|
|
||||||
b.dataset.tab=item.definition.id;
|
|
||||||
b.setAttribute('role','tab');
|
|
||||||
b.textContent=item.definition.label;
|
|
||||||
tabs.appendChild(b);
|
|
||||||
item.elements.forEach(function(el){el.setAttribute('role','tabpanel');el.setAttribute('aria-labelledby',b.id);});
|
|
||||||
});
|
});
|
||||||
|
if(!panels.length)return;
|
||||||
|
var anchor=document.getElementById('bratonien-tabs-anchor');if(!anchor)return;
|
||||||
|
var tabs=document.createElement('div');tabs.className='bratonien-tabs';tabs.setAttribute('role','tablist');tabs.setAttribute('aria-label','Bratonien Tools Bereiche');
|
||||||
|
panels.forEach(function(item){var b=document.createElement('button');b.type='button';b.className='bratonien-tab';b.id='bratonien-tab-'+item.definition.id;b.dataset.tab=item.definition.id;b.setAttribute('role','tab');b.textContent=item.definition.label;tabs.appendChild(b);item.elements.forEach(function(el){el.setAttribute('role','tabpanel');el.setAttribute('aria-labelledby',b.id);});});
|
||||||
anchor.replaceWith(tabs);
|
anchor.replaceWith(tabs);
|
||||||
|
function activate(id,remember){var valid=false;panels.forEach(function(item){var active=item.definition.id===id;item.elements.forEach(function(el){el.hidden=!active;});var b=tabs.querySelector('[data-tab="'+item.definition.id+'"]');if(b){b.classList.toggle('is-active',active);b.setAttribute('aria-selected',active?'true':'false');b.tabIndex=active?0:-1;}if(active)valid=true;});if(!valid)return activate(panels[0].definition.id,remember);if(remember!==false){try{localStorage.setItem('bratonien-tools-active-tab',id);}catch(e){}}}
|
||||||
function activate(id, remember) {
|
tabs.addEventListener('click',function(e){var b=e.target.closest('.bratonien-tab');if(b)activate(b.dataset.tab);});
|
||||||
var valid=false;
|
tabs.addEventListener('keydown',function(e){if(e.key!=='ArrowLeft'&&e.key!=='ArrowRight')return;var buttons=[].slice.call(tabs.querySelectorAll('.bratonien-tab'));var i=buttons.indexOf(document.activeElement);if(i<0)return;e.preventDefault();i+=e.key==='ArrowRight'?1:-1;if(i>=buttons.length)i=0;if(i<0)i=buttons.length-1;buttons[i].focus();activate(buttons[i].dataset.tab);});
|
||||||
panels.forEach(function (item) {
|
var initial='',hash=location.hash?location.hash.slice(1):'';panels.forEach(function(item){if(item.definition.id===hash||(item.definition.sections||[]).indexOf(hash)!==-1)initial=item.definition.id;});if(!initial){try{initial=localStorage.getItem('bratonien-tools-active-tab')||'';}catch(e){}}if(!panels.some(function(i){return i.definition.id===initial;}))initial=panels[0].definition.id;activate(initial,false);
|
||||||
var active=item.definition.id===id;
|
|
||||||
item.elements.forEach(function(el){el.hidden=!active;});
|
|
||||||
var b=tabs.querySelector('[data-tab="'+item.definition.id+'"]');
|
|
||||||
if (b) {
|
|
||||||
b.classList.toggle('is-active',active);
|
|
||||||
b.setAttribute('aria-selected',active?'true':'false');
|
|
||||||
b.tabIndex=active?0:-1;
|
|
||||||
}
|
|
||||||
if (active) valid=true;
|
|
||||||
});
|
|
||||||
if (!valid) return activate(panels[0].definition.id,remember);
|
|
||||||
if (remember!==false) { try { localStorage.setItem('bratonien-tools-active-tab',id); } catch(e) {} }
|
|
||||||
}
|
|
||||||
|
|
||||||
tabs.addEventListener('click',function(e){ var b=e.target.closest('.bratonien-tab'); if(b) activate(b.dataset.tab); });
|
|
||||||
tabs.addEventListener('keydown',function(e){
|
|
||||||
if(e.key!=='ArrowLeft'&&e.key!=='ArrowRight') return;
|
|
||||||
var buttons=[].slice.call(tabs.querySelectorAll('.bratonien-tab'));
|
|
||||||
var i=buttons.indexOf(document.activeElement); if(i<0) return;
|
|
||||||
e.preventDefault(); i += e.key==='ArrowRight'?1:-1; if(i>=buttons.length)i=0; if(i<0)i=buttons.length-1;
|
|
||||||
buttons[i].focus(); activate(buttons[i].dataset.tab);
|
|
||||||
});
|
|
||||||
|
|
||||||
var initial='';
|
|
||||||
var hash=location.hash?location.hash.slice(1):'';
|
|
||||||
panels.forEach(function(item){if(item.definition.id===hash||(item.definition.sections||[]).indexOf(hash)!==-1)initial=item.definition.id;});
|
|
||||||
if(!initial){ try { initial=localStorage.getItem('bratonien-tools-active-tab')||''; } catch(e){} }
|
|
||||||
if(!panels.some(function(i){return i.definition.id===initial})) initial=panels[0].definition.id;
|
|
||||||
activate(initial,false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initNCConnectorPolling() {
|
function initNCConnectorPolling(){
|
||||||
var section=document.getElementById('nc-connector');
|
var section=document.getElementById('nc-connector');if(!section)return;
|
||||||
if(!section) return;
|
|
||||||
var lastSeen=null;
|
|
||||||
var endpoint='plugins/bratonien_tools/nc-connector-status.php';
|
var endpoint='plugins/bratonien_tools/nc-connector-status.php';
|
||||||
|
|
||||||
function poll() {
|
function valueNodeForLabel(labelText){
|
||||||
|
var labels=[].slice.call(section.querySelectorAll('.bratonien-label'));
|
||||||
|
for(var i=0;i<labels.length;i++){
|
||||||
|
if((labels[i].textContent||'').trim()===labelText){
|
||||||
|
var node=labels[i].nextElementSibling;
|
||||||
|
return node&&node.tagName==='STRONG'?node:null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastRunNode=valueNodeForLabel('Letzter Lauf');
|
||||||
|
var nextRunNode=valueNodeForLabel('Nächster Lauf');
|
||||||
|
|
||||||
|
function poll(){
|
||||||
fetch(endpoint+'?_='+Date.now(),{credentials:'same-origin',cache:'no-store'})
|
fetch(endpoint+'?_='+Date.now(),{credentials:'same-origin',cache:'no-store'})
|
||||||
.then(function(response){if(!response.ok) throw new Error('HTTP '+response.status); return response.json();})
|
.then(function(response){if(!response.ok)throw new Error('HTTP '+response.status);return response.json();})
|
||||||
.then(function(data){
|
.then(function(data){
|
||||||
var timestamp=parseInt(data.timestamp||0,10);
|
if(lastRunNode&&data.last_run_label)lastRunNode.textContent=data.last_run_label;
|
||||||
if(!timestamp) return;
|
if(nextRunNode&&data.next_run_label)nextRunNode.textContent=data.next_run_label;
|
||||||
|
|
||||||
if(lastSeen===null) {
|
|
||||||
lastSeen=timestamp;
|
|
||||||
var resultVisible=section.textContent.indexOf('Letztes Ergebnis:')!==-1;
|
|
||||||
if(!resultVisible && data.message) {
|
|
||||||
var reloadKey='bratonien-nc-status-reload-'+timestamp;
|
|
||||||
try {
|
|
||||||
if(sessionStorage.getItem(reloadKey)!=='1') {
|
|
||||||
sessionStorage.setItem(reloadKey,'1');
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(timestamp>lastSeen) {
|
|
||||||
lastSeen=timestamp;
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(function(){});
|
.catch(function(){});
|
||||||
}
|
}
|
||||||
|
|
||||||
poll();
|
poll();window.setInterval(poll,5000);
|
||||||
window.setInterval(poll,5000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAll() {
|
function initAll(){initBratonienTabs();initNCConnectorPolling();}
|
||||||
initBratonienTabs();
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',initAll);else initAll();
|
||||||
initNCConnectorPolling();
|
|
||||||
}
|
|
||||||
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',initAll); else initAll();
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{/literal}
|
{/literal}
|
||||||
|
|||||||
@@ -1,428 +1,272 @@
|
|||||||
<section class="bratonien-section" id="nc-connector">
|
<section class="bratonien-section" id="nc-connector">
|
||||||
<h3>NC Connector</h3>
|
<h3>NC Connector</h3>
|
||||||
<p class="bratonien-section__intro">Bratonien Tools verwaltet die Nextcloud-Anbindung und den regelmäßigen Piwigo-Abgleich.</p>
|
<p class="bratonien-section__intro">Verbindet Nextcloud mit Piwigo und hält freigegebene Bilder automatisch aktuell.</p>
|
||||||
|
|
||||||
{assign var=nc_system_available value=isset($NC_CONNECTOR.system)}
|
{assign var=nc_system_available value=isset($NC_CONNECTOR.system)}
|
||||||
|
|
||||||
<div class="bratonien-grid">
|
<div class="bratonien-grid">
|
||||||
<div class="bratonien-card">
|
<div class="bratonien-card">
|
||||||
<h4>Connector-Status</h4>
|
<h4>Status</h4>
|
||||||
<div class="bratonien-form-grid">
|
<div class="bratonien-form-grid">
|
||||||
<span class="bratonien-label">Phase</span><strong>{$NC_CONNECTOR.phase|escape:html}</strong>
|
<span class="bratonien-label">Verbindungen</span><strong>{$NC_CONNECTOR.connection_count|escape:html}</strong>
|
||||||
<span class="bratonien-label">Connector-Verbindungen</span><strong>{$NC_CONNECTOR.connection_count|escape:html}</strong>
|
<span class="bratonien-label">Automatischer Abgleich</span><strong>{if $nc_system_available && $NC_CONNECTOR.system.timer_active && $NC_CONNECTOR.system.timer_enabled}Aktiv{else}Nicht aktiv{/if}</strong>
|
||||||
<span class="bratonien-label">Timer aktiv</span><strong>{if $nc_system_available && $NC_CONNECTOR.system.timer_active}Ja{else}Nein{/if}</strong>
|
<span class="bratonien-label">Letzter Lauf</span><strong data-nc-last-run>{if $nc_system_available}{$NC_CONNECTOR.system.last_run_label|escape:html}{else}Nicht verfügbar{/if}</strong>
|
||||||
<span class="bratonien-label">Timer aktiviert</span><strong>{if $nc_system_available && $NC_CONNECTOR.system.timer_enabled}Ja{else}Nein{/if}</strong>
|
<span class="bratonien-label">Nächster Lauf</span><strong data-nc-next-run>{if $nc_system_available}{$NC_CONNECTOR.system.next_run_label|escape:html}{else}Nicht verfügbar{/if}</strong>
|
||||||
<span class="bratonien-label">Letzter Lauf</span><strong>{if $nc_system_available}{$NC_CONNECTOR.system.last_run_label|escape:html}{else}Nicht verfügbar{/if}</strong>
|
|
||||||
<span class="bratonien-label">Nächster Lauf</span><strong>{if $nc_system_available}{$NC_CONNECTOR.system.next_run_label|escape:html}{else}Nicht verfügbar{/if}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
{if $nc_system_available && $NC_CONNECTOR.system.last_run_message}
|
{if $nc_system_available && $NC_CONNECTOR.system.last_run_message}<p class="bratonien-base-note">Letztes Ergebnis: <strong>{$NC_CONNECTOR.system.last_run_message|escape:html}</strong></p>{/if}
|
||||||
<p class="bratonien-base-note">Letztes Ergebnis: <strong>{$NC_CONNECTOR.system.last_run_message|escape:html}</strong></p>
|
{if $nc_system_available && $NC_CONNECTOR.system.last_run_api_state == 'error'}<p class="bratonien-main-cache__warning"><strong>API:</strong> {$NC_CONNECTOR.system.last_run_api_message|escape:html}</p>{/if}
|
||||||
{/if}
|
{if $nc_system_available && $NC_CONNECTOR.system.last_run_fallback_state == 'error'}<p class="bratonien-main-cache__warning"><strong>Fallback:</strong> {$NC_CONNECTOR.system.last_run_fallback_message|escape:html}</p>{/if}
|
||||||
{if $nc_system_available && $NC_CONNECTOR.system.last_run_api_state == 'error'}
|
{if $nc_system_available && $NC_CONNECTOR.system.last_run_error_detail}<details><summary>Technische Fehlerdetails</summary><p class="bratonien-main-cache__warning">{$NC_CONNECTOR.system.last_run_error_detail|escape:html}</p></details>{/if}
|
||||||
<p class="bratonien-main-cache__warning"><strong>API:</strong> {$NC_CONNECTOR.system.last_run_api_message|escape:html}</p>
|
|
||||||
{/if}
|
|
||||||
{if $nc_system_available && $NC_CONNECTOR.system.last_run_fallback_state == 'ok'}
|
|
||||||
<p class="bratonien-base-note"><strong>Fallback:</strong> erfolgreich übernommen.</p>
|
|
||||||
{elseif $nc_system_available && $NC_CONNECTOR.system.last_run_fallback_state == 'error'}
|
|
||||||
<p class="bratonien-main-cache__warning"><strong>Fallback:</strong> {$NC_CONNECTOR.system.last_run_fallback_message|escape:html}</p>
|
|
||||||
{/if}
|
|
||||||
{if $nc_system_available && $NC_CONNECTOR.system.last_run_error_detail}
|
|
||||||
<p class="bratonien-main-cache__warning"><strong>Fehler:</strong> {$NC_CONNECTOR.system.last_run_error_detail|escape:html}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bratonien-card">
|
<div class="bratonien-card">
|
||||||
<h4>Runtime</h4>
|
|
||||||
<p class="bratonien-base-note">Neue Verbindungen verwenden direkt die Plugin-Runtime und einen eigenen State unter <code>/var/lib/bratonien-tools/nc-connector/connection-ID</code>.</p>
|
|
||||||
{if $nc_system_available && !$NC_CONNECTOR.system.legacy_runtime_exists && !$NC_CONNECTOR.system.legacy_config_exists && !$NC_CONNECTOR.system.legacy_service_exists && !$NC_CONNECTOR.system.legacy_timer_exists}
|
|
||||||
<p class="bratonien-base-note">Legacy-Bestand: <strong>vollständig entfernt</strong>.</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bratonien-card" style="grid-column:1/-1">
|
|
||||||
<h4>Neue Verbindung</h4>
|
<h4>Neue Verbindung</h4>
|
||||||
<p class="bratonien-base-note">Wähle den geführten Assistenten oder öffne die technische Einrichtung bewusst manuell.</p>
|
<p class="bratonien-base-note">Für die normale Einrichtung empfehlen wir den Assistenten.</p>
|
||||||
<div class="bratonien-actions">
|
<div class="bratonien-actions">
|
||||||
<button class="buttonLike" type="button" id="bratonien-nc-wizard-open">Mit Assistent anlegen</button>
|
<button class="buttonLike" type="button" id="bratonien-nc-wizard-open">Mit Assistent anlegen</button>
|
||||||
<button class="buttonLike" type="button" id="bratonien-nc-technical-open">Ohne Assistent anlegen</button>
|
<button class="buttonLike" type="button" id="bratonien-nc-technical-open">Ohne Assistent anlegen</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dialog id="bratonien-nc-wizard-dialog" style="width:min(1100px,calc(100vw - 3rem));max-height:88vh;overflow:auto;background:#444;color:inherit;border:1px solid #777;border-radius:4px;padding:0;box-shadow:0 18px 60px rgba(0,0,0,.55)">
|
|
||||||
<div style="padding:1.25rem 1.5rem">
|
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:1rem">
|
|
||||||
<div>
|
|
||||||
<h4 style="margin:0">Neue Verbindung</h4>
|
|
||||||
<p class="bratonien-base-note" style="margin:.35rem 0 0"><strong>Assistent · Schritt {$NC_CONNECTOR.wizard.step|escape:html} von 4</strong></p>
|
|
||||||
</div>
|
|
||||||
<button class="buttonLike" type="button" id="bratonien-nc-wizard-close">Schließen</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{if $NC_CONNECTOR.wizard.step == 1}
|
|
||||||
<p class="bratonien-base-note">Wir beginnen nur mit der Adresse der Nextcloud und einem Benutzer, mit dem die Instanz auf den Scan antworten kann.</p>
|
|
||||||
<form method="post" data-bratonien-wizard-form>
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label" for="nc_wizard_host">Nextcloud-Host</label>
|
|
||||||
<input id="nc_wizard_host" name="nc_wizard_host" type="text" placeholder="cloud.example.de" required>
|
|
||||||
<label class="bratonien-label" for="nc_wizard_user">Nextcloud-Benutzer</label>
|
|
||||||
<input id="nc_wizard_user" name="nc_wizard_user" type="text" autocomplete="username" required>
|
|
||||||
<label class="bratonien-label" for="nc_wizard_password">Passwort</label>
|
|
||||||
<input id="nc_wizard_password" name="nc_wizard_password" type="password" autocomplete="current-password" required>
|
|
||||||
</div>
|
|
||||||
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_scan">Verbinden und scannen</button></p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{elseif $NC_CONNECTOR.wizard.step == 2}
|
|
||||||
<p class="bratonien-base-note"><strong>Nextcloud erkannt.</strong> Alles, was zuverlässig ermittelt werden konnte, wurde bereits übernommen.</p>
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<span class="bratonien-label">Nextcloud</span><strong>{$NC_CONNECTOR.wizard.base_url|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Produkt</span><strong>{$NC_CONNECTOR.wizard.product|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Version</span><strong>{if $NC_CONNECTOR.wizard.version}{$NC_CONNECTOR.wizard.version|escape:html}{else}nicht gemeldet{/if}</strong>
|
|
||||||
<span class="bratonien-label">Zugriff als</span><strong>{$NC_CONNECTOR.wizard.username|escape:html}{if $NC_CONNECTOR.wizard.display_name} · {$NC_CONNECTOR.wizard.display_name|escape:html}{/if}</strong>
|
|
||||||
<span class="bratonien-label">Technische Erkennung</span><strong>{$NC_CONNECTOR.wizard.technical_source|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Piwigo-Ziel</span><strong>{$NC_CONNECTOR.wizard.gallery_root|escape:html}</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{if !$NC_CONNECTOR.wizard.db_password_set || !$NC_CONNECTOR.wizard.db_user}
|
|
||||||
<hr>
|
|
||||||
<h5>Für den Datenabgleich fehlt noch der Datenbankzugang</h5>
|
|
||||||
<p class="bratonien-base-note">Der Assistent konnte diese Angaben nicht sicher aus Nextcloud ableiten. Deshalb werden nur die fehlenden Werte abgefragt.</p>
|
|
||||||
<form method="post" data-bratonien-wizard-form>
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label">Verbindungsname</label><input name="nc_wizard_connection_name" type="text" value="{$NC_CONNECTOR.wizard.connection_name|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Datenbank-Host</label><input name="nc_wizard_db_host" type="text" value="{$NC_CONNECTOR.wizard.db_host|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Port</label><input name="nc_wizard_db_port" type="number" min="1" max="65535" value="{$NC_CONNECTOR.wizard.db_port|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Datenbank</label><input name="nc_wizard_db_database" type="text" value="{$NC_CONNECTOR.wizard.db_database|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Reader-Benutzer</label><input name="nc_wizard_db_user" type="text" value="{$NC_CONNECTOR.wizard.db_user|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Reader-Passwort</label><input name="nc_wizard_db_password" type="password" autocomplete="new-password" required>
|
|
||||||
</div>
|
|
||||||
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_technical">Prüfen und automatisch ergänzen</button></p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{elseif !$NC_CONNECTOR.wizard.technical_complete && $NC_CONNECTOR.wizard.storage_candidates|@count > 0}
|
|
||||||
<hr>
|
|
||||||
<h5>Einige Storages konnten nicht automatisch zugeordnet werden</h5>
|
|
||||||
<p class="bratonien-main-cache__warning">Mount-Pfade werden nur hier abgefragt, weil sie nicht sicher erkannt werden konnten. Bereits erkannte Zuordnungen sind gesperrt.</p>
|
|
||||||
<form method="post" data-bratonien-wizard-form>
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
{foreach from=$NC_CONNECTOR.wizard.storage_candidates item=storage key=storage_index}
|
|
||||||
<span class="bratonien-label">Storage {$storage.storage_id|escape:html} · {$storage.source_prefix|escape:html}</span>
|
|
||||||
{if $storage.local_mount}
|
|
||||||
<strong>{$storage.local_mount|escape:html}</strong>
|
|
||||||
<input type="hidden" name="nc_wizard_storage_mount[{$storage_index|escape:html}]" value="{$storage.local_mount|escape:html}">
|
|
||||||
{else}
|
|
||||||
<input name="nc_wizard_storage_mount[{$storage_index|escape:html}]" type="text" placeholder="/mnt/nextcloud/..." required>
|
|
||||||
{/if}
|
|
||||||
{/foreach}
|
|
||||||
</div>
|
|
||||||
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_mounts">Zuordnung prüfen</button></p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{elseif $NC_CONNECTOR.wizard.technical_complete}
|
|
||||||
<hr>
|
|
||||||
<h5>Erkannte Verbindung</h5>
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<span class="bratonien-label">Datenbank</span><strong>{$NC_CONNECTOR.wizard.db_host|escape:html}:{$NC_CONNECTOR.wizard.db_port|escape:html} / {$NC_CONNECTOR.wizard.db_database|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Reader</span><strong>{$NC_CONNECTOR.wizard.db_user|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Source-View</span><strong>{$NC_CONNECTOR.wizard.source_view|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Activity-View</span><strong>{$NC_CONNECTOR.wizard.activity_view|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Storages</span><strong>{$NC_CONNECTOR.wizard.storages|@count}</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<h5>Welcher Benutzer stellt die Bilder bereit?</h5>
|
|
||||||
<p class="bratonien-base-note"><strong>Empfehlung:</strong> ein eigener Showcase-Benutzer. So bleibt die Connector-Quelle unabhängig von persönlichen Konten.</p>
|
|
||||||
<form method="post" data-bratonien-wizard-form>
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label" for="nc_wizard_connection_name">Verbindungsname</label>
|
|
||||||
<input id="nc_wizard_connection_name" name="nc_wizard_connection_name" type="text" value="{$NC_CONNECTOR.wizard.connection_name|escape:html}" required>
|
|
||||||
<label class="bratonien-label" for="nc_wizard_showcase_user">Showcase-Benutzer</label>
|
|
||||||
{if $NC_CONNECTOR.wizard.can_list_users && $NC_CONNECTOR.wizard.users|@count > 0}
|
|
||||||
<select id="nc_wizard_showcase_user" name="nc_wizard_showcase_user" required>
|
|
||||||
{foreach from=$NC_CONNECTOR.wizard.users item=nc_user}
|
|
||||||
<option value="{$nc_user|escape:html}"{if $nc_user == 'showcase'} selected{/if}>{$nc_user|escape:html}{if $nc_user == 'showcase'} · empfohlen{/if}</option>
|
|
||||||
{/foreach}
|
|
||||||
</select>
|
|
||||||
{else}
|
|
||||||
<input id="nc_wizard_showcase_user" name="nc_wizard_showcase_user" type="text" value="showcase" required>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_select_user">Weiter zur Piwigo-API</button></p>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<form method="post" style="margin-top:1rem" data-bratonien-wizard-form>
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_reset" data-bratonien-wizard-end>Neu beginnen</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{elseif $NC_CONNECTOR.wizard.step == 3}
|
|
||||||
<p class="bratonien-base-note"><strong>Nextcloud verbunden.</strong> Showcase-Benutzer: <strong>{$NC_CONNECTOR.wizard.showcase_user|escape:html}</strong></p>
|
|
||||||
<h5>Piwigo-API</h5>
|
|
||||||
<p class="bratonien-base-note">Die API ist der bevorzugte Weg. Du kannst neue Zugangsdaten eingeben, einen bereits gespeicherten API-Zugang testen oder diesen Schritt überspringen.</p>
|
|
||||||
{if $NC_CONNECTOR.wizard.api_error}
|
|
||||||
<p class="bratonien-main-cache__warning"><strong>Letzter API-Test:</strong> {$NC_CONNECTOR.wizard.api_error|escape:html}</p>
|
|
||||||
{/if}
|
|
||||||
<form method="post" data-bratonien-wizard-form>
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label" for="nc_wizard_api_key_id">API-Schlüssel-ID</label>
|
|
||||||
<input id="nc_wizard_api_key_id" name="nc_wizard_api_key_id" type="text" autocomplete="off" placeholder="leer = gespeicherten Zugang verwenden">
|
|
||||||
<label class="bratonien-label" for="nc_wizard_api_key_secret">API-Geheimnis</label>
|
|
||||||
<input id="nc_wizard_api_key_secret" name="nc_wizard_api_key_secret" type="password" autocomplete="off" placeholder="leer = gespeicherten Zugang verwenden">
|
|
||||||
</div>
|
|
||||||
<div class="bratonien-actions">
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_api_test">API testen</button>
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_api_skip" formnovalidate>API überspringen</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{elseif $NC_CONNECTOR.wizard.step == 4}
|
|
||||||
<p class="bratonien-base-note"><strong>Abschluss.</strong> Nextcloud und technische Verbindung sind vorbereitet.</p>
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<span class="bratonien-label">Verbindung</span><strong>{$NC_CONNECTOR.wizard.connection_name|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Nextcloud</span><strong>{$NC_CONNECTOR.wizard.base_url|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Showcase-Benutzer</span><strong>{$NC_CONNECTOR.wizard.showcase_user|escape:html}</strong>
|
|
||||||
<span class="bratonien-label">Piwigo-API</span><strong>{if $NC_CONNECTOR.wizard.api_status == 'ok'}Erfolgreich getestet{if $NC_CONNECTOR.wizard.api_username} · {$NC_CONNECTOR.wizard.api_username|escape:html}{/if}{else}Übersprungen{/if}</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<h5>Fallback</h5>
|
|
||||||
{if $NC_CONNECTOR.wizard.api_status == 'ok'}
|
|
||||||
<p class="bratonien-base-note">Optional. Der Fallback wird nur benötigt, wenn die API später nicht nutzbar ist.</p>
|
|
||||||
{else}
|
|
||||||
<p class="bratonien-main-cache__warning">Die API wurde übersprungen. Deshalb ist ein Fallback-Zugang erforderlich.</p>
|
|
||||||
{/if}
|
|
||||||
<form method="post" data-bratonien-wizard-form>
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label" for="nc_wizard_fallback_user">Piwigo-Benutzer</label>
|
|
||||||
<input id="nc_wizard_fallback_user" name="nc_wizard_fallback_user" type="text" autocomplete="username"{if $NC_CONNECTOR.wizard.api_status != 'ok'} required{/if}>
|
|
||||||
<label class="bratonien-label" for="nc_wizard_fallback_password">Piwigo-Passwort</label>
|
|
||||||
<input id="nc_wizard_fallback_password" name="nc_wizard_fallback_password" type="password" autocomplete="current-password"{if $NC_CONNECTOR.wizard.api_status != 'ok'} required{/if}>
|
|
||||||
</div>
|
|
||||||
<div class="bratonien-actions">
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_finish" data-bratonien-wizard-end>Verbindung anlegen</button>
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_reset" formnovalidate data-bratonien-wizard-end>Abbrechen</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<details id="bratonien-nc-technical-create" style="margin-top:1.5rem">
|
|
||||||
<summary style="display:none">Ohne Assistent anlegen</summary>
|
|
||||||
<p class="bratonien-main-cache__warning">Technische Einrichtung. Falsche Storage- oder Galeriepfade können die Synchronisierung auf den falschen Datenbestand richten.</p>
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label" for="nc_name">Name</label><input id="nc_name" name="nc_name" type="text" required>
|
|
||||||
<label class="bratonien-label" for="nc_host">PostgreSQL-Host</label><input id="nc_host" name="nc_host" type="text" required>
|
|
||||||
<label class="bratonien-label" for="nc_port">PostgreSQL-Port</label><input id="nc_port" name="nc_port" type="number" min="1" max="65535" value="5432" required>
|
|
||||||
<label class="bratonien-label" for="nc_database">Datenbank</label><input id="nc_database" name="nc_database" type="text" value="nextcloud" required>
|
|
||||||
<label class="bratonien-label" for="nc_user">Reader-Benutzer</label><input id="nc_user" name="nc_user" type="text" required>
|
|
||||||
<label class="bratonien-label" for="nc_db_password">Reader-Passwort</label><input id="nc_db_password" name="nc_db_password" type="password" autocomplete="new-password" required>
|
|
||||||
<label class="bratonien-label" for="nc_source_view">Source-View</label><input id="nc_source_view" name="nc_source_view" type="text" value="piwigo_showcase_sources" required>
|
|
||||||
<label class="bratonien-label" for="nc_activity_view">Activity-View</label><input id="nc_activity_view" name="nc_activity_view" type="text" value="piwigo_showcase_activity" required>
|
|
||||||
<label class="bratonien-label" for="nc_gallery_root">Piwigo-Galeriepfad</label><input id="nc_gallery_root" name="nc_gallery_root" type="text" placeholder="/var/www/piwigo/galleries/nextcloud" required>
|
|
||||||
<label class="bratonien-label" for="nc_piwigo_user">Piwigo-Fallback-Benutzer</label><input id="nc_piwigo_user" name="nc_piwigo_user" type="text">
|
|
||||||
<label class="bratonien-label" for="nc_piwigo_password">Piwigo-Fallback-Passwort</label><input id="nc_piwigo_password" name="nc_piwigo_password" type="password" autocomplete="new-password">
|
|
||||||
<label class="bratonien-label" for="nc_quiet_seconds">Ruhezeit</label><input id="nc_quiet_seconds" name="nc_quiet_seconds" type="number" min="0" value="120">
|
|
||||||
<label class="bratonien-label" for="nc_max_wait_seconds">Maximale Wartezeit</label><input id="nc_max_wait_seconds" name="nc_max_wait_seconds" type="number" min="60" value="900">
|
|
||||||
<label class="bratonien-label" for="nc_full_sync_seconds">Vollprüfung nach</label><input id="nc_full_sync_seconds" name="nc_full_sync_seconds" type="number" min="300" value="86400">
|
|
||||||
</div>
|
|
||||||
<p><label for="nc_storages"><strong>Storage-Zuordnungen</strong></label></p>
|
|
||||||
<p class="bratonien-base-note">Eine Zeile pro Storage: <code>storage_id | source_prefix | /lokaler/mount</code></p>
|
|
||||||
<textarea id="nc_storages" name="nc_storages" rows="4" style="width:100%" required></textarea>
|
|
||||||
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_create_local">Verbindung anlegen</button></p>
|
|
||||||
</form>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bratonien-card" style="grid-column:1/-1">
|
|
||||||
<h4>Connector-Verbindungen</h4>
|
|
||||||
{if $NC_CONNECTOR.connection_count > 0}
|
|
||||||
<table class="table2">
|
|
||||||
<thead>
|
|
||||||
<tr><th>Name</th><th>Adapter</th><th>Host</th><th>Quelle</th><th>Storages</th><th>Status</th><th>Aktion</th></tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{foreach from=$NC_CONNECTOR.connections item=connection}
|
|
||||||
<tr>
|
|
||||||
<td>{$connection.display_name|escape:html}</td>
|
|
||||||
<td>{$connection.adapter|escape:html}</td>
|
|
||||||
<td>{if $connection.host}{$connection.host|escape:html}{else}—{/if}</td>
|
|
||||||
<td>{if $connection.source_view}{$connection.source_view|escape:html}{else}—{/if}</td>
|
|
||||||
<td>{$connection.storage_count|escape:html}</td>
|
|
||||||
<td>{$connection.takeover_state|escape:html}{if $connection.enabled} · aktiv{/if}</td>
|
|
||||||
<td>
|
|
||||||
<details>
|
|
||||||
<summary>Verwalten</summary>
|
|
||||||
<form method="post" class="bratonien-actions" style="margin-top:.75rem">
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
|
|
||||||
<input name="connection_name" type="text" value="{$connection.name|escape:html}" required>
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_update_name">Name speichern</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="bratonien-actions">
|
|
||||||
{if $connection.adapter == 'local' && !$connection.enabled}
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_verify">Verbindung prüfen</button>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
{if !$connection.enabled && $connection.takeover_state != 'active'}
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
|
|
||||||
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_delete" onclick="return confirm('Verbindung wirklich löschen? Es werden nur die Connector-Einstellungen entfernt, keine Nextcloud- oder Piwigo-Bilder.');">Löschen</button>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<details style="margin-top:.75rem">
|
|
||||||
<summary>Technische Einstellungen</summary>
|
|
||||||
{if $connection.enabled || $connection.takeover_state == 'active'}
|
|
||||||
<p class="bratonien-main-cache__warning">Eine aktive Verbindung muss vor technischen Änderungen deaktiviert werden.</p>
|
|
||||||
{else}
|
|
||||||
<p class="bratonien-main-cache__warning">Nur ändern, wenn die technische Zuordnung bekannt ist. Storage- und Galeriepfade sind sicherheitskritisch.</p>
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label">PostgreSQL-Host</label><input name="nc_host" type="text" value="{$connection.config.host|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Port</label><input name="nc_port" type="number" min="1" max="65535" value="{$connection.config.port|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Datenbank</label><input name="nc_database" type="text" value="{$connection.config.database|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Reader-Benutzer</label><input name="nc_user" type="text" value="{$connection.config.user|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Reader-Passwort</label><input name="nc_db_password" type="password" placeholder="leer = unverändert">
|
|
||||||
<label class="bratonien-label">Source-View</label><input name="nc_source_view" type="text" value="{$connection.config.source_view|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Activity-View</label><input name="nc_activity_view" type="text" value="{$connection.config.activity_view|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Piwigo-Galeriepfad</label><input name="nc_gallery_root" type="text" value="{$connection.config.gallery_root|escape:html}" required>
|
|
||||||
<label class="bratonien-label">Ruhezeit</label><input name="nc_quiet_seconds" type="number" min="0" value="{$connection.config.quiet_seconds|escape:html}">
|
|
||||||
<label class="bratonien-label">Maximale Wartezeit</label><input name="nc_max_wait_seconds" type="number" min="60" value="{$connection.config.max_wait_seconds|escape:html}">
|
|
||||||
<label class="bratonien-label">Vollprüfung nach</label><input name="nc_full_sync_seconds" type="number" min="300" value="{$connection.config.full_sync_seconds|escape:html}">
|
|
||||||
</div>
|
|
||||||
<p><strong>Storage-Zuordnungen</strong></p>
|
|
||||||
<textarea name="nc_storages" rows="4" style="width:100%" required>{$connection.storage_text|escape:html}</textarea>
|
|
||||||
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_update_technical">Technische Einstellungen speichern</button></p>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</details>
|
|
||||||
</details>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{if $connection.verification_checks|@count > 0}
|
|
||||||
<tr><td colspan="7"><strong>Letzte Verifikation{if $connection.verified_at} · {$connection.verified_at|escape:html}{/if}</strong><ul>{foreach from=$connection.verification_checks item=check}<li>{if $check.ok}✓{else}✗{/if} <strong>{$check.name|escape:html}:</strong> {$check.detail|escape:html}</li>{/foreach}</ul></td></tr>
|
|
||||||
{/if}
|
|
||||||
{if isset($connection.last_sync) && $connection.last_sync.timestamp > 0}
|
|
||||||
<tr><td colspan="7"><strong>Letzter Sync · {$connection.last_sync.label|escape:html}</strong><p class="bratonien-base-note"><strong>Ergebnis:</strong> {$connection.last_sync.message|escape:html}</p>{if $connection.last_sync.api_state == 'error'}<p class="bratonien-main-cache__warning"><strong>API fehlgeschlagen:</strong> {$connection.last_sync.api_message|escape:html}</p>{/if}{if $connection.last_sync.fallback_state == 'error'}<p class="bratonien-main-cache__warning"><strong>Fallback fehlgeschlagen:</strong> {$connection.last_sync.fallback_message|escape:html}</p>{/if}{if $connection.last_sync.error_detail}<p class="bratonien-main-cache__warning"><strong>Technischer Fehler:</strong> {$connection.last_sync.error_detail|escape:html}</p>{/if}</td></tr>
|
|
||||||
{/if}
|
|
||||||
{/foreach}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{else}
|
|
||||||
<p>Noch keine Verbindung in der Connector-Verwaltung.</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bratonien-card" style="grid-column:1/-1">
|
|
||||||
<h4>Piwigo API – bevorzugter Sync-Zugang</h4>
|
|
||||||
<p class="bratonien-base-note">Hier kann der globale API-Zugang unabhängig vom Assistenten geprüft, ersetzt oder gelöscht werden.</p>
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label" for="nc_piwigo_api_key_id">API-Schlüssel-ID</label><input id="nc_piwigo_api_key_id" name="nc_piwigo_api_key_id" type="text" autocomplete="off" placeholder="pkid-..." required>
|
|
||||||
<label class="bratonien-label" for="nc_piwigo_api_key_secret">API-Geheimnis</label><input id="nc_piwigo_api_key_secret" name="nc_piwigo_api_key_secret" type="password" autocomplete="off" required>
|
|
||||||
</div>
|
|
||||||
<div class="bratonien-actions">
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_test">API prüfen und speichern</button>
|
|
||||||
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_delete" formnovalidate onclick="return confirm('Gespeicherte Piwigo-API-Zugangsdaten wirklich löschen?');">Gespeicherte API löschen</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{if isset($NC_CONNECTOR.piwigo_api_test) && $NC_CONNECTOR.piwigo_api_test}
|
|
||||||
<p class="bratonien-base-note"><strong>Bewertung:</strong> {$NC_CONNECTOR.piwigo_api_test.conclusion|escape:html}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bratonien-card" style="grid-column:1/-1">
|
|
||||||
<h4>Benutzername/Passwort-Fallback</h4>
|
|
||||||
{if $NC_CONNECTOR.connection_count > 0}
|
|
||||||
<form method="post">
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
|
||||||
<div class="bratonien-form-grid">
|
|
||||||
<label class="bratonien-label" for="nc_fallback_connection">Verbindung</label><select id="nc_fallback_connection" name="connection_id" required>{foreach from=$NC_CONNECTOR.connections item=connection}<option value="{$connection.id|escape:html}">{$connection.name|escape:html}</option>{/foreach}</select>
|
|
||||||
<label class="bratonien-label" for="nc_fallback_user">Piwigo-Benutzer</label><input id="nc_fallback_user" name="nc_fallback_user" type="text" autocomplete="username">
|
|
||||||
<label class="bratonien-label" for="nc_fallback_password">Piwigo-Passwort</label><input id="nc_fallback_password" name="nc_fallback_password" type="password" autocomplete="current-password">
|
|
||||||
</div>
|
|
||||||
<div class="bratonien-actions">
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_once">Einmalig verwenden</button>
|
|
||||||
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_save">Fest speichern</button>
|
|
||||||
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_fallback_delete" formnovalidate onclick="return confirm('Gespeicherten Benutzername/Passwort-Fallback wirklich löschen?');">Gespeicherten Fallback löschen</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{else}<p class="bratonien-base-note">Noch keine Connector-Verbindung vorhanden.</p>{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<dialog id="bratonien-nc-wizard-dialog" style="width:min(980px,calc(100vw - 3rem));max-height:88vh;overflow:auto;background:#444;color:inherit;border:1px solid #777;border-radius:4px;padding:0;box-shadow:0 18px 60px rgba(0,0,0,.55)">
|
||||||
(function () {
|
<div style="padding:1.25rem 1.5rem">
|
||||||
var dialog = document.getElementById('bratonien-nc-wizard-dialog');
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:1rem">
|
||||||
var openButton = document.getElementById('bratonien-nc-wizard-open');
|
<div><h4 style="margin:0">Neue Verbindung</h4><p class="bratonien-base-note" style="margin:.35rem 0 0"><strong>Schritt {$NC_CONNECTOR.wizard.step|escape:html} von 4</strong></p></div>
|
||||||
var closeButton = document.getElementById('bratonien-nc-wizard-close');
|
<button class="buttonLike" type="button" id="bratonien-nc-wizard-close">Schließen</button>
|
||||||
var technicalButton = document.getElementById('bratonien-nc-technical-open');
|
</div>
|
||||||
var technical = document.getElementById('bratonien-nc-technical-create');
|
|
||||||
var storageKey = 'bratonienNcWizardOpen';
|
|
||||||
|
|
||||||
if (openButton && dialog) {
|
{if $NC_CONNECTOR.wizard.step == 1}
|
||||||
openButton.addEventListener('click', function () {
|
<p class="bratonien-base-note">Adresse und Zugang reichen für den ersten Scan. Der Assistent prüft den erreichbaren Web-Zugang automatisch.</p>
|
||||||
sessionStorage.setItem(storageKey, '1');
|
<form method="post" data-bratonien-wizard-form>
|
||||||
if (typeof dialog.showModal === 'function') dialog.showModal();
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
else dialog.setAttribute('open', 'open');
|
<div class="bratonien-form-grid">
|
||||||
});
|
<label class="bratonien-label" for="nc_wizard_host">Nextcloud-Adresse</label>
|
||||||
}
|
<input id="nc_wizard_host" name="nc_wizard_host" type="text" placeholder="cloud.example.de" value="{$NC_CONNECTOR.wizard.host_input|escape:html}" required>
|
||||||
|
<label class="bratonien-label" for="nc_wizard_user">Benutzer</label>
|
||||||
|
<input id="nc_wizard_user" name="nc_wizard_user" type="text" autocomplete="username" value="{$NC_CONNECTOR.wizard.username|escape:html}" required>
|
||||||
|
<label class="bratonien-label" for="nc_wizard_password">Passwort</label>
|
||||||
|
<input id="nc_wizard_password" name="nc_wizard_password" type="password" autocomplete="current-password" value="{$NC_CONNECTOR.wizard._password|escape:html}" required>
|
||||||
|
</div>
|
||||||
|
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_scan">Verbinden und scannen</button></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
if (closeButton && dialog) {
|
{elseif $NC_CONNECTOR.wizard.step == 2}
|
||||||
closeButton.addEventListener('click', function () {
|
<p class="bratonien-base-note"><strong>Nextcloud wurde gefunden.</strong> Der Assistent prüft jetzt, wie die freigegebenen Dateien gelesen werden können.</p>
|
||||||
sessionStorage.removeItem(storageKey);
|
<div class="bratonien-form-grid">
|
||||||
if (typeof dialog.close === 'function') dialog.close();
|
<span class="bratonien-label">Adresse</span><strong>{$NC_CONNECTOR.wizard.base_url|escape:html}</strong>
|
||||||
else dialog.removeAttribute('open');
|
<span class="bratonien-label">Version</span><strong>{if $NC_CONNECTOR.wizard.version}{$NC_CONNECTOR.wizard.version|escape:html}{else}Nicht gemeldet{/if}</strong>
|
||||||
});
|
<span class="bratonien-label">Angemeldet als</span><strong>{$NC_CONNECTOR.wizard.username|escape:html}{if $NC_CONNECTOR.wizard.display_name} · {$NC_CONNECTOR.wizard.display_name|escape:html}{/if}</strong>
|
||||||
}
|
</div>
|
||||||
|
|
||||||
if (dialog) {
|
{if $NC_CONNECTOR.wizard.technical_stage == 'reader_credentials'}
|
||||||
dialog.addEventListener('cancel', function () {
|
<hr>
|
||||||
sessionStorage.removeItem(storageKey);
|
<h5>Datenzugriff</h5>
|
||||||
});
|
<p class="bratonien-base-note">Nextcloud gibt den internen Lesezugang aus Sicherheitsgründen nicht über die normale API aus. Bitte nur den dafür eingerichteten Lese-Benutzer angeben. Die üblichen Verbindungswerte prüft der Assistent selbst.</p>
|
||||||
dialog.addEventListener('click', function (event) {
|
<form method="post" data-bratonien-wizard-form>
|
||||||
if (event.target === dialog) {
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
sessionStorage.removeItem(storageKey);
|
<div class="bratonien-form-grid">
|
||||||
if (typeof dialog.close === 'function') dialog.close();
|
<label class="bratonien-label">Lese-Benutzer</label><input name="nc_wizard_db_user" type="text" value="{$NC_CONNECTOR.wizard.db_user|escape:html}" required>
|
||||||
}
|
<label class="bratonien-label">Passwort</label><input name="nc_wizard_db_password" type="password" autocomplete="new-password" value="{$NC_CONNECTOR.wizard._db_password|escape:html}" required>
|
||||||
});
|
</div>
|
||||||
dialog.querySelectorAll('form[data-bratonien-wizard-form]').forEach(function (form) {
|
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_technical">Automatisch prüfen</button></p>
|
||||||
form.addEventListener('submit', function (event) {
|
</form>
|
||||||
var submitter = event.submitter;
|
|
||||||
if (submitter && submitter.hasAttribute('data-bratonien-wizard-end')) sessionStorage.removeItem(storageKey);
|
|
||||||
else sessionStorage.setItem(storageKey, '1');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (sessionStorage.getItem(storageKey) === '1') {
|
|
||||||
if (typeof dialog.showModal === 'function' && !dialog.open) dialog.showModal();
|
|
||||||
else dialog.setAttribute('open', 'open');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (technicalButton && technical) {
|
{elseif $NC_CONNECTOR.wizard.technical_stage == 'database_details'}
|
||||||
technicalButton.addEventListener('click', function () {
|
<hr>
|
||||||
technical.open = !technical.open;
|
<h5>Datenbank-Adresse prüfen</h5>
|
||||||
if (technical.open) technical.scrollIntoView({behavior:'smooth', block:'start'});
|
<p class="bratonien-base-note">Der übliche Weg hat nicht funktioniert. Deshalb werden jetzt nur die Angaben abgefragt, die davon abweichen können.</p>
|
||||||
});
|
{if $NC_CONNECTOR.wizard.technical_error}<details><summary>Technische Details</summary><p class="bratonien-main-cache__warning">{$NC_CONNECTOR.wizard.technical_error|escape:html}</p></details>{/if}
|
||||||
}
|
<form method="post" data-bratonien-wizard-form>
|
||||||
}());
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
</script>
|
<div class="bratonien-form-grid">
|
||||||
|
<label class="bratonien-label">Datenbank-Adresse</label><input name="nc_wizard_db_host" type="text" value="{$NC_CONNECTOR.wizard.db_host|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Port</label><input name="nc_wizard_db_port" type="number" min="1" max="65535" value="{$NC_CONNECTOR.wizard.db_port|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Datenbank</label><input name="nc_wizard_db_database" type="text" value="{$NC_CONNECTOR.wizard.db_database|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Lese-Benutzer</label><input name="nc_wizard_db_user" type="text" value="{$NC_CONNECTOR.wizard.db_user|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Passwort</label><input name="nc_wizard_db_password" type="password" autocomplete="new-password" value="{$NC_CONNECTOR.wizard._db_password|escape:html}" required>
|
||||||
|
</div>
|
||||||
|
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_technical">Erneut prüfen</button></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{elseif $NC_CONNECTOR.wizard.technical_stage == 'mounts'}
|
||||||
|
<hr>
|
||||||
|
<h5>Speicherort bestätigen</h5>
|
||||||
|
<p class="bratonien-base-note">Die Datenquelle wurde gefunden. Nur Speicherorte, die nicht sicher erkannt werden konnten, müssen jetzt einmalig zugeordnet werden.</p>
|
||||||
|
<form method="post" data-bratonien-wizard-form>
|
||||||
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
|
<div class="bratonien-form-grid">
|
||||||
|
{foreach from=$NC_CONNECTOR.wizard.storage_candidates item=storage key=storage_index}
|
||||||
|
<span class="bratonien-label">Speicher {$storage_index+1}</span>
|
||||||
|
{if $storage.local_mount}
|
||||||
|
<strong>Automatisch erkannt</strong>
|
||||||
|
<input type="hidden" name="nc_wizard_storage_mount[{$storage_index|escape:html}]" value="{$storage.local_mount|escape:html}">
|
||||||
|
{else}
|
||||||
|
<input name="nc_wizard_storage_mount[{$storage_index|escape:html}]" type="text" placeholder="Eingebundener Speicherpfad" required>
|
||||||
|
{/if}
|
||||||
|
{/foreach}
|
||||||
|
</div>
|
||||||
|
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_mounts">Speicher prüfen</button></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{elseif $NC_CONNECTOR.wizard.technical_complete}
|
||||||
|
<hr>
|
||||||
|
<h5>Welche Freigaben sollen verwendet werden?</h5>
|
||||||
|
<p class="bratonien-base-note"><strong>Empfehlung:</strong> ein eigener Showcase-Benutzer. So bleibt die Verbindung unabhängig von persönlichen Konten.</p>
|
||||||
|
<form method="post" data-bratonien-wizard-form>
|
||||||
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
|
<div class="bratonien-form-grid">
|
||||||
|
<label class="bratonien-label" for="nc_wizard_connection_name">Name der Verbindung</label><input id="nc_wizard_connection_name" name="nc_wizard_connection_name" type="text" value="{$NC_CONNECTOR.wizard.connection_name|escape:html}" required>
|
||||||
|
<label class="bratonien-label" for="nc_wizard_showcase_user">Nextcloud-Benutzer</label>
|
||||||
|
{if $NC_CONNECTOR.wizard.can_list_users && $NC_CONNECTOR.wizard.users|@count > 0}
|
||||||
|
<select id="nc_wizard_showcase_user" name="nc_wizard_showcase_user" required>
|
||||||
|
{foreach from=$NC_CONNECTOR.wizard.users item=nc_user}<option value="{$nc_user|escape:html}"{if $nc_user == 'showcase'} selected{/if}>{$nc_user|escape:html}{if $nc_user == 'showcase'} · empfohlen{/if}</option>{/foreach}
|
||||||
|
</select>
|
||||||
|
{else}
|
||||||
|
<input id="nc_wizard_showcase_user" name="nc_wizard_showcase_user" type="text" value="{if $NC_CONNECTOR.wizard.showcase_user}{$NC_CONNECTOR.wizard.showcase_user|escape:html}{else}showcase{/if}" required>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_select_user">Weiter</button></p>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<form method="post" style="margin-top:1rem" data-bratonien-wizard-form><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_reset" data-bratonien-wizard-end>Neu beginnen</button></form>
|
||||||
|
|
||||||
|
{elseif $NC_CONNECTOR.wizard.step == 3}
|
||||||
|
<p class="bratonien-base-note"><strong>Nextcloud ist vorbereitet.</strong> Jetzt wird der bevorzugte Piwigo-Zugang geprüft.</p>
|
||||||
|
{if $NC_CONNECTOR.wizard.api_error}<p class="bratonien-main-cache__warning"><strong>API-Test fehlgeschlagen:</strong> {$NC_CONNECTOR.wizard.api_error|escape:html}</p>{/if}
|
||||||
|
<form method="post" data-bratonien-wizard-form>
|
||||||
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
|
<div class="bratonien-form-grid">
|
||||||
|
<label class="bratonien-label" for="nc_wizard_api_key_id">API-Schlüssel-ID</label><input id="nc_wizard_api_key_id" name="nc_wizard_api_key_id" type="text" autocomplete="off" value="{$NC_CONNECTOR.wizard._api_key_id|escape:html}" placeholder="leer = bereits gespeicherten Zugang testen">
|
||||||
|
<label class="bratonien-label" for="nc_wizard_api_key_secret">API-Geheimnis</label><input id="nc_wizard_api_key_secret" name="nc_wizard_api_key_secret" type="password" autocomplete="off" value="{$NC_CONNECTOR.wizard._api_key_secret|escape:html}" placeholder="leer = bereits gespeicherten Zugang testen">
|
||||||
|
</div>
|
||||||
|
<div class="bratonien-actions"><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_api_test">API testen</button><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_api_skip" formnovalidate>Überspringen</button></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{elseif $NC_CONNECTOR.wizard.step == 4}
|
||||||
|
<p class="bratonien-base-note"><strong>Fast fertig.</strong> Die Verbindung wurde noch nicht angelegt. Erst der letzte Button übernimmt die Einstellungen.</p>
|
||||||
|
<div class="bratonien-form-grid">
|
||||||
|
<span class="bratonien-label">Verbindung</span><strong>{$NC_CONNECTOR.wizard.connection_name|escape:html}</strong>
|
||||||
|
<span class="bratonien-label">Nextcloud</span><strong>{$NC_CONNECTOR.wizard.base_url|escape:html}</strong>
|
||||||
|
<span class="bratonien-label">Freigaben von</span><strong>{$NC_CONNECTOR.wizard.showcase_user|escape:html}</strong>
|
||||||
|
<span class="bratonien-label">Piwigo-API</span><strong>{if $NC_CONNECTOR.wizard.api_status == 'ok'}Erfolgreich geprüft{else}Übersprungen{/if}</strong>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<h5>Fallback</h5>
|
||||||
|
{if $NC_CONNECTOR.wizard.api_status == 'ok'}<p class="bratonien-base-note">Optional. Wird nur verwendet, falls die API später nicht verfügbar ist.</p>{else}<p class="bratonien-main-cache__warning">Ohne API ist ein Fallback-Zugang erforderlich.</p>{/if}
|
||||||
|
<form method="post" data-bratonien-wizard-form>
|
||||||
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
|
<div class="bratonien-form-grid">
|
||||||
|
<label class="bratonien-label" for="nc_wizard_fallback_user">Piwigo-Benutzer</label><input id="nc_wizard_fallback_user" name="nc_wizard_fallback_user" type="text" autocomplete="username" value="{$NC_CONNECTOR.wizard._fallback_user|escape:html}"{if $NC_CONNECTOR.wizard.api_status != 'ok'} required{/if}>
|
||||||
|
<label class="bratonien-label" for="nc_wizard_fallback_password">Piwigo-Passwort</label><input id="nc_wizard_fallback_password" name="nc_wizard_fallback_password" type="password" autocomplete="current-password" value="{$NC_CONNECTOR.wizard._fallback_password|escape:html}"{if $NC_CONNECTOR.wizard.api_status != 'ok'} required{/if}>
|
||||||
|
</div>
|
||||||
|
<div class="bratonien-actions"><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_finish" data-bratonien-wizard-end>Verbindung anlegen</button><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_reset" formnovalidate data-bratonien-wizard-end>Abbrechen</button></div>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<details id="bratonien-nc-technical-create" class="bratonien-card" style="margin-top:1.5rem">
|
||||||
|
<summary style="display:none">Ohne Assistent anlegen</summary>
|
||||||
|
<h4>Technische Einrichtung</h4>
|
||||||
|
<p class="bratonien-main-cache__warning">Nur verwenden, wenn die technische Zuordnung bekannt ist. Falsche Pfade können die Synchronisierung auf den falschen Datenbestand richten.</p>
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
|
||||||
|
<div class="bratonien-form-grid">
|
||||||
|
<label class="bratonien-label">Name</label><input name="nc_name" type="text" required>
|
||||||
|
<label class="bratonien-label">PostgreSQL-Host</label><input name="nc_host" type="text" required>
|
||||||
|
<label class="bratonien-label">PostgreSQL-Port</label><input name="nc_port" type="number" min="1" max="65535" value="5432" required>
|
||||||
|
<label class="bratonien-label">Datenbank</label><input name="nc_database" type="text" value="nextcloud" required>
|
||||||
|
<label class="bratonien-label">Reader-Benutzer</label><input name="nc_user" type="text" required>
|
||||||
|
<label class="bratonien-label">Reader-Passwort</label><input name="nc_db_password" type="password" autocomplete="new-password" required>
|
||||||
|
<label class="bratonien-label">Source-View</label><input name="nc_source_view" type="text" value="piwigo_showcase_sources" required>
|
||||||
|
<label class="bratonien-label">Activity-View</label><input name="nc_activity_view" type="text" value="piwigo_showcase_activity" required>
|
||||||
|
<label class="bratonien-label">Piwigo-Galeriepfad</label><input name="nc_gallery_root" type="text" placeholder="/var/www/piwigo/galleries/nextcloud" required>
|
||||||
|
<label class="bratonien-label">Fallback-Benutzer</label><input name="nc_piwigo_user" type="text">
|
||||||
|
<label class="bratonien-label">Fallback-Passwort</label><input name="nc_piwigo_password" type="password" autocomplete="new-password">
|
||||||
|
<label class="bratonien-label">Ruhezeit</label><input name="nc_quiet_seconds" type="number" min="0" value="120">
|
||||||
|
<label class="bratonien-label">Maximale Wartezeit</label><input name="nc_max_wait_seconds" type="number" min="60" value="900">
|
||||||
|
<label class="bratonien-label">Vollprüfung nach</label><input name="nc_full_sync_seconds" type="number" min="300" value="86400">
|
||||||
|
</div>
|
||||||
|
<p><strong>Storage-Zuordnungen</strong></p><p class="bratonien-base-note">Format: Storage-ID | optionales Quellpräfix | lokaler Speicherpfad</p><textarea name="nc_storages" rows="4" style="width:100%" required></textarea>
|
||||||
|
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_create_local">Verbindung anlegen</button></p>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<div class="bratonien-card" style="margin-top:1.5rem">
|
||||||
|
<h4>Bestehende Verbindungen</h4>
|
||||||
|
{if $NC_CONNECTOR.connection_count > 0}
|
||||||
|
{foreach from=$NC_CONNECTOR.connections item=connection}
|
||||||
|
<details style="margin:.6rem 0">
|
||||||
|
<summary><strong>{$connection.display_name|escape:html}</strong> · {if $connection.enabled}aktiv{else}{$connection.takeover_state|escape:html}{/if}</summary>
|
||||||
|
<div style="padding:.75rem 0">
|
||||||
|
<form method="post" class="bratonien-actions"><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><input type="hidden" name="connection_id" value="{$connection.id|escape:html}"><input name="connection_name" type="text" value="{$connection.name|escape:html}" required><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_update_name">Name speichern</button></form>
|
||||||
|
<div class="bratonien-actions" style="margin-top:.6rem">
|
||||||
|
{if $connection.adapter == 'local' && !$connection.enabled}<form method="post"><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><input type="hidden" name="connection_id" value="{$connection.id|escape:html}"><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_verify">Verbindung prüfen</button></form>{/if}
|
||||||
|
{if !$connection.enabled && $connection.takeover_state != 'active'}<form method="post"><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><input type="hidden" name="connection_id" value="{$connection.id|escape:html}"><button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_delete" onclick="return confirm('Verbindung wirklich löschen? Bilder in Nextcloud oder Piwigo werden nicht gelöscht.');">Löschen</button></form>{/if}
|
||||||
|
</div>
|
||||||
|
{if isset($connection.last_sync) && $connection.last_sync.timestamp > 0}<p class="bratonien-base-note"><strong>Letzter Abgleich:</strong> {$connection.last_sync.label|escape:html} · {$connection.last_sync.message|escape:html}</p>{/if}
|
||||||
|
{if $connection.verification_checks|@count > 0}<details><summary>Letzte Prüfung</summary><ul>{foreach from=$connection.verification_checks item=check}<li>{if $check.ok}✓{else}✗{/if} {$check.name|escape:html}: {$check.detail|escape:html}</li>{/foreach}</ul></details>{/if}
|
||||||
|
<details style="margin-top:.75rem"><summary>Technische Einstellungen</summary>
|
||||||
|
{if $connection.enabled || $connection.takeover_state == 'active'}<p class="bratonien-main-cache__warning">Aktive Verbindungen können technisch nicht geändert werden.</p>{else}
|
||||||
|
<p class="bratonien-main-cache__warning">Nur ändern, wenn die technische Zuordnung bekannt ist.</p>
|
||||||
|
<form method="post"><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
|
||||||
|
<div class="bratonien-form-grid">
|
||||||
|
<label class="bratonien-label">PostgreSQL-Host</label><input name="nc_host" type="text" value="{$connection.config.host|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Port</label><input name="nc_port" type="number" min="1" max="65535" value="{$connection.config.port|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Datenbank</label><input name="nc_database" type="text" value="{$connection.config.database|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Reader-Benutzer</label><input name="nc_user" type="text" value="{$connection.config.user|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Reader-Passwort</label><input name="nc_db_password" type="password" placeholder="leer = unverändert">
|
||||||
|
<label class="bratonien-label">Source-View</label><input name="nc_source_view" type="text" value="{$connection.config.source_view|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Activity-View</label><input name="nc_activity_view" type="text" value="{$connection.config.activity_view|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Piwigo-Galeriepfad</label><input name="nc_gallery_root" type="text" value="{$connection.config.gallery_root|escape:html}" required>
|
||||||
|
<label class="bratonien-label">Ruhezeit</label><input name="nc_quiet_seconds" type="number" min="0" value="{$connection.config.quiet_seconds|escape:html}">
|
||||||
|
<label class="bratonien-label">Maximale Wartezeit</label><input name="nc_max_wait_seconds" type="number" min="60" value="{$connection.config.max_wait_seconds|escape:html}">
|
||||||
|
<label class="bratonien-label">Vollprüfung nach</label><input name="nc_full_sync_seconds" type="number" min="300" value="{$connection.config.full_sync_seconds|escape:html}">
|
||||||
|
</div>
|
||||||
|
<p><strong>Storage-Zuordnungen</strong></p><p class="bratonien-base-note">Format: Storage-ID | optionales Quellpräfix | lokaler Speicherpfad</p><textarea name="nc_storages" rows="4" style="width:100%" required>{$connection.storage_text|escape:html}</textarea>
|
||||||
|
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_update_technical">Technische Einstellungen speichern</button></p>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{/foreach}
|
||||||
|
{else}<p class="bratonien-base-note">Noch keine Verbindung vorhanden.</p>{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="bratonien-card" style="margin-top:1.5rem"><summary>Erweiterte Piwigo-Zugänge</summary>
|
||||||
|
<h4>Piwigo API</h4>
|
||||||
|
<form method="post"><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><div class="bratonien-form-grid"><label class="bratonien-label">API-Schlüssel-ID</label><input name="nc_piwigo_api_key_id" type="text" autocomplete="off" required><label class="bratonien-label">API-Geheimnis</label><input name="nc_piwigo_api_key_secret" type="password" autocomplete="off" required></div><div class="bratonien-actions"><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_test">API prüfen und speichern</button><button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_delete" formnovalidate>Gespeicherte API löschen</button></div></form>
|
||||||
|
<h4 style="margin-top:1rem">Fallback speichern</h4>
|
||||||
|
{if $NC_CONNECTOR.connection_count > 0}<form method="post"><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><div class="bratonien-form-grid"><label class="bratonien-label">Verbindung</label><select name="connection_id" required>{foreach from=$NC_CONNECTOR.connections item=connection}<option value="{$connection.id|escape:html}">{$connection.name|escape:html}</option>{/foreach}</select><label class="bratonien-label">Piwigo-Benutzer</label><input name="nc_fallback_user" type="text" autocomplete="username"><label class="bratonien-label">Piwigo-Passwort</label><input name="nc_fallback_password" type="password" autocomplete="current-password"></div><div class="bratonien-actions"><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_save">Fallback speichern</button><button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_fallback_delete" formnovalidate>Fallback löschen</button></div></form>{/if}
|
||||||
|
<h4 style="margin-top:1rem">Einmaliger Fallback-Test</h4>
|
||||||
|
<p class="bratonien-base-note">Verwendet die eingegebenen Piwigo-Zugangsdaten einmalig und speichert sie nicht.</p>
|
||||||
|
<form method="post"><input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"><div class="bratonien-form-grid"><label class="bratonien-label">Piwigo-Benutzer</label><input name="nc_fallback_user" type="text" required><label class="bratonien-label">Piwigo-Passwort</label><input name="nc_fallback_password" type="password" required></div><p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_once">Einmalig testen</button></p></form>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
{literal}
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var dialog=document.getElementById('bratonien-nc-wizard-dialog');
|
||||||
|
var openButton=document.getElementById('bratonien-nc-wizard-open');
|
||||||
|
var closeButton=document.getElementById('bratonien-nc-wizard-close');
|
||||||
|
var technicalButton=document.getElementById('bratonien-nc-technical-open');
|
||||||
|
var technical=document.getElementById('bratonien-nc-technical-create');
|
||||||
|
var key='bratonienNcWizardOpen';
|
||||||
|
function openWizard(){try{sessionStorage.setItem(key,'1');}catch(e){} if(typeof dialog.showModal==='function'&&!dialog.open)dialog.showModal();else dialog.setAttribute('open','open');}
|
||||||
|
function closeWizard(){try{sessionStorage.removeItem(key);}catch(e){} if(typeof dialog.close==='function')dialog.close();else dialog.removeAttribute('open');}
|
||||||
|
if(openButton&&dialog)openButton.addEventListener('click',openWizard);
|
||||||
|
if(closeButton&&dialog)closeButton.addEventListener('click',closeWizard);
|
||||||
|
if(dialog){
|
||||||
|
dialog.addEventListener('cancel',function(){try{sessionStorage.removeItem(key);}catch(e){}});
|
||||||
|
dialog.addEventListener('click',function(e){if(e.target===dialog)closeWizard();});
|
||||||
|
dialog.querySelectorAll('form[data-bratonien-wizard-form]').forEach(function(form){form.addEventListener('submit',function(e){var s=e.submitter;try{if(s&&s.hasAttribute('data-bratonien-wizard-end'))sessionStorage.removeItem(key);else sessionStorage.setItem(key,'1');}catch(x){}});});
|
||||||
|
try{if(sessionStorage.getItem(key)==='1')openWizard();}catch(e){}
|
||||||
|
}
|
||||||
|
if(technicalButton&&technical)technicalButton.addEventListener('click',function(){technical.open=!technical.open;if(technical.open)technical.scrollIntoView(true);});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{/literal}
|
||||||
</section>
|
</section>
|
||||||
Reference in New Issue
Block a user