Features:

* Komplettes kabelmanagement auf Rack He Modul Basis
This commit is contained in:
Daniel Spitzer
2025-12-02 12:48:51 +01:00
parent 3677a21fc5
commit 68f29ab995
12 changed files with 7531 additions and 681 deletions

View File

@@ -1,274 +1,11 @@
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/header.php"); ?>
<link href="<?= self::getResourcePath() ?>assets/css/datatables-std.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<style>
.card-border {
border-left: 1px solid #428bca;
border-left-width: 5px;
border-radius: 3px;
}
.font-weight-500 {
font-weight: 500;
}
.order-date-pill {
margin: 2px;
white-space: nowrap;
}
.rack-he:hover {
box-shadow: 2px 2px 5px 2px rgba(0, 0, 0, 0.31);
}
.rack-color-lwl {
background: rgb(159, 255, 96);
background: linear-gradient(180deg, rgb(129 223 68) 0%, rgb(62 155 3) 100%);
color: #000000;
padding-top: 2px;
}
.rack-color-device {
background: rgb(182, 198, 240);
background: linear-gradient(180deg, rgba(182, 198, 240, 1) 0%, rgb(131 158 255 / 82%) 100%);
font-weight: 500;
padding-top: 2px;
}
.rack-color-infra {
background: rgb(251 181 109);
background: linear-gradient(180deg, rgb(247 184 118) 0%, rgb(255 149 40) 100%);
font-weight: 500;
padding-top: 2px;
}
.rack-color-panel {
background: rgb(150, 243, 222);
background: linear-gradient(180deg, rgba(150, 243, 222, 1) 0%, rgba(87, 249, 212, 1) 100%);
font-weight: 500;
padding-top: 2px;
}
.rack-color-blocked {
background: rgb(255, 0, 0);
background: linear-gradient(180deg, rgba(255, 0, 0, 1) 0%, rgba(196, 0, 0, 1) 100%);
font-weight: 500;
padding-top: 2px;
color: #fff;
}
.rack-color-rpanel {
background: rgb(88, 201, 240);
background: linear-gradient(180deg, rgba(88, 201, 240, 1) 0%, rgba(64, 188, 231, 1) 100%);
font-weight: 500;
padding-top: 2px;
}
.user-select-none {
user-select: none;
}
.fa-edit {
margin-right: 5px;
margin-top: 2px;
cursor: pointer;
}
.rack-name {
}
.move-handle {
cursor: grab;
margin-left: 5px;
margin-top: 2px;
}
.move-handle:active {
cursor: grabbing;
}
.border-right {
border-right-color: #dee2e6 !important;
}
.form-control:disabled, .form-control[readonly] {
background-color: #d7d7d7;
opacity: 1;
}
.switch-rack-side {
margin-right: 8px;
margin-top: 2px;
cursor: pointer;
}
.cable-container {
display: flex;
width: 100%;
height: 100%;
align-items: stretch;
color: #000;
cursor: pointer;
}
.cable-item {
border-right: 1px solid #797979;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 1.1;
color: #333;
background-color: #e1ffdf;
cursor: pointer;
overflow: hidden;
white-space: nowrap;
padding: 1px 0;
position: relative;
}
.cable-item .port-range, .cable-item .fiber-range {
font-size: 12px;
color: #040404;
font-weight: 500;
}
.cable-item:hover {
background-color: #cfffc8;
}
.cable-item:last-child {
border-right: none;
}
.cable-free {
flex-grow: 1;
}
.context-menu {
display: none;
position: absolute;
z-index: 1000;
width: 180px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.15);
}
.context-menu ul {
list-style: none;
padding: 5px 0;
margin: 0;
}
.context-menu ul li {
padding: 8px 15px;
cursor: pointer;
font-size: 14px;
}
.context-menu ul li:hover {
background-color: #f0f0f0;
}
.context-menu ul li i {
margin-right: 10px;
color: #555;
}
#cable-context-menu {
position: absolute; /* Popper setzt top/left */
z-index: 20000;
transform: none !important; /* falls doch mal ein transform reingeschummelt wird */
}
.dropdown-menu[x-placement^=left], .dropdown-menu[x-placement^=right], .dropdown-menu[x-placement^=top] {
top: unset;
-webkit-animation: none !important;
animation: none !important;
}
.context-span {
min-width: 25px;
display: inline-block;
}
.dropdown-item {
padding: .3rem 1.2rem;
}
.w-10 {
width: 10% !important;
}
.cable-highlight {
background-color: #e6ff00 !important;
border: 2px solid #e0a800 !important;
color: #000;
}
/* NEU: Animation für das "Aufblinken" */
.cable-flash {
/*animation: flash-animation 1s;*/
}
@keyframes flash-animation {
0% {
transform: scale(1);
}
25% {
transform: scale(1.05);
}
50% {
transform: scale(1);
}
75% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
/* Sorgt dafür, dass die Autocomplete-Vorschläge über anderen Elementen liegen */
.ui-autocomplete {
z-index: 2000;
}
.module-highlight {
/*box-shadow: inset 0 0 0 3px #007bff !important; !* Ein auffälliger, blauer Innen-Rahmen *!*/
/*transition: box-shadow 0.3s ease-in-out;*/
}
.rack-color-lwl-planned {
background: rgb(255, 0, 0);
background: linear-gradient(180deg, rgba(255, 0, 0, 1) 0%, rgba(196, 0, 0, 1) 100%);
font-weight: 500;
padding-top: 2px;
color: #fff;
}
.port-info {
font-size: 12px;
font-weight: 500;
}
.cable-description
{
font-size: 12px;
font-style: italic;
color: #000000;
white-space: normal;
line-height: 1.2;
font-weight: 400;
}
</style>
<link href="<?= self::getResourcePath() ?>assets/css/print.min.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<!--<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css">-->
<link href="<?= self::getResourcePath() ?>css/pages/Pop/Detail.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<div class="row">
<div class="col-12">
<div class="page-title-box">
@@ -692,6 +429,10 @@ if (!empty(trim($pops->vlan_ipv6)))
class="rack-name"><i
class="fa-regular fa-arrows-up-down-left-right move-handle float-left"></i><?= $poprack['rack']['name']; ?>&nbsp;<span
class="rack-side-indicator font-weight-normal">-&nbsp;Vorderseite</span></span>
<i class="fas fa-expand float-right mr-2 rack-fullscreen-btn"
title="Vollbild & Drucken"
style="cursor: pointer; margin-top: 2px;"
data-rackid="<?= $poprack['rack']['id']; ?>"></i>
<i class="fas fa-sync-alt float-right switch-rack-side"
title="Seite wechseln"></i>
@@ -779,7 +520,8 @@ if (!empty(trim($pops->vlan_ipv6)))
<div class="col-lg-1"></div>
<label class="col-lg-4 col-form-label" for="cable-description">Beschreibung</label>
<div class="col-lg-6">
<textarea id="cable-description" name="cable-description" class="form-control" rows="3"></textarea>
<textarea id="cable-description" name="cable-description" class="form-control"
rows="3"></textarea>
</div>
</div>
<div class="alert alert-danger text-center" role="alert" style="display: none"
@@ -849,7 +591,8 @@ if (!empty(trim($pops->vlan_ipv6)))
<div class="col-lg-1"></div>
<label class="col-lg-4 col-form-label" for="cable-description">Beschreibung</label>
<div class="col-lg-6">
<textarea id="edit-cable-description" name="cable-description" class="form-control" rows="3"></textarea>
<textarea id="edit-cable-description" name="cable-description" class="form-control"
rows="3"></textarea>
</div>
</div>
<div class="alert alert-danger text-center" role="alert" style="display: none"
@@ -864,6 +607,80 @@ if (!empty(trim($pops->vlan_ipv6)))
</div>
</div>
</div>
<div class="modal fade" id="fiberPlanCableModal" tabindex="-1" role="dialog" aria-labelledby="fiberPlanCableModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="fiberPlanCableModalLabel">
<i class="fa fa-cable"></i> Kabel-Details
<!-- <button class="btn btn-primary btn-sm ml-3"-->
<!-- id="modal-edit-cable-btn"-->
<!-- style="display: none;">-->
<!-- <i class="fas fa-table"></i> Excel-Editor-->
<!-- </button>-->
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="fiberPlanCableModalBody">
<div class="text-center py-5">
<div class="spinner-border text-primary" role="status"></div>
<p class="mt-3">Lade Kabel-Informationen...</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="excelEditorModal" tabindex="-1" role="dialog" style="display: none;">
<div class="modal-dialog modal-fullscreen-custom" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-table"></i> Kabel bearbeiten: <span id="cable-name"></span>
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body p-0 d-flex flex-column" style="flex: 1; overflow: hidden;">
<div id="handsontable-container" style="flex: 1; width: 100%; overflow: hidden;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fas fa-times"></i> Abbrechen
</button>
<button type="button" class="btn btn-success" id="save-excel-data">
<i class="fas fa-save"></i> Speichern
</button>
</div>
</div>
</div>
</div>
<div id="customRackOverlay">
<div class="overlay-header">
<h3 id="overlayTitle">Rack Detail</h3>
<div>
<button class="btn btn-info mr-2" onclick="printRackOverlay('landscape');" title="Querformat drucken">
<i class="fas fa-print"></i> <i class="fas fa-arrows-alt-h"></i> Quer
</button>
<button class="btn btn-info mr-2" onclick="printRackOverlay('portrait');" title="Hochformat drucken">
<i class="fas fa-print"></i> <i class="fas fa-arrows-alt-v"></i> Hoch
</button>
<button class="btn btn-secondary" onclick="$('#customRackOverlay').fadeOut(); $('body').css('overflow', '');">
<i class="fas fa-times"></i> Schließen
</button>
</div>
</div>
<div id="overlayContent" class="d-flex justify-content-center overlayContent">
</div>
</div>
<div id="cable-context-menu" class="dropdown-menu" style="display:none;">
<a class="dropdown-item" href="#" data-action="view"><span class="context-span"><i
class="fas fa-eye fa-fw"></i></span> Ansehen</a>
@@ -889,10 +706,26 @@ if (!empty(trim($pops->vlan_ipv6)))
let linkAddCable = "<?= self::getUrl("Poprackmodulecable", "api", ['do' => 'addCable']) ?>";
let linkRemoveCable = "<?= self::getUrl("Poprackmodulecable", "api", ['do' => 'removeCable']) ?>";
let linkUpdateCable = "<?= self::getUrl("Poprackmodulecable", "api", ['do' => 'updateCable']) ?>";
let linkGetCableDetails = "<?= self::getUrl('Pop', 'api', ['do' => 'getCableDetails']) ?>";
let linkGetFiberPath = "<?= self::getUrl('Pop', 'api', ['do' => 'getFiberPath']) ?>";
let linkGetCableFibersForEdit = "<?php echo mfBaseController::getUrl('Pop', 'api', array('do' => 'getCableFibersForEdit')); ?>";
let linkSaveCableFibers = "<?php echo mfBaseController::getUrl('Pop', 'api', array('do' => 'saveCableFibers')); ?>";
L.MakiMarkers.accessToken = '<?=TT_MAPBOX_TILE_API_TOKEN?>';
var allCables = <?php echo $cables_json ?? '[]'; ?>;
var popNetworkIds = <?php echo $popnetwork_ids ?? '[]'; ?>;
$(function () {
$('[data-toggle="popover"]').popover('dispose');
$('[data-toggle="popover"]').popover();
});
</script>
<!--<script src="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js"></script>-->
<script type="text/javascript" src="<?= self::getResourcePath() ?>assets/js/print.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript" src="<?= self::getResourcePath() ?>js/pages/pop/detail.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript" src="<?= self::getResourcePath() ?>js/pages/pop/fiber.js?<?= $git_merge_ts ?>"></script>
<!--<script type="text/javascript"-->
<!-- src="--><?php //= self::getResourcePath() ?><!--js/pages/pop/fibertable.js?--><?php //= $git_merge_ts ?><!--"></script>-->
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/datatables-std.js?<?= $git_merge_ts ?>"></script>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -54,25 +54,53 @@ for ($i = 1; $i <= $rack_he; $i++) : ?>
}
$portCount = $cable['port_end'] - $cable['port_start'] + 1;
$cableWidth = ($portCount / $slots['ports']) * 100;
$cableTitle = htmlspecialchars($cable['cable_name'] . ' (Ports ' . $cable['port_start'] . '-' . $cable['port_end'] . ')', ENT_QUOTES);
if (!empty($cable['description'])) {
$cableTitle .= ' | ' . htmlspecialchars($cable['description'], ENT_QUOTES);
$popTitle = htmlspecialchars($cable['cable_name'], ENT_QUOTES);
$popContent = '<strong>Ports:</strong> ' . $cable['port_start'] . '-' . $cable['port_end'];
if (!empty($cable['fiber_start'])) {
$popContent .= '<br><strong>Fasern:</strong> ' . $cable['fiber_start'] . '-' . $cable['fiber_end'];
}
$cellContent .= '<div class="cable-item" title="' . $cableTitle . '" style="width: ' . $cableWidth . '%;" data-cable-id="' . $cable['id'] . '"
if (!empty($cable['description'])) {
$popContent .= '<br><div class="mt-1 text-muted border-top pt-1 small"><em>' . htmlspecialchars($cable['description'], ENT_QUOTES) . '</em></div>';
}
$narrowClass = ($cableWidth < 12) ? 'narrow' : '';
$cellContent .= '<div class="cable-item ' . $narrowClass . '"
data-toggle="popover"
data-trigger="hover"
data-html="true"
data-placement="top"
data-container="body"
title="' . $popTitle . '"
data-content="' . htmlspecialchars($popContent, ENT_QUOTES) . '"
style="width: ' . $cableWidth . '%;"
data-cable-id="' . $cable['id'] . '"
data-cable-name="' . htmlspecialchars($cable['cable_name'], ENT_QUOTES) . '"
data-port-start="' . $cable['port_start'] . '"
data-port-end="' . $cable['port_end'] . '"
data-fiber-start="' . ($cable['fiber_start'] ?? '') . '"
data-fiber-end="' . ($cable['fiber_end'] ?? '') . '"
data-description="' . htmlspecialchars($cable['description'] ?? '', ENT_QUOTES) . '">';
$cellContent .= '<span><b>' . $cable['cable_name'] . "</b></span>";
$fontSize = ($cableWidth < 10) ? '10px' : 'inherit';
$cellContent .= '<span style="font-size:'.$fontSize.'; overflow:hidden; text-overflow:ellipsis; max-width:100%; white-space:nowrap;"><b>' . $cable['cable_name'] . "</b></span>";
if (!empty($cable['description'])) {
$cellContent .= '<span class="cable-description" >' . htmlspecialchars($cable['description'], ENT_QUOTES) . '</span>';
$descVisibilityClass = ($cableWidth <= 20) ? 'hide-if-narrow' : '';
$cellContent .= '<span class="cable-description ' . $descVisibilityClass . '" >' . htmlspecialchars($cable['description'], ENT_QUOTES) . '</span>';
}
$cellContent .= '<div class="port-ranges-container" style="display: flex; justify-content: center; width: 100%; font-size: 10px; gap: 8px;">';
$cellContent .= '<tool class="port-range">P: ' . $cable['port_start'] . '-' . $cable['port_end'] . '</tool>';
$rangesVisibilityClass = ($cableWidth <= 5) ? 'hide-if-narrow' : '';
$infoSize = '10px';
$infoGap = '8px';
if ($cableWidth < 8) { $infoSize = '8px'; $infoGap = '1px'; }
elseif ($cableWidth < 15) { $infoSize = '9px'; $infoGap = '3px'; }
$toolStyle = 'font-size: ' . $infoSize . '; white-space: nowrap;';
$cellContent .= '<div class="port-ranges-container ' . $rangesVisibilityClass . '" style="display: flex; justify-content: center; width: 100%; line-height: 1.1; gap: ' . $infoGap . ';">';
$cellContent .= '<tool class="port-range" style="' . $toolStyle . '">P: ' . $cable['port_start'] . '-' . $cable['port_end'] . '</tool>';
if (!empty($cable['fiber_start'])) {
$cellContent .= '<tool class="fiber-range">F: ' . $cable['fiber_start'] . '-' . $cable['fiber_end'] . "</tool>";
$cellContent .= '<tool class="fiber-range" style="' . $toolStyle . '">F: ' . $cable['fiber_start'] . '-' . $cable['fiber_end'] . "</tool>";
}
$cellContent .= '</div>';