1 Commits

Author SHA1 Message Date
Johnny
61bb9dd7cb Merge pull request #1298 from itflow-org/develop
Develop to Master for Release
2026-09-01 13:01:18 -04:00
8 changed files with 36 additions and 52 deletions

View File

@@ -2,26 +2,6 @@
This file documents all notable changes made to ITFlow.
## [26.09.1] Maint Release
### Upgrading to 26.09.1
Update from Maintenance > Update — Queue Update hands the job to cron and it applies on its own. There is no database change in this release, so nothing else is required.
### Breaking Changes and Notes
- API: a contact must be archived before the delete endpoint will remove it. Deleting an active contact is refused and reports nothing deleted, so archive it first and then delete. Thanks to @Wrongecho.
### Bug Fixes
- Assets: IP address fields demanded all three digits of every octet, so `10.0.0.1` had to be entered as `010.000.000.001`. They take natural input again, and a field holding DHCP is now left alone rather than being emptied the moment the modal opens.
- Networks: in the IP list, an empty hostname or description shows a dash rather than a blank cell, the column headings match the rest of the app, and the table is tighter so more addresses fit on screen.
### Developer Updates
- The IPv4 mask in `js/app.js` is a regex mask rather than four `IMask.MaskedRange` blocks. A pattern mask will not advance past a separator until the current block reaches its `maxLength`, which is what forced the three-digit octets. A regex mask has no per-block completeness rule and tests the whole value on each keystroke, so a partial `10.0.` is valid on its own. Octets are still bounded to 0-255 and leading zeros are still accepted, matching what the old jquery.inputmask `ip` alias allowed. Any value not made purely of digits and dots is skipped, because `interface_ip` and `asset_ip` are `varchar(200)` and also carry the literal `DHCP` written by the checkbox on those same modals.
- Dead display variables removed from the asset, expense and product listings. `$asset_description_display`, `$client_name_display` and `$product_description_display` are folded into `?: '-'` at the point of assignment.
## [26.09]
### Upgrading to 26.09

View File

@@ -556,6 +556,11 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$asset_type = escapeHtml($row['asset_type']);
$asset_name = escapeHtml($row['asset_name']);
$asset_description = escapeHtml($row['asset_description']);
if ($asset_description) {
$asset_description_display = $asset_description;
} else {
$asset_description_display = "-";
}
$asset_make = escapeHtml($row['asset_make']);
$asset_model = escapeHtml($row['asset_model']);
$asset_serial = escapeHtml($row['asset_serial']);

View File

