Files
itflow/agent/modals/asset/asset_interface_multiple_add.php
johnnyq 9ba21cf352 Assets: add NAS, UPS, PDU, Modem/ONT and NVR/DVR types from a new registry in functions/asset_type.php that sets each type's icon, tone, group, fields and details; the asset form shows only the fields a type uses (fields that already hold a value stay shown) and adds a Details section per type (CPU/RAM/storage, VM host/vCPUs, switch ports/SFP/PoE/management, NAS drives/capacity/RAID, UPS capacity/output/runtime/outlets/battery dates, PDU kind/outlets/input, modem circuit ID/ISP account, printer, phone, mobile, display, camera and NVR details) stored in a new asset_specs table (database 3.4.9); new or changed port and SFP counts add the missing interfaces, continuing the asset's numbering, skipping Wi-Fi, VLAN and management ports and never deleting any; the asset page and quick look show the details, a server or NAS lists the VMs that run on it, and Assigned and Warranty show only on types that use them; the list gets Servers & NAS and Power tabs, an optional Details column, a UPS battery badge, an Assigned picker only on assigned types, and New buttons named for what they add; nightly UPS Battery Due notifications at 45, 7 and 1 days as a Support notification; copy leaves out per-unit details, Transfer to Client carries details and interface types, export adds per-detail columns, import reads detail columns after the standard twelve (VM hosts by name, resolved after every row is in) and reports values it could not keep, and the API reads and writes asset_specs; racks, client portal, client export, overview, document templates and AI lists group the new types; fix asset import carrying physical location and notes into the next row, saving 0000-00-00 for a missing purchase date and warning on an unknown contact or location, save API and import types in the list's spelling, and keep interface types on transfer; changelog updated
2026-10-27 15:59:03 -04:00

173 lines
8.5 KiB
PHP

