451 lines
26 KiB
PHP
451 lines
26 KiB
PHP
<?php
|
|
// --- Configuration ---
|
|
$pagination_baseurl = $this->getUrl($Mod,"Index");
|
|
$pagination_baseurl_params = ["filter" => $filter]; // Filters passed via GET
|
|
$pagination_entity_name = "Patchungen";
|
|
|
|
// Ensure $filter is always an array
|
|
if(!is_array($filter)) {
|
|
$filter = [];
|
|
}
|
|
|
|
// Helper function for safe output (assuming not already handled by framework/template engine)
|
|
function e($string) {
|
|
return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
$additionalHead = [
|
|
'<link rel="manifest" href="/assets/pwa/patching-manifest.json">'
|
|
];
|
|
|
|
?>
|
|
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/header.php"); ?>
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-title-box">
|
|
<div class="page-title-right">
|
|
<ol class="breadcrumb m-0">
|
|
<li class="breadcrumb-item"><a href="<?=e(self::getUrl("Dashboard"))?>"><?=e(MFAPPNAME_SLUG)?></a></li>
|
|
<li class="breadcrumb-item active">Patchungen</li>
|
|
</ol>
|
|
</div>
|
|
<h4 class="page-title">Patchungen</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-lg-12">
|
|
|
|
<div class="card">
|
|
<div class="card-body mb-3">
|
|
<h4 class="header-title mb-3">Filter</h4>
|
|
|
|
<form method="get" action="<?=e(self::getUrl("Patching"))?>">
|
|
<div class="row">
|
|
|
|
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
|
<label class="form-label" for="filter_network_id">Netzgebiet</label>
|
|
<select name="filter[network_id]" id="filter_network_id" class="form-control">
|
|
<option value=""></option>
|
|
<?php foreach($mynetworks as $fnet): ?>
|
|
<option value="<?=e($fnet->id)?>" <?=($filter['network_id'] ?? null == $fnet->id) ? "selected='selected'" : ""?>><?=e($fnet->name)?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
|
<label class="form-label" for="filter_pop_id">POP</label>
|
|
<select name="filter[pop_id]" id="filter_pop_id" class="form-control">
|
|
<option value=""></option>
|
|
<?php foreach($mynetworks as $fnet): ?>
|
|
<?php if(is_array($fnet->pops) && count($fnet->pops)): ?>
|
|
<optgroup label="<?=e($fnet->name)?>">
|
|
<?php foreach($fnet->pops as $pop): ?>
|
|
<option value="<?=e($pop->id)?>" <?=($filter['pop_id'] ?? null == $pop->id) ? "selected='selected'" : ""?>><?=e($pop->name)?></option>
|
|
<?php endforeach; ?>
|
|
</optgroup>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
|
<label class="form-label" for="filter_patched">Patchstatus</label>
|
|
<select name="filter[patched]" id="filter_patched" class="form-control">
|
|
<option value="0" <?=((int)($filter['patched'] ?? 0) < 1) ? "selected='selected'" : ""?>>Nicht gepatched</option>
|
|
<option value="1" <?=((int)($filter['patched'] ?? 0) == 1) ? "selected='selected'" : ""?>>Gepatched</option>
|
|
<option value="2" <?=((int)($filter['patched'] ?? 0) == 2) ? "selected='selected'" : ""?>>Vorpatchen</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
|
<label class="form-label" for="filter_hide_delayed_finish">Verzögerte Herstellung</label>
|
|
<select name="filter[hide_delayed_finish]" id="filter_hide_delayed_finish" class="form-control">
|
|
<option value="0" <?=(isset($filter["hide_delayed_finish"]) && $filter['hide_delayed_finish'] != 1) ? "selected='selected'" : ""?>>Anzeigen</option>
|
|
<option value="1" <?=(!isset($filter["hide_delayed_finish"]) || $filter['hide_delayed_finish'] == 1) ? "selected='selected'" : ""?>>Nicht anzeigen</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6 col-lg-1 mb-3">
|
|
<label class="form-label" for="filter_code">Objekt ID</label>
|
|
<input type="text" class="form-control" name="filter[code]" id="filter_code" value="<?=e($filter['code'] ?? '')?>" />
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6 col-lg-3 mb-3">
|
|
<label class="form-label" for="filter_street">Straße</label>
|
|
<input type="text" class="form-control" name="filter[street]" id="filter_street" value="<?=e($filter['street'] ?? '')?>" />
|
|
</div>
|
|
|
|
</div>
|
|
<div class="row mt-2">
|
|
<div class="col-12">
|
|
<button type="submit" class="btn btn-primary">Filter anwenden</button>
|
|
<a class="btn btn-secondary" href="<?=e(self::getUrl("Patching"))?>">Filter zurücksetzen</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-body mb-3">
|
|
<h4 class="header-title">Patchungen</h4>
|
|
|
|
<?php // Include pagination controls - Top ?>
|
|
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
|
|
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
|
|
|
|
<div class="table-responsive mt-3"> <?php // Added table-responsive wrapper and margin-top ?>
|
|
<table class="table table-striped table-hover">
|
|
<thead class="table-bordered"> <?php // Added thead and border class ?>
|
|
<tr>
|
|
<th colspan="4" class="text-center">Standort</th>
|
|
<th colspan="2" class="text-center">ODF</th>
|
|
<th colspan="5" class="text-center">Abschluss/Device</th>
|
|
<th>Aktion</th> <?php // Renamed last header ?>
|
|
</tr>
|
|
<tr>
|
|
<th>Netzgebiet</th>
|
|
<th class="text-nowrap">POP</th> <?php // Added text-nowrap for min-width effect ?>
|
|
<th class="text-nowrap">Kunde</th> <?php // Added text-nowrap ?>
|
|
<th>Standort</th> <?php // Allow wrapping ?>
|
|
<th class="text-nowrap">Patchposition ODF</th> <?php // Added text-nowrap ?>
|
|
<th style="min-width: 105px">ODF Port</th> <?php // Select handles resize ok ?>
|
|
<th style="min-width: 195px">Typ</th> <?php // Select handles resize ok ?>
|
|
<th style="min-width: 190px" class="text-nowrap">Splitter / Gerät</th> <?php // Added text-nowrap ?>
|
|
<th style="min-width: 90px" class="text-nowrap">Port</th> <?php // Added text-nowrap ?>
|
|
<th>Gepatched</th>
|
|
<th class="text-nowrap">Von</th> <?php // Added text-nowrap ?>
|
|
<th class="text-nowrap">Speichern</th> <?php // Added text-nowrap ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach($terminations as $term): ?>
|
|
<?php
|
|
// --- Prepare complex data for cleaner output ---
|
|
|
|
// ODF Patch Position
|
|
$wfItems = $term->workflowitems ?? [];
|
|
$istSchrankValue = $wfItems["ist_schrank"]->value ?? null;
|
|
$schrankValue = $wfItems["schrank"]->value ?? null;
|
|
$istBaugruppeValue = $wfItems["ist_baugruppe"]->value ?? null;
|
|
$baugruppeValue = $wfItems["baugruppe"]->value ?? null;
|
|
$istModulValue = $wfItems["ist_modul"]->value ?? null;
|
|
$modulValue = $wfItems["modul"]->value ?? null;
|
|
$istPortsValue = $wfItems["ist_ports"]->value ?? null;
|
|
$portsValue = $wfItems["ports"]->value ?? null;
|
|
|
|
$schrank = ($istSchrankValue && $istSchrankValue->id) ? $istSchrankValue->value_string : ($schrankValue ? $schrankValue->value_string : '');
|
|
$baugruppe = ($istBaugruppeValue && $istBaugruppeValue->id) ? $istBaugruppeValue->value_string : ($baugruppeValue ? $baugruppeValue->value_string : '');
|
|
$modul = ($istModulValue && $istModulValue->id) ? $istModulValue->value_string : ($modulValue ? $modulValue->value_string : '');
|
|
$ports = ($istPortsValue && $istPortsValue->id) ? $istPortsValue->value_string : ($portsValue ? $portsValue->value_string : '');
|
|
|
|
$odfPositionTitle = "Schrank: " . e($schrank) . " / Einschub: " . e($baugruppe) . " / Modul: " . e($modul) . " / Port: " . e($ports);
|
|
$odfPositionDisplay = e($schrank) . " / " . e($baugruppe) . " / " . e($modul) . " / " . e($ports);
|
|
|
|
// Patched By Info
|
|
$patching = $term->patching ?? null;
|
|
$patcher = $patching ? ($patching->patcher ?? null) : null;
|
|
$patchedByTitle = '';
|
|
$patchedByDisplay = '';
|
|
if ($patching && $patching->patched == 1 && $patching->patched_by && $patcher) {
|
|
$patcherName = $patcher->name ?? 'Unbekannt';
|
|
$patcherCompany = $patcher->address ? $patcher->address->getCompanyOrName(true) : 'N/A';
|
|
$patchedDate = date("d.m.Y H:i", $patching->patched_date);
|
|
$patchedByTitle = "Gepatched: {$patchedDate} von " . e($patcherName) . " (" . e($patcherCompany) . ")";
|
|
$patchedByDisplay = $patcher->getAbbrName() ?? '';
|
|
}
|
|
|
|
// Customer Info
|
|
$customer = $term->order->owner ?? null;
|
|
$customerNumber = $customer ? $customer->customer_number : '';
|
|
$customerName = $customer ? $customer->getCompanyOrName() : '';
|
|
|
|
// Location Info
|
|
$building = $term->building ?? null;
|
|
$networkName = $building ? ($building->network->name ?? '') : '';
|
|
$popName = '';
|
|
$termPop = $term->getPop(); // Call potentially expensive method once
|
|
if ($termPop) {
|
|
$popName = $termPop->name ?? '';
|
|
} elseif ($building && $building->pop) {
|
|
$popName = $building->pop->name ?? '';
|
|
}
|
|
$street = $building->street ?? '';
|
|
$zip = $building->zip ?? '';
|
|
$city = $building->city ?? '';
|
|
|
|
// Linework Ports
|
|
$lineworkPorts = $term->getLineworkportPairs() ?? [];
|
|
$currentLineworkPort = $patching ? $patching->linework_ports : null;
|
|
|
|
// Device Info
|
|
$deviceType = $patching ? $patching->device_type : '';
|
|
$deviceName = $patching ? $patching->device_name : '';
|
|
$devicePort = $patching ? $patching->device_port : '';
|
|
$isPatched = $patching ? ($patching->patched == 1) : false;
|
|
?>
|
|
<tr class="align-middle"> <?php // Added align-middle ?>
|
|
<td><?=e($networkName)?></td>
|
|
<td><?=e($popName)?></td>
|
|
<td><?=e($customerNumber)?><br /><?=e($customerName)?></td>
|
|
<td>
|
|
<?=e($street)?><br />
|
|
<?=e($zip)?> <?=e($city)?>
|
|
</td>
|
|
<td class="text-mono text-primary" title="<?=$odfPositionTitle?>">
|
|
<?=$odfPositionDisplay?>
|
|
</td>
|
|
<td>
|
|
<select name="linework_port" form="term-form-<?=e($term->id)?>" class="form-control"> <?php // Removed -sm ?>
|
|
<?php foreach($lineworkPorts as $portGroup): ?>
|
|
<?php foreach($portGroup as $p): ?>
|
|
<option value="<?=e($p)?>" <?=($currentLineworkPort == $p) ? "selected='selected'" : ""?>><?=e($p)?></option>
|
|
<?php endforeach; ?>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<select name="device_type" form="term-form-<?=e($term->id)?>" class="form-control"> <?php // Removed -sm ?>
|
|
<option value=""></option>
|
|
<option value="splitter" <?=($deviceType == "splitter") ? "selected='selected'" : ""?>>Splitter</option>
|
|
<option value="pon" <?=($deviceType == "pon") ? "selected='selected'" : ""?>>Shared PON-Port</option>
|
|
<option value="switch" <?=($deviceType == "switch") ? "selected='selected'" : ""?>>Switch</option>
|
|
</select>
|
|
</td>
|
|
<td><input type="text" class="form-control device-name-input" form="term-form-<?=e($term->id)?>" name="device_name" value="<?=e($deviceName)?>" placeholder="Splitter / Gerät" /></td> <?php // Removed -sm ?>
|
|
<td><input type="text" class="form-control" form="term-form-<?=e($term->id)?>" name="device_port" value="<?=e($devicePort)?>" placeholder="Port" /></td> <?php // Removed -sm ?>
|
|
<td class="text-center"> <?php // Added text-center ?>
|
|
<input type="hidden" name="patched" value="0" form="term-form-<?=e($term->id)?>" /> <?php // Ensure 0 is sent if checkbox is unchecked ?>
|
|
<input type="checkbox" class="form-check-input" style="position:unset !important;height: 2em; width: 2em;" form="term-form-<?=e($term->id)?>" name="patched" value="1" <?=($isPatched) ? "checked='checked'" : ""?> /> <?php // Adjusted checkbox style slightly ?>
|
|
</td>
|
|
<td <?=!empty($patchedByTitle) ? 'title="' . $patchedByTitle . '"' : ''?>>
|
|
<?=e($patchedByDisplay)?>
|
|
</td>
|
|
<td>
|
|
<form method="post" id="term-form-<?=e($term->id)?>" action="<?=e(self::getUrl("Patching","save", ["s" => $pagination['start'] ?? 0, "filter" => $filter]))?>">
|
|
<input type="hidden" name="termination_id" value="<?=e($term->id)?>" />
|
|
<button type="submit" class="btn btn-primary">Speichern</button> <?php // Changed value to button text, removed -sm ?>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div> <?php // End table-responsive ?>
|
|
|
|
<?php // Include pagination controls - Bottom ?>
|
|
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
|
|
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
@media (max-width: 768px) {
|
|
.pagination {
|
|
overflow-x: auto;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.select2-selection__rendered {
|
|
line-height: 38px !important;
|
|
}
|
|
.select2-container .select2-selection--single {
|
|
height: 38px !important;
|
|
}
|
|
.select2-selection__arrow {
|
|
height: 37px !important;
|
|
}
|
|
|
|
@media all and (display-mode: standalone) {
|
|
#topnav {
|
|
display: none !important;
|
|
}
|
|
|
|
.wrapper {
|
|
padding-top: 0 !important;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
window.DEVICES = <?= json_encode($devices) ?>;
|
|
window.DEVICES = window.DEVICES.filter(device => {
|
|
return device.name.toLowerCase().includes('olt') || device.name.toLowerCase().includes('pon')
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
$(function() {
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.register('/Patching/sw', { scope: '/' })
|
|
.then(registration => {
|
|
console.log('Patching PWA Service Worker registered with scope:', registration.scope);
|
|
})
|
|
.catch(error => {
|
|
console.error('Patching PWA Service Worker registration failed:', error);
|
|
});
|
|
}
|
|
|
|
if (typeof $().select2 === 'undefined') {
|
|
console.error('Select2 library is not loaded.');
|
|
// Optionally display an error to the user or fall back gracefully
|
|
return; // Stop execution if Select2 is missing
|
|
}
|
|
|
|
const ipNumRegex = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[/-](\d+)$/;
|
|
|
|
// Helper: Find device object by IP
|
|
function findDeviceByIp(ip) {
|
|
if (!window.DEVICES || !Array.isArray(window.DEVICES)) {
|
|
console.error('DEVICES array not found or invalid.');
|
|
return null;
|
|
}
|
|
return window.DEVICES.find(device => device.ip === ip);
|
|
}
|
|
|
|
// Helper: Update the original hidden input field's value
|
|
function updateOriginalInput($originalInput, ip, number) {
|
|
const cleanNumber = String(number).trim(); // Ensure number is a string and trimmed
|
|
if (ip && cleanNumber) {
|
|
$originalInput.val(ip + '/' + cleanNumber).trigger('change'); // Update and trigger change
|
|
} else {
|
|
$originalInput.val('').trigger('change'); // Clear if incomplete and trigger change
|
|
}
|
|
}
|
|
|
|
// --- Initialize for each device name input ---
|
|
$('.device-name-input').each(function() {
|
|
const $originalInput = $(this);
|
|
const $td = $originalInput.closest('td'); // Get the parent cell for structure
|
|
let initialValue = $originalInput.val().trim();
|
|
|
|
// Create wrapper for the Select2 component (Select + Number Input)
|
|
// Hide it initially
|
|
const $selectWrapper = $('<div class="device-select-wrapper" style="display: none; align-items: center; gap: 5px;"></div>');
|
|
const $select = $('<select class="form-control device-select" style="flex-grow: 1; width: auto;"></select>'); // Let flexbox handle width
|
|
const $numberInput = $('<input type="number" class="form-control device-number" placeholder="Nr." style="width: 70px; flex-grow: 0; flex-shrink: 0;">'); // Fixed width number input
|
|
|
|
// Populate Select options
|
|
$select.append('<option value="">Gerät auswählen...</option>'); // Placeholder
|
|
if (window.DEVICES && Array.isArray(window.DEVICES)) {
|
|
window.DEVICES.forEach(device => {
|
|
// Display name and IP for clarity in dropdown
|
|
const displayText = `${device.name} (${device.ip})`;
|
|
$select.append($('<option></option>').val(device.ip).text(displayText));
|
|
});
|
|
} else {
|
|
console.warn("DEVICES array is missing or invalid for row:", $originalInput.attr('form'));
|
|
// Optionally add a disabled option indicating an error
|
|
$select.append('<option value="" disabled>Device list error</option>');
|
|
}
|
|
|
|
|
|
$selectWrapper.append($select).append($numberInput);
|
|
$td.append($selectWrapper); // Add the hidden wrapper to the cell
|
|
|
|
// Function to show Select2 component and hide original input
|
|
function showSelectComponent(ip, number) {
|
|
$originalInput.hide();
|
|
$select.val(ip || '').trigger('change.select2'); // Set value and update Select2 display
|
|
$numberInput.val(number || '');
|
|
$selectWrapper.css('display', 'flex'); // Use flex to show side-by-side
|
|
}
|
|
|
|
// Function to show original input and hide Select2 component
|
|
function showOriginalInput() {
|
|
$selectWrapper.hide();
|
|
$originalInput.show();
|
|
}
|
|
|
|
// Initial state determination
|
|
const match = initialValue.match(ipNumRegex);
|
|
if (match) {
|
|
const initialIp = match[1];
|
|
const initialNumber = match[2];
|
|
const foundDevice = findDeviceByIp(initialIp);
|
|
if (foundDevice) {
|
|
// Valid format and known device -> Show Select2 component
|
|
showSelectComponent(initialIp, initialNumber);
|
|
} else {
|
|
// Valid format but unknown device -> Show original input
|
|
showOriginalInput();
|
|
}
|
|
} else if (!initialValue) {
|
|
// Empty -> Show Select2 component (empty)
|
|
showSelectComponent(null, null);
|
|
} else {
|
|
// Invalid format -> Show original input
|
|
showOriginalInput();
|
|
}
|
|
|
|
// Initialize Select2 *after* adding options and inserting into DOM
|
|
$select.select2({
|
|
width: 'style', // Adjust width based on container
|
|
// dropdownParent: $td // Optional: Attach dropdown to cell if needed for positioning
|
|
});
|
|
|
|
// --- Event Listeners ---
|
|
|
|
// Update original input when Select2 or number changes
|
|
$select.on('change', function() {
|
|
const selectedIp = $(this).val();
|
|
const currentNumber = $numberInput.val();
|
|
updateOriginalInput($originalInput, selectedIp, currentNumber);
|
|
});
|
|
|
|
$numberInput.on('input change', function() { // Use 'input change' for better responsiveness
|
|
const currentIp = $select.val();
|
|
const selectedNumber = $(this).val();
|
|
updateOriginalInput($originalInput, currentIp, selectedNumber);
|
|
});
|
|
|
|
// Check original input on change/blur to potentially switch view
|
|
$originalInput.on('change blur', function() {
|
|
const currentValue = $(this).val().trim();
|
|
const currentMatch = currentValue.match(ipNumRegex);
|
|
if (currentMatch) {
|
|
const currentIp = currentMatch[1];
|
|
const currentNumber = currentMatch[2];
|
|
const device = findDeviceByIp(currentIp);
|
|
if (device) {
|
|
// Switched to valid/known format -> Show Select2
|
|
showSelectComponent(currentIp, currentNumber);
|
|
}
|
|
// else: Keep showing original input (valid format, but unknown device)
|
|
}
|
|
// else: Keep showing original input (invalid format)
|
|
});
|
|
|
|
}); // End .each loop
|
|
|
|
});
|
|
</script>
|
|
|
|
|
|
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>
|