Merge branch 'CpeProvisioning/add-new' into 'master'
Add modal for configuring Chrome Extension ID and enhance CPE provisioning functionality See merge request fronk/thetool!1888
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ Vue.component('Cpeprovisioning', {
|
||||
<tt-textarea label="Kommentar" v-model="item.cpe_data.note" @input="markDirty(item)" sm no-form-group/>
|
||||
</div>
|
||||
|
||||
<div class="content-column action-column">
|
||||
<div class="content-column">
|
||||
<h5>Produkt & VLANs</h5>
|
||||
<p><strong>{{ item.product_name }}</strong> <small class="text-muted">{{ item.product_code }}</small></p>
|
||||
<p>
|
||||
@@ -103,6 +103,22 @@ Vue.component('Cpeprovisioning', {
|
||||
</tt-chip>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-column action-column">
|
||||
<h5>Aktionen</h5>
|
||||
<div class="action-buttons">
|
||||
<tt-button text="In Radius anlegen"
|
||||
@click="createRadiusUser(item)"
|
||||
:disabled="!isValidMac(item.cpe_data.mac)"
|
||||
sm
|
||||
additional-class="btn-primary" />
|
||||
<tt-button text="ACS Auto VLAN Zuweisung testen"
|
||||
@click="testAcsVlan(item)"
|
||||
:disabled="!isVlanSelected(item) || !isValidMac(item.cpe_data.mac)"
|
||||
sm
|
||||
additional-class="btn-info mt-2" />
|
||||
</div>
|
||||
<div class="finish-wrapper mt-auto">
|
||||
<label class="col-form-label col-form-label-sm">Konfig abgeschlossen</label>
|
||||
<tt-switch v-model="item.cpe_data.routerconfig_finished" @input="markDirty(item)"/>
|
||||
@@ -125,6 +141,20 @@ Vue.component('Cpeprovisioning', {
|
||||
<div class="text-center mt-4 mb-4" v-if="!loading && pagination.total_pages && page <= pagination.total_pages">
|
||||
<tt-button text="Weitere laden" @click="fetchData(false)" additional-class="btn-primary" />
|
||||
</div>
|
||||
|
||||
<tt-modal :show="showExtensionIdModal" title="Extension ID Konfigurieren" @close="showExtensionIdModal=false">
|
||||
<div>
|
||||
<div class="field"><label style="margin-bottom: 8px; font-size: 14px;">Chrome Extension ID</label>
|
||||
<div class="input-wrap"><i class="fa-duotone fa-puzzle-piece input-icon"></i><input class="ri" type="text"
|
||||
v-model.trim="extensionId"
|
||||
placeholder="z.B. jglijfiddilckddlmbnlojmmlahboffh">
|
||||
</div>
|
||||
</div>
|
||||
<div class="cluster" style="justify-content: flex-end; margin-top: 24px; gap: 12px;">
|
||||
<button class="primary-btn" @click="saveExtensionId">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</tt-modal>
|
||||
</div>
|
||||
`,
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user