@@ -268,7 +268,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$category_name = escapeHtml($row['category_name']);
$account_name = escapeHtml($row['account_name']);
$expense_account_id = intval($row['expense_account_id']);
$client_name = escapeHtml($row['client_name']) ?: '-';
$client_name = escapeHtml($row['client_name']);
if(empty($client_name)) {
$client_name_display = "-";
} else {
$client_name_display = $client_name;
}
$expense_client_id = intval($row['expense_client_id']);
if (empty($expense_receipt)) {
@@ -302,7 +307,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
<td><?= $vendor_name ?></td>
<td class="text-end font-monospace"><?= numfmt_format_currency($currency_format, $expense_amount, $expense_currency_code) ?></td>
<td><?= $account_name ?></td>
<td><?= $client_name ?></td>
<td><?= $client_name_display ?></td>
<td>
<div class="dropdown dropstart text-center">
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">

View File

@@ -224,7 +224,7 @@ if (mysqli_num_rows($sql) == 0) {
<form id="bulkActions" action="post.php" method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<table class="table table-striped table-borderless table-hover table-sm mb-0">
<table class="table table-striped table-borderless table-hover mb-0">
<thead class="text-dark <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
<tr>
<td class="checkbox-column border-end">
@@ -233,17 +233,17 @@ if (mysqli_num_rows($sql) == 0) {
</div>
</td>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ip_address&order=<?= $disp ?>">
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=ip_address&order=<?= $disp ?>">
IP Address <?php if ($sort == 'ip_address') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ip_hostname&order=<?= $disp ?>">
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=ip_hostname&order=<?= $disp ?>">
Hostname <?php if ($sort == 'ip_hostname') { echo $order_icon; } ?>
</a>
</th>
<th>
<a class="text-dark" href="?<?= $url_query_strings_sort ?>&sort=ip_description&order=<?= $disp ?>">
<a class="text-secondary" href="?<?= $url_query_strings_sort ?>&sort=ip_description&order=<?= $disp ?>">
Description <?php if ($sort == 'ip_description') { echo $order_icon; } ?>
</a>
</th>
@@ -256,8 +256,8 @@ if (mysqli_num_rows($sql) == 0) {
while ($row = mysqli_fetch_assoc($sql_ips)) {
$ip_id = intval($row['ip_id']);
$ip_address = escapeHtml($row['ip_address']);
$ip_hostname = escapeHtml($row['ip_hostname']) ?: '-';
$ip_description = escapeHtml($row['ip_description']) ?: '-';
$ip_hostname = escapeHtml($row['ip_hostname']);
$ip_description = escapeHtml($row['ip_description']);
?>
<tr>

View File

@@ -232,7 +232,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
while ($row = mysqli_fetch_assoc($sql)) {
$product_id = intval($row['product_id']);
$product_name = escapeHtml($row['product_name']);
$product_description = escapeHtml($row['product_description']) ?: '-';
$product_description = escapeHtml($row['product_description']);
if (empty($product_description)) {
$product_description_display = "-";
} else {
$product_description_display = "<div style='white-space:pre-line'>$product_description</div>";
}
$product_qty = intval($row['product_qty']);
$product_code = escapeHtml($row['product_code']);
$product_location = escapeHtml($row['product_location']) ?: '-';
@@ -262,7 +267,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
</a>
</td>
<td><?= $category_name ?></td>
<td><div style='white-space:pre-line'><?= $product_description ?></div></td>
<td><?= $product_description_display ?></td>
<?php if ($type_filter == 'product') { ?>
<td><?= $product_qty ?></td>
<td><?= $product_location ?></td>

View File

@@ -12,10 +12,10 @@ $contact_id = intval($_POST['contact_id']);
$delete_count = false;
if (!empty($contact_id)) {
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT contact_name FROM contacts WHERE contact_id = $contact_id AND contact_client_id = $client_id AND contact_archived_at IS NOT NULL LIMIT 1"));
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT contact_name FROM contacts WHERE contact_id = $contact_id AND contact_client_id = $client_id LIMIT 1"));
$contact_name = escapeSql($row['contact_name'] ?? '');
$delete_sql = mysqli_query($mysqli, "DELETE FROM contacts WHERE contact_id = $contact_id AND contact_client_id = $client_id AND contact_archived_at IS NOT NULL LIMIT 1");
$delete_sql = mysqli_query($mysqli, "DELETE FROM contacts WHERE contact_id = $contact_id AND contact_client_id = $client_id LIMIT 1");
// Check delete & get affected rows
if ($delete_sql && !empty($contact_name)) {

View File

@@ -5,4 +5,4 @@
* Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month.
*/
DEFINE("APP_VERSION", "26.09.1");
DEFINE("APP_VERSION", "26.09");

View File

@@ -568,26 +568,15 @@ function itflowInit() {
el.dataset.imaskReady = '1';
var spec = el.getAttribute('data-inputmask') || '';
if (spec.indexOf('ip') !== -1) {
// A pattern mask built from MaskedRange blocks will not advance past the dot
// until the block reaches its maxLength, so 10.0.0.1 had to be entered as
// 010.000.000.001. A regex mask has no per-block completeness rule - it tests
// the whole value on every keystroke, so partial input like "10.0." is valid on
// its own. Octets are still bounded to 0-255 and leading zeros are still
// accepted, matching what jquery.inputmask's 'ip' alias allowed.
//
// interface_ip / asset_ip are varchar(200) and hold free text as well - 'DHCP'
// is written there by the checkbox on these same modals. Masking a value like
// that would strip it to nothing the moment the modal opened, so anything not
// made of digits and dots is left unmasked instead.
if (el.value && !/^[\d.]*$/.test(el.value)) {
return;
}
var octet = '(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)';
var octet4 = octet + '?';
var octet3 = '(' + octet + '(\\.' + octet4 + ')?)?';
var octet2 = '(' + octet + '(\\.' + octet3 + ')?)?';
IMask(el, {
mask: new RegExp('^(' + octet + '(\\.' + octet2 + ')?)?$')
mask: 'a.b.c.d',
blocks: {
a: { mask: IMask.MaskedRange, from: 0, to: 255 },
b: { mask: IMask.MaskedRange, from: 0, to: 255 },
c: { mask: IMask.MaskedRange, from: 0, to: 255 },
d: { mask: IMask.MaskedRange, from: 0, to: 255 }
},
lazy: true
});
} else if (spec.indexOf('mac') !== -1) {
IMask(el, {