feat: Initial commit of KeyHelp Manager module

This commit is contained in:
Kevin Feiler
2025-10-15 22:29:11 +02:00
parent 4df2eddc6a
commit 351da5fe19
8 changed files with 2584 additions and 0 deletions

View File

@@ -0,0 +1,961 @@
<?php
/**
* WHMCS KeyHelp Manager Provisioning Module
*
* @author Kevin Feiler <info@avvgo.de>
* @copyright Copyright (c) 2024 Kevin Feiler / AVVGO
* @license MIT License
* @link https://avvgo.de
* @version 1.0.0
*
* Dieses Modul ermöglicht die automatische Verwaltung von KeyHelp-Hosting-Accounts
* direkt aus WHMCS heraus. Es unterstützt Erstellung, Sperrung, Entsperrung und
* Löschung von Accounts sowie die Anzeige von Account-Informationen im Client-Bereich.
*
* Entwickelt von Kevin Feiler / AVVGO
*
* Kompatibel mit:
* - PHP 8.3+
* - WHMCS 8.13+
* - KeyHelp (neueste stabile Version)
*/
if (!defined("WHMCS")) {
die("This file cannot be accessed directly");
}
use WHMCS\Database\Capsule;
/**
* Modul-Metadaten
*
* Definiert die grundlegenden Informationen über das Modul.
*
* @return array Modul-Metadaten
*/
function keyhelpmanager_MetaData()
{
return [
"DisplayName" => "KeyHelp Manager",
"APIVersion" => "1.1",
"RequiresServer" => true,
"DefaultNonSSLPort" => "80",
"DefaultSSLPort" => "443",
"ServiceSingleSignOnLabel" => "Login zu KeyHelp",
"AdminSingleSignOnLabel" => "Als Admin einloggen",
];
}
/**
* Modul-Konfigurationsoptionen
*
* Definiert die Felder, die in der Server-Konfiguration in WHMCS angezeigt werden.
*
* @return array Konfigurations-Array
*/
function keyhelpmanager_ConfigOptions()
{
return [
"hostname" => [
"FriendlyName" => "Hostname (IP or FQDN)",
"Type" => "text",
"Size" => "25",
"Default" => "",
"Description" =>
"Hostname oder IP-Adresse deines KeyHelp-Servers (ohne http:// oder https://)",
"Required" => true,
],
"apikey" => [
"FriendlyName" => "API Key",
"Type" => "password",
"Size" => "50",
"Default" => "",
"Description" =>
"Dein KeyHelp API-Schlüssel (zu finden in KeyHelp unter Einstellungen → API)",
"Required" => true,
],
"usessl" => [
"FriendlyName" => "Use SSL",
"Type" => "yesno",
"Default" => "on",
"Description" =>
"SSL für die Verbindung zur API verwenden (dringend empfohlen)",
],
"verify_ssl" => [
"FriendlyName" => "Verify SSL Certificate",
"Type" => "yesno",
"Default" => "on",
"Description" =>
"SSL-Zertifikat verifizieren (bei selbstsignierten Zertifikaten deaktivieren)",
],
];
}
/**
* API-Request Hilfsfunktion
*
* Sendet HTTP-Anfragen an die KeyHelp-API und verarbeitet die Antworten.
*
* @param array $params WHMCS-Parameter-Array
* @param string $endpoint API-Endpunkt (z.B. '/users', '/users/123')
* @param string $method HTTP-Methode (GET, POST, PUT, DELETE)
* @param array $data Request-Body-Daten
* @return array Ergebnis mit 'success' (bool) und 'data' oder 'error'
*/
function _keyhelpmanager_APIRequest(
array $params,
string $endpoint,
string $method = "GET",
array $data = [],
) {
try {
// Konfiguration auslesen
$hostname =
$params["serverhostname"] ?? ($params["configoption1"] ?? "");
$apiKey = $params["serverpassword"] ?? ($params["configoption2"] ?? "");
$useSSL = $params["configoption3"] ?? "on";
$verifySSL = $params["configoption4"] ?? "on";
// Validierung
if (empty($hostname)) {
return [
"success" => false,
"error" => "KeyHelp-Hostname ist nicht konfiguriert.",
];
}
if (empty($apiKey)) {
return [
"success" => false,
"error" => "KeyHelp API-Schlüssel ist nicht konfiguriert.",
];
}
// Protocol bestimmen
$protocol = $useSSL === "on" ? "https" : "http";
// Basis-URL zusammenbauen
$baseUrl = sprintf("%s://%s/api/v2", $protocol, $hostname);
$url = $baseUrl . $endpoint;
// Guzzle HTTP-Client initialisieren
$client = new \GuzzleHttp\Client([
"verify" => $verifySSL === "on",
"timeout" => 30,
"http_errors" => false,
]);
// Request-Optionen
$options = [
"headers" => [
"X-API-Key" => $apiKey,
"Content-Type" => "application/json",
"Accept" => "application/json",
],
];
// Body-Daten hinzufügen wenn vorhanden
if (!empty($data) && in_array($method, ["POST", "PUT", "PATCH"])) {
$options["json"] = $data;
}
// API-Request ausführen
$response = $client->request($method, $url, $options);
$statusCode = $response->getStatusCode();
$body = (string) $response->getBody();
// JSON-Antwort dekodieren
$responseData = json_decode($body, true);
// Logging für Debugging
logModuleCall(
"keyhelpmanager",
$method . " " . $endpoint,
$data,
$statusCode . " - " . $body,
$responseData,
[$apiKey], // API-Key in Logs maskieren
);
// Erfolgreiche Antworten (2xx)
if ($statusCode >= 200 && $statusCode < 300) {
return [
"success" => true,
"data" => $responseData,
"status_code" => $statusCode,
];
}
// Fehlerbehandlung
$errorMessage = "KeyHelp API-Fehler: ";
if (isset($responseData["message"])) {
$errorMessage .= $responseData["message"];
} elseif (isset($responseData["error"])) {
$errorMessage .= $responseData["error"];
} else {
$errorMessage .= sprintf("HTTP Status %d - %s", $statusCode, $body);
}
return [
"success" => false,
"error" => $errorMessage,
"status_code" => $statusCode,
];
} catch (\GuzzleHttp\Exception\ConnectException $e) {
return [
"success" => false,
"error" =>
"Verbindung zum KeyHelp-Server fehlgeschlagen: " .
$e->getMessage(),
];
} catch (\GuzzleHttp\Exception\RequestException $e) {
return [
"success" => false,
"error" => "API-Request fehlgeschlagen: " . $e->getMessage(),
];
} catch (\Exception $e) {
return [
"success" => false,
"error" => "Unerwarteter Fehler: " . $e->getMessage(),
];
}
}
/**
* Account erstellen
*
* Wird von WHMCS nach erfolgreicher Bezahlung aufgerufen.
* Erstellt einen neuen Benutzer-Account in KeyHelp.
*
* @param array $params WHMCS-Parameter
* @return string Erfolgs- oder Fehlermeldung
*/
function keyhelpmanager_CreateAccount(array $params)
{
try {
// Extrahiere notwendige Parameter
$domain = $params["domain"] ?? "";
$username = $params["username"] ?? "";
$password = $params["password"] ?? "";
$clientEmail = $params["clientsdetails"]["email"] ?? "";
$firstName = $params["clientsdetails"]["firstname"] ?? "";
$lastName = $params["clientsdetails"]["lastname"] ?? "";
// Validierung
if (empty($domain)) {
return "Domain ist erforderlich.";
}
if (empty($username)) {
// Generiere Benutzernamen aus Domain wenn nicht vorhanden
$username = _keyhelpmanager_GenerateUsername($domain);
}
if (empty($password)) {
// Generiere sicheres Passwort wenn nicht vorhanden
$password = _keyhelpmanager_GeneratePassword();
}
// Account-Daten für KeyHelp API vorbereiten
$accountData = [
"login_name" => $username,
"password" => $password,
"email" => $clientEmail,
"display_name" => trim($firstName . " " . $lastName),
];
// Optional: Plan/Tarif aus konfigurierbaren Optionen
if (!empty($params["configoptions"]["KeyHelp Plan"])) {
$accountData["plan"] = $params["configoptions"]["KeyHelp Plan"];
}
// Benutzer über API erstellen
$result = _keyhelpmanager_APIRequest(
$params,
"/users",
"POST",
$accountData,
);
if (!$result["success"]) {
return $result["error"];
}
$userId = $result["data"]["id"] ?? null;
if (empty($userId)) {
return "Benutzer wurde erstellt, aber keine User-ID zurückgegeben.";
}
// Domain zum Benutzer hinzufügen
$domainData = [
"domain_name" => $domain,
"user_id" => $userId,
];
$domainResult = _keyhelpmanager_APIRequest(
$params,
"/domains",
"POST",
$domainData,
);
if (!$domainResult["success"]) {
// Rollback: Benutzer wieder löschen
_keyhelpmanager_APIRequest($params, "/users/" . $userId, "DELETE");
return "Domain konnte nicht hinzugefügt werden: " .
$domainResult["error"];
}
// Speichere Username und Passwort in WHMCS Custom Fields
_keyhelpmanager_SaveAccountDetails($params["serviceid"], [
"username" => $username,
"password" => $password,
"userid" => $userId,
]);
return "success";
} catch (\Exception $e) {
return "Fehler beim Erstellen des Accounts: " . $e->getMessage();
}
}
/**
* Account sperren
*
* Wird von WHMCS aufgerufen, wenn ein Account gesperrt werden soll
* (z.B. bei Zahlungsverzug).
*
* @param array $params WHMCS-Parameter
* @return string Erfolgs- oder Fehlermeldung
*/
function keyhelpmanager_SuspendAccount(array $params)
{
try {
$userId = _keyhelpmanager_GetUserId($params);
if (empty($userId)) {
return "Benutzer-ID nicht gefunden. Account wurde möglicherweise nicht korrekt erstellt.";
}
// Benutzer-Status auf "gesperrt" setzen
$updateData = [
"is_locked" => true,
];
$result = _keyhelpmanager_APIRequest(
$params,
"/users/" . $userId,
"PUT",
$updateData,
);
if (!$result["success"]) {
return $result["error"];
}
return "success";
} catch (\Exception $e) {
return "Fehler beim Sperren des Accounts: " . $e->getMessage();
}
}
/**
* Account entsperren
*
* Wird von WHMCS aufgerufen, wenn ein gesperrter Account wieder
* aktiviert werden soll.
*
* @param array $params WHMCS-Parameter
* @return string Erfolgs- oder Fehlermeldung
*/
function keyhelpmanager_UnsuspendAccount(array $params)
{
try {
$userId = _keyhelpmanager_GetUserId($params);
if (empty($userId)) {
return "Benutzer-ID nicht gefunden. Account wurde möglicherweise nicht korrekt erstellt.";
}
// Benutzer-Status auf "aktiv" setzen
$updateData = [
"is_locked" => false,
];
$result = _keyhelpmanager_APIRequest(
$params,
"/users/" . $userId,
"PUT",
$updateData,
);
if (!$result["success"]) {
return $result["error"];
}
return "success";
} catch (\Exception $e) {
return "Fehler beim Entsperren des Accounts: " . $e->getMessage();
}
}
/**
* Account löschen
*
* Wird von WHMCS aufgerufen, wenn ein Account endgültig gelöscht werden soll.
* ACHTUNG: Dies löscht alle Daten unwiderruflich!
*
* @param array $params WHMCS-Parameter
* @return string Erfolgs- oder Fehlermeldung
*/
function keyhelpmanager_TerminateAccount(array $params)
{
try {
$userId = _keyhelpmanager_GetUserId($params);
if (empty($userId)) {
return "Benutzer-ID nicht gefunden. Account wurde möglicherweise bereits gelöscht.";
}
// Benutzer und alle zugehörigen Daten löschen
$result = _keyhelpmanager_APIRequest(
$params,
"/users/" . $userId,
"DELETE",
);
if (!$result["success"]) {
return $result["error"];
}
// Entferne gespeicherte Account-Details
_keyhelpmanager_DeleteAccountDetails($params["serviceid"]);
return "success";
} catch (\Exception $e) {
return "Fehler beim Löschen des Accounts: " . $e->getMessage();
}
}
/**
* Client-Bereich
*
* Gibt Informationen für den Client-Bereich in WHMCS zurück.
* Zeigt Login-Details und Hosting-Statistiken an.
*
* @param array $params WHMCS-Parameter
* @return array Template-Variablen
*/
function keyhelpmanager_ClientArea(array $params)
{
try {
$accountDetails = _keyhelpmanager_GetAccountDetails(
$params["serviceid"],
);
$userId = $accountDetails["userid"] ?? null;
$hostname =
$params["serverhostname"] ?? ($params["configoption1"] ?? "");
$useSSL = $params["configoption3"] ?? "on";
$protocol = $useSSL === "on" ? "https" : "http";
// Login-URL
$loginUrl = sprintf("%s://%s", $protocol, $hostname);
// Basis-Informationen
$templateVars = [
"login_url" => $loginUrl,
"username" =>
$accountDetails["username"] ??
($params["username"] ?? "Nicht verfügbar"),
"password" =>
$accountDetails["password"] ??
($params["password"] ?? "••••••••"),
"domain" => $params["domain"] ?? "Nicht verfügbar",
"stats" => [],
"error" => null,
];
// Statistiken von KeyHelp API abrufen wenn User-ID vorhanden
if (!empty($userId)) {
$statsResult = _keyhelpmanager_APIRequest(
$params,
"/users/" . $userId . "/statistics",
"GET",
);
if ($statsResult["success"] && isset($statsResult["data"])) {
$stats = $statsResult["data"];
$templateVars["stats"] = [
"disk_used" => _keyhelpmanager_FormatBytes(
$stats["disk_usage"] ?? 0,
),
"disk_limit" => _keyhelpmanager_FormatBytes(
$stats["disk_limit"] ?? 0,
),
"disk_percent" => _keyhelpmanager_CalculatePercent(
$stats["disk_usage"] ?? 0,
$stats["disk_limit"] ?? 0,
),
"bandwidth_used" => _keyhelpmanager_FormatBytes(
$stats["traffic_usage"] ?? 0,
),
"bandwidth_limit" => _keyhelpmanager_FormatBytes(
$stats["traffic_limit"] ?? 0,
),
"bandwidth_percent" => _keyhelpmanager_CalculatePercent(
$stats["traffic_usage"] ?? 0,
$stats["traffic_limit"] ?? 0,
),
"domains" => $stats["domains_count"] ?? 0,
"databases" => $stats["databases_count"] ?? 0,
"email_accounts" => $stats["email_accounts_count"] ?? 0,
];
}
}
return [
"templatefile" => "clientarea",
"vars" => $templateVars,
];
} catch (\Exception $e) {
return [
"templatefile" => "clientarea",
"vars" => [
"error" =>
"Fehler beim Laden der Account-Informationen: " .
$e->getMessage(),
],
];
}
}
/**
* Admin-Login-Link (Single Sign-On)
*
* Erstellt einen direkten Login-Link für Admins, um sich im KeyHelp-Panel
* des Kunden anzumelden, ohne das Passwort eingeben zu müssen.
*
* @param array $params WHMCS-Parameter
* @return array Login-Link-Konfiguration
*/
function keyhelpmanager_LoginLink(array $params)
{
try {
$accountDetails = _keyhelpmanager_GetAccountDetails(
$params["serviceid"],
);
$userId = $accountDetails["userid"] ?? null;
if (empty($userId)) {
return [
"success" => false,
"errorMsg" => "Benutzer-ID nicht gefunden.",
];
}
// Session-Token von KeyHelp API anfordern
$sessionData = [
"user_id" => $userId,
"lifetime" => 300, // 5 Minuten Gültigkeit
];
$result = _keyhelpmanager_APIRequest(
$params,
"/sessions",
"POST",
$sessionData,
);
if (!$result["success"]) {
return [
"success" => false,
"errorMsg" =>
"Login-Session konnte nicht erstellt werden: " .
$result["error"],
];
}
$sessionToken = $result["data"]["token"] ?? null;
if (empty($sessionToken)) {
return [
"success" => false,
"errorMsg" => "Kein Session-Token erhalten.",
];
}
// Login-URL mit Session-Token
$hostname =
$params["serverhostname"] ?? ($params["configoption1"] ?? "");
$useSSL = $params["configoption3"] ?? "on";
$protocol = $useSSL === "on" ? "https" : "http";
$loginUrl = sprintf(
"%s://%s/login?token=%s",
$protocol,
$hostname,
$sessionToken,
);
return [
"success" => true,
"redirectTo" => $loginUrl,
];
} catch (\Exception $e) {
return [
"success" => false,
"errorMsg" =>
"Fehler beim Erstellen des Login-Links: " . $e->getMessage(),
];
}
}
/**
* Passwort ändern
*
* Ermöglicht das Ändern des Passworts für einen KeyHelp-Account.
*
* @param array $params WHMCS-Parameter
* @return string Erfolgs- oder Fehlermeldung
*/
function keyhelpmanager_ChangePassword(array $params)
{
try {
$userId = _keyhelpmanager_GetUserId($params);
$newPassword = $params["password"] ?? "";
if (empty($userId)) {
return "Benutzer-ID nicht gefunden.";
}
if (empty($newPassword)) {
return "Neues Passwort ist erforderlich.";
}
// Passwort über API ändern
$updateData = [
"password" => $newPassword,
];
$result = _keyhelpmanager_APIRequest(
$params,
"/users/" . $userId,
"PUT",
$updateData,
);
if (!$result["success"]) {
return $result["error"];
}
// Aktualisiere gespeichertes Passwort
_keyhelpmanager_UpdateAccountDetail(
$params["serviceid"],
"password",
$newPassword,
);
return "success";
} catch (\Exception $e) {
return "Fehler beim Ändern des Passworts: " . $e->getMessage();
}
}
/**
* Test-Verbindung
*
* Testet die Verbindung zur KeyHelp-API.
*
* @param array $params WHMCS-Parameter
* @return array Test-Ergebnis
*/
function keyhelpmanager_TestConnection(array $params)
{
try {
// Einfache API-Abfrage zum Testen
$result = _keyhelpmanager_APIRequest($params, "/server/version", "GET");
if (!$result["success"]) {
return [
"success" => false,
"error" => $result["error"],
];
}
$version = $result["data"]["version"] ?? "Unbekannt";
return [
"success" => true,
"message" => sprintf(
"Verbindung erfolgreich! KeyHelp Version: %s",
$version,
),
];
} catch (\Exception $e) {
return [
"success" => false,
"error" => $e->getMessage(),
];
}
}
// ============================================================================
// HILFSFUNKTIONEN
// ============================================================================
/**
* Generiert einen sicheren Benutzernamen aus einer Domain
*
* @param string $domain Domain-Name
* @return string Generierter Benutzername
*/
function _keyhelpmanager_GenerateUsername(string $domain): string
{
// Entferne TLD und Sonderzeichen
$username = preg_replace('/\.[^.]+$/', "", $domain);
$username = preg_replace("/[^a-z0-9]/i", "", $username);
$username = strtolower($username);
// Begrenze auf 16 Zeichen
$username = substr($username, 0, 16);
// Füge zufällige Zahlen hinzu wenn zu kurz
if (strlen($username) < 6) {
$username .= rand(1000, 9999);
}
return $username;
}
/**
* Generiert ein sicheres Zufallspasswort
*
* @param int $length Passwort-Länge
* @return string Generiertes Passwort
*/
function _keyhelpmanager_GeneratePassword(int $length = 16): string
{
$chars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+';
$password = "";
$charsLength = strlen($chars);
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, $charsLength - 1)];
}
return $password;
}
/**
* Speichert Account-Details in der WHMCS-Datenbank
*
* @param int $serviceId Service-ID
* @param array $details Account-Details (username, password, userid)
* @return void
*/
function _keyhelpmanager_SaveAccountDetails(
int $serviceId,
array $details,
): void {
try {
foreach ($details as $key => $value) {
Capsule::table("tblhosting")
->where("id", $serviceId)
->update([
$key === "username" ? "username" : "password" => $value,
]);
}
// Speichere userid in Custom Field oder Service Properties
if (isset($details["userid"])) {
Capsule::table("tblcustomfieldsvalues")->updateOrInsert(
[
"relid" => $serviceId,
"fieldid" => _keyhelpmanager_GetCustomFieldId(
"KeyHelp User ID",
),
],
[
"value" => $details["userid"],
],
);
}
} catch (\Exception $e) {
logActivity(
"KeyHelpManager: Fehler beim Speichern der Account-Details: " .
$e->getMessage(),
);
}
}
/**
* Lädt Account-Details aus der WHMCS-Datenbank
*
* @param int $serviceId Service-ID
* @return array Account-Details
*/
function _keyhelpmanager_GetAccountDetails(int $serviceId): array
{
try {
$service = Capsule::table("tblhosting")
->where("id", $serviceId)
->first();
$userid = Capsule::table("tblcustomfieldsvalues")
->where("relid", $serviceId)
->where(
"fieldid",
_keyhelpmanager_GetCustomFieldId("KeyHelp User ID"),
)
->value("value");
return [
"username" => $service->username ?? "",
"password" => decrypt($service->password ?? ""),
"userid" => $userid ?? null,
];
} catch (\Exception $e) {
logActivity(
"KeyHelpManager: Fehler beim Laden der Account-Details: " .
$e->getMessage(),
);
return [];
}
}
/**
* Holt die KeyHelp User-ID für einen Service
*
* @param array $params WHMCS-Parameter
* @return int|null User-ID oder null
*/
function _keyhelpmanager_GetUserId(array $params): ?int
{
$accountDetails = _keyhelpmanager_GetAccountDetails($params["serviceid"]);
return $accountDetails["userid"] ?? null;
}
/**
* Aktualisiert ein einzelnes Account-Detail
*
* @param int $serviceId Service-ID
* @param string $field Feldname
* @param string $value Wert
* @return void
*/
function _keyhelpmanager_UpdateAccountDetail(
int $serviceId,
string $field,
string $value,
): void {
try {
if ($field === "username" || $field === "password") {
Capsule::table("tblhosting")
->where("id", $serviceId)
->update([
$field => $value,
]);
}
} catch (\Exception $e) {
logActivity(
"KeyHelpManager: Fehler beim Aktualisieren von " .
$field .
": " .
$e->getMessage(),
);
}
}
/**
* Löscht gespeicherte Account-Details
*
* @param int $serviceId Service-ID
* @return void
*/
function _keyhelpmanager_DeleteAccountDetails(int $serviceId): void
{
try {
Capsule::table("tblcustomfieldsvalues")
->where("relid", $serviceId)
->delete();
} catch (\Exception $e) {
logActivity(
"KeyHelpManager: Fehler beim Löschen der Account-Details: " .
$e->getMessage(),
);
}
}
/**
* Holt oder erstellt die Custom Field ID für ein Feld
*
* @param string $fieldName Feldname
* @return int Field-ID
*/
function _keyhelpmanager_GetCustomFieldId(string $fieldName): int
{
try {
$field = Capsule::table("tblcustomfields")
->where("fieldname", $fieldName)
->where("type", "product")
->first();
if (!$field) {
// Erstelle Custom Field wenn nicht vorhanden
$fieldId = Capsule::table("tblcustomfields")->insertGetId([
"type" => "product",
"fieldname" => $fieldName,
"fieldtype" => "text",
"description" => "",
"adminonly" => "on",
]);
return $fieldId;
}
return $field->id;
} catch (\Exception $e) {
logActivity(
"KeyHelpManager: Fehler beim Abrufen der Custom Field ID: " .
$e->getMessage(),
);
return 0;
}
}
/**
* Formatiert Bytes in eine lesbare Größe
*
* @param int $bytes Bytes
* @return string Formatierte Größe
*/
function _keyhelpmanager_FormatBytes(int $bytes): string
{
$units = ["B", "KB", "MB", "GB", "TB"];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return number_format($bytes / pow(1024, $power), 2, ",", ".") .
" " .
$units[$power];
}
/**
* Berechnet Prozentsatz
*
* @param int $used Verbrauchter Wert
* @param int $limit Limit
* @return float Prozentsatz
*/
function _keyhelpmanager_CalculatePercent(int $used, int $limit): float
{
if ($limit == 0) {
return 0;
}
return round(($used / $limit) * 100, 2);
}