<?php
/*
* A run of interfaces on an asset at once - a switch's 24 ports, a server's
* four NICs: a prefix, the first number, how many and how the numbers are
* padded, with the names it will make shown as they are typed. Names the
* asset has already are skipped.
*/
require_once '../../../includes/modal_header.php';
$asset = interfaceAssetForModal($_GET['asset_id'] ?? 0);
$asset_id = intval($asset['asset_id']);
$client_id = intval($asset['asset_client_id']);
$networks = interfaceNetworkOptions($client_id);
$types = interfaceTypes();
// A start that follows on from ports already there - Gi1/0/1 to 24 there, start at 25 (functions/asset_interface.php)
$existing = [];
$sql = mysqli_query($mysqli, "SELECT interface_name FROM asset_interfaces WHERE interface_asset_id = $asset_id AND interface_archived_at IS NULL");
while ($row = mysqli_fetch_row($sql)) {
$existing[] = $row[0];
}
[$prefix, $start, $pad] = interfaceSeriesNext($asset_id, $asset['asset_type']);
$count = $asset['asset_type'] === 'Switch' ? 24 : 4;
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title text-truncate"><i class="fa fa-fw fa-ethernet me-2"></i>Add ports to <strong><?= escapeHtml($asset['asset_name']) ?></strong></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off" id="interfaceSeriesForm" data-existing="<?= escapeHtml(json_encode(array_values(array_map('mb_strtolower', $existing)))) ?>">
<input type="hidden" name="asset_id" value="<?= $asset_id ?>">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-5">
<label class="form-label">Name before the number</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-font"></i></span>
<input type="text" class="form-control font-monospace" name="name_prefix" maxlength="190" placeholder="Gi1/0/, eth, Port " value="<?= escapeHtml($prefix) ?>" data-series="prefix">
</div>
</div>
<div class="col-md-3 col-6">
<label class="form-label">First number</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-hashtag"></i></span>
<input type="number" class="form-control" name="interface_start" min="0" value="<?= $start ?>" required data-series="start">
</div>
</div>
<div class="col-md-4 col-6">
<label class="form-label">How many</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="number" class="form-control" name="interfaces" min="1" max="128" value="<?= $count ?>" required data-series="count">
</div>
</div>
<div class="col-md-5">
<label class="form-label">Numbers</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-sort-numeric-down"></i></span>
<select class="form-select" name="pad" data-series="pad">
<option value="0">1, 2 ... 10</option>
<option value="2" <?= $pad === 2 ? 'selected' : '' ?>>01, 02 ... 10</option>
<option value="3" <?= $pad === 3 ? 'selected' : '' ?>>001, 002 ... 010</option>
</select>
</div>
</div>
<div class="col-md-7">
<label class="form-label">Type</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-plug"></i></span>
<select class="form-select select2" name="type">
<option value="">- No type -</option>
<?= categoryOptionsHtml('network_interface', isset($types['Ethernet']) ? 'Ethernet' : null) ?>
</select>
<?= categoryAddButtonHtml('network_interface', 'btn btn-secondary') ?>
</div>
</div>
<div class="col-md-7">
<label class="form-label">Network</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-network-wired"></i></span>
<select class="form-select select2" name="network_id">
<option value="0">- No network -</option>
<?php foreach ($networks as $network) { ?>
<option value="<?= intval($network['network_id']) ?>"<?= selectOptionNetwork($network) ?>><?= escapeHtml($network['network_name']) ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-5">
<label class="form-label">VLANs</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-layer-group"></i></span>
<input type="text" class="form-control" name="vlans" maxlength="200" placeholder="e.g. 10">
</div>
</div>
<div class="col-12">
<label class="form-label">Notes</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-pen"></i></span>
<input type="text" class="form-control" name="notes" placeholder="On every one of them">
</div>
</div>
</div>
<div class="small mt-3 p-2 rounded bg-body-tertiary" data-series="preview"></div>
</div>
<div class="modal-footer">
<button type="submit" name="add_asset_multiple_interfaces" class="btn btn-primary text-bold"><i class="fas fa-check me-2"></i>Add</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>
<script>
(function () {
// The names it will make, as they are typed - the ones already on the asset struck through
var form = document.getElementById('interfaceSeriesForm');
var existing = JSON.parse(form.dataset.existing || '[]');
function field(name) {
return form.querySelector('[data-series="' + name + '"]');
}
function names() {
var prefix = field('prefix').value;
var start = Math.max(0, parseInt(field('start').value, 10) || 0);
var count = Math.max(0, Math.min(128, parseInt(field('count').value, 10) || 0));
var pad = parseInt(field('pad').value, 10) || 0;
var list = [];
for (var n = start; n < start + count; n++) {
list.push(prefix + String(n).padStart(pad, '0'));
}
return list;
}
function preview() {
var list = names();
var box = field('preview');
box.innerHTML = '';
if (!list.length) {
box.textContent = 'Nothing to add.';
return;
}
var shown = list.length > 6 ? list.slice(0, 3).concat(['...'], list.slice(-2)) : list;
var skipped = list.filter(function (name) { return existing.indexOf(name.toLowerCase()) !== -1; }).length;
var head = document.createElement('div');
head.className = 'fw-semibold mb-1';
head.textContent = (list.length - skipped) + ' new port' + (list.length - skipped === 1 ? '' : 's') + (skipped ? ' - ' + skipped + ' already there, skipped' : '');
var line = document.createElement('div');
line.className = 'font-monospace';
shown.forEach(function (name, i) {
var span = document.createElement('span');
span.textContent = name;
if (existing.indexOf(name.toLowerCase()) !== -1) {
span.className = 'text-decoration-line-through text-body-secondary';
}
line.append(i ? ', ' : '', span);
});
box.append(head, line);
}
form.querySelectorAll('[data-series]').forEach(function (input) {
input.addEventListener('input', preview);
input.addEventListener('change', preview);
});
preview();
})();
</script>
<?php
require_once '../../../includes/modal_footer.php';