diff --git a/application/ADBNetzgebiet/ADBNetzgebietController.php b/application/ADBNetzgebiet/ADBNetzgebietController.php index c4304dcd3..414546f4a 100644 --- a/application/ADBNetzgebiet/ADBNetzgebietController.php +++ b/application/ADBNetzgebiet/ADBNetzgebietController.php @@ -24,6 +24,9 @@ class ADBNetzgebietController extends mfBaseController { "GET_URL" => $this::getUrl("ADBNetzgebiet/getNetzgebiete"), "SAVE_URL" => $this::getUrl("ADBNetzgebiet/save"), "HISTORY_URL" => $this::getUrl("ADBNetzgebiet/getHistory"), + "START_RIMO_IMPORT_URL" => $this::getUrl("ADBNetzgebiet/startRimoImport"), + "GET_RIMO_IMPORT_STATUS_URL" => $this::getUrl("ADBNetzgebiet/getRimoImportStatus"), + "GET_RIMO_IMPORT_LOG_URL" => $this::getUrl("ADBNetzgebiet/getRimoImportLog"), "NETWORK_URL" => $this::getUrl("Network/Index"), "NETWORK_CREATE_URL" => $this::getUrl("Network/add"), "CAMPAIGN_URL" => $this::getUrl("Preordercampaign/edit"), @@ -130,6 +133,193 @@ class ADBNetzgebietController extends mfBaseController { self::returnJson(['success' => true, 'data' => $history]); } + protected function startRimoImportAction(): void { + $id = $_GET['id'] ?? null; + if (empty($id)) { + self::returnJson(['success' => false, 'message' => "Netzgebiet ID required."]); + return; + } + + $netzgebiet = ADBNetzgebiet::get($id); + if (!$netzgebiet || !$netzgebiet->id) { + self::returnJson(['success' => false, 'message' => "Netzgebiet not found."]); + return; + } + + if (strpos($netzgebiet->source, 'rimo-') !== 0) { + self::returnJson(['success' => false, 'message' => "This action is only for RIMO-source Netzgebiete."]); + return; + } + + if (empty($netzgebiet->source_id)) { + self::returnJson(['success' => false, 'message' => "Netzgebiet has no Source ID."]); + return; + } + + $safeSourceId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $netzgebiet->source_id); + $importTempDir = TEMP_DIR . "/ADBNetzgebietRimoImport/"; + $logDir = $importTempDir . $safeSourceId; + $logFile = $logDir . "/import.log"; + $lockFile = $logDir . "/import.lock"; + + if (is_dir($importTempDir)) { + foreach (glob($importTempDir . "*") as $dir) { + if (is_dir($dir) && (time() - filemtime($dir)) > 86400) { + // simple cleanup + if (file_exists($dir . "/import.log")) @unlink($dir . "/import.log"); + if (file_exists($dir . "/import.lock")) @unlink($dir . "/import.lock"); + @rmdir($dir); + } + } + } + + if (!is_dir($logDir)) { + mkdir($logDir, 0755, true); + } + + if (file_exists($lockFile)) { + if ((time() - filemtime($lockFile)) > 3600) { // stale lock for 1h + @unlink($lockFile); + } else { + self::returnJson(['success' => false, 'message' => "Import is already running.", 'status' => 'running']); + return; + } + } + + if (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { + $remaining = 900 - (time() - filemtime($logFile)); + self::returnJson(['success' => false, 'message' => "Please wait before starting another import.", 'status' => 'cooldown', 'remaining' => $remaining]); + return; + } + + touch($lockFile); + + $projectRoot = dirname(dirname(__DIR__)); + $scriptRelativePath = 'scripts/adb-rimo-import/rimo-import.php'; + $scriptFullPath = $projectRoot . '/' . $scriptRelativePath; + + if (!file_exists($scriptFullPath)) { + self::returnJson(['success' => false, 'message' => "Import script not found."]); + return; + } + + $php_executable = "php"; + $command = "$php_executable $scriptRelativePath " . escapeshellarg($netzgebiet->source_id); + + $bgCommand = 'cd ' . escapeshellarg($projectRoot) . ' && ' . $command . ' > ' . escapeshellarg($logFile) . ' 2>&1 & echo $!'; + $pid = shell_exec($bgCommand); + + if(empty($pid) || !is_numeric(trim($pid))) { + self::returnJson(['success' => false, 'message' => "Failed to start background process."]); + return; + } + + file_put_contents($lockFile, trim($pid)); + + self::returnJson(['success' => true, 'message' => 'RIMO import started.']); + } + + protected function getRimoImportStatusAction(): void { + $ids = $this->postData['ids'] ?? []; + if (empty($ids)) { + self::returnJson(['success' => true, 'data' => []]); + return; + } + + $statuses = []; + foreach ($ids as $id) { + $netzgebiet = ADBNetzgebiet::get($id); + if (!$netzgebiet || !$netzgebiet->id || strpos($netzgebiet->source, 'rimo-') !== 0 || empty($netzgebiet->source_id)) { + $statuses[$id] = ['status' => 'not_applicable']; + continue; + } + + $safeSourceId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $netzgebiet->source_id); + $logDir = TEMP_DIR . "/ADBNetzgebietRimoImport/" . $safeSourceId; + $logFile = $logDir . "/import.log"; + $lockFile = $logDir . "/import.lock"; + + if (file_exists($lockFile)) { + $pid = trim(file_get_contents($lockFile)); + // Check if process is still running. posix_getpgid returns false if process does not exist. + if (is_numeric($pid) && posix_getpgid((int)$pid) !== false) { + $statuses[$id] = ['status' => 'running']; + } else { + // Stale lock file, process is gone. + @unlink($lockFile); + // Check for cooldown based on log file from the finished process + if (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { + $statuses[$id] = [ + 'status' => 'cooldown', + 'remaining' => 900 - (time() - filemtime($logFile)) + ]; + } else { + $statuses[$id] = ['status' => 'idle']; + } + } + } elseif (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { + $statuses[$id] = [ + 'status' => 'cooldown', + 'remaining' => 900 - (time() - filemtime($logFile)) + ]; + } else { + $statuses[$id] = ['status' => 'idle']; + } + } + self::returnJson(['success' => true, 'data' => $statuses]); + } + + protected function getRimoImportLogAction(): void { + $id = $_GET['id'] ?? null; + if (empty($id)) { + self::returnJson(['success' => false, 'message' => "Netzgebiet ID required."]); + return; + } + + $netzgebiet = ADBNetzgebiet::get($id); + if (!$netzgebiet || !$netzgebiet->id || empty($netzgebiet->source_id)) { + self::returnJson(['success' => false, 'message' => "Netzgebiet not found or not applicable."]); + return; + } + + $safeSourceId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $netzgebiet->source_id); + $logDir = TEMP_DIR . "/ADBNetzgebietRimoImport/" . $safeSourceId; + $logFile = $logDir . "/import.log"; + $lockFile = $logDir . "/import.lock"; + + $logContent = ""; + if (file_exists($logFile)) { + $logContent = file_get_contents($logFile); + } + + $status = 'idle'; + if (file_exists($lockFile)) { + $pid = trim(file_get_contents($lockFile)); + if (is_numeric($pid) && posix_getpgid((int)$pid) !== false) { + $status = 'running'; + } else { + @unlink($lockFile); // Stale lock, process is gone + } + } + + if ($status !== 'running') { + if (file_exists($logFile) && (time() - filemtime($logFile)) < 900) { + $status = 'cooldown'; + } else { + $status = file_exists($logFile) ? 'finished' : 'idle'; + } + } + + self::returnJson([ + 'success' => true, + 'data' => [ + 'log' => $logContent, + 'status' => $status, + 'timestamp' => file_exists($logFile) ? filemtime($logFile) : null + ] + ]); + } + // TODO: Implement RIMO API check protected function checkRimoSourceIdAction(): void { self::returnJson(['success' => false, 'message' => "RIMO API check not available."]); diff --git a/public/js/pages/ADBNetzgebiet/ADBNetzgebiet.js b/public/js/pages/ADBNetzgebiet/ADBNetzgebiet.js index 46c51c14e..35cadf513 100644 --- a/public/js/pages/ADBNetzgebiet/ADBNetzgebiet.js +++ b/public/js/pages/ADBNetzgebiet/ADBNetzgebiet.js @@ -133,6 +133,14 @@ const ADBNetzgebiet = { + @@ -274,6 +282,25 @@ const ADBNetzgebiet = { + + + +
+ + Lade Log... +
+
+ + Kein Log vorhanden. +
+
{{ rimoLogContent }}
+ +
`, @@ -294,6 +321,16 @@ const ADBNetzgebiet = { historyItems: [], historyTitle: 'Verlauf', expandedIds: {}, + + // RIMO Import + importStatus: {}, + showRimoLogModal: false, + rimoLogContent: '', + rimoLogTitle: '', + rimoLogStatus: 'idle', + rimoLogInterval: null, + statusInterval: null, + freigabeLabels: { interest: 'Interest', provision: 'Provision', order: 'Order', reorder: 'Reorder' }, freigabeOptions: [ { key: 'interest', label: 'Interest' }, @@ -367,12 +404,21 @@ const ADBNetzgebiet = { filteredNetzgebiete() { if (this.currentPage > this.totalPages) this.currentPage = 1; } }, - async mounted() { await this.fetchNetzgebiete(); }, + async mounted() { + await this.fetchNetzgebiete(); + this.fetchImportStatus(); + this.statusInterval = setInterval(this.fetchImportStatus, 15000); // Poll every 15s + }, + + beforeDestroy() { + clearInterval(this.statusInterval); + clearInterval(this.rimoLogInterval); + }, methods: { formatConsentName(name) { if (name && name.startsWith('Glasfaserprojekt')) { - return name.replace('Glasfaserprojekt', 'Glasfaserprojekt
'); + return name.replace('Glasfaserprojekt', 'Glasfaserprojekt
'); } return name; }, @@ -476,6 +522,96 @@ const ADBNetzgebiet = { } catch { window.notify?.('error', 'Verlauf konnte nicht geladen werden.'); } finally { this.historyLoading = false; } }, + + // RIMO Import Methods + async fetchImportStatus() { + const rimoIds = this.netzgebiete + .filter(item => item.netzgebiet.source && item.netzgebiet.source.startsWith('rimo-')) + .map(item => item.netzgebiet.id); + if (!rimoIds.length) return; + try { + const response = await axios.post(window.TT_CONFIG.GET_RIMO_IMPORT_STATUS_URL, { ids: rimoIds }); + if (response.data.success) { + this.importStatus = response.data.data; + } + } catch (error) { + console.error("Could not fetch RIMO import statuses.", error); + } + }, + handleRimoImportClick(item) { + const status = this.importStatus[item.netzgebiet.id]?.status || 'idle'; + if (status === 'running') { + this.openRimoLogModal(item); + } else if (status === 'cooldown') { + const remaining = this.importStatus[item.netzgebiet.id]?.remaining || 0; + window.notify?.('info', `Bitte warten Sie noch ${Math.ceil(remaining / 60)} Minuten.`); + this.openRimoLogModal(item); + } else { + this.startRimoImport(item); + } + }, + getImportButtonTitle(id) { + const status = this.importStatus[id]?.status || 'idle'; + if (status === 'running') return 'Import-Log anzeigen'; + if (status === 'cooldown') return 'Manueller RIMO-Import (Abkühlphase)'; + return 'Manuellen RIMO-Import starten'; + }, + getImportButtonDisabled(id) { + const status = this.importStatus[id]?.status || 'idle'; + return false; // Never truly disabled, just changes action + }, + getImportButtonIcon(id) { + const status = this.importStatus[id]?.status || 'idle'; + if (status === 'running') return 'fa-spinner fa-spin'; + if (status === 'cooldown') return 'fa-hourglass-half'; + return 'fa-rocket'; + }, + async startRimoImport(item) { + try { + const response = await axios.get(window.TT_CONFIG.START_RIMO_IMPORT_URL + '?id=' + item.netzgebiet.id); + if (response.data.success) { + window.notify?.('success', 'RIMO Import gestartet.'); + this.importStatus[item.netzgebiet.id] = { status: 'running' }; + this.openRimoLogModal(item); + } else { + window.notify?.('error', response.data.message || 'Import konnte nicht gestartet werden.'); + } + } catch (error) { + window.notify?.('error', 'Fehler beim Starten des Imports.'); + console.error(error); + } + }, + openRimoLogModal(item) { + this.rimoLogTitle = `RIMO Import: ${item.netzgebiet.name}`; + this.showRimoLogModal = true; + this.fetchRimoLog(item); // initial fetch + this.rimoLogInterval = setInterval(() => this.fetchRimoLog(item), 3000); + }, + closeRimoLogModal() { + this.showRimoLogModal = false; + clearInterval(this.rimoLogInterval); + this.rimoLogContent = ''; + this.rimoLogTitle = ''; + this.rimoLogStatus = 'idle'; + }, + async fetchRimoLog(item) { + try { + const response = await axios.get(window.TT_CONFIG.GET_RIMO_IMPORT_LOG_URL + '?id=' + item.netzgebiet.id); + if (response.data.success) { + this.rimoLogContent = response.data.data.log; + this.rimoLogStatus = response.data.data.status; + // If no longer running, stop polling + if (this.rimoLogStatus !== 'running') { + clearInterval(this.rimoLogInterval); + this.fetchImportStatus(); // refresh overall status + } + } + } catch (error) { + console.error('Could not fetch RIMO log.', error); + clearInterval(this.rimoLogInterval); + } + }, + translateAction(action) { return { create: 'Erstellt', update: 'Geändert', delete: 'Gelöscht' }[action] || action; }, translateField(field) { return { name: 'Name', extref: 'ExtRef', source: 'Quelle', source_id: 'Source ID', diff --git a/scripts/adb-rimo-import/rimo-import.php b/scripts/adb-rimo-import/rimo-import.php index 1b9829a8b..41205a64f 100755 --- a/scripts/adb-rimo-import/rimo-import.php +++ b/scripts/adb-rimo-import/rimo-import.php @@ -7,7 +7,7 @@ use ADBRimoImport\ADBAddressHelper; //use ADBRimoImport\importer\CitycomImporter; //require 'vendor/autoload.php'; -require("../../config/config.php"); +require(__DIR__ . "/../../config/config.php"); require("ADBAddressHelper/address_helper.php"); define('FRONKDB_SQLDEBUG', false);