View File

@@ -0,0 +1,258 @@
{**
* KeyHelp Manager - Client Area Template
*
* Zeigt Login-Details und Account-Statistiken für Kunden an
*}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fas fa-server"></i> KeyHelp Hosting Account
</h3>
</div>
<div class="panel-body">
{if $error}
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle"></i> {$error}
</div>
{else}
{* Login-Informationen *}
<div class="row">
<div class="col-md-12">
<h4><i class="fas fa-sign-in-alt"></i> Login-Informationen</h4>
<hr>
</div>
</div>
<div class="row">
<div class="col-sm-4 text-right">
<strong>KeyHelp Login-URL:</strong>
</div>
<div class="col-sm-8">
<a href="{$login_url}" target="_blank" class="btn btn-success btn-sm">
<i class="fas fa-external-link-alt"></i> {$login_url}
</a>
</div>
</div>
<div class="row" style="margin-top: 10px;">
<div class="col-sm-4 text-right">
<strong>Benutzername:</strong>
</div>
<div class="col-sm-8">
<div class="input-group">
<input type="text" class="form-control" value="{$username}" readonly id="kh-username">
<span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="copyToClipboard('kh-username')" title="Kopieren">
<i class="fas fa-copy"></i>
</button>
</span>
</div>
</div>
</div>
<div class="row" style="margin-top: 10px;">
<div class="col-sm-4 text-right">
<strong>Passwort:</strong>
</div>
<div class="col-sm-8">
<div class="input-group">
<input type="password" class="form-control" value="{$password}" readonly id="kh-password">
<span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="togglePassword('kh-password')" title="Anzeigen/Verbergen">
<i class="fas fa-eye" id="kh-password-icon"></i>
</button>
<button class="btn btn-default" type="button" onclick="copyToClipboard('kh-password')" title="Kopieren">
<i class="fas fa-copy"></i>
</button>
</span>
</div>
</div>
</div>
<div class="row" style="margin-top: 10px;">
<div class="col-sm-4 text-right">
<strong>Domain:</strong>
</div>
<div class="col-sm-8">
<code>{$domain}</code>
</div>
</div>
{* Account-Statistiken *}
{if $stats}
<div class="row" style="margin-top: 30px;">
<div class="col-md-12">
<h4><i class="fas fa-chart-bar"></i> Account-Statistiken</h4>
<hr>
</div>
</div>
{* Speicherplatz *}
{if $stats.disk_used}
<div class="row" style="margin-top: 15px;">
<div class="col-sm-4 text-right">
<strong>Speicherplatz:</strong>
</div>
<div class="col-sm-8">
<div class="progress" style="margin-bottom: 5px;">
<div class="progress-bar {if $stats.disk_percent >= 90}progress-bar-danger{elseif $stats.disk_percent >= 75}progress-bar-warning{else}progress-bar-success{/if}"
role="progressbar"
aria-valuenow="{$stats.disk_percent}"
aria-valuemin="0"
aria-valuemax="100"
style="width: {$stats.disk_percent}%">
{$stats.disk_percent}%
</div>
</div>
<small class="text-muted">
{$stats.disk_used} von {$stats.disk_limit} verwendet
</small>
</div>
</div>
{/if}
{* Bandbreite / Traffic *}
{if $stats.bandwidth_used}
<div class="row" style="margin-top: 15px;">
<div class="col-sm-4 text-right">
<strong>Traffic (Monat):</strong>
</div>
<div class="col-sm-8">
<div class="progress" style="margin-bottom: 5px;">
<div class="progress-bar {if $stats.bandwidth_percent >= 90}progress-bar-danger{elseif $stats.bandwidth_percent >= 75}progress-bar-warning{else}progress-bar-info{/if}"
role="progressbar"
aria-valuenow="{$stats.bandwidth_percent}"
aria-valuemin="0"
aria-valuemax="100"
style="width: {$stats.bandwidth_percent}%">
{$stats.bandwidth_percent}%
</div>
</div>
<small class="text-muted">
{$stats.bandwidth_used} von {$stats.bandwidth_limit} verwendet
</small>
</div>
</div>
{/if}
{* Weitere Statistiken *}
<div class="row" style="margin-top: 20px;">
<div class="col-sm-4 col-xs-6 text-center">
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-primary" style="margin: 0;">
<i class="fas fa-globe"></i> {$stats.domains}
</h3>
<small class="text-muted">Domains</small>
</div>
</div>
</div>
<div class="col-sm-4 col-xs-6 text-center">
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-success" style="margin: 0;">
<i class="fas fa-database"></i> {$stats.databases}
</h3>
<small class="text-muted">Datenbanken</small>
</div>
</div>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-info" style="margin: 0;">
<i class="fas fa-envelope"></i> {$stats.email_accounts}
</h3>
<small class="text-muted">E-Mail-Konten</small>
</div>
</div>
</div>
</div>
{/if}
{* Schnellzugriff-Button *}
<div class="row" style="margin-top: 20px;">
<div class="col-md-12 text-center">
<a href="{$login_url}" target="_blank" class="btn btn-primary btn-lg">
<i class="fas fa-sign-in-alt"></i> Jetzt zu KeyHelp einloggen
</a>
</div>
</div>
{/if}
</div>
</div>
{* JavaScript für Funktionalität *}
<script type="text/javascript">
function togglePassword(fieldId) {
var field = document.getElementById(fieldId);
var icon = document.getElementById(fieldId + '-icon');
if (field.type === "password") {
field.type = "text";
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
field.type = "password";
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function copyToClipboard(fieldId) {
var field = document.getElementById(fieldId);
field.select();
field.setSelectionRange(0, 99999); // Für mobile Geräte
try {
document.execCommand('copy');
// Visuelles Feedback
var btn = event.target.closest('button');
var originalHTML = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i>';
btn.classList.add('btn-success');
setTimeout(function() {
btn.innerHTML = originalHTML;
btn.classList.remove('btn-success');
}, 1500);
} catch (err) {
alert('Kopieren fehlgeschlagen. Bitte manuell kopieren.');
}
// Deselektiere das Feld
window.getSelection().removeAllRanges();
}
</script>
<style>
.panel {
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.input-group-btn button {
border-left: 0;
}
.progress {
height: 24px;
}
.progress-bar {
line-height: 24px;
font-weight: bold;
}
@media (max-width: 768px) {
.text-right {
text-align: left !important;
margin-bottom: 5px;
}
}
</style>