Revised the network info sidebar, added ip count, prefixed subnet IP in form, ajax check within network

This commit is contained in:
johnnyq
2026-08-25 12:35:34 -04:00
parent 1381add82b
commit 59cb772a7f
6 changed files with 308 additions and 24 deletions

View File

@@ -913,6 +913,47 @@ if (isset($_GET['client_duplicate_check'])) {
echo json_encode($response);
}
/*
* Live check behind the IP address field on the add/edit network IP modals.
*
* Runs the exact same checkIpForNetwork() the POST handler runs, so what the
* field says while you type is what the save will do - no second copy of the
* rules to drift. It's advisory only; the handler still checks on submit.
*/
if (isset($_GET['network_ip_check'])) {
enforceUserPermission('module_support', 2);
$network_id = intval($_GET['network_id'] ?? 0);
$ip_id = intval($_GET['ip_id'] ?? 0);
$client_id = intval(getFieldById('networks', $network_id, 'network_client_id'));
enforceClientAccess();
$ip = $_GET['ip'] ?? '';
$response = ['ok' => false, 'ip' => '', 'message' => ''];
if (trim($ip) !== '') {
$ip_error = checkIpForNetwork($ip, $network_id, $ip_id);
if ($ip_error === '') {
$response['ok'] = true;
$response['ip'] = $ip;
$response['message'] = "<i class='fas fa-fw fa-check me-2'></i>" . escapeHtml($ip) . " is available";
} else {
// checkIpForNetwork() builds its message for flashAlert(), which
// escapes at render - this lands in innerHTML instead, so it has
// to be escaped here
$response['message'] = "<i class='fas fa-fw fa-exclamation-triangle me-2'></i>" . alertMessageHtml($ip_error);
}
}
echo json_encode($response);
}
if (isset($_GET['contact_email_check'])) {
enforceUserPermission('module_client', 2);

View File

@@ -15,6 +15,11 @@ $client_id = intval($row['network_client_id']);
enforceClientAccess();
// The part of the address this subnet fixes. Empty for IPv6, a prefix under
// /8, or a subnet that won't parse - then the field takes a whole address.
$ip_fixed_octets = ipSubnetFixedOctets($row['network']);
$ip_host_octets = $ip_fixed_octets ? 4 - substr_count($ip_fixed_octets, '.') : 0;
ob_start();
?>
@@ -31,9 +36,19 @@ ob_start();
<div class="mb-3">
<label>IP Address <strong class="text-danger">*</strong></label>
<div class="input-group">
<?php if ($ip_fixed_octets) { ?>
<span class="input-group-text font-monospace text-bold"><?= escapeHtml($ip_fixed_octets) ?></span>
<input type="text" class="form-control font-monospace" name="ip_address" id="networkIpAddress"
placeholder="<?= $ip_host_octets === 1 ? '10' : str_repeat('0.', $ip_host_octets - 1) . '10' ?>"
<?= $ip_host_octets === 1 ? 'inputmode="numeric"' : '' ?>
maxlength="<?= ($ip_host_octets * 4) - 1 ?>" required autofocus>
<?php } else { ?>
<span class="input-group-text"><i class="fa fa-fw fa-map-pin"></i></span>
<input type="text" class="form-control font-monospace" name="ip_address" placeholder="Must be inside <?= $network ?>" maxlength="45" required autofocus>
<input type="text" class="form-control font-monospace" name="ip_address" id="networkIpAddress"
placeholder="Must be inside <?= $network ?>" maxlength="45" required autofocus>
<?php } ?>
</div>
<div class="small mt-1" id="networkIpFeedback"></div>
<small class="text-secondary"><?= $network_name ?> &mdash; <span class="font-monospace"><?= $network ?></span></small>
</div>
@@ -60,6 +75,12 @@ ob_start();
</div>
</form>
<script>
// Live version of the duplicate / inside-the-subnet checks. Advisory only -
// agent/post/network_ip.php runs the same checks again on submit.
itflowWatchNetworkIp(<?= $network_id ?>, 0);
</script>
<?php
require_once '../../../includes/modal_footer.php';

View File

@@ -11,6 +11,7 @@ $sql = mysqli_query($mysqli, "SELECT ip_address, ip_description, ip_hostname, ip
WHERE ip_id = $ip_id LIMIT 1");
$row = mysqli_fetch_assoc($sql);
$network_id = intval($row['ip_network_id']);
$ip_address = escapeHtml($row['ip_address']);
$ip_hostname = escapeHtml($row['ip_hostname']);
$ip_description = escapeHtml($row['ip_description']);
@@ -20,6 +21,14 @@ $client_id = intval($row['network_client_id']);
enforceClientAccess();
// Same split as the add modal - the fixed octets are shown, the host part is
// what's editable. An address that predates the subnet (or sits outside it)
// won't match the prefix and is shown in full so it can still be corrected.
$ip_fixed_octets = ipSubnetFixedOctets($row['network']);
$ip_host_octets = $ip_fixed_octets ? 4 - substr_count($ip_fixed_octets, '.') : 0;
$ip_host_value = escapeHtml(ipSuffixForDisplay($row['ip_address'], $row['network']));
$ip_prefix_matched = ($ip_fixed_octets !== '' && $ip_host_value !== $ip_address);
ob_start();
?>
@@ -36,9 +45,19 @@ ob_start();
<div class="mb-3">
<label>IP Address <strong class="text-danger">*</strong></label>
<div class="input-group">
<?php if ($ip_prefix_matched) { ?>
<span class="input-group-text font-monospace text-bold"><?= escapeHtml($ip_fixed_octets) ?></span>
<input type="text" class="form-control font-monospace" name="ip_address" id="networkIpAddress"
value="<?= $ip_host_value ?>"
<?= $ip_host_octets === 1 ? 'inputmode="numeric"' : '' ?>
maxlength="<?= ($ip_host_octets * 4) - 1 ?>" required>
<?php } else { ?>
<span class="input-group-text"><i class="fa fa-fw fa-map-pin"></i></span>
<input type="text" class="form-control font-monospace" name="ip_address" value="<?= $ip_address ?>" maxlength="45" required>
<input type="text" class="form-control font-monospace" name="ip_address" id="networkIpAddress"
value="<?= $ip_address ?>" maxlength="45" required>
<?php } ?>
</div>
<div class="small mt-1" id="networkIpFeedback"></div>
<small class="text-secondary"><?= $network_name ?> &mdash; <span class="font-monospace"><?= $network ?></span></small>
</div>
@@ -65,6 +84,12 @@ ob_start();
</div>
</form>
<script>
// This row's own id is passed so saving an unchanged address doesn't report
// itself as a duplicate
itflowWatchNetworkIp(<?= $network_id ?>, <?= $ip_id ?>);
</script>
<?php
require_once '../../../includes/modal_footer.php';

View File

@@ -77,8 +77,23 @@ if (mysqli_num_rows($sql) == 0) {
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
// The count above reflects the search box - the sidebar wants the real total
$ip_total = intval(mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(ip_id) FROM network_ips WHERE ip_network_id = $network_id"))[0]);
?>
<ol class="breadcrumb d-print-none">
<li class="breadcrumb-item">
<a href="client_overview.php?client_id=<?= $client_id ?>"><?= $client_name ?></a>
</li>
<li class="breadcrumb-item">
<a href="networks.php?<?= $client_url ?>">Networks</a>
</li>
<li class="breadcrumb-item active">
<i class="fas fa-fw fa-network-wired"></i> <?= $network_name ?>
</li>
</ol>
<div class="row">
<div class="col-md-3">
@@ -95,30 +110,38 @@ if (mysqli_num_rows($sql) == 0) {
<?php } ?>
</div>
<div class="card-body">
<div><i class="fa fa-fw fa-network-wired text-secondary me-2"></i><span class="font-monospace"><?= $network ?></span></div>
<div class="text-secondary small text-uppercase">Network</div>
<div class="font-monospace mb-3"><i class="fa fa-fw fa-network-wired text-secondary me-2"></i><?= $network ?></div>
<?php if ($network_vlan) { ?>
<div class="mt-2"><i class="fa fa-fw fa-layer-group text-secondary me-2"></i>VLAN <span class="font-monospace"><?= $network_vlan ?></span></div>
<div class="text-secondary small text-uppercase">VLAN</div>
<div class="font-monospace mb-3"><i class="fa fa-fw fa-layer-group text-secondary me-2"></i><?= $network_vlan ?></div>
<?php }
if ($network_gateway) { ?>
<div class="mt-2"><i class="fa fa-fw fa-route text-secondary me-2"></i><span class="font-monospace"><?= $network_gateway ?></span></div>
<div class="text-secondary small text-uppercase">Gateway</div>
<div class="font-monospace mb-3"><i class="fa fa-fw fa-route text-secondary me-2"></i><?= $network_gateway ?></div>
<?php }
if ($network_dhcp_range) { ?>
<div class="mt-2"><i class="fa fa-fw fa-arrows-alt-h text-secondary me-2"></i><span class="font-monospace"><?= $network_dhcp_range ?></span></div>
<div class="text-secondary small text-uppercase">Assignable IP Range</div>
<div class="font-monospace mb-3"><i class="fa fa-fw fa-arrows-alt-h text-secondary me-2"></i><?= $network_dhcp_range ?></div>
<?php }
if ($network_primary_dns) { ?>
<div class="mt-2"><i class="fa fa-fw fa-globe text-secondary me-2"></i><span class="font-monospace"><?= $network_primary_dns ?></span></div>
<div class="text-secondary small text-uppercase">Primary DNS</div>
<div class="font-monospace mb-3"><i class="fa fa-fw fa-globe text-secondary me-2"></i><?= $network_primary_dns ?></div>
<?php }
if ($network_secondary_dns) { ?>
<div class="mt-2"><i class="fa fa-fw fa-globe text-secondary me-2"></i><span class="font-monospace"><?= $network_secondary_dns ?></span></div>
<div class="text-secondary small text-uppercase">Secondary DNS</div>
<div class="font-monospace mb-3"><i class="fa fa-fw fa-globe text-secondary me-2"></i><?= $network_secondary_dns ?></div>
<?php }
if ($location_name) { ?>
<div class="mt-2"><i class="fa fa-fw fa-map-marker-alt text-secondary me-2"></i><?= $location_name ?></div>
<div class="text-secondary small text-uppercase">Location</div>
<div class="mb-3"><i class="fa fa-fw fa-map-marker-alt text-secondary me-2"></i><?= $location_name ?></div>
<?php } ?>
<div class="mt-3">
<a class="btn btn-light btn-sm" href="networks.php?<?= $client_url ?>">
<i class="fa fa-fw fa-arrow-left me-2"></i>All Networks
</a>
</div>
<div class="text-secondary small text-uppercase">Documented IPs</div>
<div class="font-monospace"><i class="fa fa-fw fa-map-pin text-secondary me-2"></i><?= $ip_total ?></div>
</div>
</div>