From a09085ec5c012934bfc7d541f162151a68cd6997 Mon Sep 17 00:00:00 2001 From: Luca Haid Date: Mon, 17 Nov 2025 08:36:25 +0100 Subject: [PATCH] Add modal for configuring Chrome Extension ID and enhance CPE provisioning functionality --- .../CpeprovisioningController.php | 53 +++++++ .../Cpeprovisioning/CpeprovisioningModel.php | 7 + .../pages/Cpeprovisioning/Cpeprovisioning.css | 2 +- .../pages/Cpeprovisioning/Cpeprovisioning.js | 136 ++++++++++++++++-- public/js/pages/Radius/RadiusUsers.js | 2 +- 5 files changed, 190 insertions(+), 10 deletions(-) diff --git a/application/Cpeprovisioning/CpeprovisioningController.php b/application/Cpeprovisioning/CpeprovisioningController.php index 7ef383c45..3ca148fe2 100644 --- a/application/Cpeprovisioning/CpeprovisioningController.php +++ b/application/Cpeprovisioning/CpeprovisioningController.php @@ -529,6 +529,7 @@ class CpeprovisioningController extends mfBaseController { // Pass API URLs and initial data to the frontend "CPE_PROV_API_GET_URL" => $this->getUrl("Cpeprovisioning", "apiGet"), "CPE_PROV_API_SAVE_URL" => $this->getUrl("Cpeprovisioning", "apiSave"), + "CPE_PROV_API_TEST_ACS_VLAN_URL" => $this->getUrl("Cpeprovisioning", "getAcsVlan"), "CPE_PROV_PRINT_PDF_URL" => $this->getUrl("Cpeprovisioning", "printPDF"), "ORDER_URL" => $this->getUrl("Order"), "NETWORKS" => NetworkModel::getAll(), @@ -565,6 +566,58 @@ class CpeprovisioningController extends mfBaseController { ); } + protected function getAcsVlanAction() { + $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? null; + $isApiCall = defined('TT_CPE_PROV_ACS_API_KEY') && $apiKey && $apiKey === TT_CPE_PROV_ACS_API_KEY; + $isLoggedInUser = $this->me && $this->me->id; + + if (!$isApiCall && !$isLoggedInUser) { + http_response_code(403); + self::returnJson(['success' => false, 'message' => 'Forbidden']); + return; + } + + try { + $p = json_decode(file_get_contents('php://input'), true); + $mac = $p['mac'] ?? null; + + if (empty($mac)) { + throw new Exception("MAC address is required."); + } + + $cpe = CpeprovisioningModel::getFirst(['mac' => $mac]); + if (!$cpe || !$cpe->termination_id) { + throw new Exception("No active provisioning entry found for this MAC address."); + } + + $term = new Termination($cpe->termination_id); + $product = $cpe->orderproduct; + if (!$term->id || !$product->id) { + throw new Exception("Could not load termination or product details."); + } + + $attrs = $product->product->attributes; + + $vlanPublicDefault = $term->getPop()->vlan_public ?? $attrs['vlan_default_public']->value ?? null; + $vlanNatDefault = $term->getPop()->vlan_nat ?? $attrs['vlan_default_nat']->value ?? null; + $vlanIpv6Default = $term->getPop()->vlan_ipv6 ?? $attrs['vlan_default_ipv6']->value ?? null; + + // For the test, we just return the first available VLAN that would be assigned. + // The logic can be expanded if a specific type is requested. + $assignedVlan = $vlanPublicDefault ?? $vlanNatDefault ?? $vlanIpv6Default; + + if ($assignedVlan) { + self::returnJson(['success' => true, 'vlan_id' => $assignedVlan]); + } else { + throw new Exception("No default VLAN could be determined for this product/POP combination."); + } + + } catch (Exception $e) { + http_response_code(400); + self::returnJson(['success' => false, 'message' => $e->getMessage()]); + } + } + private function fixCpeData($data) { if (!$data) return []; $data->shipping = (bool)$data->shipping; diff --git a/application/Cpeprovisioning/CpeprovisioningModel.php b/application/Cpeprovisioning/CpeprovisioningModel.php index 19d17cfe6..fb79df3c3 100644 --- a/application/Cpeprovisioning/CpeprovisioningModel.php +++ b/application/Cpeprovisioning/CpeprovisioningModel.php @@ -181,6 +181,13 @@ class CpeprovisioningModel { } } + if(array_key_exists("mac", $filter)) { + $mac = FronkDB::singleton()->escape($filter['mac']); + if($mac) { + $where .= " AND mac='$mac'"; + } + } + //var_dump($filter, $where);exit; return $where; } diff --git a/public/js/pages/Cpeprovisioning/Cpeprovisioning.css b/public/js/pages/Cpeprovisioning/Cpeprovisioning.css index 431fe7c92..0456872a3 100644 --- a/public/js/pages/Cpeprovisioning/Cpeprovisioning.css +++ b/public/js/pages/Cpeprovisioning/Cpeprovisioning.css @@ -83,7 +83,7 @@ body { .cpe-card-content { padding: 0.75rem 1rem; display: grid; - grid-template-columns: minmax(280px, 1.25fr) minmax(280px, 1.25fr) minmax(280px, 1.5fr); + grid-template-columns: repeat(4, minmax(280px, 1fr)); /* Changed to 4 columns */ gap: 1rem 1.5rem; } diff --git a/public/js/pages/Cpeprovisioning/Cpeprovisioning.js b/public/js/pages/Cpeprovisioning/Cpeprovisioning.js index ec93ede76..ac3150a99 100644 --- a/public/js/pages/Cpeprovisioning/Cpeprovisioning.js +++ b/public/js/pages/Cpeprovisioning/Cpeprovisioning.js @@ -87,7 +87,7 @@ Vue.component('Cpeprovisioning', { -
+
Produkt & VLANs

{{ item.product_name }} {{ item.product_code }}

@@ -103,6 +103,22 @@ Vue.component('Cpeprovisioning', {

+
+ +
+
Aktionen
+
+ + +
@@ -125,6 +141,20 @@ Vue.component('Cpeprovisioning', {
+ + +
+
+
+
+
+
+ +
+
+
`, data() { @@ -143,7 +173,9 @@ Vue.component('Cpeprovisioning', { delayOptions: [ { value: '1', text: 'Nicht anzeigen' }, { value: '0', text: 'Anzeigen' } ], page: 1, pagination: {}, - debouncedFetchData: null + debouncedFetchData: null, + extensionId: 'jglijfiddilckddlmbnlojmmlahboffh', + showExtensionIdModal: false } }, computed: { @@ -157,8 +189,35 @@ Vue.component('Cpeprovisioning', { }, created() { this.debouncedFetchData = _.debounce(this.fetchData.bind(this, true), 400); + const savedExtensionId = localStorage.getItem('radiusExtensionId'); + if (savedExtensionId) { + this.extensionId = savedExtensionId; + } + window.addEventListener('keydown', this.handleKeydown); + }, + beforeDestroy() { + window.removeEventListener('keydown', this.handleKeydown); }, methods: { + handleKeydown(e) { + if (e.code === 'KeyE' && e.ctrlKey && e.altKey) { + e.preventDefault(); + this.openExtensionIdModal(); + } + }, + openExtensionIdModal() { + this.showExtensionIdModal = true; + }, + saveExtensionId() { + localStorage.setItem('radiusExtensionId', this.extensionId); + this.showExtensionIdModal = false; + window.notify('success', 'Extension ID gespeichert.'); + }, + isValidMac(mac) { + if (!mac) return false; + const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; + return macRegex.test(mac); + }, async fetchData(isNewSearch = false) { if (isNewSearch) { this.page = 1; @@ -215,15 +274,15 @@ Vue.component('Cpeprovisioning', { markDirty(item) { this.$set(item, 'isDirty', true); }, - async checkShipping(item) { - await this.$nextTick(); + checkShipping(item) { if (item.cpe_data.shipping && item.cpe_data.routertype) { const shippingData = this.window.TT_CONFIG.ROUTER_SHIPPING_DATA[item.cpe_data.routertype]; if (shippingData) { - this.$set(item.cpe_data, 'ship_weight', shippingData.weight); - this.$set(item.cpe_data, 'ship_length', shippingData.length); - this.$set(item.cpe_data, 'ship_width', shippingData.width); - this.$set(item.cpe_data, 'ship_height', shippingData.height); + item.cpe_data.ship_weight = shippingData.weight; + item.cpe_data.ship_length = shippingData.length; + item.cpe_data.ship_width = shippingData.width; + item.cpe_data.ship_height = shippingData.height; + item.cpe_data = { ...item.cpe_data }; // Trigger reactivity this.window.notify('success', 'Versanddaten wurden automatisch ausgefüllt.'); } } else if (!item.cpe_data.shipping) { @@ -231,6 +290,66 @@ Vue.component('Cpeprovisioning', { item.cpe_data.ship_length = ''; item.cpe_data.ship_width = ''; item.cpe_data.ship_height = ''; + item.cpe_data = { ...item.cpe_data }; // Trigger reactivity + } + }, + isVlanSelected(item) { + return item.vlans && Object.values(item.vlans).some(v => v.checked); + }, + createRadiusUser(item) { + const message = { + type: "INITIATE_CREATE_RADIUS_USER", + payload: { + customerName: item.customer, + address: item.owner_full_address, + servicePin: item.spin, + routerMac: item.cpe_data.mac, + customerNumber: item.owner_customer_number + } + }; + + if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) { + try { + chrome.runtime.sendMessage(this.extensionId, message, (response) => { + if (chrome.runtime.lastError) { + console.warn("Senden an Erweiterung fehlgeschlagen:", chrome.runtime.lastError.message); + window.notify('warning', 'Daten konnten nicht an die Erweiterung gesendet werden. (Drücke STRG + ALT + E zum Konfigurieren)'); + } else { + console.log("Erweiterung hat geantwortet:", response); + window.notify('success', 'Radius Anlage an Erweiterung übergeben.'); + } + }); + } catch (e) { + console.error("Fehler beim Senden an die Erweiterung:", e); + window.notify('error', 'Fehler beim Senden an die Erweiterung.'); + } + } else { + console.warn("Chrome Extension Messaging API nicht verfügbar."); + window.notify('warning', 'Chrome Messaging API nicht gefunden.'); + } + }, + async testAcsVlan(item) { + const button = this.$el.querySelector(`[data-orderproduct-id="${item.orderproduct_id}"] .btn-info`); + if (button) { + button.disabled = true; + } + + try { + const { data } = await axios.post(window.TT_CONFIG.CPE_PROV_API_TEST_ACS_VLAN_URL, { + mac: item.cpe_data.mac + }); + + if (data.success) { + window.notify('success', `ACS VLAN Zuweisung erfolgreich: VLAN ${data.vlan_id}`); + } else { + window.notify('error', data.message || 'Fehler bei der ACS VLAN Zuweisung.'); + } + } catch (error) { + window.notify('error', 'Ein unerwarteter Fehler ist aufgetreten.'); + } finally { + if (button) { + button.disabled = false; + } } }, _buildSavePayload(item) { @@ -275,5 +394,6 @@ Vue.component('Cpeprovisioning', { }, mounted() { this.fetchData(true); + window.addEventListener('keydown', this.handleKeydown); } }); \ No newline at end of file diff --git a/public/js/pages/Radius/RadiusUsers.js b/public/js/pages/Radius/RadiusUsers.js index ca066fd8c..0507dd526 100644 --- a/public/js/pages/Radius/RadiusUsers.js +++ b/public/js/pages/Radius/RadiusUsers.js @@ -610,7 +610,7 @@ Vue.component('radius-users', { chrome.runtime.sendMessage(extensionId, message, (response) => { if (chrome.runtime.lastError) { console.warn("Senden an Erweiterung fehlgeschlagen:", chrome.runtime.lastError.message); - window.notify('warning', 'Scan-Daten konnten nicht an die Erweiterung gesendet werden.'); + window.notify('warning', 'Scan-Daten konnten nicht an die Erweiterung gesendet werden. (Drücke STRG + ALT + E zum Konfigurieren)'); } else { console.log("Erweiterung hat geantwortet:", response); }