commit update

This commit is contained in:
Kevin Feiler
2025-10-15 22:40:34 +02:00
parent 351da5fe19
commit 8259ec4621
2 changed files with 513 additions and 632 deletions

View File

@@ -1,23 +1,12 @@
<?php <?php
/** /**
* WHMCS KeyHelp Manager Provisioning Module * KeyHelp Manager - WHMCS Provisioning Module
* *
* @author Kevin Feiler <info@avvgo.de> * @author Kevin Feiler <info@avvgo.de>
* @copyright Copyright (c) 2024 Kevin Feiler / AVVGO * @copyright Copyright (c) 2024 Kevin Feiler / AVVGO
* @license MIT License * @license MIT License
* @link https://avvgo.de * @link https://avvgo.de
* @version 1.0.0 * @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")) { if (!defined("WHMCS")) {
@@ -26,13 +15,6 @@ if (!defined("WHMCS")) {
use WHMCS\Database\Capsule; use WHMCS\Database\Capsule;
/**
* Modul-Metadaten
*
* Definiert die grundlegenden Informationen über das Modul.
*
* @return array Modul-Metadaten
*/
function keyhelpmanager_MetaData() function keyhelpmanager_MetaData()
{ {
return [ return [
@@ -41,18 +23,11 @@ function keyhelpmanager_MetaData()
"RequiresServer" => true, "RequiresServer" => true,
"DefaultNonSSLPort" => "80", "DefaultNonSSLPort" => "80",
"DefaultSSLPort" => "443", "DefaultSSLPort" => "443",
"ServiceSingleSignOnLabel" => "Login zu KeyHelp", "ServiceSingleSignOnLabel" => "Login to KeyHelp",
"AdminSingleSignOnLabel" => "Als Admin einloggen", "AdminSingleSignOnLabel" => "Login as Admin",
]; ];
} }
/**
* Modul-Konfigurationsoptionen
*
* Definiert die Felder, die in der Server-Konfiguration in WHMCS angezeigt werden.
*
* @return array Konfigurations-Array
*/
function keyhelpmanager_ConfigOptions() function keyhelpmanager_ConfigOptions()
{ {
return [ return [
@@ -60,48 +35,32 @@ function keyhelpmanager_ConfigOptions()
"FriendlyName" => "Hostname (IP or FQDN)", "FriendlyName" => "Hostname (IP or FQDN)",
"Type" => "text", "Type" => "text",
"Size" => "25", "Size" => "25",
"Default" => "",
"Description" => "Description" =>
"Hostname oder IP-Adresse deines KeyHelp-Servers (ohne http:// oder https://)", "KeyHelp server hostname or IP address (without http://)",
"Required" => true, "Required" => true,
], ],
"apikey" => [ "apikey" => [
"FriendlyName" => "API Key", "FriendlyName" => "API Key",
"Type" => "password", "Type" => "password",
"Size" => "50", "Size" => "50",
"Default" => "", "Description" => "KeyHelp API Key from Settings → API",
"Description" =>
"Dein KeyHelp API-Schlüssel (zu finden in KeyHelp unter Einstellungen → API)",
"Required" => true, "Required" => true,
], ],
"usessl" => [ "usessl" => [
"FriendlyName" => "Use SSL", "FriendlyName" => "Use SSL",
"Type" => "yesno", "Type" => "yesno",
"Default" => "on", "Default" => "on",
"Description" => "Description" => "Use SSL for API connection (recommended)",
"SSL für die Verbindung zur API verwenden (dringend empfohlen)",
], ],
"verify_ssl" => [ "verify_ssl" => [
"FriendlyName" => "Verify SSL Certificate", "FriendlyName" => "Verify SSL Certificate",
"Type" => "yesno", "Type" => "yesno",
"Default" => "on", "Default" => "on",
"Description" => "Description" => "Verify SSL certificate (disable for self-signed)",
"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( function _keyhelpmanager_APIRequest(
array $params, array $params,
string $endpoint, string $endpoint,
@@ -109,43 +68,28 @@ function _keyhelpmanager_APIRequest(
array $data = [], array $data = [],
) { ) {
try { try {
// Konfiguration auslesen
$hostname = $hostname =
$params["serverhostname"] ?? ($params["configoption1"] ?? ""); $params["serverhostname"] ?? ($params["configoption1"] ?? "");
$apiKey = $params["serverpassword"] ?? ($params["configoption2"] ?? ""); $apiKey = $params["serverpassword"] ?? ($params["configoption2"] ?? "");
$useSSL = $params["configoption3"] ?? "on"; $useSSL = $params["configoption3"] ?? "on";
$verifySSL = $params["configoption4"] ?? "on"; $verifySSL = $params["configoption4"] ?? "on";
// Validierung if (empty($hostname) || empty($apiKey)) {
if (empty($hostname)) {
return [ return [
"success" => false, "success" => false,
"error" => "KeyHelp-Hostname ist nicht konfiguriert.", "error" => "KeyHelp server not configured",
]; ];
} }
if (empty($apiKey)) {
return [
"success" => false,
"error" => "KeyHelp API-Schlüssel ist nicht konfiguriert.",
];
}
// Protocol bestimmen
$protocol = $useSSL === "on" ? "https" : "http"; $protocol = $useSSL === "on" ? "https" : "http";
$url = sprintf("%s://%s/api/v2%s", $protocol, $hostname, $endpoint);
// Basis-URL zusammenbauen
$baseUrl = sprintf("%s://%s/api/v2", $protocol, $hostname);
$url = $baseUrl . $endpoint;
// Guzzle HTTP-Client initialisieren
$client = new \GuzzleHttp\Client([ $client = new \GuzzleHttp\Client([
"verify" => $verifySSL === "on", "verify" => $verifySSL === "on",
"timeout" => 30, "timeout" => 30,
"http_errors" => false, "http_errors" => false,
]); ]);
// Request-Optionen
$options = [ $options = [
"headers" => [ "headers" => [
"X-API-Key" => $apiKey, "X-API-Key" => $apiKey,
@@ -154,30 +98,24 @@ function _keyhelpmanager_APIRequest(
], ],
]; ];
// Body-Daten hinzufügen wenn vorhanden
if (!empty($data) && in_array($method, ["POST", "PUT", "PATCH"])) { if (!empty($data) && in_array($method, ["POST", "PUT", "PATCH"])) {
$options["json"] = $data; $options["json"] = $data;
} }
// API-Request ausführen
$response = $client->request($method, $url, $options); $response = $client->request($method, $url, $options);
$statusCode = $response->getStatusCode(); $statusCode = $response->getStatusCode();
$body = (string) $response->getBody(); $body = (string) $response->getBody();
// JSON-Antwort dekodieren
$responseData = json_decode($body, true); $responseData = json_decode($body, true);
// Logging für Debugging
logModuleCall( logModuleCall(
"keyhelpmanager", "keyhelpmanager",
$method . " " . $endpoint, $method . " " . $endpoint,
$data, $data,
$statusCode . " - " . $body, $statusCode . " - " . $body,
$responseData, $responseData,
[$apiKey], // API-Key in Logs maskieren [$apiKey],
); );
// Erfolgreiche Antworten (2xx)
if ($statusCode >= 200 && $statusCode < 300) { if ($statusCode >= 200 && $statusCode < 300) {
return [ return [
"success" => true, "success" => true,
@@ -186,91 +124,58 @@ function _keyhelpmanager_APIRequest(
]; ];
} }
// Fehlerbehandlung $errorMessage =
$errorMessage = "KeyHelp API-Fehler: "; $responseData["message"] ??
($responseData["error"] ??
if (isset($responseData["message"])) { sprintf("HTTP %d - %s", $statusCode, $body));
$errorMessage .= $responseData["message"];
} elseif (isset($responseData["error"])) {
$errorMessage .= $responseData["error"];
} else {
$errorMessage .= sprintf("HTTP Status %d - %s", $statusCode, $body);
}
return [ return [
"success" => false, "success" => false,
"error" => $errorMessage, "error" => "KeyHelp API Error: " . $errorMessage,
"status_code" => $statusCode, "status_code" => $statusCode,
]; ];
} catch (\GuzzleHttp\Exception\ConnectException $e) { } catch (\GuzzleHttp\Exception\ConnectException $e) {
return [ return [
"success" => false, "success" => false,
"error" => "error" => "Connection failed: " . $e->getMessage(),
"Verbindung zum KeyHelp-Server fehlgeschlagen: " .
$e->getMessage(),
];
} catch (\GuzzleHttp\Exception\RequestException $e) {
return [
"success" => false,
"error" => "API-Request fehlgeschlagen: " . $e->getMessage(),
]; ];
} catch (\Exception $e) { } catch (\Exception $e) {
return [ return [
"success" => false, '
"error" => "Unerwarteter Fehler: " . $e->getMessage(), success' => false,
"error" => "Unexpected error: " . $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) function keyhelpmanager_CreateAccount(array $params)
{ {
try { try {
// Extrahiere notwendige Parameter
$domain = $params["domain"] ?? ""; $domain = $params["domain"] ?? "";
$username = $params["username"] ?? ""; $username =
$password = $params["password"] ?? ""; $params["username"] ?? _keyhelpmanager_GenerateUsername($domain);
$password = $params["password"] ?? _keyhelpmanager_GeneratePassword();
$clientEmail = $params["clientsdetails"]["email"] ?? ""; $clientEmail = $params["clientsdetails"]["email"] ?? "";
$firstName = $params["clientsdetails"]["firstname"] ?? ""; $clientName = trim(
$lastName = $params["clientsdetails"]["lastname"] ?? ""; ($params["clientsdetails"]["firstname"] ?? "") .
" " .
($params["clientsdetails"]["lastname"] ?? ""),
);
// Validierung
if (empty($domain)) { if (empty($domain)) {
return "Domain ist erforderlich."; return "Domain is required";
} }
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 = [ $accountData = [
"login_name" => $username, "login_name" => $username,
"password" => $password, "password" => $password,
"email" => $clientEmail, "email" => $clientEmail,
"display_name" => trim($firstName . " " . $lastName), "display_name" => $clientName,
]; ];
// Optional: Plan/Tarif aus konfigurierbaren Optionen
if (!empty($params["configoptions"]["KeyHelp Plan"])) { if (!empty($params["configoptions"]["KeyHelp Plan"])) {
$accountData["plan"] = $params["configoptions"]["KeyHelp Plan"]; $accountData["plan"] = $params["configoptions"]["KeyHelp Plan"];
} }
// Benutzer über API erstellen
$result = _keyhelpmanager_APIRequest( $result = _keyhelpmanager_APIRequest(
$params, $params,
"/users", "/users",
@@ -285,15 +190,10 @@ function keyhelpmanager_CreateAccount(array $params)
$userId = $result["data"]["id"] ?? null; $userId = $result["data"]["id"] ?? null;
if (empty($userId)) { if (empty($userId)) {
return "Benutzer wurde erstellt, aber keine User-ID zurückgegeben."; return "User created but no ID returned";
} }
// Domain zum Benutzer hinzufügen $domainData = ["domain_name" => $domain, "user_id" => $userId];
$domainData = [
"domain_name" => $domain,
"user_id" => $userId,
];
$domainResult = _keyhelpmanager_APIRequest( $domainResult = _keyhelpmanager_APIRequest(
$params, $params,
"/domains", "/domains",
@@ -302,13 +202,10 @@ function keyhelpmanager_CreateAccount(array $params)
); );
if (!$domainResult["success"]) { if (!$domainResult["success"]) {
// Rollback: Benutzer wieder löschen
_keyhelpmanager_APIRequest($params, "/users/" . $userId, "DELETE"); _keyhelpmanager_APIRequest($params, "/users/" . $userId, "DELETE");
return "Domain konnte nicht hinzugefügt werden: " . return "Domain creation failed: " . $domainResult["error"];
$domainResult["error"];
} }
// Speichere Username und Passwort in WHMCS Custom Fields
_keyhelpmanager_SaveAccountDetails($params["serviceid"], [ _keyhelpmanager_SaveAccountDetails($params["serviceid"], [
"username" => $username, "username" => $username,
"password" => $password, "password" => $password,
@@ -317,137 +214,80 @@ function keyhelpmanager_CreateAccount(array $params)
return "success"; return "success";
} catch (\Exception $e) { } catch (\Exception $e) {
return "Fehler beim Erstellen des Accounts: " . $e->getMessage(); return "Account creation failed: " . $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) function keyhelpmanager_SuspendAccount(array $params)
{ {
try { try {
$userId = _keyhelpmanager_GetUserId($params); $userId = _keyhelpmanager_GetUserId($params);
if (empty($userId)) { if (empty($userId)) {
return "Benutzer-ID nicht gefunden. Account wurde möglicherweise nicht korrekt erstellt."; return "User ID not found";
} }
// Benutzer-Status auf "gesperrt" setzen
$updateData = [
"is_locked" => true,
];
$result = _keyhelpmanager_APIRequest( $result = _keyhelpmanager_APIRequest(
$params, $params,
"/users/" . $userId, "/users/" . $userId,
"PUT", "PUT",
$updateData, ["is_locked" => true],
); );
if (!$result["success"]) { return $result["success"] ? "success" : $result["error"];
return $result["error"];
}
return "success";
} catch (\Exception $e) { } catch (\Exception $e) {
return "Fehler beim Sperren des Accounts: " . $e->getMessage(); return "Suspend failed: " . $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) function keyhelpmanager_UnsuspendAccount(array $params)
{ {
try { try {
$userId = _keyhelpmanager_GetUserId($params); $userId = _keyhelpmanager_GetUserId($params);
if (empty($userId)) { if (empty($userId)) {
return "Benutzer-ID nicht gefunden. Account wurde möglicherweise nicht korrekt erstellt."; return "User ID not found";
} }
// Benutzer-Status auf "aktiv" setzen
$updateData = [
"is_locked" => false,
];
$result = _keyhelpmanager_APIRequest( $result = _keyhelpmanager_APIRequest(
$params, $params,
"/users/" . $userId, "/users/" . $userId,
"PUT", "PUT",
$updateData, ["is_locked" => false],
); );
if (!$result["success"]) { return $result["success"] ? "success" : $result["error"];
return $result["error"];
}
return "success";
} catch (\Exception $e) { } catch (\Exception $e) {
return "Fehler beim Entsperren des Accounts: " . $e->getMessage(); return "Unsuspend failed: " . $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) function keyhelpmanager_TerminateAccount(array $params)
{ {
try { try {
$userId = _keyhelpmanager_GetUserId($params); $userId = _keyhelpmanager_GetUserId($params);
if (empty($userId)) { if (empty($userId)) {
return "Benutzer-ID nicht gefunden. Account wurde möglicherweise bereits gelöscht."; return "User ID not found";
} }
// Benutzer und alle zugehörigen Daten löschen
$result = _keyhelpmanager_APIRequest( $result = _keyhelpmanager_APIRequest(
$params, $params,
"/users/" . $userId, "/users/" . $userId,
"DELETE", "DELETE",
); );
if (!$result["success"]) { if ($result["success"]) {
return $result["error"]; _keyhelpmanager_DeleteAccountDetails($params["serviceid"]);
return "success";
} }
// Entferne gespeicherte Account-Details return $result["error"];
_keyhelpmanager_DeleteAccountDetails($params["serviceid"]);
return "success";
} catch (\Exception $e) { } catch (\Exception $e) {
return "Fehler beim Löschen des Accounts: " . $e->getMessage(); return "Termination failed: " . $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) function keyhelpmanager_ClientArea(array $params)
{ {
try { try {
@@ -455,30 +295,24 @@ function keyhelpmanager_ClientArea(array $params)
$params["serviceid"], $params["serviceid"],
); );
$userId = $accountDetails["userid"] ?? null; $userId = $accountDetails["userid"] ?? null;
$hostname = $hostname =
$params["serverhostname"] ?? ($params["configoption1"] ?? ""); $params["serverhostname"] ?? ($params["configoption1"] ?? "");
$useSSL = $params["configoption3"] ?? "on"; $useSSL = $params["configoption3"] ?? "on";
$protocol = $useSSL === "on" ? "https" : "http"; $protocol = $useSSL === "on" ? "https" : "http";
// Login-URL
$loginUrl = sprintf("%s://%s", $protocol, $hostname); $loginUrl = sprintf("%s://%s", $protocol, $hostname);
// Basis-Informationen
$templateVars = [ $templateVars = [
"login_url" => $loginUrl, "login_url" => $loginUrl,
"username" => "username" =>
$accountDetails["username"] ?? $accountDetails["username"] ?? ($params["username"] ?? "N/A"),
($params["username"] ?? "Nicht verfügbar"),
"password" => "password" =>
$accountDetails["password"] ?? $accountDetails["password"] ??
($params["password"] ?? "••••••••"), ($params["password"] ?? "••••••••"),
"domain" => $params["domain"] ?? "Nicht verfügbar", "domain" => $params["domain"] ?? "N/A",
"stats" => [], "stats" => null,
"error" => null, "error" => null,
]; ];
// Statistiken von KeyHelp API abrufen wenn User-ID vorhanden
if (!empty($userId)) { if (!empty($userId)) {
$statsResult = _keyhelpmanager_APIRequest( $statsResult = _keyhelpmanager_APIRequest(
$params, $params,
@@ -490,20 +324,24 @@ function keyhelpmanager_ClientArea(array $params)
$stats = $statsResult["data"]; $stats = $statsResult["data"];
$templateVars["stats"] = [ $templateVars["stats"] = [
"disk_used" => _keyhelpmanager_FormatBytes( "disk_used" => $stats["disk_usage"] ?? 0,
"disk_limit" => $stats["disk_limit"] ?? 0,
"disk_used_formatted" => _keyhelpmanager_FormatBytes(
$stats["disk_usage"] ?? 0, $stats["disk_usage"] ?? 0,
), ),
"disk_limit" => _keyhelpmanager_FormatBytes( "disk_limit_formatted" => _keyhelpmanager_FormatBytes(
$stats["disk_limit"] ?? 0, $stats["disk_limit"] ?? 0,
), ),
"disk_percent" => _keyhelpmanager_CalculatePercent( "disk_percent" => _keyhelpmanager_CalculatePercent(
$stats["disk_usage"] ?? 0, $stats["disk_usage"] ?? 0,
$stats["disk_limit"] ?? 0, $stats["disk_limit"] ?? 0,
), ),
"bandwidth_used" => _keyhelpmanager_FormatBytes( "bandwidth_used" => $stats["traffic_usage"] ?? 0,
"bandwidth_limit" => $stats["traffic_limit"] ?? 0,
"bandwidth_used_formatted" => _keyhelpmanager_FormatBytes(
$stats["traffic_usage"] ?? 0, $stats["traffic_usage"] ?? 0,
), ),
"bandwidth_limit" => _keyhelpmanager_FormatBytes( "bandwidth_limit_formatted" => _keyhelpmanager_FormatBytes(
$stats["traffic_limit"] ?? 0, $stats["traffic_limit"] ?? 0,
), ),
"bandwidth_percent" => _keyhelpmanager_CalculatePercent( "bandwidth_percent" => _keyhelpmanager_CalculatePercent(
@@ -517,31 +355,18 @@ function keyhelpmanager_ClientArea(array $params)
} }
} }
return [ return ["templatefile" => "clientarea", "vars" => $templateVars];
"templatefile" => "clientarea",
"vars" => $templateVars,
];
} catch (\Exception $e) { } catch (\Exception $e) {
return [ return [
"templatefile" => "clientarea", "templatefile" => "clientarea",
"vars" => [ "vars" => [
"error" => "error" =>
"Fehler beim Laden der Account-Informationen: " . "Failed to load account information: " . $e->getMessage(),
$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) function keyhelpmanager_LoginLink(array $params)
{ {
try { try {
@@ -551,18 +376,10 @@ function keyhelpmanager_LoginLink(array $params)
$userId = $accountDetails["userid"] ?? null; $userId = $accountDetails["userid"] ?? null;
if (empty($userId)) { if (empty($userId)) {
return [ return ["success" => false, "errorMsg" => "User ID not found"];
"success" => false,
"errorMsg" => "Benutzer-ID nicht gefunden.",
];
} }
// Session-Token von KeyHelp API anfordern $sessionData = ["user_id" => $userId, "lifetime" => 300];
$sessionData = [
"user_id" => $userId,
"lifetime" => 300, // 5 Minuten Gültigkeit
];
$result = _keyhelpmanager_APIRequest( $result = _keyhelpmanager_APIRequest(
$params, $params,
"/sessions", "/sessions",
@@ -573,9 +390,7 @@ function keyhelpmanager_LoginLink(array $params)
if (!$result["success"]) { if (!$result["success"]) {
return [ return [
"success" => false, "success" => false,
"errorMsg" => "errorMsg" => "Session creation failed: " . $result["error"],
"Login-Session konnte nicht erstellt werden: " .
$result["error"],
]; ];
} }
@@ -584,16 +399,14 @@ function keyhelpmanager_LoginLink(array $params)
if (empty($sessionToken)) { if (empty($sessionToken)) {
return [ return [
"success" => false, "success" => false,
"errorMsg" => "Kein Session-Token erhalten.", "errorMsg" => "No session token received",
]; ];
} }
// Login-URL mit Session-Token
$hostname = $hostname =
$params["serverhostname"] ?? ($params["configoption1"] ?? ""); $params["serverhostname"] ?? ($params["configoption1"] ?? "");
$useSSL = $params["configoption3"] ?? "on"; $useSSL = $params["configoption3"] ?? "on";
$protocol = $useSSL === "on" ? "https" : "http"; $protocol = $useSSL === "on" ? "https" : "http";
$loginUrl = sprintf( $loginUrl = sprintf(
"%s://%s/login?token=%s", "%s://%s/login?token=%s",
$protocol, $protocol,
@@ -601,27 +414,15 @@ function keyhelpmanager_LoginLink(array $params)
$sessionToken, $sessionToken,
); );
return [ return ["success" => true, "redirectTo" => $loginUrl];
"success" => true,
"redirectTo" => $loginUrl,
];
} catch (\Exception $e) { } catch (\Exception $e) {
return [ return [
"success" => false, "success" => false,
"errorMsg" => "errorMsg" => "Login link creation failed: " . $e->getMessage(),
"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) function keyhelpmanager_ChangePassword(array $params)
{ {
try { try {
@@ -629,101 +430,66 @@ function keyhelpmanager_ChangePassword(array $params)
$newPassword = $params["password"] ?? ""; $newPassword = $params["password"] ?? "";
if (empty($userId)) { if (empty($userId)) {
return "Benutzer-ID nicht gefunden."; return "User ID not found";
} }
if (empty($newPassword)) { if (empty($newPassword)) {
return "Neues Passwort ist erforderlich."; return "New password is required";
} }
// Passwort über API ändern
$updateData = [
"password" => $newPassword,
];
$result = _keyhelpmanager_APIRequest( $result = _keyhelpmanager_APIRequest(
$params, $params,
"/users/" . $userId, "/users/" . $userId,
"PUT", "PUT",
$updateData, ["password" => $newPassword],
); );
if (!$result["success"]) { if ($result["success"]) {
return $result["error"]; _keyhelpmanager_UpdateAccountDetail(
$params["serviceid"],
"password",
$newPassword,
);
return "success";
} }
// Aktualisiere gespeichertes Passwort return $result["error"];
_keyhelpmanager_UpdateAccountDetail(
$params["serviceid"],
"password",
$newPassword,
);
return "success";
} catch (\Exception $e) { } catch (\Exception $e) {
return "Fehler beim Ändern des Passworts: " . $e->getMessage(); return "Password change failed: " . $e->getMessage();
} }
} }
/**
* Test-Verbindung
*
* Testet die Verbindung zur KeyHelp-API.
*
* @param array $params WHMCS-Parameter
* @return array Test-Ergebnis
*/
function keyhelpmanager_TestConnection(array $params) function keyhelpmanager_TestConnection(array $params)
{ {
try { try {
// Einfache API-Abfrage zum Testen
$result = _keyhelpmanager_APIRequest($params, "/server/version", "GET"); $result = _keyhelpmanager_APIRequest($params, "/server/version", "GET");
if (!$result["success"]) { if (!$result["success"]) {
return [ return ["success" => false, "error" => $result["error"]];
"success" => false,
"error" => $result["error"],
];
} }
$version = $result["data"]["version"] ?? "Unbekannt"; $version = $result["data"]["version"] ?? "Unknown";
return [ return [
"success" => true, "success" => true,
"message" => sprintf( "message" => sprintf(
"Verbindung erfolgreich! KeyHelp Version: %s", "Connection successful! KeyHelp Version: %s",
$version, $version,
), ),
]; ];
} catch (\Exception $e) { } catch (\Exception $e) {
return [ return ["success" => false, "error" => $e->getMessage()];
"success" => false,
"error" => $e->getMessage(),
];
} }
} }
// ============================================================================ // Helper Functions
// HILFSFUNKTIONEN
// ============================================================================
/**
* Generiert einen sicheren Benutzernamen aus einer Domain
*
* @param string $domain Domain-Name
* @return string Generierter Benutzername
*/
function _keyhelpmanager_GenerateUsername(string $domain): string function _keyhelpmanager_GenerateUsername(string $domain): string
{ {
// Entferne TLD und Sonderzeichen
$username = preg_replace('/\.[^.]+$/', "", $domain); $username = preg_replace('/\.[^.]+$/', "", $domain);
$username = preg_replace("/[^a-z0-9]/i", "", $username); $username = preg_replace("/[^a-z0-9]/i", "", $username);
$username = strtolower($username); $username = strtolower(substr($username, 0, 16));
// Begrenze auf 16 Zeichen
$username = substr($username, 0, 16);
// Füge zufällige Zahlen hinzu wenn zu kurz
if (strlen($username) < 6) { if (strlen($username) < 6) {
$username .= rand(1000, 9999); $username .= rand(1000, 9999);
} }
@@ -731,12 +497,6 @@ function _keyhelpmanager_GenerateUsername(string $domain): string
return $username; return $username;
} }
/**
* Generiert ein sicheres Zufallspasswort
*
* @param int $length Passwort-Länge
* @return string Generiertes Passwort
*/
function _keyhelpmanager_GeneratePassword(int $length = 16): string function _keyhelpmanager_GeneratePassword(int $length = 16): string
{ {
$chars = $chars =
@@ -751,27 +511,19 @@ function _keyhelpmanager_GeneratePassword(int $length = 16): string
return $password; 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( function _keyhelpmanager_SaveAccountDetails(
int $serviceId, int $serviceId,
array $details, array $details,
): void { ): void {
try { try {
foreach ($details as $key => $value) { foreach ($details as $key => $value) {
Capsule::table("tblhosting") if ($key === "username" || $key === "password") {
->where("id", $serviceId) Capsule::table("tblhosting")
->update([ ->where("id", $serviceId)
$key === "username" ? "username" : "password" => $value, ->update([$key => $value]);
]); }
} }
// Speichere userid in Custom Field oder Service Properties
if (isset($details["userid"])) { if (isset($details["userid"])) {
Capsule::table("tblcustomfieldsvalues")->updateOrInsert( Capsule::table("tblcustomfieldsvalues")->updateOrInsert(
[ [
@@ -780,32 +532,22 @@ function _keyhelpmanager_SaveAccountDetails(
"KeyHelp User ID", "KeyHelp User ID",
), ),
], ],
[ ["value" => $details["userid"]],
"value" => $details["userid"],
],
); );
} }
} catch (\Exception $e) { } catch (\Exception $e) {
logActivity( logActivity(
"KeyHelpManager: Fehler beim Speichern der Account-Details: " . "KeyHelpManager: Save account details failed - " . $e->getMessage(),
$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 function _keyhelpmanager_GetAccountDetails(int $serviceId): array
{ {
try { try {
$service = Capsule::table("tblhosting") $service = Capsule::table("tblhosting")
->where("id", $serviceId) ->where("id", $serviceId)
->first(); ->first();
$userid = Capsule::table("tblcustomfieldsvalues") $userid = Capsule::table("tblcustomfieldsvalues")
->where("relid", $serviceId) ->where("relid", $serviceId)
->where( ->where(
@@ -821,33 +563,18 @@ function _keyhelpmanager_GetAccountDetails(int $serviceId): array
]; ];
} catch (\Exception $e) { } catch (\Exception $e) {
logActivity( logActivity(
"KeyHelpManager: Fehler beim Laden der Account-Details: " . "KeyHelpManager: Get account details failed - " . $e->getMessage(),
$e->getMessage(),
); );
return []; 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 function _keyhelpmanager_GetUserId(array $params): ?int
{ {
$accountDetails = _keyhelpmanager_GetAccountDetails($params["serviceid"]); $accountDetails = _keyhelpmanager_GetAccountDetails($params["serviceid"]);
return $accountDetails["userid"] ?? null; 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( function _keyhelpmanager_UpdateAccountDetail(
int $serviceId, int $serviceId,
string $field, string $field,
@@ -857,26 +584,16 @@ function _keyhelpmanager_UpdateAccountDetail(
if ($field === "username" || $field === "password") { if ($field === "username" || $field === "password") {
Capsule::table("tblhosting") Capsule::table("tblhosting")
->where("id", $serviceId) ->where("id", $serviceId)
->update([ ->update([$field => $value]);
$field => $value,
]);
} }
} catch (\Exception $e) { } catch (\Exception $e) {
logActivity( logActivity(
"KeyHelpManager: Fehler beim Aktualisieren von " . "KeyHelpManager: Update account detail failed - " .
$field .
": " .
$e->getMessage(), $e->getMessage(),
); );
} }
} }
/**
* Löscht gespeicherte Account-Details
*
* @param int $serviceId Service-ID
* @return void
*/
function _keyhelpmanager_DeleteAccountDetails(int $serviceId): void function _keyhelpmanager_DeleteAccountDetails(int $serviceId): void
{ {
try { try {
@@ -885,18 +602,12 @@ function _keyhelpmanager_DeleteAccountDetails(int $serviceId): void
->delete(); ->delete();
} catch (\Exception $e) { } catch (\Exception $e) {
logActivity( logActivity(
"KeyHelpManager: Fehler beim Löschen der Account-Details: " . "KeyHelpManager: Delete account details failed - " .
$e->getMessage(), $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 function _keyhelpmanager_GetCustomFieldId(string $fieldName): int
{ {
try { try {
@@ -906,56 +617,34 @@ function _keyhelpmanager_GetCustomFieldId(string $fieldName): int
->first(); ->first();
if (!$field) { if (!$field) {
// Erstelle Custom Field wenn nicht vorhanden return Capsule::table("tblcustomfields")->insertGetId([
$fieldId = Capsule::table("tblcustomfields")->insertGetId([
"type" => "product", "type" => "product",
"fieldname" => $fieldName, "fieldname" => $fieldName,
"fieldtype" => "text", "fieldtype" => "text",
"description" => "", "description" => "",
"adminonly" => "on", "adminonly" => "on",
]); ]);
return $fieldId;
} }
return $field->id; return $field->id;
} catch (\Exception $e) { } catch (\Exception $e) {
logActivity( logActivity(
"KeyHelpManager: Fehler beim Abrufen der Custom Field ID: " . "KeyHelpManager: Get custom field ID failed - " . $e->getMessage(),
$e->getMessage(),
); );
return 0; return 0;
} }
} }
/**
* Formatiert Bytes in eine lesbare Größe
*
* @param int $bytes Bytes
* @return string Formatierte Größe
*/
function _keyhelpmanager_FormatBytes(int $bytes): string function _keyhelpmanager_FormatBytes(int $bytes): string
{ {
$units = ["B", "KB", "MB", "GB", "TB"]; $units = ["B", "KB", "MB", "GB", "TB"];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0; $power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return number_format($bytes / pow(1024, $power), 2, ".", ",") .
return number_format($bytes / pow(1024, $power), 2, ",", ".") .
" " . " " .
$units[$power]; $units[$power];
} }
/**
* Berechnet Prozentsatz
*
* @param int $used Verbrauchter Wert
* @param int $limit Limit
* @return float Prozentsatz
*/
function _keyhelpmanager_CalculatePercent(int $used, int $limit): float function _keyhelpmanager_CalculatePercent(int $used, int $limit): float
{ {
if ($limit == 0) { return $limit > 0 ? round(($used / $limit) * 100, 2) : 0;
return 0;
}
return round(($used / $limit) * 100, 2);
} }

View File

@@ -1,258 +1,450 @@
{** {**
* KeyHelp Manager - Client Area Template * KeyHelp Manager - Modern Client Area Template
* * @author Kevin Feiler / AVVGO
* Zeigt Login-Details und Account-Statistiken für Kunden an * @copyright 2024 Kevin Feiler / AVVGO
*} *}
<div class="panel panel-default"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<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} <style>
<div class="alert alert-danger"> .kh-container {
<i class="fas fa-exclamation-triangle"></i> {$error} font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
max-width: 100%;
margin: 0;
}
.kh-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 32px;
color: white;
margin-bottom: 24px;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.kh-card::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 200px;
height: 200px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
transform: translate(50%, -50%);
}
.kh-card-title {
font-size: 24px;
font-weight: 700;
margin: 0 0 8px 0;
display: flex;
align-items: center;
gap: 12px;
}
.kh-card-subtitle {
font-size: 14px;
opacity: 0.9;
margin: 0;
}
.kh-login-btn {
background: white;
color: #667eea;
border: none;
padding: 14px 28px;
border-radius: 12px;
font-weight: 600;
font-size: 15px;
margin-top: 20px;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.kh-login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
color: #667eea;
text-decoration: none;
}
.kh-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-top: 24px;
}
.kh-info-card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
border: 1px solid #e5e7eb;
transition: all 0.3s ease;
}
.kh-info-card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
.kh-info-label {
font-size: 13px;
color: #6b7280;
font-weight: 500;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.kh-info-value {
display: flex;
align-items: center;
gap: 8px;
}
.kh-info-input {
flex: 1;
border: 2px solid #e5e7eb;
padding: 10px 14px;
border-radius: 8px;
font-size: 14px;
font-family: 'SF Mono', Monaco, monospace;
transition: all 0.2s ease;
background: #f9fafb;
}
.kh-info-input:focus {
outline: none;
border-color: #667eea;
background: white;
}
.kh-icon-btn {
background: #f3f4f6;
border: none;
width: 40px;
height: 40px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
color: #6b7280;
}
.kh-icon-btn:hover {
background: #667eea;
color: white;
transform: scale(1.05);
}
.kh-icon-btn.success {
background: #10b981;
color: white;
}
.kh-stats-section {
margin-top: 24px;
}
.kh-stats-title {
font-size: 20px;
font-weight: 700;
color: #1f2937;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 8px;
}
.kh-stat-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
border: 1px solid #e5e7eb;
margin-bottom: 16px;
}
.kh-stat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.kh-stat-label {
font-size: 14px;
font-weight: 600;
color: #374151;
}
.kh-stat-value {
font-size: 13px;
color: #6b7280;
font-family: 'SF Mono', Monaco, monospace;
}
.kh-progress-bar {
height: 8px;
background: #e5e7eb;
border-radius: 100px;
overflow: hidden;
position: relative;
}
.kh-progress-fill {
height: 100%;
border-radius: 100px;
transition: width 0.6s ease;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
position: relative;
overflow: hidden;
}
.kh-progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.kh-progress-fill.warning {
background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
}
.kh-progress-fill.danger {
background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
}
.kh-metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 16px;
margin-top: 20px;
}
.kh-metric {
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
border-radius: 12px;
padding: 20px;
text-align: center;
border: 1px solid #d1d5db;
}
.kh-metric-value {
font-size: 32px;
font-weight: 700;
color: #667eea;
margin-bottom: 4px;
}
.kh-metric-label {
font-size: 13px;
color: #6b7280;
font-weight: 500;
}
.kh-error {
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
border: 1px solid #fca5a5;
color: #991b1b;
padding: 16px 20px;
border-radius: 12px;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 12px;
}
.kh-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 100px;
font-size: 12px;
font-weight: 600;
background: rgba(255, 255, 255, 0.2);
margin-left: 8px;
}
@media (max-width: 768px) {
.kh-card {
padding: 24px;
}
.kh-info-grid {
grid-template-columns: 1fr;
}
.kh-stat-header {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
}
</style>
<div class="kh-container">
{if $error}
<div class="kh-error">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
</svg>
<div>{$error}</div>
</div>
{else}
<div class="kh-card">
<div class="kh-card-title">
<svg width="32" height="32" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M2 5a2 2 0 012-2h12a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V5zm3.293 1.293a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 01-1.414-1.414L7.586 10 5.293 7.707a1 1 0 010-1.414zM11 12a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/>
</svg>
KeyHelp Hosting
<span class="kh-badge">ACTIVE</span>
</div> </div>
{else} <p class="kh-card-subtitle">{$domain}</p>
<a href="{$login_url}" target="_blank" class="kh-login-btn">
<svg width="18" height="18" fill="currentColor" viewBox="0 0 20 20" style="display: inline-block; vertical-align: middle; margin-right: 8px;">
<path fill-rule="evenodd" d="M3 3a1 1 0 011 1v12a1 1 0 11-2 0V4a1 1 0 011-1zm7.707 3.293a1 1 0 010 1.414L9.414 9H17a1 1 0 110 2H9.414l1.293 1.293a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
Open Control Panel
</a>
</div>
{* Login-Informationen *} <div class="kh-info-grid">
<div class="row"> <div class="kh-info-card">
<div class="col-md-12"> <div class="kh-info-label">Username</div>
<h4><i class="fas fa-sign-in-alt"></i> Login-Informationen</h4> <div class="kh-info-value">
<hr> <input type="text" class="kh-info-input" value="{$username}" readonly id="kh-username">
<button class="kh-icon-btn" onclick="copyToClipboard('kh-username')" title="Copy">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"/>
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"/>
</svg>
</button>
</div> </div>
</div> </div>
<div class="row"> <div class="kh-info-card">
<div class="col-sm-4 text-right"> <div class="kh-info-label">Password</div>
<strong>KeyHelp Login-URL:</strong> <div class="kh-info-value">
</div> <input type="password" class="kh-info-input" value="{$password}" readonly id="kh-password">
<div class="col-sm-8"> <button class="kh-icon-btn" onclick="togglePassword()" title="Show/Hide">
<a href="{$login_url}" target="_blank" class="btn btn-success btn-sm"> <svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20" id="eye-icon">
<i class="fas fa-external-link-alt"></i> {$login_url} <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
</a> <path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
</svg>
</button>
<button class="kh-icon-btn" onclick="copyToClipboard('kh-password')" title="Copy">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"/>
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"/>
</svg>
</button>
</div> </div>
</div> </div>
</div>
<div class="row" style="margin-top: 10px;"> {if $stats}
<div class="col-sm-4 text-right"> <div class="kh-stats-section">
<strong>Benutzername:</strong> <div class="kh-stats-title">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
</svg>
Resource Usage
</div> </div>
<div class="col-sm-8">
<div class="input-group"> <div class="kh-stat-card">
<input type="text" class="form-control" value="{$username}" readonly id="kh-username"> <div class="kh-stat-header">
<span class="input-group-btn"> <span class="kh-stat-label">💾 Disk Space</span>
<button class="btn btn-default" type="button" onclick="copyToClipboard('kh-username')" title="Kopieren"> <span class="kh-stat-value">{$stats.disk_used_formatted} / {$stats.disk_limit_formatted}</span>
<i class="fas fa-copy"></i> </div>
</button> <div class="kh-progress-bar">
</span> <div class="kh-progress-fill {if $stats.disk_percent >= 90}danger{elseif $stats.disk_percent >= 75}warning{/if}" style="width: {$stats.disk_percent}%"></div>
</div>
</div>
<div class="kh-stat-card">
<div class="kh-stat-header">
<span class="kh-stat-label">📊 Bandwidth</span>
<span class="kh-stat-value">{$stats.bandwidth_used_formatted} / {$stats.bandwidth_limit_formatted}</span>
</div>
<div class="kh-progress-bar">
<div class="kh-progress-fill {if $stats.bandwidth_percent >= 90}danger{elseif $stats.bandwidth_percent >= 75}warning{/if}" style="width: {$stats.bandwidth_percent}%"></div>
</div>
</div>
<div class="kh-metrics-grid">
<div class="kh-metric">
<div class="kh-metric-value">{$stats.domains}</div>
<div class="kh-metric-label">🌐 Domains</div>
</div>
<div class="kh-metric">
<div class="kh-metric-value">{$stats.databases}</div>
<div class="kh-metric-label">🗄️ Databases</div>
</div>
<div class="kh-metric">
<div class="kh-metric-value">{$stats.email_accounts}</div>
<div class="kh-metric-label">📧 Email Accounts</div>
</div> </div>
</div> </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} {/if}
{/if}
</div>
</div> </div>
{* JavaScript für Funktionalität *} <script>
<script type="text/javascript"> function togglePassword() {
function togglePassword(fieldId) { const field = document.getElementById('kh-password');
var field = document.getElementById(fieldId); const icon = document.getElementById('eye-icon');
var icon = document.getElementById(fieldId + '-icon');
if (field.type === "password") { if (field.type === 'password') {
field.type = "text"; field.type = 'text';
icon.classList.remove('fa-eye'); icon.innerHTML = '<path d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z"/><path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z"/>';
icon.classList.add('fa-eye-slash');
} else { } else {
field.type = "password"; field.type = 'password';
icon.classList.remove('fa-eye-slash'); icon.innerHTML = '<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>';
icon.classList.add('fa-eye');
} }
} }
function copyToClipboard(fieldId) { function copyToClipboard(fieldId) {
var field = document.getElementById(fieldId); const field = document.getElementById(fieldId);
const button = event.currentTarget;
field.select(); field.select();
field.setSelectionRange(0, 99999); // Für mobile Geräte field.setSelectionRange(0, 99999);
try { try {
document.execCommand('copy'); document.execCommand('copy');
// Visuelles Feedback button.classList.add('success');
var btn = event.target.closest('button'); button.innerHTML = '<svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
var originalHTML = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i>';
btn.classList.add('btn-success');
setTimeout(function() { setTimeout(() => {
btn.innerHTML = originalHTML; button.classList.remove('success');
btn.classList.remove('btn-success'); button.innerHTML = '<svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20"><path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"/><path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"/></svg>';
}, 1500); }, 2000);
} catch (err) { } catch (err) {
alert('Kopieren fehlgeschlagen. Bitte manuell kopieren.'); console.error('Copy failed:', err);
} }
// Deselektiere das Feld
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
} }
</script> </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>