Merge branch 'main' into Snmp-Update
This commit is contained in:
commit
9e385c0348
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,4 @@
|
|||||||
/.idea/
|
/.idea/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/storage/
|
/storage/
|
||||||
/config.php
|
/config.php
|
||||||
94
README.md
94
README.md
@ -1,19 +1,65 @@
|
|||||||
# PHP_AdminTool_Projekt
|
# PHP_AdminTool_Projekt
|
||||||
Admin Tool Projekt für das Fach PHP
|
|
||||||
|
|
||||||
---
|
Dieses Repository enthält das gemeinsame PHP AdminTool, das auf IIS unter Windows betrieben wird und über LDAP/AD, SNMP und PowerShell administrative Aufgaben ermöglicht. Das Projekt wird von mehreren Entwicklern gepflegt und folgt einem klar definierten Workflow, um Qualität, Stabilität und Nachvollziehbarkeit sicherzustellen.
|
||||||
Ich (@blaerf) habe mich mal hingesetzt und einen Windows Server 2025 als VM aufgesetzt. Darauf habe ich die Active Directory und IIS Rolle installiert.
|
|
||||||
Zudem habe ich alles so konfiguriert, dass wir PHP mit IIS nutzen können und per LDAPS auf das AD per PHP zugreifen können.
|
|
||||||
|
|
||||||
Ich habe mir auch noch Gedanken zum Git-Workflow gemacht und es nieder geschrieben. So können wir sauber und sicher arbeiten.
|
Ein zentraler Bestandteil der Arbeit mit diesem Repository ist der verbindliche Gitea-Workflow im Wiki.
|
||||||
Eine Ordner Struktur habe ich mir auch überlegt und schon mal angelegt. Alle Infos habe ich soweit im Wiki zusammen getragen.
|
|
||||||
Meine Tests habe ich, wie im Git-workflow beschrieben, in ein eigenes branch (structure/first-structure) gepackt.
|
|
||||||
|
|
||||||
Bitte auch das Wiki beachten und durchlesen, falls nötig!
|
|
||||||
|
**Hinweis:** Der [Gitea-Workflow](https://git.eckertplayground.de/taarly/PHP_AdminTool_Projekt/wiki) ist zwingend zu lesen und einzuhalten. Er legt fest, wie Branches erstellt, gemerged und getestet werden.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Aufgaben: ##
|
## Überblick
|
||||||
|
|
||||||
|
Das Projekt dient als webbasiertes Administrationswerkzeug und umfasst unter anderem:
|
||||||
|
|
||||||
|
- LDAP/AD‑Anbindung (LDAPS)
|
||||||
|
- Benutzer- und Gruppenverwaltung
|
||||||
|
- SNMP‑Auswertungen
|
||||||
|
- PowerShell‑Integration
|
||||||
|
- Weboberfläche zur zentralen Administration
|
||||||
|
|
||||||
|
Die Anwendung läuft in zwei Umgebungen:
|
||||||
|
|
||||||
|
- Produktion (`main`): https://itfa.schraubenfuzzi.de
|
||||||
|
- Test (`develop`): https://test.itfa.schraubenfuzzi.de
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ordnerstruktur (Auszug)
|
||||||
|
|
||||||
|
- `app/` – Anwendungscode
|
||||||
|
- `public/` – Webroot
|
||||||
|
- `config/` – Konfigurationen für LDAP, Systemvariablen usw.
|
||||||
|
- `scripts/` – Hilfs- und Bereitstellungsskripte
|
||||||
|
- `docs/` – interne Dokumente
|
||||||
|
- `.gitignore` – ausgeschlossene Dateien
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
Die Entwicklung folgt einem strengen, verbindlichen Ablauf:
|
||||||
|
|
||||||
|
1. **Feature‑Branch erstellen** (`feature/name`)
|
||||||
|
2. Feature entwickeln, committen, pushen
|
||||||
|
3. **PR nach `develop`** für Tests auf der Testinstanz
|
||||||
|
- wird vom Autor selbst gemerged
|
||||||
|
- Branch bleibt bestehen
|
||||||
|
4. Nach bestandenem Test: **PR nach `main`**
|
||||||
|
- mit Review
|
||||||
|
- Merge per Squash
|
||||||
|
- danach wird der Feature‑Branch gelöscht
|
||||||
|
|
||||||
|
Details stehen im Wiki.
|
||||||
|
|
||||||
|
**Wichtiger Hinweis:**
|
||||||
|
Der komplette Ablauf ist im [Gitea-Workflow](https://git.eckertplayground.de/taarly/PHP_AdminTool_Projekt/wiki) beschrieben und muss befolgt werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
|
||||||
| Aufgabe | Benutzer |
|
| Aufgabe | Benutzer |
|
||||||
| :---- | :---- |
|
| :---- | :---- |
|
||||||
| Benutzer und Gruppen über LDAP anzeigen | Jens E (@blaerf), Stefan W (@viperion) |
|
| Benutzer und Gruppen über LDAP anzeigen | Jens E (@blaerf), Stefan W (@viperion) |
|
||||||
@ -23,4 +69,32 @@ Bitte auch das Wiki beachten und durchlesen, falls nötig!
|
|||||||
| UI/UX anpassen | Yasin B (@Muchentuchen), Alexander M (@Alexander), Torsten J (@tojacobs) |
|
| UI/UX anpassen | Yasin B (@Muchentuchen), Alexander M (@Alexander), Torsten J (@tojacobs) |
|
||||||
|
|
||||||
---
|
---
|
||||||
## Infos gibt es im [Wiki](https://git.eckertplayground.de/taarly/PHP_AdminTool_Projekt/wiki)
|
|
||||||
|
## Dokumentation
|
||||||
|
|
||||||
|
Alle weiteren Informationen wie Regeln, technische Details, Konzepte und Anleitungen befinden sich im Wiki:
|
||||||
|
|
||||||
|
`Wiki → Gitea‑Workflow`
|
||||||
|
`Wiki → Implementierung / LDAP / IIS / Setup`
|
||||||
|
|
||||||
|
Dieser Bereich muss von allen Entwicklern gelesen werden, bevor am Projekt gearbeitet wird.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mitwirken
|
||||||
|
|
||||||
|
Wer etwas ändern oder erweitern möchte:
|
||||||
|
|
||||||
|
- Branch vom aktuellen Stand erstellen
|
||||||
|
- Entwickeln, Committen, Pushen
|
||||||
|
- PR zuerst nach `develop`, später nach bestandenen Tests nach `main`
|
||||||
|
- Reviewer zuweisen (für `main`)
|
||||||
|
|
||||||
|
Nur tested und reviewed Code gelangt in die Produktion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Das AdminTool soll eine wartbare, erweiterbare und zuverlässige Verwaltungsoberfläche bieten, die zentrale Aufgaben des AD‑Umfelds über eine moderne Weboberfläche ermöglicht.
|
||||||
|
|
||||||
|
|||||||
153
app/Controllers/UserManagementController.php
Normal file
153
app/Controllers/UserManagementController.php
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Services\Ldap\LdapDirectoryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller für die Benutzer- und Gruppenanzeige.
|
||||||
|
*
|
||||||
|
* Aufgaben:
|
||||||
|
* - holt über den LdapDirectoryService die Listen von Benutzern und Gruppen
|
||||||
|
* - behandelt technische Fehler und bereitet eine Fehlermeldung für die View auf
|
||||||
|
* - gibt die Daten an eine View-Datei (public/views/users.php) weiter
|
||||||
|
*
|
||||||
|
* WICHTIG:
|
||||||
|
* - Es werden aktuell nur Daten angezeigt (Read-only).
|
||||||
|
* - Es findet keine Änderung im Active Directory statt.
|
||||||
|
*/
|
||||||
|
class UserManagementController
|
||||||
|
{
|
||||||
|
/** @var array<string, mixed> Vollständige Anwendungskonfiguration (aus config.php) */
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
/** @var LdapDirectoryService Service für das Lesen von Benutzern und Gruppen aus dem LDAP/AD */
|
||||||
|
private LdapDirectoryService $directoryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $config Vollständige Konfiguration aus config.php
|
||||||
|
*/
|
||||||
|
public function __construct(array $config)
|
||||||
|
{
|
||||||
|
// Komplette Konfiguration speichern (falls später weitere Werte benötigt werden).
|
||||||
|
$this->config = $config;
|
||||||
|
|
||||||
|
// LDAP-Konfiguration aus der Gesamt-Konfiguration herausziehen.
|
||||||
|
$ldapConfig = $config['ldap'] ?? [];
|
||||||
|
|
||||||
|
// Directory-Service initialisieren, der die eigentliche LDAP-Arbeit übernimmt.
|
||||||
|
$this->directoryService = new LdapDirectoryService($ldapConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt Benutzer- und Gruppenliste an.
|
||||||
|
* Wird typischerweise über die Route "users" (index.php?route=users) aufgerufen.
|
||||||
|
*/
|
||||||
|
public function show(): void
|
||||||
|
{
|
||||||
|
// Standardwerte für die View-Variablen vorbereiten.
|
||||||
|
$error = null;
|
||||||
|
$users = [];
|
||||||
|
$groups = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Benutzer- und Gruppenlisten aus dem AD laden.
|
||||||
|
$users = $this->directoryService->getUsers();
|
||||||
|
$groups = $this->directoryService->getGroups();
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
// Sämtliche technischen Fehler (z. B. Verbindungs- oder Konfigurationsprobleme)
|
||||||
|
// werden hier in eine für den Benutzer lesbare Fehlermeldung übersetzt.
|
||||||
|
$error = 'Fehler beim Laden von Benutzern/Gruppen: ' . $exception->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfad zur eigentlichen View-Datei bestimmen.
|
||||||
|
$viewPath = __DIR__ . '/../../public/views/users.php';
|
||||||
|
|
||||||
|
// Falls die View-Datei (noch) nicht existiert, Fallback-Ausgabe verwenden.
|
||||||
|
if (file_exists($viewPath) === false) {
|
||||||
|
$this->renderInline($users, $groups, $error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variablen $users, $groups, $error stehen in der View zur Verfügung,
|
||||||
|
// weil sie im aktuellen Scope definiert sind.
|
||||||
|
require $viewPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback-Ausgabe, falls noch keine View-Datei existiert.
|
||||||
|
*
|
||||||
|
* @param array<int, array<string, string>> $users Liste der Benutzer-Datensätze
|
||||||
|
* @param array<int, array<string, string>> $groups Liste der Gruppen-Datensätze
|
||||||
|
* @param string|null $error Fehlermeldung (falls vorhanden)
|
||||||
|
*/
|
||||||
|
private function renderInline(array $users, array $groups, ?string $error): void
|
||||||
|
{
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>AD Admin Tool – Benutzer & Gruppen</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Benutzer & Gruppen</h1>
|
||||||
|
|
||||||
|
<?php if ($error !== null): ?>
|
||||||
|
<!-- Fehlermeldung ausgeben, HTML-sicher maskiert -->
|
||||||
|
<p style="color: red;"><?php echo htmlspecialchars($error, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<h2>Benutzer</h2>
|
||||||
|
<table border="1" cellpadding="4" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Benutzername (sAMAccountName)</th>
|
||||||
|
<th>Anzeigename</th>
|
||||||
|
<th>E-Mail</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($users as $user): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($user['samaccountname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['displayname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['mail'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Gruppen</h2>
|
||||||
|
<table border="1" cellpadding="4" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Gruppenname (sAMAccountName)</th>
|
||||||
|
<th>CN</th>
|
||||||
|
<th>Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($groups as $group): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($group['samaccountname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($group['cn'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($group['description'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="index.php?route=dashboard">Zurück zum Dashboard</a> |
|
||||||
|
<a href="index.php?route=logout">Logout</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,21 +1,37 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Services\Ldap;
|
namespace App\Services\Ldap;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hilfsklasse zum Aufbau einer LDAP/LDAPS-Verbindung.
|
||||||
|
*
|
||||||
|
* Aufgaben:
|
||||||
|
* - liest Server, Port und Timeout aus der LDAP-Konfiguration
|
||||||
|
* - erstellt eine LDAP-Verbindung
|
||||||
|
* - setzt die notwendigen Optionen (Protokollversion, Netzwerk-Timeout)
|
||||||
|
*
|
||||||
|
* Wichtig:
|
||||||
|
* - Diese Klasse führt KEIN ldap_bind durch.
|
||||||
|
* - Das Bind (mit Benutzer- oder Service-Konto) erfolgt in den Fach-Services
|
||||||
|
* wie LdapAuthService oder LdapDirectoryService.
|
||||||
|
*/
|
||||||
class LdapConnectionHelper
|
class LdapConnectionHelper
|
||||||
{
|
{
|
||||||
/** @var array<string, mixed> */
|
/** @var array<string, mixed> LDAP-spezifische Konfiguration (server, port, timeout, etc.) */
|
||||||
private array $config;
|
private array $config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $ldapConfig
|
* @param array<string, mixed> $ldapConfig Teilbereich "ldap" aus der config.php
|
||||||
*/
|
*/
|
||||||
public function __construct(array $ldapConfig)
|
public function __construct(array $ldapConfig)
|
||||||
{
|
{
|
||||||
|
// LDAP-Konfiguration in der Instanz speichern.
|
||||||
|
// Dadurch steht sie in allen Methoden dieser Klasse zur Verfügung.
|
||||||
$this->config = $ldapConfig;
|
$this->config = $ldapConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,26 +40,43 @@ class LdapConnectionHelper
|
|||||||
* aber ohne Bind. Den Bind führen die aufrufenden Services durch.
|
* aber ohne Bind. Den Bind führen die aufrufenden Services durch.
|
||||||
*
|
*
|
||||||
* @return resource LDAP-Verbindungs-Handle
|
* @return resource LDAP-Verbindungs-Handle
|
||||||
|
*
|
||||||
|
* @throws RuntimeException wenn der Server nicht konfiguriert ist oder die Verbindung scheitert
|
||||||
*/
|
*/
|
||||||
public function createConnection()
|
public function createConnection()
|
||||||
{
|
{
|
||||||
|
// Server, Port und Timeout aus der Konfiguration lesen.
|
||||||
|
// Die Null-Koaleszenz-Operatoren (??) setzen Standardwerte, falls Keys fehlen.
|
||||||
$server = (string)($this->config['server'] ?? '');
|
$server = (string)($this->config['server'] ?? '');
|
||||||
$port = (int)($this->config['port'] ?? 636);
|
$port = (int)($this->config['port'] ?? 636);
|
||||||
$timeout = (int)($this->config['timeout'] ?? 5);
|
$timeout = (int)($this->config['timeout'] ?? 5);
|
||||||
|
|
||||||
|
// Ohne Server-Adresse kann keine Verbindung aufgebaut werden.
|
||||||
if ($server === '') {
|
if ($server === '') {
|
||||||
throw new RuntimeException('LDAP-Konfiguration ist unvollständig (server fehlt).');
|
throw new RuntimeException('LDAP-Konfiguration ist unvollständig (server fehlt).');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verbindung zum LDAP/AD-Server herstellen.
|
||||||
|
// ldap_connect liefert entweder ein Verbindungs-Handle (Resource) oder false.
|
||||||
$connection = ldap_connect($server, $port);
|
$connection = ldap_connect($server, $port);
|
||||||
|
|
||||||
|
// Wenn keine Verbindung aufgebaut werden konnte, Exception werfen.
|
||||||
if ($connection === false) {
|
if ($connection === false) {
|
||||||
throw new RuntimeException('LDAP-Verbindung konnte nicht aufgebaut werden.');
|
throw new RuntimeException('LDAP-Verbindung konnte nicht aufgebaut werden.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LDAP-Version auf 3 setzen, dies ist die gängige Standardversion.
|
||||||
ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);
|
ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||||
|
|
||||||
|
// Netzwerk-Timeout setzen, damit Anfragen nicht ewig hängen bleiben.
|
||||||
ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, $timeout);
|
ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, $timeout);
|
||||||
|
|
||||||
|
// Referral-Following deaktivieren (0 = aus).
|
||||||
|
// Das verhindert, dass der Client automatisch zu anderen LDAP-Servern weitergeleitet wird.
|
||||||
|
ldap_set_option($connection, LDAP_OPT_REFERRALS, 0);
|
||||||
|
|
||||||
|
// Verbindungs-Handle an aufrufende Services zurückgeben.
|
||||||
|
// Dort wird dann der eigentliche ldap_bind durchgeführt.
|
||||||
return $connection;
|
return $connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
203
app/Services/Ldap/LdapDirectoryService.php
Normal file
203
app/Services/Ldap/LdapDirectoryService.php
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Ldap;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service zum Lesen von Objekten aus dem Active Directory.
|
||||||
|
*
|
||||||
|
* Aktueller Umfang:
|
||||||
|
* - Liste von Benutzern (sAMAccountName, displayName, mail)
|
||||||
|
* - Liste von Gruppen (sAMAccountName, cn, description)
|
||||||
|
*
|
||||||
|
* Technische Details:
|
||||||
|
* - Verwendet ein technisches Konto (bind_dn + bind_password) für Lesezugriffe
|
||||||
|
* - Nutzt LdapConnectionHelper zum Aufbau der Verbindung
|
||||||
|
*/
|
||||||
|
class LdapDirectoryService
|
||||||
|
{
|
||||||
|
/** @var array<string, mixed> LDAP-Konfiguration (inkl. base_dn, bind_dn, bind_password) */
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
/** @var LdapConnectionHelper Zentrale Hilfsklasse für den Aufbau von LDAP-Verbindungen */
|
||||||
|
private LdapConnectionHelper $connectionHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $ldapConfig Teilbereich "ldap" aus der config.php
|
||||||
|
*/
|
||||||
|
public function __construct(array $ldapConfig)
|
||||||
|
{
|
||||||
|
// Vollständige LDAP-Konfiguration speichern.
|
||||||
|
$this->config = $ldapConfig;
|
||||||
|
|
||||||
|
// Gemeinsamen Verbindungs-Helper initialisieren.
|
||||||
|
// Dieser kümmert sich um ldap_connect und die grundlegenden Optionen.
|
||||||
|
$this->connectionHelper = new LdapConnectionHelper($ldapConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stellt eine LDAP-Verbindung her und bindet sich mit dem technischen Konto.
|
||||||
|
*
|
||||||
|
* @return resource LDAP-Verbindungs-Handle
|
||||||
|
*
|
||||||
|
* @throws RuntimeException wenn Bind-Daten fehlen oder der Bind fehlschlägt
|
||||||
|
*/
|
||||||
|
private function connect()
|
||||||
|
{
|
||||||
|
// Technischen Bind-DN (Service-Account) aus der Konfiguration lesen.
|
||||||
|
$bindDn = (string)($this->config['bind_dn'] ?? '');
|
||||||
|
$bindPassword = (string)($this->config['bind_password'] ?? '');
|
||||||
|
|
||||||
|
// Ohne Bind-DN oder Passwort kann kein technischer Bind durchgeführt werden.
|
||||||
|
if ($bindDn === '' || $bindPassword === '') {
|
||||||
|
throw new RuntimeException('LDAP-Binddaten für technisches Konto sind nicht konfiguriert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbindung über den zentralen Helper erstellen (Server, Port, Optionen).
|
||||||
|
$connection = $this->connectionHelper->createConnection();
|
||||||
|
|
||||||
|
// Mit dem technischen Konto binden, um Lesezugriffe durchführen zu können.
|
||||||
|
// Das @-Zeichen unterdrückt PHP-Warnings bei Fehlversuchen.
|
||||||
|
if (@ldap_bind($connection, $bindDn, $bindPassword) !== true) {
|
||||||
|
// Bei fehlgeschlagenem Bind Verbindung wieder schließen.
|
||||||
|
ldap_unbind($connection);
|
||||||
|
throw new RuntimeException('LDAP-Bind mit technischem Konto ist fehlgeschlagen.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erfolgreich gebundene Verbindung zurückgeben.
|
||||||
|
return $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert eine Liste von Benutzern aus dem AD.
|
||||||
|
*
|
||||||
|
* @return array<int, array<string, string>> Liste von Benutzer-Datensätzen
|
||||||
|
* [
|
||||||
|
* [
|
||||||
|
* 'samaccountname' => 'user1',
|
||||||
|
* 'displayname' => 'User Eins',
|
||||||
|
* 'mail' => 'user1@example.local',
|
||||||
|
* ],
|
||||||
|
* ...
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* @throws RuntimeException bei Konfigurations- oder Verbindungsproblemen
|
||||||
|
*/
|
||||||
|
public function getUsers(): array
|
||||||
|
{
|
||||||
|
// Base-DN ist der Startpunkt der Suche im Verzeichnisbaum (z. B. DC=...,DC=...).
|
||||||
|
$baseDn = (string)($this->config['base_dn'] ?? '');
|
||||||
|
if ($baseDn === '') {
|
||||||
|
throw new RuntimeException('LDAP base_dn für Benutzersuche ist nicht konfiguriert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbindung mit technischem Bind herstellen.
|
||||||
|
$connection = $this->connect();
|
||||||
|
|
||||||
|
// Standard-Filter für Benutzer in AD:
|
||||||
|
// - objectClass=user
|
||||||
|
// - objectCategory=person
|
||||||
|
$filter = '(&(objectClass=user)(objectCategory=person))';
|
||||||
|
|
||||||
|
// Nur relevante Attribute auslesen, um Datenmenge klein zu halten.
|
||||||
|
$attributes = ['sAMAccountName', 'displayName', 'mail'];
|
||||||
|
|
||||||
|
// LDAP-Suche unterhalb des Base-DN durchführen.
|
||||||
|
$search = @ldap_search($connection, $baseDn, $filter, $attributes);
|
||||||
|
if ($search === false) {
|
||||||
|
// Bei einem Fehler die Verbindung schließen und Exception werfen.
|
||||||
|
ldap_unbind($connection);
|
||||||
|
throw new RuntimeException('LDAP-Suche nach Benutzern ist fehlgeschlagen.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suchergebnisse in ein PHP-Array umwandeln.
|
||||||
|
$entries = ldap_get_entries($connection, $search);
|
||||||
|
|
||||||
|
// Verbindung wieder schließen, da sie nicht mehr benötigt wird.
|
||||||
|
ldap_unbind($connection);
|
||||||
|
|
||||||
|
// Ergebnisliste für die View vorbereiten.
|
||||||
|
$users = [];
|
||||||
|
|
||||||
|
// Wenn die Struktur unerwartet ist, leere Liste zurückgeben.
|
||||||
|
if (!is_array($entries) || !isset($entries['count'])) {
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jeden einzelnen Eintrag aus dem Ergebnis verarbeiten.
|
||||||
|
for ($i = 0; $i < $entries['count']; $i++) {
|
||||||
|
$entry = $entries[$i];
|
||||||
|
|
||||||
|
// Attribute sind im Ergebnis verschachtelt, daher Zugriff über [...][0].
|
||||||
|
$users[] = [
|
||||||
|
'samaccountname' => isset($entry['samaccountname'][0]) ? (string)$entry['samaccountname'][0] : '',
|
||||||
|
'displayname' => isset($entry['displayname'][0]) ? (string)$entry['displayname'][0] : '',
|
||||||
|
'mail' => isset($entry['mail'][0]) ? (string)$entry['mail'][0] : '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert eine Liste von Gruppen aus dem AD.
|
||||||
|
*
|
||||||
|
* @return array<int, array<string, string>> Liste von Gruppen-Datensätzen
|
||||||
|
*
|
||||||
|
* @throws RuntimeException bei Konfigurations- oder Verbindungsproblemen
|
||||||
|
*/
|
||||||
|
public function getGroups(): array
|
||||||
|
{
|
||||||
|
// Base-DN für die Gruppensuche aus der Konfiguration lesen.
|
||||||
|
$baseDn = (string)($this->config['base_dn'] ?? '');
|
||||||
|
if ($baseDn === '') {
|
||||||
|
throw new RuntimeException('LDAP base_dn für Gruppensuche ist nicht konfiguriert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbindung mit technischem Bind herstellen.
|
||||||
|
$connection = $this->connect();
|
||||||
|
|
||||||
|
// Filter für Gruppenobjekte im AD.
|
||||||
|
$filter = '(objectClass=group)';
|
||||||
|
|
||||||
|
// Attribute, die für die Darstellung relevant sind.
|
||||||
|
$attributes = ['sAMAccountName', 'cn', 'description'];
|
||||||
|
|
||||||
|
// LDAP-Suche ausführen.
|
||||||
|
$search = @ldap_search($connection, $baseDn, $filter, $attributes);
|
||||||
|
if ($search === false) {
|
||||||
|
ldap_unbind($connection);
|
||||||
|
throw new RuntimeException('LDAP-Suche nach Gruppen ist fehlgeschlagen.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ergebnisse holen.
|
||||||
|
$entries = ldap_get_entries($connection, $search);
|
||||||
|
|
||||||
|
// Verbindung schließen.
|
||||||
|
ldap_unbind($connection);
|
||||||
|
|
||||||
|
$groups = [];
|
||||||
|
|
||||||
|
// Struktur prüfen, leere Liste zurückgeben, falls unerwartet.
|
||||||
|
if (!is_array($entries) || !isset($entries['count'])) {
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Einträge in ein flacheres Array-Format transformieren.
|
||||||
|
for ($i = 0; $i < $entries['count']; $i++) {
|
||||||
|
$entry = $entries[$i];
|
||||||
|
|
||||||
|
$groups[] = [
|
||||||
|
'samaccountname' => isset($entry['samaccountname'][0]) ? (string)$entry['samaccountname'][0] : '',
|
||||||
|
'cn' => isset($entry['cn'][0]) ? (string)$entry['cn'][0] : '',
|
||||||
|
'description' => isset($entry['description'][0]) ? (string)$entry['description'][0] : '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,9 @@ return [
|
|||||||
|
|
||||||
// Optional: Timeout in Sekunden
|
// Optional: Timeout in Sekunden
|
||||||
'timeout' => 5,
|
'timeout' => 5,
|
||||||
|
|
||||||
|
'bind_dn' => 'CN=Service IIS,OU=WebAppUsers,DC=ITFA-PROJ-DOM,DC=local',
|
||||||
|
'bind_password' => '$7aE!R$l$D!p1Q9l458K8@O6&',
|
||||||
],
|
],
|
||||||
|
|
||||||
'security' => [
|
'security' => [
|
||||||
|
|||||||
@ -47,6 +47,7 @@ $config = require $configPath;
|
|||||||
|
|
||||||
use App\Controllers\AuthController;
|
use App\Controllers\AuthController;
|
||||||
use App\Controllers\DashboardController;
|
use App\Controllers\DashboardController;
|
||||||
|
use App\Controllers\UserManagementController;
|
||||||
|
|
||||||
// Hilfsfunktion für geschützte Routen
|
// Hilfsfunktion für geschützte Routen
|
||||||
function requireLogin(array $config): void
|
function requireLogin(array $config): void
|
||||||
@ -71,6 +72,9 @@ $authController = new AuthController($config);
|
|||||||
// Neue Instanz der Klasse DashboardController erstellen (wird bei Bedarf über den Autoloader geladen).
|
// Neue Instanz der Klasse DashboardController erstellen (wird bei Bedarf über den Autoloader geladen).
|
||||||
$dashboardController = new DashboardController($config);
|
$dashboardController = new DashboardController($config);
|
||||||
|
|
||||||
|
// Neue Instanz der Klasse UserManagmentController erstellen (wird bei Bedarf über den Autoloader geladen).
|
||||||
|
$userManagementController = new UserManagementController($config);
|
||||||
|
|
||||||
// Anhand des Routing-Ziels (route) entscheiden, welcher Code ausgeführt wird.
|
// Anhand des Routing-Ziels (route) entscheiden, welcher Code ausgeführt wird.
|
||||||
switch ($route) {
|
switch ($route) {
|
||||||
case 'login':
|
case 'login':
|
||||||
@ -94,6 +98,11 @@ switch ($route) {
|
|||||||
$dashboardController->show();
|
$dashboardController->show();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'users':
|
||||||
|
requireLogin($config);
|
||||||
|
$userManagementController->show();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo 'Route nicht gefunden.';
|
echo 'Route nicht gefunden.';
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
// Call the dataTables jQuery plugin
|
// Call the dataTables jQuery plugin
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#dataTable').DataTable();
|
$('#dataTable').DataTable();
|
||||||
|
$('#usersTable').DataTable();
|
||||||
|
$('#groupsTable').DataTable();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -36,7 +36,7 @@ declare(strict_types=1);
|
|||||||
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
|
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
|
||||||
|
|
||||||
<!-- Sidebar - Brand -->
|
<!-- Sidebar - Brand -->
|
||||||
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="dashboard.php">
|
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="../index.php?route=dashboard">
|
||||||
<div class="sidebar-brand-icon rotate-n-15">
|
<div class="sidebar-brand-icon rotate-n-15">
|
||||||
<i class="fas fa-laugh-wink"></i>
|
<i class="fas fa-laugh-wink"></i>
|
||||||
</div>
|
</div>
|
||||||
@ -48,11 +48,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
<!-- Nav Item - Dashboard -->
|
<!-- Nav Item - Dashboard -->
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="dashboard.php">
|
<a class="nav-link" href="../index.php?route=dashboard">
|
||||||
<i class="fas fa-fw fa-tachometer-alt"></i>
|
<i class="fas fa-fw fa-tachometer-alt"></i>
|
||||||
<span>Dashboard</span></a>
|
<span>Dashboard</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="../index.php?route=users">
|
||||||
|
<i class="fas fa-fw fa-users"></i>
|
||||||
|
<span>Users</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<!-- Divider -->
|
<!-- Divider -->
|
||||||
<hr class="sidebar-divider">
|
<hr class="sidebar-divider">
|
||||||
|
|
||||||
|
|||||||
520
public/views/users.php
Normal file
520
public/views/users.php
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/** @var array<int, array<string, string>> $users */
|
||||||
|
/** @var array<int, array<string, string>> $groups */
|
||||||
|
/** @var string|null $error */
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<title>SB Admin 2 - Tables</title>
|
||||||
|
|
||||||
|
<!-- Custom fonts for this template -->
|
||||||
|
<link href="../vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
|
||||||
|
rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Custom styles for this template -->
|
||||||
|
<link href="../css/sb-admin-2.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Custom styles for this page -->
|
||||||
|
<link href="../vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="page-top">
|
||||||
|
|
||||||
|
<!-- Page Wrapper -->
|
||||||
|
<div id="wrapper">
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
|
||||||
|
|
||||||
|
<!-- Sidebar - Brand -->
|
||||||
|
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="../views/dashboard.php">
|
||||||
|
<div class="sidebar-brand-icon rotate-n-15">
|
||||||
|
<i class="fas fa-laugh-wink"></i>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-brand-text mx-3">SB Admin <sup>2</sup></div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<hr class="sidebar-divider my-0">
|
||||||
|
|
||||||
|
<!-- Nav Item - Dashboard -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="../views/dashboard.php">
|
||||||
|
<i class="fas fa-fw fa-tachometer-alt"></i>
|
||||||
|
<span>Dashboard</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<hr class="sidebar-divider">
|
||||||
|
|
||||||
|
<!-- Heading -->
|
||||||
|
<div class="sidebar-heading">
|
||||||
|
Interface
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nav Item - Pages Collapse Menu -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo"
|
||||||
|
aria-expanded="true" aria-controls="collapseTwo">
|
||||||
|
<i class="fas fa-fw fa-cog"></i>
|
||||||
|
<span>Components</span>
|
||||||
|
</a>
|
||||||
|
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionSidebar">
|
||||||
|
<div class="bg-white py-2 collapse-inner rounded">
|
||||||
|
<h6 class="collapse-header">Custom Components:</h6>
|
||||||
|
<a class="collapse-item" href="../buttons.php">Buttons</a>
|
||||||
|
<a class="collapse-item" href="../cards.php">Cards</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nav Item - Utilities Collapse Menu -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUtilities"
|
||||||
|
aria-expanded="true" aria-controls="collapseUtilities">
|
||||||
|
<i class="fas fa-fw fa-wrench"></i>
|
||||||
|
<span>Utilities</span>
|
||||||
|
</a>
|
||||||
|
<div id="collapseUtilities" class="collapse" aria-labelledby="headingUtilities"
|
||||||
|
data-parent="#accordionSidebar">
|
||||||
|
<div class="bg-white py-2 collapse-inner rounded">
|
||||||
|
<h6 class="collapse-header">Custom Utilities:</h6>
|
||||||
|
<a class="collapse-item" href="../utilities-color.php">Colors</a>
|
||||||
|
<a class="collapse-item" href="../utilities-border.php">Borders</a>
|
||||||
|
<a class="collapse-item" href="../utilities-animation.php">Animations</a>
|
||||||
|
<a class="collapse-item" href="../utilities-other.php">Other</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<hr class="sidebar-divider">
|
||||||
|
|
||||||
|
<!-- Heading -->
|
||||||
|
<div class="sidebar-heading">
|
||||||
|
Addons
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nav Item - Pages Collapse Menu -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapsePages"
|
||||||
|
aria-expanded="true" aria-controls="collapsePages">
|
||||||
|
<i class="fas fa-fw fa-folder"></i>
|
||||||
|
<span>Pages</span>
|
||||||
|
</a>
|
||||||
|
<div id="collapsePages" class="collapse" aria-labelledby="headingPages" data-parent="#accordionSidebar">
|
||||||
|
<div class="bg-white py-2 collapse-inner rounded">
|
||||||
|
<h6 class="collapse-header">Login Screens:</h6>
|
||||||
|
<a class="collapse-item" href="login.php">Login</a>
|
||||||
|
<a class="collapse-item" href="../register.php">Register</a>
|
||||||
|
<a class="collapse-item" href="../forgot-password.php">Forgot Password</a>
|
||||||
|
<div class="collapse-divider"></div>
|
||||||
|
<h6 class="collapse-header">Other Pages:</h6>
|
||||||
|
<a class="collapse-item" href="../404.php">404 Page</a>
|
||||||
|
<a class="collapse-item" href="../blank.php">Blank Page</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nav Item - Charts -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="../charts.php">
|
||||||
|
<i class="fas fa-fw fa-chart-area"></i>
|
||||||
|
<span>Charts</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nav Item - Tables -->
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="../tables.html">
|
||||||
|
<i class="fas fa-fw fa-table"></i>
|
||||||
|
<span>Tables</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<hr class="sidebar-divider d-none d-md-block">
|
||||||
|
|
||||||
|
<!-- Sidebar Toggler (Sidebar) -->
|
||||||
|
<div class="text-center d-none d-md-inline">
|
||||||
|
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<!-- End of Sidebar -->
|
||||||
|
|
||||||
|
<!-- Content Wrapper -->
|
||||||
|
<div id="content-wrapper" class="d-flex flex-column">
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<!-- Topbar -->
|
||||||
|
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
|
||||||
|
|
||||||
|
<!-- Sidebar Toggle (Topbar) -->
|
||||||
|
<form class="form-inline">
|
||||||
|
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
|
||||||
|
<i class="fa fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Topbar Search -->
|
||||||
|
<form
|
||||||
|
class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control bg-light border-0 small" placeholder="Search for..."
|
||||||
|
aria-label="Search" aria-describedby="basic-addon2">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-primary" type="button">
|
||||||
|
<i class="fas fa-search fa-sm"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Topbar Navbar -->
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
|
||||||
|
<!-- Nav Item - Search Dropdown (Visible Only XS) -->
|
||||||
|
<li class="nav-item dropdown no-arrow d-sm-none">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button"
|
||||||
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fas fa-search fa-fw"></i>
|
||||||
|
</a>
|
||||||
|
<!-- Dropdown - Messages -->
|
||||||
|
<div class="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in"
|
||||||
|
aria-labelledby="searchDropdown">
|
||||||
|
<form class="form-inline mr-auto w-100 navbar-search">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control bg-light border-0 small"
|
||||||
|
placeholder="Search for..." aria-label="Search"
|
||||||
|
aria-describedby="basic-addon2">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-primary" type="button">
|
||||||
|
<i class="fas fa-search fa-sm"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nav Item - Alerts -->
|
||||||
|
<li class="nav-item dropdown no-arrow mx-1">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button"
|
||||||
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fas fa-bell fa-fw"></i>
|
||||||
|
<!-- Counter - Alerts -->
|
||||||
|
<span class="badge badge-danger badge-counter">3+</span>
|
||||||
|
</a>
|
||||||
|
<!-- Dropdown - Alerts -->
|
||||||
|
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||||
|
aria-labelledby="alertsDropdown">
|
||||||
|
<h6 class="dropdown-header">
|
||||||
|
Alerts Center
|
||||||
|
</h6>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||||
|
<div class="mr-3">
|
||||||
|
<div class="icon-circle bg-primary">
|
||||||
|
<i class="fas fa-file-alt text-white"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="small text-gray-500">December 12, 2019</div>
|
||||||
|
<span class="font-weight-bold">A new monthly report is ready to download!</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||||
|
<div class="mr-3">
|
||||||
|
<div class="icon-circle bg-success">
|
||||||
|
<i class="fas fa-donate text-white"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="small text-gray-500">December 7, 2019</div>
|
||||||
|
$290.29 has been deposited into your account!
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||||
|
<div class="mr-3">
|
||||||
|
<div class="icon-circle bg-warning">
|
||||||
|
<i class="fas fa-exclamation-triangle text-white"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="small text-gray-500">December 2, 2019</div>
|
||||||
|
Spending Alert: We've noticed unusually high spending for your account.
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-center small text-gray-500" href="#">Show All Alerts</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nav Item - Messages -->
|
||||||
|
<li class="nav-item dropdown no-arrow mx-1">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="messagesDropdown" role="button"
|
||||||
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fas fa-envelope fa-fw"></i>
|
||||||
|
<!-- Counter - Messages -->
|
||||||
|
<span class="badge badge-danger badge-counter">7</span>
|
||||||
|
</a>
|
||||||
|
<!-- Dropdown - Messages -->
|
||||||
|
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||||
|
aria-labelledby="messagesDropdown">
|
||||||
|
<h6 class="dropdown-header">
|
||||||
|
Message Center
|
||||||
|
</h6>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||||
|
<div class="dropdown-list-image mr-3">
|
||||||
|
<img class="rounded-circle" src="../images/undraw_profile_1.svg"
|
||||||
|
alt="...">
|
||||||
|
<div class="status-indicator bg-success"></div>
|
||||||
|
</div>
|
||||||
|
<div class="font-weight-bold">
|
||||||
|
<div class="text-truncate">Hi there! I am wondering if you can help me with a
|
||||||
|
problem I've been having.</div>
|
||||||
|
<div class="small text-gray-500">Emily Fowler · 58m</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||||
|
<div class="dropdown-list-image mr-3">
|
||||||
|
<img class="rounded-circle" src="../images/undraw_profile_2.svg"
|
||||||
|
alt="...">
|
||||||
|
<div class="status-indicator"></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-truncate">I have the photos that you ordered last month, how
|
||||||
|
would you like them sent to you?</div>
|
||||||
|
<div class="small text-gray-500">Jae Chun · 1d</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||||
|
<div class="dropdown-list-image mr-3">
|
||||||
|
<img class="rounded-circle" src="../images/undraw_profile_3.svg"
|
||||||
|
alt="...">
|
||||||
|
<div class="status-indicator bg-warning"></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-truncate">Last month's report looks great, I am very happy with
|
||||||
|
the progress so far, keep up the good work!</div>
|
||||||
|
<div class="small text-gray-500">Morgan Alvarez · 2d</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||||
|
<div class="dropdown-list-image mr-3">
|
||||||
|
<img class="rounded-circle" src="https://source.unsplash.com/Mv9hjnEUHR4/60x60"
|
||||||
|
alt="...">
|
||||||
|
<div class="status-indicator bg-success"></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-truncate">Am I a good boy? The reason I ask is because someone
|
||||||
|
told me that people say this to all dogs, even if they aren't good...</div>
|
||||||
|
<div class="small text-gray-500">Chicken the Dog · 2w</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-center small text-gray-500" href="#">Read More Messages</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<div class="topbar-divider d-none d-sm-block"></div>
|
||||||
|
|
||||||
|
<!-- Nav Item - User Information -->
|
||||||
|
<li class="nav-item dropdown no-arrow">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
|
||||||
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Douglas McGee</span>
|
||||||
|
<img class="img-profile rounded-circle"
|
||||||
|
src="../images/undraw_profile.svg">
|
||||||
|
</a>
|
||||||
|
<!-- Dropdown - User Information -->
|
||||||
|
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||||
|
aria-labelledby="userDropdown">
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||||
|
Profile
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<i class="fas fa-cogs fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<i class="fas fa-list fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||||
|
Activity Log
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
|
||||||
|
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
<!-- End of Topbar -->
|
||||||
|
|
||||||
|
<!-- Begin Page Content -->
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<!-- Page Heading -->
|
||||||
|
<h1 class="h3 mb-2 text-gray-800">Benutzer & Gruppen</h1>
|
||||||
|
<?php if ($error !== null): ?>
|
||||||
|
<p class="error">
|
||||||
|
<?php echo htmlspecialchars($error, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<p class="mb-4">DataTables is a third party plugin that is used to generate the demo table below.
|
||||||
|
For more information about DataTables, please visit the <a target="_blank"
|
||||||
|
href="https://datatables.net">official DataTables documentation</a>.</p>
|
||||||
|
|
||||||
|
<!-- DataTales Example -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 font-weight-bold text-primary">Benutzer</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered" id="usersTable" width="100%" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Anmeldename</th>
|
||||||
|
<th>Anzeigename</th>
|
||||||
|
<th>E-Mail</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th>Anmeldename</th>
|
||||||
|
<th>Anzeigename</th>
|
||||||
|
<th>E-Mail</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($users as $user): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($user['samaccountname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['displayname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['mail'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 font-weight-bold text-primary">Gruppen</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered" id="groupsTable" width="100%" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Gruppenname</th>
|
||||||
|
<th>CN</th>
|
||||||
|
<th>Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th>Gruppenname</th>
|
||||||
|
<th>CN</th>
|
||||||
|
<th>Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($groups as $group): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($group['samaccountname'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($group['cn'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($group['description'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /.container-fluid -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- End of Main Content -->
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="sticky-footer bg-white">
|
||||||
|
<div class="container my-auto">
|
||||||
|
<div class="copyright text-center my-auto">
|
||||||
|
<span>Copyright © Your Website 2020</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<!-- End of Footer -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- End of Content Wrapper -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- End of Page Wrapper -->
|
||||||
|
|
||||||
|
<!-- Scroll to Top Button-->
|
||||||
|
<a class="scroll-to-top rounded" href="#page-top">
|
||||||
|
<i class="fas fa-angle-up"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Logout Modal-->
|
||||||
|
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLabel">Ready to Leave?</h5>
|
||||||
|
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||||
|
<a class="btn btn-primary" href="login.php">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap core JavaScript-->
|
||||||
|
<script src="../vendor/jquery/jquery.min.js"></script>
|
||||||
|
<script src="../vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Core plugin JavaScript-->
|
||||||
|
<script src="../vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom scripts for all pages-->
|
||||||
|
<script src="../js/sb-admin-2.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Page level plugins -->
|
||||||
|
<script src="../vendor/datatables/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="../vendor/datatables/dataTables.bootstrap4.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Page level custom scripts -->
|
||||||
|
<script src="../js/demo/datatables-demo.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user