mirror of
https://github.com/itflow-org/itflow
synced 2026-09-02 21:05:13 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1ea055f51 | ||
|
|
da28d285fb | ||
|
|
70216f97b9 | ||
|
|
9e8c4d002a | ||
|
|
0c690c4e51 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -2,6 +2,26 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -556,11 +556,6 @@ $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']);
|
||||
|
||||
@@ -268,12 +268,7 @@ $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']);
|
||||
if(empty($client_name)) {
|
||||
$client_name_display = "-";
|
||||
} else {
|
||||
$client_name_display = $client_name;
|
||||
}
|
||||
$client_name = escapeHtml($row['client_name']) ?: '-';
|
||||
$expense_client_id = intval($row['expense_client_id']);
|
||||
|
||||
if (empty($expense_receipt)) {
|
||||
@@ -307,7 +302,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_display ?></td>
|
||||
<td><?= $client_name ?></td>
|
||||
<td>
|
||||
<div class="dropdown dropstart text-center">
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="dropdown">
|
||||
|
||||
@@ -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 mb-0">
|
||||
<table class="table table-striped table-borderless table-hover table-sm 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-secondary" href="?<?= $url_query_strings_sort ?>&sort=ip_address&order=<?= $disp ?>">
|
||||
<a class="text-dark" 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-secondary" href="?<?= $url_query_strings_sort ?>&sort=ip_hostname&order=<?= $disp ?>">
|
||||
<a class="text-dark" 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-secondary" href="?<?= $url_query_strings_sort ?>&sort=ip_description&order=<?= $disp ?>">
|
||||
<a class="text-dark" 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>
|
||||
|
||||
@@ -232,12 +232,7 @@ $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']);
|
||||
if (empty($product_description)) {
|
||||
$product_description_display = "-";
|
||||
} else {
|
||||
$product_description_display = "<div style='white-space:pre-line'>$product_description</div>";
|
||||
}
|
||||
$product_description = escapeHtml($row['product_description']) ?: '-';
|
||||
$product_qty = intval($row['product_qty']);
|
||||
$product_code = escapeHtml($row['product_code']);
|
||||
$product_location = escapeHtml($row['product_location']) ?: '-';
|
||||
@@ -267,7 +262,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
||||
</a>
|
||||
</td>
|
||||
<td><?= $category_name ?></td>
|
||||
<td><?= $product_description_display ?></td>
|
||||
<td><div style='white-space:pre-line'><?= $product_description ?></div></td>
|
||||
<?php if ($type_filter == 'product') { ?>
|
||||
<td><?= $product_qty ?></td>
|
||||
<td><?= $product_location ?></td>
|
||||
|
||||
@@ -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 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 AND contact_archived_at IS NOT NULL 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 LIMIT 1");
|
||||
$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");
|
||||
|
||||
// Check delete & get affected rows
|
||||
if ($delete_sql && !empty($contact_name)) {
|
||||
|
||||
@@ -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");
|
||||
DEFINE("APP_VERSION", "26.09.1");
|
||||
|
||||
27
js/app.js
27
js/app.js
@@ -568,15 +568,26 @@ 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: '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
|
||||
mask: new RegExp('^(' + octet + '(\\.' + octet2 + ')?)?$')
|
||||
});
|
||||
} else if (spec.indexOf('mac') !== -1) {
|
||||
IMask(el, {
|
||||
|
||||
Reference in New Issue
Block a user