Assets makeover: list with type icons, coloured status pills, OS icons, copy buttons on serial and IP, connect chips by scheme (Web/SSH/RDP), expiring and expired warranty flags, rack position, type tabs with counts, tag search and more sortable columns; asset page with a header (photo, status, tags, connect buttons, New / Link / Edit / Copy / Archive), Network / Hardware / Where & who / Quick notes cards, vendor and physical location shown, and related records in counted tabs that remember the last one; one sectioned Add/Edit/Copy form with a DHCP toggle, photo removal and a History tab; quick look gets the status pill, copy buttons and warranty in words. Fixes OS/firmware and purchase/warranty fields wiped on edit for some asset types, location/contact/vendor/network picks now limited to the asset's own client, credential audit entry missing the asset name, warning when replacing a missing photo, add form script broken with no OS values, recurring ticket menu markup, dead Files view toggle, per-row and per-license queries, error on unknown sort column, quick-look Credentials tab shown without permission, ticket queries trimmed to the columns used, edit/delete actions gated consistently. Adds functions/asset.php. No DB change

This commit is contained in:
johnnyq
2026-10-14 16:29:07 -04:00
parent 2d6e40bcf6
commit 2bd66cfcbb
13 changed files with 2553 additions and 2952 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -286,7 +286,7 @@ if (isset($_GET['contact_id'])) {
<i class="fa fa-fw fa-recycle me-2"></i>New Recurring Ticket
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-dark ajax-modal" href="#" data-modal-url="modals/asset/asset_add.php?<?= $client_url ?>&contact_id=<?= $contact_id ?>">
<a class="dropdown-item text-dark ajax-modal" href="#" data-modal-size="lg" data-modal-url="modals/asset/asset_add.php?<?= $client_url ?>&contact_id=<?= $contact_id ?>">
<i class="fa fa-fw fa-desktop me-2"></i>New Asset
</a>
<div class="dropdown-divider"></div>
@@ -457,11 +457,11 @@ if (isset($_GET['contact_id'])) {
<button class="btn btn-secondary btn-sm" title="More actions" type="button" data-bs-toggle="dropdown"><i class="fas fa-ellipsis-h"></i></button>
<div class="dropdown-menu">
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/asset/asset_edit.php?id=<?= $asset_id ?>">
data-modal-size="lg" data-modal-url="modals/asset/asset_edit.php?id=<?= $asset_id ?>">
<i class="fas fa-fw fa-edit me-2"></i>Edit
</a>
<a class="dropdown-item ajax-modal" href="#"
data-modal-url="modals/asset/asset_copy.php?id=<?= $asset_id ?>">
data-modal-size="lg" data-modal-url="modals/asset/asset_copy.php?id=<?= $asset_id ?>">
<i class="fas fa-fw fa-copy me-2"></i>Copy
</a>
<div class="dropdown-divider"></div>

View File

@@ -154,18 +154,19 @@ $sql_related_credentials = mysqli_query($mysqli, "
credentials.credential_contact_id,
credentials.credential_asset_id
FROM credentials
LEFT JOIN credential_tags ON credential_tags.credential_id = credentials.credential_id
LEFT JOIN tags ON tags.tag_id = credential_tags.tag_id
WHERE credential_asset_id = $asset_id
AND credential_archived_at IS NULL
GROUP BY credentials.credential_id
ORDER BY credential_name DESC
");
$credential_count = mysqli_num_rows($sql_related_credentials);
// No module_credential, no Credentials tab - the pane below was already gated, the tab wasn't
$credential_count = lookupUserPermission('module_credential') ? mysqli_num_rows($sql_related_credentials) : 0;
// Related Tickets Query
$sql_related_tickets = mysqli_query($mysqli, "
SELECT tickets.*, users.*, ticket_statuses.*
SELECT tickets.ticket_id, ticket_prefix, ticket_number, ticket_subject, ticket_priority, ticket_created_at,
ticket_updated_at, ticket_closed_at, ticket_assigned_to, user_name, user_avatar,
ticket_status_id, ticket_status_name, ticket_status_color
FROM tickets
LEFT JOIN users ON ticket_assigned_to = user_id
LEFT JOIN ticket_statuses ON ticket_status_id = ticket_status
@@ -249,6 +250,7 @@ ob_start();
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-<?= $device_icon ?> me-2"></i><strong><?= $asset_name ?></strong>
<?php if ($asset_favorite) { ?><i class="fas fa-fw text-warning fa-star" title="Favorite"></i><?php } ?>
<span class="fs-6 ms-1 align-middle"><?= assetStatusPill(html_entity_decode($asset_status)) ?></span>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
@@ -348,10 +350,10 @@ ob_start();
<div class="mt-2"><i class="fa fa-fw fa-circle text-secondary me-2"></i><?= "$asset_make $asset_model" ?></div>
<?php }
if ($asset_os) { ?>
<div class="mt-2"><i class="fab fa-fw fa-windows text-secondary me-2"></i><?= "$asset_os" ?></div>
<div class="mt-2"><i class="<?= assetOsIcon(html_entity_decode($asset_os)) ?> fa-fw text-secondary me-2"></i><?= $asset_os ?></div>
<?php }
if ($asset_serial) { ?>
<div class="mt-2"><i class="fa fa-fw fa-barcode text-secondary me-2"></i><span class="font-monospace"><?= $asset_serial ?></span></div>
<div class="mt-2 itflow-copy-always"><i class="fa fa-fw fa-barcode text-secondary me-2"></i><span class="font-monospace"><?= $asset_serial ?></span><button type="button" class="itflow-copy clipboardjs" title="Copy serial" data-clipboard-text="<?= $asset_serial ?>"><i class="far fa-copy"></i></button></div>
<?php }
if ($asset_purchase_date) { ?>
<div class="mt-2"><i class="fa fa-fw fa-shopping-cart text-secondary me-2"></i><?= date('Y-m-d', strtotime($asset_purchase_date)) ?></div>
@@ -359,8 +361,8 @@ ob_start();
if ($asset_install_date) { ?>
<div class="mt-2"><i class="fa fa-fw fa-calendar-check text-secondary me-2"></i><?= date('Y-m-d', strtotime($asset_install_date)) ?></div>
<?php }
if ($asset_warranty_expire) { ?>
<div class="mt-2"><i class="fa fa-fw fa-exclamation-triangle text-secondary me-2"></i><?= date('Y-m-d', strtotime($asset_warranty_expire)) ?></div>
if ($asset_warranty = assetWarranty(html_entity_decode($asset_warranty_expire))) { ?>
<div class="mt-2"><i class="fa fa-fw fa-shield-alt text-secondary me-2"></i><span class="text-<?= $asset_warranty['tone'] ?>-emphasis">Warranty <?= strtolower(escapeHtml($asset_warranty['text'])) ?></span><?= $asset_warranty['tone'] !== 'success' ? ' <span class="text-secondary">(' . escapeHtml($asset_warranty['date']) . ')</span>' : '' ?></div>
<?php } ?>
</div>
</div>
@@ -371,13 +373,13 @@ ob_start();
</div>
<div class="card-body">
<?php if ($asset_ip) { ?>
<div><i class="fa fa-fw fa-globe text-secondary me-2"></i><span class="font-monospace"><?= $asset_ip ?></span></div>
<div class="itflow-copy-always"><i class="fa fa-fw fa-globe text-secondary me-2"></i><span class="font-monospace"><?= $asset_ip ?></span><?php if ($asset_ip !== 'DHCP') { ?><button type="button" class="itflow-copy clipboardjs" title="Copy IP" data-clipboard-text="<?= $asset_ip ?>"><i class="far fa-copy"></i></button><?php } ?></div>
<?php } ?>
<?php if ($asset_nat_ip) { ?>
<div class="mt-2"><i class="fa fa-fw fa-random text-secondary me-2"></i><span class="font-monospace"><?= $asset_nat_ip ?></span></div>
<div class="mt-2 itflow-copy-always"><i class="fa fa-fw fa-random text-secondary me-2"></i><span class="font-monospace"><?= $asset_nat_ip ?></span><button type="button" class="itflow-copy clipboardjs" title="Copy NAT IP" data-clipboard-text="<?= $asset_nat_ip ?>"><i class="far fa-copy"></i></button></div>
<?php }
if ($asset_mac) { ?>
<div class="mt-2"><i class="fa fa-fw fa-ethernet text-secondary me-2"></i><span class="font-monospace"><?= $asset_mac ?></span></div>
<div class="mt-2 itflow-copy-always"><i class="fa fa-fw fa-ethernet text-secondary me-2"></i><span class="font-monospace"><?= $asset_mac ?></span><button type="button" class="itflow-copy clipboardjs" title="Copy MAC" data-clipboard-text="<?= $asset_mac ?>"><i class="far fa-copy"></i></button></div>
<?php }
if ($asset_uri) { ?>
<div class="mt-2"><i class="fa fa-fw fa-link text-secondary me-2"></i><a href="<?= $asset_uri ?>" target="_blank" title="<?= $asset_uri ?>"><?= truncate($asset_uri, 20) ?></a></div>
@@ -966,9 +968,11 @@ ob_start();
<a href="asset.php?client_id=<?= $client_id ?>&asset_id=<?= $asset_id ?>"
class="btn btn-primary text-bold"><span class="text-white"><i class="fas fa-info-circle me-2"></i>More Details</span>
</a>
<a href="#" class="btn btn-secondary ajax-modal" data-modal-url="modals/asset/asset_edit.php?id=<?= $asset_id ?>">
<span class="text-white"><i class="fas fa-edit me-2"></i>Edit</span>
</a>
<?php if (lookupUserPermission('module_support') >= 2) { ?>
<a href="#" class="btn btn-secondary ajax-modal" data-modal-size="lg" data-modal-url="modals/asset/asset_edit.php?id=<?= $asset_id ?>">
<span class="text-white"><i class="fas fa-edit me-2"></i>Edit</span>
</a>
<?php } ?>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Close</button>
</div>

View File

@@ -2,456 +2,45 @@
require_once '../../../includes/modal_header.php';
enforceUserPermission('module_support', 2);
$client_id = intval($_GET['client_id'] ?? 0);
$contact_id = intval($_GET['contact_id'] ?? 0);
$type = escapeHtml(ucwords($_GET['type']) ?? '');
if ($client_id) {
$sql_network_select = mysqli_query($mysqli, "SELECT network, network_id, network_name FROM networks WHERE network_archived_at IS NULL AND network_client_id = $client_id ORDER BY network_name ASC");
$sql_vendor_select = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_archived_at IS NULL AND vendor_client_id = $client_id ORDER BY vendor_name ASC");
$sql_location_select = mysqli_query($mysqli, "SELECT location_id, location_name FROM locations WHERE location_archived_at IS NULL AND location_client_id = $client_id ORDER BY location_name ASC");
$sql_contact_select = mysqli_query($mysqli, "SELECT contact_id, contact_name FROM contacts WHERE contact_archived_at IS NULL AND contact_client_id = $client_id ORDER BY contact_name ASC");
} else {
$sql_client_select = mysqli_query($mysqli, "SELECT client_id, client_name FROM clients WHERE client_archived_at IS NULL " . clientScopeSql('clients.client_id') . " ORDER BY client_name ASC");
enforceClientAccess();
}
// OS typeahead suggestions
$os_sql = mysqli_query($mysqli, "SELECT DISTINCT asset_os AS label FROM assets WHERE asset_archived_at IS NULL");
if ($os_sql && mysqli_num_rows($os_sql) > 0) {
$os_arr = [];
while ($row = mysqli_fetch_assoc($os_sql)) {
// jQuery UI Autocomplete expects {label: "...", value: "..."}
$label = $row['label'];
$os_arr[] = ['label' => $label, 'value' => $label];
}
$json_os = json_encode($os_arr);
}
// The assets list passes the tab it was on - start on that kind of asset
$type_presets = ['workstation' => 'Desktop', 'server' => 'Server', 'virtual' => 'Virtual Machine', 'network' => 'Switch'];
$type_param = strtolower((string) ($_GET['type'] ?? ''));
$sql_tags_select = mysqli_query($mysqli, "SELECT tag_id, tag_name FROM tags WHERE tag_type = 5 ORDER BY tag_name ASC");
$asset_form_mode = 'add';
$asset_id = 0;
$asset_type = $type_presets[$type_param] ?? '';
$asset_name = $asset_description = $asset_make = $asset_model = $asset_serial = $asset_os = '';
$asset_ip = $asset_ipv6 = $asset_nat_ip = $asset_mac = $asset_uri = $asset_uri_2 = $asset_uri_client = '';
$asset_status = $asset_purchase_reference = $asset_purchase_date = $asset_install_date = $asset_warranty_expire = '';
$asset_physical_location = $asset_notes = $asset_photo = '';
$asset_favorite = 0;
$asset_location_id = $asset_vendor_id = $asset_network_id = 0;
$asset_contact_id = intval($_GET['contact_id'] ?? 0);
$asset_tag_id_array = [];
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fa fa-fw fa-desktop me-2"></i>New <?php if ($type) { echo $type; } ?> Asset</h5>
<h5 class="modal-title"><i class="fa fa-fw fa-desktop me-2"></i>New Asset</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<?php if ($client_id) { ?>
<input type="hidden" name="client_id" value="<?= $client_id ?>">
<?php } ?>
<div class="modal-body ui-front">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="pill" href="#pills-asset-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-asset-assignment">Assignment</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-asset-network">Network</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-asset-purchase">Purchase</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-asset-login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-asset-notes">Notes</a>
</li>
</ul>
<hr>
<div class="tab-content">
<div class="tab-pane fade show active" id="pills-asset-details">
<?php if ($client_id) { ?>
<input type="hidden" name="client_id" value="<?= $client_id ?>">
<?php } else { ?>
<div class="mb-3">
<label>Client <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
<select class="form-select select2" name="client_id" required>
<option value="">- Select Client -</option>
<?php
while ($row = mysqli_fetch_assoc($sql_client_select)) {
$client_id_select = intval($row['client_id']);
$client_name = escapeHtml($row['client_name']); ?>
<option <?php if ($client_id == $client_id_select) { echo "selected"; } ?> value="<?= $client_id_select ?>"><?= $client_name ?></option>
<?php } ?>
</select>
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-layer-group"></i></span>
<select class="form-select select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach($asset_types_array as $asset_type => $asset_icon) { ?>
<option><?= $asset_type ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong> / <span class="text-secondary" title="Pin to Overview">Favorite</span></label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-fw fa-tag"></i></span>
<input type="text" class="form-control" name="name" placeholder="e.g. SRV-DC01" maxlength="200" required autofocus>
<div class="input-group-text">
<label class="star-toggle mb-0" title="Favorite">
<input class="form-check-input" type="checkbox" name="favorite" value="1"><i class="far fa-star"></i>
</label>
</div>
</div>
</div>
<?php //Do not display Make Model or Serial if Virtual is selected
if ($type !== 'Virtual') { ?>
<div class="mb-3">
<label>Make</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<input type="text" class="form-control" name="make" placeholder="e.g. Dell, HP, Lenovo" maxlength="200">
</div>
</div>
<div class="mb-3">
<label>Model</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
<input type="text" class="form-control" name="model" placeholder="e.g. PowerEdge R740" maxlength="200">
</div>
</div>
<div class="mb-3">
<label>Serial Number</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-barcode"></i></span>
<input type="text" class="form-control font-monospace" name="serial" placeholder="e.g. ABC1234XYZ" maxlength="200">
</div>
</div>
<?php } ?>
<?php if ($type !== 'Network' && $type !== 'Other') { ?>
<div class="mb-3">
<label>Operating System</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-fw fa-laptop-code"></i></span>
<input type="text" class="form-control" name="os" id="os" placeholder="e.g. Windows 11 Pro, Ubuntu 24.04" maxlength="200">
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-align-left"></i></span>
<input type="text" class="form-control" name="description" placeholder="e.g. Domain controller for HQ" maxlength="255">
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-asset-assignment">
<?php if ($client_id) { ?>
<div class="mb-3">
<label>Location</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<select class="form-select select2" name="location">
<option value="">- Select Location -</option>
<?php
while ($row = mysqli_fetch_assoc($sql_location_select)) {
$location_id = intval($row['location_id']);
$location_name = escapeHtml($row['location_name']);
?>
<option value="<?= $location_id ?>"><?= $location_name ?></option>
<?php } ?>
</select>
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Physical Location</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-map-marker-alt"></i></span>
<input type="text" class="form-control" name="physical_location" placeholder="e.g. Floor 2, Closet B" maxlength="200">
</div>
</div>
<?php if ($client_id) { ?>
<div class="mb-3">
<label>Assign To</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-user-check"></i></span>
<select class="form-select select2" name="contact">
<option value="">- Select Contact -</option>
<?php
while ($row = mysqli_fetch_assoc($sql_contact_select)) {
$contact_id_select = intval($row['contact_id']);
$contact_name = escapeHtml($row['contact_name']);
?>
<option
<?php if ($contact_id == $contact_id_select) {
echo "selected"; }
?>
value="<?= $contact_id_select ?>"><?= $contact_name ?>
</option>
<?php } ?>
</select>
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Status</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-circle"></i></span>
<select class="form-select select2" name="status">
<option value="">- Select Status -</option>
<?php
$sql_interface_types_select = mysqli_query($mysqli, "
SELECT category_name, category_color, category_icon FROM categories
WHERE category_type = 'asset_status'
AND category_archived_at IS NULL
ORDER BY category_order ASC, category_name ASC
");
while ($row = mysqli_fetch_assoc($sql_interface_types_select)) {
$asset_status_select = escapeHtml($row['category_name']);
?>
<option<?= selectOptionColor($row['category_color'] ?? '', $row['category_icon'] ?? '') ?>><?= $asset_status_select ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-asset-network">
<?php if ($client_id) { ?>
<div class="mb-3">
<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">
<option value="">- Select Network -</option>
<?php
while ($row = mysqli_fetch_assoc($sql_network_select)) {
$network_id = intval($row['network_id']);
$network_name = escapeHtml($row['network_name']);
$network = escapeHtml($row['network']);
?>
<option value="<?= $network_id ?>"><?= $network_name ?> - <?= $network ?></option>
<?php } ?>
</select>
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>IPv4 Address / <span class="text-muted">DHCP</span></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="ip" placeholder="e.g. 192.168.1.10" maxlength="200" data-inputmask="'alias': 'ip'" data-mask>
<div class="input-group-text">
<input class="form-check-input" type="checkbox" name="dhcp" value="1">
</div>
</div>
</div>
<div class="mb-3">
<label>MAC Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="mac" placeholder="e.g. 00:1A:2B:3C:4D:5E" data-inputmask="'alias': 'mac'" maxlength="200" data-mask>
</div>
</div>
<div class="mb-3">
<label>IPv6 Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="ipv6" placeholder="e.g. 2001:db8::1" maxlength="200">
</div>
</div>
<div class="mb-3">
<label>NAT Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-exchange-alt"></i></span>
<input type="text" class="form-control font-monospace" name="nat_ip" placeholder="e.g. 203.0.113.10 or 10.0.0.5" data-inputmask="'alias': 'ip'" maxlength="200" data-mask>
</div>
</div>
<div class="mb-3">
<label>URI</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri" placeholder="e.g. https:// or ssh://" maxlength="500">
</div>
</div>
<div class="mb-3">
<label>URI 2</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri_2" placeholder="e.g. https:// or ssh://" maxlength="500">
</div>
</div>
<div class="mb-3">
<label>Client URI</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri_client" placeholder="e.g. https:// or ssh://" maxlength="500">
</div>
<small class="text-muted">Viewable in client portal.</small>
</div>
</div>
<div class="tab-pane fade" id="pills-asset-purchase">
<?php if ($client_id) { ?>
<div class="mb-3">
<label>Vendor</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<select class="form-select select2" name="vendor">
<option value="">- Select Vendor -</option>
<?php
while ($row = mysqli_fetch_assoc($sql_vendor_select)) {
$vendor_id = intval($row['vendor_id']);
$vendor_name = escapeHtml($row['vendor_name']);
?>
<option value="<?= $vendor_id ?>"><?= $vendor_name ?></option>
<?php } ?>
</select>
</div>
</div>
<?php } ?>
<?php if ($type !== 'Virtual') { ?>
<div class="mb-3">
<label>Purchase Reference</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-receipt"></i></span>
<input type="text" class="form-control" name="purchase_reference" placeholder="e.g. INV-1045 or PO-7782" maxlength="200">
</div>
</div>
<div class="mb-3">
<label>Purchase Date</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31">
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Install Date</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-check"></i></span>
<input type="date" class="form-control" name="install_date" max="2999-12-31">
</div>
</div>
<?php if ($type !== 'Virtual') { ?>
<div class="mb-3">
<label>Warranty Expire</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-times"></i></span>
<input type="date" class="form-control" name="warranty_expire" max="2999-12-31">
</div>
</div>
<?php } ?>
</div>
<div class="tab-pane fade" id="pills-asset-login">
<div class="mb-3">
<label>Username</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
<input type="text" class="form-control" name="username" placeholder="e.g. admin" maxlength="350">
</div>
</div>
<div class="mb-3">
<label>Password</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
<input type="text" class="form-control" name="password" placeholder="Password" maxlength="350" autocomplete="off">
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-asset-notes">
<div class="mb-3">
<textarea class="form-control" rows="8" placeholder="Additional notes or configuration details" name="notes"></textarea>
</div>
<div class="mb-3">
<label>Upload Photo</label>
<input type="file" class="form-control" name="file" accept="image/*">
</div>
<div class="mb-3">
<label>Tags</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-tags"></i></span>
<select class="form-select select2" name="tags[]" data-placeholder="Add some tags" multiple>
<?php
while ($row = mysqli_fetch_assoc($sql_tags_select)) {
$tag_id = intval($row['tag_id']);
$tag_name = escapeHtml($row['tag_name']);
?>
<option value="<?= $tag_id ?>"><?= $tag_name ?></option>
<?php } ?>
</select>
<button class="btn btn-secondary ajax-modal" title="New tag" type="button"
data-modal-url="../admin/modals/tag/tag_add.php?type=5">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
</div>
<?php require 'asset_form.php'; ?>
</div>
<div class="modal-footer">
<button type="submit" name="add_asset" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create</button>
@@ -459,28 +48,6 @@ ob_start();
</div>
</form>
<!-- JSON Autocomplete / type ahead -->
<script>
itflowReady(function () {
var operatingSystems = <?= $json_os ?>;
var osInput = document.getElementById('os');
if (!osInput) {
return;
}
itflowAutocomplete(osInput, {
minLength: 1,
source: operatingSystems,
onSelect: function (item) {
osInput.value = item.label;
}
});
});
</script>
<?php
require_once '../../../includes/modal_footer.php';

View File

@@ -4,408 +4,80 @@ require_once '../../../includes/modal_header.php';
enforceUserPermission('module_support', 2);
$asset_id = intval($_GET['id']);
$source_asset_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT asset_archived_at, asset_client_id, asset_contact_id, asset_created_at, asset_description,
asset_id, asset_install_date, asset_location_id, asset_make, asset_model, asset_name,
asset_notes, asset_os, asset_photo, asset_physical_location, asset_purchase_date,
asset_purchase_reference, asset_serial, asset_status, asset_type, asset_uri, asset_uri_2,
asset_vendor_id, asset_warranty_expire, interface_ip, interface_ipv6, interface_mac,
interface_nat_ip, interface_network_id FROM assets
$sql = mysqli_query($mysqli, "SELECT asset_client_id, asset_contact_id, asset_description,
asset_install_date, asset_location_id, asset_make, asset_model, asset_name,
asset_notes, asset_os, asset_physical_location, asset_purchase_date,
asset_purchase_reference, asset_status, asset_type, asset_vendor_id, asset_warranty_expire,
interface_network_id FROM assets
LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1
WHERE asset_id = $asset_id LIMIT 1"
WHERE asset_id = $source_asset_id LIMIT 1"
);
$row = mysqli_fetch_assoc($sql);
if (!$row) {
echo json_encode(['error' => 'That asset no longer exists.']);
exit;
}
$client_id = intval($row['asset_client_id']);
$asset_id = intval($row['asset_id']);
enforceClientAccess();
/*
* A copy is the next unit of the same kind - same model, place and paperwork,
* but its own serial, addresses, links and photo, so those start empty.
*/
$asset_form_mode = 'copy';
$asset_id = 0;
$asset_type = escapeHtml($row['asset_type']);
$asset_name = escapeHtml($row['asset_name']);
$asset_description = escapeHtml($row['asset_description']);
$asset_make = escapeHtml($row['asset_make']);
$asset_model = escapeHtml($row['asset_model']);
$asset_serial = escapeHtml($row['asset_serial']);
$asset_os = escapeHtml($row['asset_os']);
$asset_ip = escapeHtml($row['interface_ip']);
$asset_ipv6 = escapeHtml($row['interface_ipv6']);
$asset_nat_ip = escapeHtml($row['interface_nat_ip']);
$asset_mac = escapeHtml($row['interface_mac']);
$asset_uri = escapeHtml($row['asset_uri']);
$asset_uri_2 = escapeHtml($row['asset_uri_2']);
$asset_status = escapeHtml($row['asset_status']);
$asset_purchase_reference = escapeHtml($row['asset_purchase_reference']);
$asset_purchase_date = escapeHtml($row['asset_purchase_date']);
$asset_warranty_expire = escapeHtml($row['asset_warranty_expire']);
$asset_install_date = escapeHtml($row['asset_install_date']);
$asset_photo = escapeHtml($row['asset_photo']);
$asset_physical_location = escapeHtml($row['asset_physical_location']);
$asset_notes = escapeHtml($row['asset_notes']);
$asset_created_at = escapeHtml($row['asset_created_at']);
$asset_archived_at = escapeHtml($row['asset_archived_at']);
$asset_vendor_id = intval($row['asset_vendor_id']);
$asset_location_id = intval($row['asset_location_id']);
$asset_contact_id = intval($row['asset_contact_id']);
$asset_network_id = intval($row['interface_network_id']);
$device_icon = getAssetIcon($asset_type);
$asset_serial = $asset_ip = $asset_ipv6 = $asset_nat_ip = $asset_mac = '';
$asset_uri = $asset_uri_2 = $asset_uri_client = $asset_photo = '';
$asset_favorite = 0;
enforceClientAccess();
$device_icon = getAssetIcon($row['asset_type']);
// Tags come along
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT asset_tag_tag_id FROM asset_tags WHERE asset_tag_asset_id = $source_asset_id");
while ($row = mysqli_fetch_assoc($sql_asset_tags)) {
$asset_tag_id_array[] = intval($row['asset_tag_tag_id']);
}
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class='fa fa-fw fa-<?= $device_icon ?> me-2'></i>Copying asset: <strong><?= $asset_name ?></strong></h5>
<h5 class="modal-title text-truncate"><i class='fa fa-fw fa-<?= $device_icon ?> me-2'></i>Copying asset: <strong><?= $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" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="client_id" value="<?= $client_id ?>">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="pill" href="#pillsDetailsCopy<?= $asset_id ?>">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pillsAssignmentCopy<?= $asset_id ?>">Assignment</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pillsNetworkCopy<?= $asset_id ?>">Network</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pillsPurchaseCopy<?= $asset_id ?>">Purchase</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pillsLoginCopy<?= $asset_id ?>">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pillsNotesCopy<?= $asset_id ?>">Notes</a>
</li>
</ul>
<hr>
<div class="tab-content">
<div class="tab-pane fade show active" id="pillsDetailsCopy<?= $asset_id ?>">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-layer-group"></i></span>
<select class="form-select select2" name="type" required>
<?php foreach($asset_types_array as $asset_type_select => $asset_icon_select) { ?>
<option <?php if ($asset_type_select == $asset_type) { echo "selected"; } ?>><?= $asset_type_select ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-tag"></i></span>
<input type="text" class="form-control" name="name" placeholder="Name the asset" maxlength="200" value="<?= $asset_name ?>" required>
</div>
</div>
<?php //Do not display Make Model or Serial if Virtual is selected
if ($asset_type !== 'virtual') { ?>
<div class="mb-3">
<label>Make</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<input type="text" class="form-control" name="make" placeholder="Manufacturer" maxlength="200" value="<?= $asset_make ?>">
</div>
</div>
<div class="mb-3">
<label>Model</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
<input type="text" class="form-control" name="model" placeholder="Model Number" maxlength="200" value="<?= $asset_model ?>">
</div>
</div>
<div class="mb-3">
<label>Serial Number</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-barcode"></i></span>
<input type="text" class="form-control font-monospace" name="serial" placeholder="Serial number" maxlength="200">
</div>
</div>
<?php } ?>
<?php if ($asset_type !== 'Phone' && $asset_type !== 'Mobile Phone' && $asset_type !== 'Tablet' && $asset_type !== 'Access Point' && $asset_type !== 'Printer' && $asset_type !== 'Camera' && $asset_type !== 'TV' && $asset_type !== 'Other') { ?>
<div class="mb-3">
<label>Operating System</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-fw fa-laptop-code"></i></span>
<input type="text" class="form-control" name="os" placeholder="ex Windows 10 Pro" maxlength="200" value="<?= $asset_os ?>">
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-align-left"></i></span>
<input type="text" class="form-control" name="description" placeholder="Description of the asset" maxlength="255" value="<?= $asset_description ?>">
</div>
</div>
</div>
<div class="tab-pane fade" id="pillsAssignmentCopy<?= $asset_id ?>">
<div class="mb-3">
<label>Location</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<select class="form-select select2" name="location">
<option value="">- Select Location -</option>
<?php
$sql_locations = mysqli_query($mysqli, "SELECT location_id, location_name FROM locations WHERE location_archived_at IS NULL AND location_client_id = $client_id ORDER BY location_name ASC");
while ($row = mysqli_fetch_assoc($sql_locations)) {
$location_id_select = intval($row['location_id']);
$location_name_select = escapeHtml($row['location_name']);
?>
<option <?php if ($asset_location_id == $location_id_select) { echo "selected"; } ?> value="<?= $location_id_select ?>"><?= $location_name_select ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Physical Location</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-map-marker-alt"></i></span>
<input type="text" class="form-control" name="physical_location" placeholder="Physical location eg. Floor 2, Closet B" maxlength="200">
</div>
</div>
<div class="mb-3">
<label>Assign To</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-user-check"></i></span>
<select class="form-select select2" name="contact">
<option value="">- Select Contact -</option>
<?php
$sql_contacts = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_client_id, contact_photo, contact_title FROM contacts WHERE contact_archived_at IS NULL AND contact_client_id = $client_id ORDER BY contact_name ASC");
while ($row = mysqli_fetch_assoc($sql_contacts)) {
$contact_id_select = intval($row['contact_id']);
$contact_name_select = escapeHtml($row['contact_name']);
?>
<option value="<?= $contact_id_select ?>"<?= selectOptionContact($row['contact_client_id'] ?? 0, $row['contact_name'], $row['contact_photo'] ?? '', $row['contact_title'] ?? '') ?>><?= $contact_name_select ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Status</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-circle"></i></span>
<select class="form-select select2" name="status">
<option value="">- Select Status -</option>
<?php
$sql_interface_types_select = mysqli_query($mysqli, "
SELECT category_name, category_color, category_icon FROM categories
WHERE category_type = 'asset_status'
AND category_archived_at IS NULL
ORDER BY category_order ASC, category_name ASC
");
while ($row = mysqli_fetch_assoc($sql_interface_types_select)) {
$asset_status_select = escapeHtml($row['category_name']);
?>
<option <?php if ($asset_status_select == $asset_status) { echo "selected"; } ?><?= selectOptionColor($row['category_color'] ?? '', $row['category_icon'] ?? '') ?>>
<?= $asset_status_select ?>
</option>
<?php } ?>
</select>
</div>
</div>
</div>
<div class="tab-pane fade" id="pillsNetworkCopy<?= $asset_id ?>">
<div class="mb-3">
<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">
<option value="">- Select Network -</option>
<?php
$sql_networks = mysqli_query($mysqli, "SELECT network, network_id, network_name FROM networks WHERE network_archived_at IS NULL AND network_client_id = $client_id ORDER BY network_name ASC");
while ($row = mysqli_fetch_assoc($sql_networks)) {
$network_id_select = intval($row['network_id']);
$network_name_select = escapeHtml($row['network_name']);
$network_select = escapeHtml($row['network']);
?>
<option <?php if ($asset_network_id == $network_id_select) { echo "selected"; } ?> value="<?= $network_id_select ?>"><?= $network_name_select ?> - <?= $network_select ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>IP Address / <span class="text-muted">DHCP</span></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="ip" placeholder="192.168.10.250" maxlength="200" data-inputmask="'alias': 'ip'" data-mask>
<div class="input-group-text">
<input class="form-check-input" type="checkbox" name="dhcp" value="1" <?php if($asset_ip == 'DHCP'){ echo "checked"; } ?>>
</div>
</div>
</div>
<div class="mb-3">
<label>MAC Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="mac" placeholder="00:11:22:AA:BB:CC" maxlength="200" data-inputmask="'alias': 'mac'" data-mask>
</div>
</div>
<div class="mb-3">
<label>IPv6 Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="ipv6" value="<?= $asset_ipv6 ?>" placeholder="2001:0db8:1000::3" maxlength="200">
</div>
</div>
<div class="mb-3">
<label>NAT Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-exchange-alt"></i></span>
<input type="text" class="form-control font-monospace" name="nat_ip" placeholder="10.52.4.55" maxlength="200" data-inputmask="'alias': 'ip'" data-mask>
</div>
</div>
<div class="mb-3">
<label>URI</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri" placeholder="URI http:// ftp:// ssh: etc" maxlength="500">
</div>
</div>
<div class="mb-3">
<label>URI 2</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri_2" placeholder="URI http:// ftp:// ssh: etc" maxlength="500">
</div>
</div>
</div>
<div class="tab-pane fade" id="pillsPurchaseCopy<?= $asset_id ?>">
<div class="mb-3">
<label>Vendor</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<select class="form-select select2" name="vendor">
<option value="">- Select Vendor -</option>
<?php
$sql_vendors = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_archived_at IS NULL AND vendor_client_id = $client_id ORDER BY vendor_name ASC");
while ($row = mysqli_fetch_assoc($sql_vendors)) {
$vendor_id_select = intval($row['vendor_id']);
$vendor_name_select = escapeHtml($row['vendor_name']);
?>
<option <?php if ($asset_vendor_id == $vendor_id_select) { echo "selected"; } ?> value="<?= $vendor_id_select ?>"><?= $vendor_name_select ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Install Date</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-check"></i></span>
<input type="date" class="form-control" name="install_date" max="2999-12-31" value="<?= $asset_install_date ?>">
</div>
</div>
<?php if ($asset_type !== 'Virtual Machine') { ?>
<div class="mb-3">
<label>Purchase Reference</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
<input type="text" class="form-control" name="purchase_reference" placeholder="eg. Invoice, PO Number" maxlength="200" value="<?= $asset_purchase_reference ?>">
</div>
</div>
<div class="mb-3">
<label>Purchase Date</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-receipt"></i></span>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31" value="<?= $asset_purchase_date ?>">
</div>
</div>
<div class="mb-3">
<label>Warranty Expire</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-times"></i></span>
<input type="date" class="form-control" name="warranty_expire" max="2999-12-31" value="<?= $asset_warranty_expire ?>">
</div>
</div>
<?php } ?>
</div>
<div class="tab-pane fade" id="pillsLoginCopy<?= $asset_id ?>">
<div class="mb-3">
<label>Username</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-user"></i></span>
<input type="text" class="form-control" name="username" placeholder="Username" maxlength="350">
</div>
</div>
<div class="mb-3">
<label>Password</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-lock"></i></span>
<input type="text" class="form-control" name="password" placeholder="Password" maxlength="350" autocomplete="off">
</div>
</div>
</div>
<div class="tab-pane fade" id="pillsNotesCopy<?= $asset_id ?>">
<div class="mb-3">
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"><?= $asset_notes ?></textarea>
</div>
<div class="mb-3">
<label>Upload Photo</label>
<input type="file" class="form-control" name="file">
</div>
</div>
</div>
<div class="modal-body ui-front">
<div class="alert alert-info small py-2"><i class="fas fa-info-circle me-2"></i>Serial, addresses, links and photo start empty - they belong to the unit you're copying.</div>
<?php require 'asset_form.php'; ?>
</div>
<div class="modal-footer">
<button type="submit" name="add_asset" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Copy</button>
<button type="submit" name="add_asset" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Create Copy</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>

View File

@@ -6,7 +6,7 @@ enforceUserPermission('module_support', 2);
$asset_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT asset_archived_at, asset_client_id, asset_contact_id, asset_created_at, asset_description,
$sql = mysqli_query($mysqli, "SELECT asset_client_id, asset_contact_id, asset_description,
asset_favorite, asset_id, asset_install_date, asset_location_id, asset_make, asset_model,
asset_name, asset_notes, asset_os, asset_photo, asset_physical_location,
asset_purchase_date, asset_purchase_reference, asset_serial, asset_status, asset_type,
@@ -17,8 +17,15 @@ $sql = mysqli_query($mysqli, "SELECT asset_archived_at, asset_client_id, asset_c
);
$row = mysqli_fetch_assoc($sql);
if (!$row) {
echo json_encode(['error' => 'That asset no longer exists.']);
exit;
}
$client_id = intval($row['asset_client_id']);
$asset_id = intval($row['asset_id']);
enforceClientAccess();
$asset_form_mode = 'edit';
$asset_type = escapeHtml($row['asset_type']);
$asset_name = escapeHtml($row['asset_name']);
$asset_description = escapeHtml($row['asset_description']);
@@ -42,460 +49,41 @@ $asset_photo = escapeHtml($row['asset_photo']);
$asset_physical_location = escapeHtml($row['asset_physical_location']);
$asset_notes = escapeHtml($row['asset_notes']);
$asset_favorite = intval($row['asset_favorite']);
$asset_created_at = escapeHtml($row['asset_created_at']);
$asset_archived_at = escapeHtml($row['asset_archived_at']);
$asset_vendor_id = intval($row['asset_vendor_id']);
$asset_location_id = intval($row['asset_location_id']);
$asset_contact_id = intval($row['asset_contact_id']);
$asset_network_id = intval($row['interface_network_id']);
$device_icon = getAssetIcon($asset_type);
$device_icon = getAssetIcon($row['asset_type']);
// Asset History Query
// History, newest first
$sql_asset_history = mysqli_query($mysqli, "SELECT asset_history_created_at, asset_history_description, asset_history_status FROM asset_history
WHERE asset_history_asset_id = $asset_id
ORDER BY asset_history_id
DESC LIMIT 10"
ORDER BY asset_history_id DESC
LIMIT 25"
);
// Tags
$asset_tag_id_array = array();
$sql_asset_tags = mysqli_query($mysqli, "SELECT asset_tag_tag_id FROM asset_tags WHERE asset_tag_asset_id = $asset_id");
while ($row = mysqli_fetch_assoc($sql_asset_tags)) {
$asset_tag_tag_id = intval($row['asset_tag_tag_id']);
$asset_tag_id_array[] = $asset_tag_tag_id;
$asset_tag_id_array[] = intval($row['asset_tag_tag_id']);
}
enforceClientAccess();
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class='fa fa-fw fa-<?= $device_icon ?> me-2'></i>Editing asset: <strong><?= $asset_name ?></strong></h5>
<h5 class="modal-title text-truncate"><i class='fa fa-fw fa-<?= $device_icon ?> me-2'></i>Editing asset: <strong><?= $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" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="asset_id" value="<?= $asset_id ?>">
<div class="modal-body">
<ul class="nav nav-pills nav-justified mb-3">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="pill" href="#pills-details">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-assignment">Assignment</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-network">Network</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-purchase">Purchase</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-notes">Notes</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#pills-history">History</a>
</li>
</ul>
<hr>
<div class="tab-content" <?php if (lookupUserPermission('module_support') <= 1) { echo 'inert'; } ?>>
<div class="tab-pane fade show active" id="pills-details">
<div class="mb-3">
<label>Type <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-layer-group"></i></span>
<select class="form-select select2" name="type" required>
<?php foreach($asset_types_array as $asset_type_select => $asset_icon_select) { ?>
<option <?php if ($asset_type_select == $asset_type) { echo "selected"; } ?>><?= $asset_type_select ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-fw fa-tag"></i></span>
<input type="text" class="form-control" name="name" placeholder="Asset name or asset tag" maxlength="200" value="<?= $asset_name ?>" required>
<div class="input-group-text">
<label class="star-toggle mb-0" title="Favorite">
<input class="form-check-input" type="checkbox"
name="favorite"
value="1"
<?php if($asset_favorite) { echo 'checked'; } ?>>
<i class="far fa-star"></i>
</label>
</div>
</div>
</div>
<?php //Do not display Make Model or Serial if Virtual is selected
if ($asset_type !== 'virtual') { ?>
<div class="mb-3">
<label>Make</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<input type="text" class="form-control" name="make" placeholder="Manufacturer" maxlength="200" value="<?= $asset_make ?>">
</div>
</div>
<div class="mb-3">
<label>Model</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-cube"></i></span>
<input type="text" class="form-control" name="model" placeholder="Model Number" maxlength="200" value="<?= $asset_model ?>">
</div>
</div>
<div class="mb-3">
<label>Serial Number</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-barcode"></i></span>
<input type="text" class="form-control font-monospace" name="serial" placeholder="Serial number" maxlength="200" value="<?= $asset_serial ?>">
</div>
</div>
<?php } ?>
<?php if ($asset_type !== 'Phone' && $asset_type !== 'Mobile Phone' && $asset_type !== 'Tablet' && $asset_type !== 'Access Point' && $asset_type !== 'Printer' && $asset_type !== 'Camera' && $asset_type !== 'TV' && $asset_type !== 'Other') { ?>
<div class="mb-3">
<label>Operating System</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-fw fa-laptop-code"></i></span>
<input type="text" class="form-control" name="os" id="os" placeholder="ex Windows 10 Pro" maxlength="200" value="<?= $asset_os ?>">
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Description</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-align-left"></i></span>
<input type="text" class="form-control" name="description" placeholder="Description of the asset" maxlength="255" value="<?= $asset_description ?>">
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-assignment">
<div class="mb-3">
<label>Location</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<select class="form-select select2" name="location">
<option value="">- Select Location -</option>
<?php
$sql_locations = mysqli_query($mysqli, "SELECT location_archived_at, location_id, location_name FROM locations WHERE location_id = $asset_location_id OR location_archived_at IS NULL AND location_client_id = $client_id ORDER BY location_name ASC");
while ($row = mysqli_fetch_assoc($sql_locations)) {
$location_id_select = intval($row['location_id']);
$location_name_select = escapeHtml($row['location_name']);
$location_archived_at = escapeHtml($row['location_archived_at']);
if ($location_archived_at) {
$location_name_select_display = "($location_name_select) - ARCHIVED";
} else {
$location_name_select_display = $location_name_select;
}
?>
<option <?php if ($asset_location_id == $location_id_select) { echo "selected"; } ?> value="<?= $location_id_select ?>"><?= $location_name_select_display ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Physical Location</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-map-marker-alt"></i></span>
<input type="text" class="form-control" name="physical_location" placeholder="Physical location eg. Floor 2, Closet B" maxlength="200" value="<?= $asset_physical_location ?>">
</div>
</div>
<div class="mb-3">
<label>Assign To</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-user-check"></i></span>
<select class="form-select select2" name="contact">
<option value="">- Select Contact -</option>
<?php
$sql_contacts = mysqli_query($mysqli, "SELECT contact_archived_at, contact_id, contact_name, contact_client_id, contact_photo, contact_title FROM contacts WHERE contact_id = $asset_contact_id OR contact_archived_at IS NULL AND contact_client_id = $client_id ORDER BY contact_name ASC");
while ($row = mysqli_fetch_assoc($sql_contacts)) {
$contact_id_select = intval($row['contact_id']);
$contact_name_select = escapeHtml($row['contact_name']);
$contact_archived_at = escapeHtml($row['contact_archived_at']);
if ($contact_archived_at) {
$contact_name_select_display = "($contact_name_select) - ARCHIVED";
} else {
$contact_name_select_display = $contact_name_select;
}
?>
<option <?php if ($asset_contact_id == $contact_id_select) { echo "selected"; } ?> value="<?= $contact_id_select ?>"<?= selectOptionContact($row['contact_client_id'] ?? 0, $row['contact_name'], $row['contact_photo'] ?? '', $row['contact_title'] ?? '') ?>>
<?= $contact_name_select_display ?>
</option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>Status</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-circle"></i></span>
<select class="form-select select2" name="status">
<option value="">- Select Status -</option>
<?php
$sql_interface_types_select = mysqli_query($mysqli, "
SELECT category_name, category_color, category_icon FROM categories
WHERE category_type = 'asset_status'
AND category_archived_at IS NULL
ORDER BY category_order ASC, category_name ASC
");
while ($row = mysqli_fetch_assoc($sql_interface_types_select)) {
$asset_status_select = escapeHtml($row['category_name']);
?>
<option <?php if ($asset_status_select == $asset_status) { echo "selected"; } ?><?= selectOptionColor($row['category_color'] ?? '', $row['category_icon'] ?? '') ?>>
<?= $asset_status_select ?>
</option>
<?php } ?>
</select>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-network">
<div class="mb-3">
<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">
<option value="">- Select Network -</option>
<?php
$sql_networks = mysqli_query($mysqli, "SELECT network, network_archived_at, network_id, network_name FROM networks WHERE network_id = $asset_network_id OR network_archived_at IS NULL AND network_client_id = $client_id ORDER BY network_name ASC");
while ($row = mysqli_fetch_assoc($sql_networks)) {
$network_id_select = intval($row['network_id']);
$network_name_select = escapeHtml($row['network_name']);
$network_select = escapeHtml($row['network']);
$network_archived_at = escapeHtml($row['network_archived_at']);
if ($network_archived_at) {
$network_name_select_display = "($network_name_select - $network_select) - ARCHIVED";
} else {
$network_name_select_display = "$network_name_select - $network_select";
}
?>
<option <?php if ($asset_network_id == $network_id_select) { echo "selected"; } ?> value="<?= $network_id_select ?>"><?= $network_name_select_display ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="mb-3">
<label>IPv4 Address / <span class="text-muted">DHCP</span></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="ip" value="<?= $asset_ip ?>" placeholder="192.168.10.250" data-inputmask="'alias': 'ip'" maxlength="200" data-mask>
<div class="input-group-text">
<input class="form-check-input" type="checkbox" name="dhcp" value="1" <?php if($asset_ip == 'DHCP'){ echo "checked"; } ?>>
</div>
</div>
</div>
<div class="mb-3">
<label>MAC Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="mac" value="<?= $asset_mac ?>" placeholder="00:11:22:AA:BB:CC" data-inputmask="'alias': 'mac'" maxlength="200" data-mask>
</div>
</div>
<div class="mb-3">
<label>IPv6 Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-ethernet"></i></span>
<input type="text" class="form-control font-monospace" name="ipv6" value="<?= $asset_ipv6 ?>" placeholder="2001:0db8:1000::3" maxlength="200">
</div>
</div>
<div class="mb-3">
<label>NAT Address</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-exchange-alt"></i></span>
<input type="text" class="form-control font-monospace" name="nat_ip" value="<?= $asset_nat_ip ?>" placeholder="10.52.4.55" data-inputmask="'alias': 'ip'" maxlength="200" data-mask>
</div>
</div>
<div class="mb-3">
<label>URI</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri" placeholder="URI http:// ftp:// ssh: etc" maxlength="500" value="<?= $asset_uri ?>">
</div>
</div>
<div class="mb-3">
<label>URI 2</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri_2" placeholder="URI http:// ftp:// ssh: etc" maxlength="500" value="<?= $asset_uri_2 ?>">
</div>
</div>
<div class="mb-3">
<label>Client URI</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-globe"></i></span>
<input type="text" class="form-control" name="uri_client" placeholder="URI http:// ftp:// ssh: etc -- viewable in Client Portal" maxlength="500" value="<?= $asset_uri_client ?>">
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-purchase">
<div class="mb-3">
<label>Vendor</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
<select class="form-select select2" name="vendor">
<option value="">- Select Vendor -</option>
<?php
$sql_vendors = mysqli_query($mysqli, "SELECT vendor_archived_at, vendor_id, vendor_name FROM vendors WHERE vendor_id = $asset_vendor_id OR vendor_archived_at IS NULL AND vendor_client_id = $client_id ORDER BY vendor_name ASC");
while ($row = mysqli_fetch_assoc($sql_vendors)) {
$vendor_id_select = intval($row['vendor_id']);
$vendor_name_select = escapeHtml($row['vendor_name']);
$vendor_archived_at = escapeHtml($row['vendor_archived_at']);
if ($vendor_archived_at) {
$vendor_name_select_display = "($vendor_name_select) - ARCHIVED";
} else {
$vendor_name_select_display = $vendor_name_select;
}
?>
<option <?php if ($asset_vendor_id == $vendor_id_select) { echo "selected"; } ?> value="<?= $vendor_id_select ?>"><?= $vendor_name_select_display ?></option>
<?php } ?>
</select>
</div>
</div>
<?php if ($asset_type !== 'Virtual Machine') { ?>
<div class="mb-3">
<label>Purchase Reference</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-receipt"></i></span>
<input type="text" class="form-control" name="purchase_reference" placeholder="eg. Invoice, PO Number" maxlength="200" value="<?= $asset_purchase_reference ?>">
</div>
</div>
<div class="mb-3">
<label>Purchase Date</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31" value="<?= $asset_purchase_date ?>">
</div>
</div>
<?php } ?>
<div class="mb-3">
<label>Install Date</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-check"></i></span>
<input type="date" class="form-control" name="install_date" max="2999-12-31" value="<?= $asset_install_date ?>">
</div>
</div>
<?php if ($asset_type !== 'Virtual Machine') { ?>
<div class="mb-3">
<label>Warranty Expire</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-times"></i></span>
<input type="date" class="form-control" name="warranty_expire" max="2999-12-31" value="<?= $asset_warranty_expire ?>">
</div>
</div>
<?php } ?>
</div>
<div class="tab-pane fade" id="pills-notes">
<div class="mb-3 text-center">
<?php if ($asset_photo) { ?>
<img class="img-fluid" alt="asset_photo" src="<?= "../uploads/clients/$client_id/$asset_photo" ?>">
<?php } ?>
</div>
<div class="mb-3">
<label>Upload / Replace Photo</label>
<input type="file" class="form-control" name="file" accept="image/*">
</div>
<div class="mb-3">
<textarea class="form-control" rows="8" placeholder="Enter some notes" name="notes"><?= $asset_notes ?></textarea>
</div>
<div class="mb-3">
<label>Tags</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-tags"></i></span>
<select class="form-select select2" name="tags[]" data-placeholder="Add some tags" multiple>
<?php
$sql_tags_select = mysqli_query($mysqli, "SELECT tag_id, tag_name, tag_color, tag_icon FROM tags WHERE tag_type = 5 ORDER BY tag_name ASC");
while ($row = mysqli_fetch_assoc($sql_tags_select)) {
$tag_id_select = intval($row['tag_id']);
$tag_name_select = escapeHtml($row['tag_name']);
?>
<option value="<?= $tag_id_select ?>" <?php if (in_array($tag_id_select, $asset_tag_id_array)) { echo "selected"; } ?><?= selectOptionColor($row['tag_color'] ?? '', $row['tag_icon'] ?? '') ?>><?= $tag_name_select ?></option>
<?php } ?>
</select>
<button class="btn btn-secondary ajax-modal" title="New tag" type="button"
data-modal-url="../admin/modals/tag/tag_add.php?type=5">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<p class="text-muted text-end">Asset ID: <span class="font-monospace"><?= $asset_id ?></span></p>
</div>
<div class="tab-pane fade" id="pills-history">
<div class="mb-3">
<label>Asset History</label>
<ul>
<?php
while ($row = mysqli_fetch_assoc($sql_asset_history)) {
$asset_history_status = escapeHtml($row['asset_history_status']);
$asset_history_description = escapeHtml($row['asset_history_description']);
$asset_history_created_at = escapeHtml($row['asset_history_created_at']);
echo "<li><span class='font-monospace'>$asset_history_created_at</span> <span class='badge rounded-pill bg-secondary mt-1'>$asset_history_status</span><br/>$asset_history_description</li><br/>";
}
?>
</ul>
</div>
</div>
</div>
<div class="modal-body ui-front">
<?php require 'asset_form.php'; ?>
</div>
<div class="modal-footer">
<button type="submit" name="edit_asset" class="btn btn-primary text-bold"><i class="fa fa-check me-2"></i>Save</button>

View File

@@ -0,0 +1,390 @@
<?php
/*
* Asset form fields - shared by asset_add.php, asset_edit.php and
* asset_copy.php, which set $asset_form_mode ('add' | 'edit' | 'copy'),
* $client_id (0 = pick the client here) and the $asset_* values (escaped,
* empty for a new asset) before including this. Posts the field set
* agent/post/asset_model.php reads.
*
* One page of sections instead of six tabs. Every field is always in the
* form: fields that came and went with the asset type used to drop out of the
* POST entirely, and the handler then saved them as empty - editing an access
* point wiped its firmware version.
*
* Should not be accessed directly.
*/
isset($mysqli) || die("Direct file access is not allowed");
// Status - from Admin > Categories, keeping a retired value that is still set
$status_options = [];
$sql_status_select = mysqli_query($mysqli, "SELECT category_name, category_color, category_icon FROM categories
WHERE category_type = 'asset_status' AND category_archived_at IS NULL
ORDER BY category_order ASC, category_name ASC");
while ($row = mysqli_fetch_assoc($sql_status_select)) {
$status_options[escapeHtml($row['category_name'])] = selectOptionColor($row['category_color'] ?? '', $row['category_icon'] ?? '');
}
if ($asset_status !== '' && !isset($status_options[$asset_status])) {
$status_options[$asset_status] = '';
}
// The client's own lists - only once the client is known
$location_options = $contact_options = $network_options = $vendor_options = [];
if ($client_id) {
$sql = mysqli_query($mysqli, "SELECT location_archived_at, location_id, location_name FROM locations
WHERE location_client_id = $client_id AND (location_archived_at IS NULL OR location_id = $asset_location_id)
ORDER BY location_name ASC");
while ($row = mysqli_fetch_assoc($sql)) {
$location_options[] = $row;
}
$sql = mysqli_query($mysqli, "SELECT contact_archived_at, contact_id, contact_name, contact_client_id, contact_photo, contact_title FROM contacts
WHERE contact_client_id = $client_id AND (contact_archived_at IS NULL OR contact_id = $asset_contact_id)
ORDER BY contact_name ASC");
while ($row = mysqli_fetch_assoc($sql)) {
$contact_options[] = $row;
}
$sql = mysqli_query($mysqli, "SELECT network, network_archived_at, network_id, network_name FROM networks
WHERE network_client_id = $client_id AND (network_archived_at IS NULL OR network_id = $asset_network_id)
ORDER BY network_name ASC");
while ($row = mysqli_fetch_assoc($sql)) {
$network_options[] = $row;
}
$sql = mysqli_query($mysqli, "SELECT vendor_archived_at, vendor_id, vendor_name FROM vendors
WHERE vendor_client_id = $client_id AND (vendor_archived_at IS NULL OR vendor_id = $asset_vendor_id)
ORDER BY vendor_name ASC");
while ($row = mysqli_fetch_assoc($sql)) {
$vendor_options[] = $row;
}
} else {
$client_options = [];
$sql = mysqli_query($mysqli, "SELECT client_id, client_name, client_abbreviation FROM clients WHERE client_archived_at IS NULL " . clientScopeSql('clients.client_id') . " ORDER BY client_name ASC");
while ($row = mysqli_fetch_assoc($sql)) {
$client_options[] = $row;
}
}
// OS suggestions - what's already in use, most common first
$os_suggestions = [];
$sql = mysqli_query($mysqli, "SELECT asset_os FROM assets WHERE asset_archived_at IS NULL AND asset_os IS NOT NULL AND asset_os != '' GROUP BY asset_os ORDER BY COUNT(*) DESC LIMIT 200");
while ($row = mysqli_fetch_assoc($sql)) {
$os_suggestions[] = ['label' => $row['asset_os'], 'value' => $row['asset_os']];
}
$sql_tags_select = mysqli_query($mysqli, "SELECT tag_id, tag_name, tag_color, tag_icon FROM tags WHERE tag_type = 5 ORDER BY tag_name ASC");
$asset_ip_is_dhcp = $asset_ip === 'DHCP';
?>
<?php if ($asset_form_mode === 'edit') { ?>
<ul class="nav nav-pills mb-3">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="pill" href="#assetFormDetails">Details</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="pill" href="#assetFormHistory">History</a>
</li>
</ul>
<?php } ?>
<div class="tab-content">
<div class="tab-pane fade show active" id="assetFormDetails">
<div class="itflow-form-section">Basics</div>
<div class="row g-3 mb-4">
<?php if (!$client_id) { ?>
<div class="col-12">
<label class="form-label">Client <strong class="text-danger">*</strong></label>
<select class="form-select select2" name="client_id" required>
<option value="">- Select Client -</option>
<?php foreach ($client_options as $client_option) { ?>
<option value="<?= intval($client_option['client_id']) ?>"<?= selectOptionClient($client_option['client_id'], $client_option['client_name'], $client_option['client_abbreviation'] ?? '') ?>><?= escapeHtml($client_option['client_name']) ?></option>
<?php } ?>
</select>
<div class="form-text">Pick the asset's location, contact, network and vendor from its page once it's saved.</div>
</div>
<?php } ?>
<div class="col-md-4">
<label class="form-label">Type <strong class="text-danger">*</strong></label>
<select class="form-select select2" name="type" required>
<option value="">- Select Type -</option>
<?php foreach ($asset_types_array as $asset_type_select => $asset_icon_select) { ?>
<option <?php if ($asset_type_select === html_entity_decode($asset_type)) { echo "selected"; } ?><?= selectOptionData(['icon' => "fas $asset_icon_select"]) ?>><?= $asset_type_select ?></option>
<?php } ?>
</select>
</div>
<div class="col-md-5">
<label class="form-label">Name <strong class="text-danger">*</strong></label>
<div class="input-group">
<input type="text" class="form-control" name="name" placeholder="SRV-DC01" maxlength="200" value="<?= $asset_name ?>" required <?= $asset_form_mode === 'edit' ? '' : 'autofocus' ?>>
<div class="input-group-text">
<label class="star-toggle mb-0" title="Favorite - pinned to the client overview">
<input class="form-check-input" type="checkbox" name="favorite" value="1" <?= $asset_favorite ? 'checked' : '' ?>><i class="far fa-star"></i>
</label>
</div>
</div>
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select class="form-select select2" name="status">
<option value="">- Status -</option>
<?php foreach ($status_options as $status_option => $status_option_data) { ?>
<option <?php if ($status_option === $asset_status) { echo "selected"; } ?><?= $status_option_data ?>><?= $status_option ?></option>
<?php } ?>
</select>
</div>
<div class="col-md-7">
<label class="form-label">Description</label>
<input type="text" class="form-control" name="description" placeholder="Domain controller for HQ" maxlength="255" value="<?= $asset_description ?>">
</div>
<div class="col-md-5">
<label class="form-label">Tags</label>
<div class="input-group">
<select class="form-select select2" name="tags[]" data-placeholder="Add some tags" multiple>
<?php while ($row = mysqli_fetch_assoc($sql_tags_select)) {
$tag_id_select = intval($row['tag_id']); ?>
<option value="<?= $tag_id_select ?>" <?php if (in_array($tag_id_select, $asset_tag_id_array)) { echo "selected"; } ?><?= selectOptionColor($row['tag_color'] ?? '', $row['tag_icon'] ?? '') ?>><?= escapeHtml($row['tag_name']) ?></option>
<?php } ?>
</select>
<button class="btn btn-secondary ajax-modal" title="New tag" type="button" data-modal-url="../admin/modals/tag/tag_add.php?type=5">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
<div class="itflow-form-section">Hardware</div>
<div class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label">Make</label>
<input type="text" class="form-control" name="make" placeholder="Dell" maxlength="200" value="<?= $asset_make ?>">
</div>
<div class="col-md-4">
<label class="form-label">Model</label>
<input type="text" class="form-control" name="model" placeholder="PowerEdge R650" maxlength="200" value="<?= $asset_model ?>">
</div>
<div class="col-md-5">
<label class="form-label">Serial number</label>
<input type="text" class="form-control font-monospace" name="serial" placeholder="ABC1234XYZ" maxlength="200" value="<?= $asset_serial ?>">
</div>
<div class="col-12">
<label class="form-label">Operating system / firmware</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-fw fa-laptop-code"></i></span>
<input type="text" class="form-control" name="os" id="assetFormOs" placeholder="Windows 11 Pro 24H2, Ubuntu 24.04, UniFi firmware 7.0" maxlength="200" value="<?= $asset_os ?>">
</div>
</div>
</div>
<div class="itflow-form-section">Network <span class="fw-normal text-body-secondary text-lowercase">&middot; the primary interface</span></div>
<div class="row g-3 mb-4">
<?php if ($client_id) { ?>
<div class="col-md-6">
<label class="form-label">Network</label>
<select class="form-select select2" name="network">
<option value="">- Select Network -</option>
<?php foreach ($network_options as $network_option) {
$network_option_id = intval($network_option['network_id']); ?>
<option value="<?= $network_option_id ?>" <?php if ($asset_network_id === $network_option_id) { echo "selected"; } ?><?= selectOptionData(['description' => $network_option['network']]) ?>><?= escapeHtml($network_option['network_name']) ?><?= $network_option['network_archived_at'] ? ' (archived)' : '' ?></option>
<?php } ?>
</select>
</div>
<?php } ?>
<div class="col-md-6">
<label class="form-label">IPv4 address</label>
<div class="input-group">
<input type="text" class="form-control font-monospace" name="ip" id="assetFormIp" placeholder="192.168.1.10" maxlength="200" value="<?= $asset_ip_is_dhcp ? '' : $asset_ip ?>" data-inputmask="'alias': 'ip'" data-mask <?= $asset_ip_is_dhcp ? 'disabled' : '' ?>>
<div class="input-group-text">
<div class="form-check mb-0">
<input class="form-check-input" type="checkbox" name="dhcp" value="1" id="assetFormDhcp" <?= $asset_ip_is_dhcp ? 'checked' : '' ?>>
<label class="form-check-label small" for="assetFormDhcp">DHCP</label>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label">MAC address</label>
<input type="text" class="form-control font-monospace" name="mac" placeholder="00:1A:2B:3C:4D:5E" maxlength="200" value="<?= $asset_mac ?>" data-inputmask="'alias': 'mac'" data-mask>
</div>
<div class="col-md-6">
<label class="form-label">NAT address</label>
<input type="text" class="form-control font-monospace" name="nat_ip" placeholder="203.0.113.10" maxlength="200" value="<?= $asset_nat_ip ?>" data-inputmask="'alias': 'ip'" data-mask>
</div>
<div class="col-12">
<label class="form-label">IPv6 address</label>
<input type="text" class="form-control font-monospace" name="ipv6" placeholder="2001:db8::1" maxlength="200" value="<?= $asset_ipv6 ?>">
</div>
<div class="col-md-6">
<label class="form-label">Link</label>
<input type="text" class="form-control" name="uri" placeholder="https://, ssh://, rdp://" maxlength="500" value="<?= $asset_uri ?>">
</div>
<div class="col-md-6">
<label class="form-label">Second link</label>
<input type="text" class="form-control" name="uri_2" placeholder="https://, ssh://, rdp://" maxlength="500" value="<?= $asset_uri_2 ?>">
</div>
<div class="col-12">
<label class="form-label">Client link</label>
<input type="text" class="form-control" name="uri_client" placeholder="https://" maxlength="500" value="<?= $asset_uri_client ?>">
<div class="form-text">Shown to the client in their portal.</div>
</div>
</div>
<?php if ($client_id) { ?>
<div class="itflow-form-section">Where &amp; who</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Location</label>
<select class="form-select select2" name="location">
<option value="">- Select Location -</option>
<?php foreach ($location_options as $location_option) {
$location_option_id = intval($location_option['location_id']); ?>
<option value="<?= $location_option_id ?>" <?php if ($asset_location_id === $location_option_id) { echo "selected"; } ?>><?= escapeHtml($location_option['location_name']) ?><?= $location_option['location_archived_at'] ? ' (archived)' : '' ?></option>
<?php } ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Where in the building</label>
<input type="text" class="form-control" name="physical_location" placeholder="Floor 2, comms closet B" maxlength="200" value="<?= $asset_physical_location ?>">
</div>
<div class="col-12">
<label class="form-label">Assigned to</label>
<select class="form-select select2" name="contact">
<option value="">- Nobody -</option>
<?php foreach ($contact_options as $contact_option) {
$contact_option_id = intval($contact_option['contact_id']); ?>
<option value="<?= $contact_option_id ?>" <?php if ($asset_contact_id === $contact_option_id) { echo "selected"; } ?><?= selectOptionContact($contact_option['contact_client_id'] ?? 0, $contact_option['contact_name'], $contact_option['contact_photo'] ?? '', $contact_option['contact_title'] ?? '') ?>><?= escapeHtml($contact_option['contact_name']) ?><?= $contact_option['contact_archived_at'] ? ' (archived)' : '' ?></option>
<?php } ?>
</select>
</div>
</div>
<?php } else { ?>
<div class="itflow-form-section">Where</div>
<div class="row g-3 mb-4">
<div class="col-12">
<label class="form-label">Where in the building</label>
<input type="text" class="form-control" name="physical_location" placeholder="Floor 2, comms closet B" maxlength="200" value="<?= $asset_physical_location ?>">
</div>
</div>
<?php } ?>
<div class="itflow-form-section">Purchase &amp; warranty</div>
<div class="row g-3 mb-4">
<?php if ($client_id) { ?>
<div class="col-md-6">
<label class="form-label">Vendor</label>
<select class="form-select select2" name="vendor">
<option value="">- Select Vendor -</option>
<?php foreach ($vendor_options as $vendor_option) {
$vendor_option_id = intval($vendor_option['vendor_id']); ?>
<option value="<?= $vendor_option_id ?>" <?php if ($asset_vendor_id === $vendor_option_id) { echo "selected"; } ?>><?= escapeHtml($vendor_option['vendor_name']) ?><?= $vendor_option['vendor_archived_at'] ? ' (archived)' : '' ?></option>
<?php } ?>
</select>
</div>
<?php } ?>
<div class="col-md-6">
<label class="form-label">Order / invoice reference</label>
<input type="text" class="form-control" name="purchase_reference" placeholder="INV-1045, PO-7782" maxlength="200" value="<?= $asset_purchase_reference ?>">
</div>
<div class="col-md-4">
<label class="form-label">Purchased</label>
<input type="date" class="form-control" name="purchase_date" max="2999-12-31" value="<?= $asset_purchase_date ?>">
</div>
<div class="col-md-4">
<label class="form-label">Installed</label>
<input type="date" class="form-control" name="install_date" max="2999-12-31" value="<?= $asset_install_date ?>">
</div>
<div class="col-md-4">
<label class="form-label">Warranty expires</label>
<input type="date" class="form-control" name="warranty_expire" max="2999-12-31" value="<?= $asset_warranty_expire ?>">
</div>
</div>
<?php if ($asset_form_mode !== 'edit') { ?>
<div class="itflow-form-section">Login <span class="fw-normal text-body-secondary text-lowercase">&middot; saved as a credential on the asset</span></div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Username</label>
<input type="text" class="form-control" name="username" placeholder="admin" maxlength="350" autocomplete="off">
</div>
<div class="col-md-6">
<label class="form-label">Password</label>
<input type="text" class="form-control" name="password" placeholder="Leave empty to skip" maxlength="350" autocomplete="off">
</div>
</div>
<?php } ?>
<div class="itflow-form-section">Photo &amp; notes</div>
<div class="row g-3">
<div class="col-md-5">
<label class="form-label">Photo</label>
<?php if ($asset_photo) { ?>
<div class="mb-2"><img class="img-thumbnail" style="max-height: 7rem" alt="Current photo" src="<?= "../uploads/clients/$client_id/$asset_photo" ?>"></div>
<?php } ?>
<input type="file" class="form-control" name="file" accept="image/jpeg,image/png,image/gif,image/webp">
<?php if ($asset_photo) { ?>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" name="remove_photo" value="1" id="assetFormRemovePhoto">
<label class="form-check-label" for="assetFormRemovePhoto">Remove this photo</label>
</div>
<?php } ?>
</div>
<div class="col-md-7">
<label class="form-label">Notes</label>
<textarea class="form-control" rows="4" placeholder="Configuration details, quirks, anything worth knowing" name="notes"><?= $asset_notes ?></textarea>
</div>
</div>
<?php if ($asset_form_mode === 'edit') { ?>
<p class="text-body-secondary small text-end mt-3 mb-0">Asset ID <span class="font-monospace"><?= $asset_id ?></span></p>
<?php } ?>
</div>
<?php if ($asset_form_mode === 'edit') { ?>
<div class="tab-pane fade" id="assetFormHistory">
<?php if (mysqli_num_rows($sql_asset_history)) { ?>
<ul class="list-group list-group-flush">
<?php while ($row = mysqli_fetch_assoc($sql_asset_history)) { ?>
<li class="list-group-item px-0">
<div class="d-flex justify-content-between gap-2">
<span><?= escapeHtml($row['asset_history_description']) ?></span>
<?php if ($row['asset_history_status']) { ?>
<?= assetStatusPill($row['asset_history_status']) ?>
<?php } ?>
</div>
<div class="small text-body-secondary" title="<?= escapeHtml($row['asset_history_created_at']) ?>"><?= timeAgo($row['asset_history_created_at']) ?> &middot; <?= escapeHtml($row['asset_history_created_at']) ?></div>
</li>
<?php } ?>
</ul>
<?php } else { ?>
<p class="text-body-secondary text-center my-4">No history recorded yet.</p>
<?php } ?>
</div>
<?php } ?>
</div>
<script>
itflowReady(function () {
var form = document.getElementById('assetFormOs').closest('form');
// OS / firmware suggestions from what's already in use
var osInput = form.querySelector('#assetFormOs');
itflowAutocomplete(osInput, {
minLength: 1,
source: <?= json_encode($os_suggestions, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>,
onSelect: function (item) {
osInput.value = item.label;
}
});
// DHCP means no fixed address to type
var dhcp = form.querySelector('#assetFormDhcp');
var ip = form.querySelector('#assetFormIp');
dhcp.addEventListener('change', function () {
ip.disabled = dhcp.checked;
if (!dhcp.checked) {
ip.focus();
}
});
});
</script>

View File

@@ -18,6 +18,12 @@ if (isset($_POST['add_asset'])) {
enforceClientAccess();
// The picks have to be this client's own
$location = assetClientOwnedId('location', $location, $client_id);
$contact = assetClientOwnedId('contact', $contact, $client_id);
$vendor = assetClientOwnedId('vendor', $vendor, $client_id);
$network = assetClientOwnedId('network', $network, $client_id);
// Only the two credential fields this handler writes - name/description/uri here
// belong to the asset, not the credential, and have their own column widths.
// Checked before the asset is created, so an overlong credential can't leave a
@@ -74,7 +80,7 @@ if (isset($_POST['add_asset'])) {
$credential_id = mysqli_insert_id($mysqli);
logAudit("Credential", "Create", "$session_name created login credential for asset $asset_name", $client_id, $credential_id);
logAudit("Credential", "Create", "$session_name created login credential for asset $name", $client_id, $credential_id);
$alert_extended = " along with login credentials";
@@ -104,11 +110,17 @@ if (isset($_POST['edit_asset'])) {
// Get Existing Photo and assigned client_id
$sql = mysqli_query($mysqli,"SELECT asset_photo, asset_client_id FROM assets WHERE asset_id = $asset_id");
$row = mysqli_fetch_assoc($sql);
$existing_file_name = escapeSql($row['asset_photo']);
$existing_file_name = $row['asset_photo'] ?? '';
$client_id = intval($row['asset_client_id']);
enforceClientAccess();
// The picks have to be this client's own
$location = assetClientOwnedId('location', $location, $client_id);
$contact = assetClientOwnedId('contact', $contact, $client_id);
$vendor = assetClientOwnedId('vendor', $vendor, $client_id);
$network = assetClientOwnedId('network', $network, $client_id);
mysqli_query($mysqli,"UPDATE assets SET asset_name = '$name', asset_description = '$description', asset_type = '$type', asset_make = '$make', asset_model = '$model', asset_serial = '$serial', asset_os = '$os', asset_uri = '$uri', asset_uri_2 = '$uri_2', asset_uri_client = '$uri_client', asset_location_id = $location, asset_vendor_id = $vendor, asset_contact_id = $contact, asset_status = '$status', asset_purchase_reference = '$purchase_reference', asset_purchase_date = $purchase_date, asset_warranty_expire = $warranty_expire, asset_install_date = $install_date, asset_physical_location = '$physical_location', asset_notes = '$notes', asset_favorite = $favorite WHERE asset_id = $asset_id");
$sql_interfaces = mysqli_query($mysqli, "SELECT 1 FROM asset_interfaces WHERE interface_asset_id = $asset_id AND interface_primary = 1");
@@ -121,20 +133,25 @@ if (isset($_POST['edit_asset'])) {
mysqli_query($mysqli,"UPDATE asset_interfaces SET interface_mac = '$mac', interface_ip = '$ip', interface_nat_ip = '$nat_ip', interface_ipv6 = '$ipv6', interface_network_id = $network WHERE interface_asset_id = $asset_id AND interface_primary = 1");
}
// Update Photo
if ($new_file_name = checkFileUpload($_FILES['file'], array('jpg', 'jpeg', 'gif', 'png', 'webp'))) {
// Photo - a new upload replaces the old file; Remove only counts when nothing new came in
$existing_photo_path = $existing_file_name !== '' ? "../uploads/clients/$client_id/" . basename($existing_file_name) : '';
if (isset($_FILES['file']) && $new_file_name = checkFileUpload($_FILES['file'], array('jpg', 'jpeg', 'gif', 'png', 'webp'))) {
// Set directory in which the uploaded file will be moved
$file_tmp_path = $_FILES['file']['tmp_name'];
$upload_file_dir = "../uploads/clients/$client_id/";
$dest_path = $upload_file_dir . $new_file_name;
if (!is_dir("../uploads/clients/$client_id")) {
mkdir("../uploads/clients/$client_id", 0755, true);
}
move_uploaded_file($_FILES['file']['tmp_name'], "../uploads/clients/$client_id/$new_file_name");
move_uploaded_file($file_tmp_path, $dest_path);
//Delete old file
unlink("../uploads/clients/$client_id/$existing_file_name");
if ($existing_photo_path && is_file($existing_photo_path)) {
unlink($existing_photo_path);
}
mysqli_query($mysqli,"UPDATE assets SET asset_photo = '$new_file_name' WHERE asset_id = $asset_id");
} elseif (!empty($_POST['remove_photo']) && $existing_photo_path) {
if (is_file($existing_photo_path)) {
unlink($existing_photo_path);
}
mysqli_query($mysqli,"UPDATE assets SET asset_photo = NULL WHERE asset_id = $asset_id");
}
// Tags

View File

@@ -1,48 +1,48 @@
<?php
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
$name = escapeSql($_POST['name']);
$description = escapeSql($_POST['description']);
$type = escapeSql($_POST['type']);
$make = escapeSql($_POST['make']);
$model = escapeSql($_POST['model']);
$serial = escapeSql($_POST['serial']);
$os = escapeSql($_POST['os']);
$ip = escapeSql($_POST['ip']);
$name = escapeSql($_POST['name'] ?? '');
$description = escapeSql($_POST['description'] ?? '');
$type = escapeSql($_POST['type'] ?? '');
$make = escapeSql($_POST['make'] ?? '');
$model = escapeSql($_POST['model'] ?? '');
$serial = escapeSql($_POST['serial'] ?? '');
$os = escapeSql($_POST['os'] ?? '');
$ip = escapeSql($_POST['ip'] ?? '');
$dhcp = intval($_POST['dhcp'] ?? 0);
if ($dhcp == 1) {
$ip = 'DHCP';
}
$ipv6 = escapeSql($_POST['ipv6']);
$nat_ip = escapeSql($_POST['nat_ip']);
$mac = escapeSql($_POST['mac']);
$uri = escapeSql($_POST['uri']);
$uri_2 = escapeSql($_POST['uri_2']);
$uri_client = escapeSql($_POST['uri_client']);
$status = escapeSql($_POST['status']);
$ipv6 = escapeSql($_POST['ipv6'] ?? '');
$nat_ip = escapeSql($_POST['nat_ip'] ?? '');
$mac = escapeSql($_POST['mac'] ?? '');
$uri = escapeSql($_POST['uri'] ?? '');
$uri_2 = escapeSql($_POST['uri_2'] ?? '');
$uri_client = escapeSql($_POST['uri_client'] ?? '');
$status = escapeSql($_POST['status'] ?? '');
$location = intval($_POST['location'] ?? 0);
$physical_location = escapeSql($_POST['physical_location']);
$physical_location = escapeSql($_POST['physical_location'] ?? '');
$vendor = intval($_POST['vendor'] ?? 0);
$contact = intval($_POST['contact'] ?? 0);
$network = intval($_POST['network'] ?? 0);
$purchase_reference = escapeSql($_POST['purchase_reference']);
$purchase_date = escapeSql($_POST['purchase_date']);
$purchase_reference = escapeSql($_POST['purchase_reference'] ?? '');
$purchase_date = escapeSql($_POST['purchase_date'] ?? '');
if (empty($purchase_date)) {
$purchase_date = "NULL";
} else {
$purchase_date = "'" . $purchase_date . "'";
}
$warranty_expire = escapeSql($_POST['warranty_expire']);
$warranty_expire = escapeSql($_POST['warranty_expire'] ?? '');
if (empty($warranty_expire)) {
$warranty_expire = "NULL";
} else {
$warranty_expire = "'" . $warranty_expire . "'";
}
$install_date = escapeSql($_POST['install_date']);
$install_date = escapeSql($_POST['install_date'] ?? '');
if (empty($install_date)) {
$install_date = "NULL";
} else {
$install_date = "'" . $install_date . "'";
}
$notes = escapeSql($_POST['notes']);
$notes = escapeSql($_POST['notes'] ?? '');
$favorite = intval($_POST['favorite'] ?? 0);

View File

@@ -3542,3 +3542,206 @@ button.itflow-rack-item-body:hover {
break-inside: avoid;
}
}
/* Assets - agent/assets.php, agent/asset.php, agent/modals/asset/
Each kind of asset has a colour (functions/asset.php assetTypeTone()), used
for the icon tile on the list, the asset page header and the related-asset
chips. The tile is the colour at low strength with the icon at full, so it
reads in light and dark alike. */
.itflow-asset-tone-workstation { --itflow-asset-tone: #3d72bb; }
.itflow-asset-tone-server { --itflow-asset-tone: #6d5fc4; }
.itflow-asset-tone-virtual { --itflow-asset-tone: #9a5cc6; }
.itflow-asset-tone-network { --itflow-asset-tone: #23897a; }
.itflow-asset-tone-firewall { --itflow-asset-tone: #c04e4e; }
.itflow-asset-tone-wireless { --itflow-asset-tone: #1b8fc2; }
.itflow-asset-tone-phone { --itflow-asset-tone: #b8862f; }
.itflow-asset-tone-peripheral { --itflow-asset-tone: #5b6f8a; }
.itflow-asset-tone-other { --itflow-asset-tone: #6c757d; }
[data-bs-theme="dark"] .itflow-asset-tone-workstation { --itflow-asset-tone: #6f9fe0; }
[data-bs-theme="dark"] .itflow-asset-tone-server { --itflow-asset-tone: #a197ee; }
[data-bs-theme="dark"] .itflow-asset-tone-virtual { --itflow-asset-tone: #c595ea; }
[data-bs-theme="dark"] .itflow-asset-tone-network { --itflow-asset-tone: #4fc4b0; }
[data-bs-theme="dark"] .itflow-asset-tone-firewall { --itflow-asset-tone: #ec8585; }
[data-bs-theme="dark"] .itflow-asset-tone-wireless { --itflow-asset-tone: #5cc0ec; }
[data-bs-theme="dark"] .itflow-asset-tone-phone { --itflow-asset-tone: #e3b35c; }
[data-bs-theme="dark"] .itflow-asset-tone-peripheral { --itflow-asset-tone: #9fb2cc; }
[data-bs-theme="dark"] .itflow-asset-tone-other { --itflow-asset-tone: #adb5bd; }
.itflow-asset-icon {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: var(--bs-border-radius-lg);
background-color: color-mix(in srgb, var(--itflow-asset-tone) 14%, transparent);
color: var(--itflow-asset-tone);
font-size: 1.05rem;
}
.itflow-asset-icon-lg {
width: 3.5rem;
height: 3.5rem;
font-size: 1.5rem;
}
.itflow-asset-icon-sm {
width: 1.75rem;
height: 1.75rem;
border-radius: var(--bs-border-radius);
font-size: .8rem;
}
img.itflow-asset-icon {
object-fit: cover;
background: none;
}
/* Kind-of-asset tabs above the filter bar */
.itflow-asset-tabs .nav-link {
padding: .3rem .75rem;
color: var(--bs-secondary-color);
}
.itflow-asset-tabs .nav-link .badge {
background-color: var(--bs-secondary-bg);
color: var(--bs-secondary-color);
font-weight: 500;
}
.itflow-asset-tabs .nav-link.active .badge {
background-color: rgba(255, 255, 255, .25);
color: inherit;
}
/* List rows */
.itflow-asset-table td {
padding-top: .6rem;
padding-bottom: .6rem;
}
.itflow-asset-name-cell {
min-width: 15rem;
}
.itflow-asset-desc {
max-width: 18rem;
}
.itflow-asset-model-cell {
min-width: 9rem;
}
/* Tag pills and other controls inside a cell with a stretched-link stay clickable */
.itflow-above-stretch {
position: relative;
z-index: 2;
}
.btn.btn-xs {
--bs-btn-padding-y: 0;
--bs-btn-padding-x: .4rem;
--bs-btn-font-size: .7rem;
--bs-btn-border-radius: var(--bs-border-radius-sm);
font-size: .7rem;
line-height: 1.4;
}
/* Copy button beside an IP / MAC / serial - quiet until the row is pointed at */
.itflow-copy {
margin-left: .3rem;
padding: 0 .15rem;
border: 0;
background: none;
font-size: .8em;
color: var(--bs-secondary-color);
opacity: 0;
transition: opacity .12s ease-in-out;
}
tr:hover .itflow-copy,
.itflow-copy-always .itflow-copy,
.itflow-copy:focus-visible {
opacity: 1;
}
.itflow-copy:hover {
color: var(--bs-emphasis-color);
}
@media (hover: none) {
.itflow-copy {
opacity: 1;
}
}
/* Asset page */
.itflow-asset-dl {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: .45rem 1rem;
font-size: .875rem;
}
.itflow-asset-dl dt {
font-weight: 500;
color: var(--bs-secondary-color);
}
.itflow-asset-dl dd {
margin: 0;
overflow-wrap: anywhere;
}
.itflow-asset-pills {
gap: .25rem;
}
.itflow-asset-related .itflow-asset-pills .nav-link {
padding: .3rem .7rem;
font-size: .875rem;
}
.itflow-asset-related .itflow-asset-pills .nav-link:not(.active) {
color: var(--bs-body-color);
}
.itflow-asset-related .itflow-asset-pills .nav-link.is-empty:not(.active) {
color: var(--bs-secondary-color);
}
.itflow-asset-pills .nav-link .badge {
background-color: var(--bs-secondary-bg);
color: var(--bs-secondary-color);
font-weight: 500;
}
.itflow-asset-pills .nav-link .badge.text-bg-danger {
background-color: var(--bs-danger) !important;
color: #fff !important;
}
.itflow-asset-pills .nav-link.active .badge {
background-color: rgba(255, 255, 255, .25);
color: inherit;
}
.itflow-asset-pane-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: .5rem;
padding: .75rem 1rem;
border-bottom: 1px solid var(--bs-border-color-translucent);
}
.itflow-asset-related .table > :not(caption) > * > :first-child {
padding-left: 1rem;
}
.itflow-asset-related .table > :not(caption) > * > :last-child {
padding-right: 1rem;
}
.itflow-asset-related .table td:first-child {
min-width: 11rem;
}
.itflow-asset-related .table td.checkbox-column {
min-width: 0;
}
.itflow-asset-empty {
padding: 2.5rem 1rem;
text-align: center;
color: var(--bs-secondary-color);
}
.itflow-asset-dl .font-monospace {
font-size: .82rem;
}
/* Section heading inside a long form (asset add/edit) */
.itflow-form-section {
margin-bottom: .75rem;
padding-bottom: .35rem;
border-bottom: 1px solid var(--bs-border-color-translucent);
font-size: .75rem;
font-weight: 600;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--bs-secondary-color);
}

View File

@@ -19,6 +19,7 @@ require_once __DIR__ . '/functions/files.php';
require_once __DIR__ . '/functions/domain.php';
require_once __DIR__ . '/functions/network.php';
require_once __DIR__ . '/functions/rack.php';
require_once __DIR__ . '/functions/asset.php';
require_once __DIR__ . '/functions/auth.php';
require_once __DIR__ . '/functions/logging.php';
require_once __DIR__ . '/functions/app.php';

241
functions/asset.php Normal file
View File

@@ -0,0 +1,241 @@
<?php
/*
* Assets - how an asset is shown: the colour of its type, the badge for its
* status, the icon for its operating system and how its warranty and age read.
* Shared by the assets list (agent/assets.php), the asset page
* (agent/asset.php) and the quick-look modal (agent/modals/asset/asset.php).
*
* Everything here takes raw values and returns plain strings or arrays of
* them - escape anything that came from the database before echoing it.
*/
/** The colour family an asset type is drawn in - a CSS class suffix (itflow-asset-tone-*). */
function assetTypeTone($asset_type) {
$tones = [
'Laptop' => 'workstation',
'Desktop' => 'workstation',
'Server' => 'server',
'Virtual Machine' => 'virtual',
'Switch' => 'network',
'Firewall/Router' => 'firewall',
'Access Point' => 'wireless',
'Phone' => 'phone',
'Mobile Phone' => 'phone',
'Tablet' => 'phone',
'Printer' => 'peripheral',
'Display' => 'peripheral',
'Camera' => 'peripheral',
];
return $tones[(string) $asset_type] ?? 'other';
}
/**
* Bootstrap colour for an asset status. A colour set on the status in
* Admin > Categories wins; otherwise the built-in statuses get a sensible
* default and anything else is grey.
*
* Returns ['class' => bootstrap tone or '', 'style' => inline background or '']
*/
function assetStatusBadge($status, $category_color = '') {
$category_color = trim((string) $category_color);
if (preg_match('/^(#[0-9a-f]{3,8}|[a-z]{3,24})$/i', $category_color)) {
return ['class' => '', 'style' => "background-color: $category_color"];
}
$defaults = [
'Deployed' => 'success',
'Ready to Deploy' => 'info',
'Out for Repair' => 'warning',
'Lost' => 'danger',
'Stolen' => 'danger',
'Retired' => 'secondary',
];
return ['class' => $defaults[(string) $status] ?? 'secondary', 'style' => ''];
}
/** Status colours from Admin > Categories, keyed by status name (raw). One query per page. */
function assetStatusColors() {
global $mysqli;
static $colors = null;
if ($colors === null) {
$colors = [];
$sql = mysqli_query($mysqli, "SELECT category_name, category_color FROM categories WHERE category_type = 'asset_status' AND category_archived_at IS NULL");
while ($row = mysqli_fetch_assoc($sql)) {
$colors[$row['category_name']] = (string) $row['category_color'];
}
}
return $colors;
}
/** The status badge, ready to echo. */
function assetStatusPill($status) {
$status = (string) $status;
if ($status === '') {
return '';
}
$badge = assetStatusBadge($status, assetStatusColors()[$status] ?? '');
$class = $badge['class'] ? "text-bg-{$badge['class']}" : 'text-white';
$style = $badge['style'] ? " style=\"{$badge['style']}\"" : '';
return "<span class=\"badge rounded-pill fw-normal $class\"$style>" . escapeHtml($status) . "</span>";
}
/** A brand icon for the operating system, where one is recognisable (Font Awesome 5 class). */
function assetOsIcon($os) {
$os = strtolower((string) $os);
$icons = [
'windows' => 'fab fa-windows',
'macos' => 'fab fa-apple',
'mac os' => 'fab fa-apple',
'ios' => 'fab fa-apple',
'ipados' => 'fab fa-apple',
'android' => 'fab fa-android',
'ubuntu' => 'fab fa-ubuntu',
'red hat' => 'fab fa-redhat',
'rhel' => 'fab fa-redhat',
'centos' => 'fab fa-centos',
'fedora' => 'fab fa-fedora',
'suse' => 'fab fa-suse',
'freebsd' => 'fab fa-freebsd',
'pfsense' => 'fab fa-freebsd',
'opnsense' => 'fab fa-freebsd',
'truenas' => 'fab fa-freebsd',
'raspberry' => 'fab fa-raspberry-pi',
'linux' => 'fab fa-linux',
'debian' => 'fab fa-linux',
'mint' => 'fab fa-linux',
'proxmox' => 'fab fa-linux',
'pop!_os' => 'fab fa-linux',
'firmware' => 'fas fa-microchip',
'routeros' => 'fas fa-microchip',
];
foreach ($icons as $needle => $icon) {
if (strpos($os, $needle) !== false) {
return $icon;
}
}
return 'fas fa-compact-disc';
}
/**
* Where the warranty stands, or null when there's no date.
*
* Returns ['tone' => danger|warning|success, 'short' => "Expired" / "23 days left" / "Covered",
* 'text' => "Expired 3 months ago" / "Expires in 23 days" / "Until 2028-11-08",
* 'days' => days left (negative once expired)]
*/
function assetWarranty($warranty_expire, $soon_days = 90) {
$warranty_expire = trim((string) $warranty_expire);
if ($warranty_expire === '' || strpos($warranty_expire, '0000-00-00') === 0) {
return null;
}
$expires = DateTime::createFromFormat('!Y-m-d', substr($warranty_expire, 0, 10));
if (!$expires) {
return null;
}
$today = new DateTime('today');
$days = (int) $today->diff($expires)->format('%r%a');
$date = $expires->format('Y-m-d');
if ($days < 0) {
return ['tone' => 'danger', 'short' => 'Expired', 'text' => 'Expired ' . timeAgo($date), 'days' => $days, 'date' => $date];
}
if ($days <= $soon_days) {
$left = $days === 0 ? 'today' : ($days === 1 ? '1 day left' : "$days days left");
return ['tone' => 'warning', 'short' => $left, 'text' => $days === 0 ? 'Expires today' : "Expires in $days day" . ($days === 1 ? '' : 's'), 'days' => $days, 'date' => $date];
}
return ['tone' => 'success', 'short' => 'Covered', 'text' => "Until $date", 'days' => $days, 'date' => $date];
}
/** "3 yrs 2 mos" since a date, or '' without one. For purchase / install dates. */
function assetAge($date) {
$date = trim((string) $date);
if ($date === '' || strpos($date, '0000-00-00') === 0) {
return '';
}
$from = DateTime::createFromFormat('!Y-m-d', substr($date, 0, 10));
if (!$from) {
return '';
}
$diff = $from->diff(new DateTime('today'));
if ($diff->invert) {
return '';
}
$parts = [];
if ($diff->y) {
$parts[] = $diff->y . ' yr' . ($diff->y === 1 ? '' : 's');
}
if ($diff->m) {
$parts[] = $diff->m . ' mo' . ($diff->m === 1 ? '' : 's');
}
if (!$parts) {
return $diff->d <= 1 ? 'new' : $diff->d . ' days';
}
return implode(' ', $parts);
}
/**
* The asset's links (URI, URI 2, client URI) as [label, url] pairs, skipping
* empty ones. Raw - escapeUrl() each url before it goes in an href.
*/
function assetLinks($uri, $uri_2, $uri_client) {
$links = [];
foreach ([['Open', $uri], ['Open 2', $uri_2], ['Client link', $uri_client]] as [$label, $url]) {
$url = trim((string) $url);
if ($url !== '') {
$links[] = [$label, $url];
}
}
return $links;
}
/** A short name for a link, by what it opens: "Web", "SSH", "RDP"... The full URL goes in its title. */
function assetLinkLabel($url) {
$scheme = strtolower((string) parse_url((string) $url, PHP_URL_SCHEME));
$labels = ['http' => 'Web', 'https' => 'Web', 'ssh' => 'SSH', 'telnet' => 'Telnet', 'rdp' => 'RDP', 'vnc' => 'VNC',
'ftp' => 'FTP', 'sftp' => 'SFTP', 'smb' => 'SMB'];
return $labels[$scheme] ?? ($scheme !== '' ? strtoupper($scheme) : 'Link');
}
/** Icon for a link by its scheme. */
function assetLinkIcon($url) {
$scheme = strtolower((string) parse_url((string) $url, PHP_URL_SCHEME));
$icons = [
'ssh' => 'fas fa-terminal',
'telnet' => 'fas fa-terminal',
'rdp' => 'fas fa-desktop',
'vnc' => 'fas fa-desktop',
'ftp' => 'fas fa-folder-open',
'sftp' => 'fas fa-folder-open',
'smb' => 'fas fa-folder-open',
'http' => 'fas fa-globe',
'https' => 'fas fa-globe',
];
return $icons[$scheme] ?? 'fas fa-external-link-alt';
}
/**
* $id if that location / contact / vendor / network belongs to the client,
* else 0. The asset handlers run their picks through this so a hand-edited
* POST can't hang another client's contact or location off an asset.
*/
function assetClientOwnedId($kind, $id, $client_id) {
global $mysqli;
$tables = [
'location' => ['locations', 'location_id', 'location_client_id'],
'contact' => ['contacts', 'contact_id', 'contact_client_id'],
'vendor' => ['vendors', 'vendor_id', 'vendor_client_id'],
'network' => ['networks', 'network_id', 'network_client_id'],
];
$id = intval($id);
$client_id = intval($client_id);
if (!$id || !isset($tables[$kind])) {
return 0;
}
[$table, $id_column, $client_column] = $tables[$kind];
$sql = mysqli_query($mysqli, "SELECT 1 FROM $table WHERE $id_column = $id AND $client_column = $client_id LIMIT 1");
return mysqli_num_rows($sql) ? $id : 0;
}