Files
itflow/agent/modals/asset/asset.php
johnnyq ade9d5aac3 Services modernization: new services list with Needs attention, Review due, High importance, No review set, Starred and Archived views, dependency chips, in-place importance, bulk importance/category/review schedule, mark reviewed, star, archive/restore/delete and CSV/PDF export; new service page with service facts, backup and recovery, who to call (vendor and client contact details), auto-saving notes and tabs for the assets it runs on, logins, domains and certificates, documents, tickets and history; compact quick look; shared add/edit form with importance, category suggestions, a review schedule and dependency pickers on add; review modal with checklist and history note; attention flags for expiring or expired domains and certificates, certificate check problems, infrastructure out of warranty or out of action, and archived records still linked; Report a problem opens a prefilled ticket linked to the service, plus a service_tickets table, ticket page services list and link/unlink from both sides; services, domains and certificates added to the generic link system, legacy service link actions routed through it with same-client checks; services shown on the asset, contact and vendor pages, the asset/domain/certificate/credential quick looks, the document Linked card, the client overview (card, attention items, upcoming reviews), the dashboard, calendar, global search, client PDF pack and nightly review notifications; archived services left out of nav counts and lookups; services read endpoint in the API; demo data review schedules; database update 2.8.7 (archive and review columns, wider category and backup, link table dedupe with primary keys, service_domains foreign keys, service_tickets)
2026-10-15 17:32:09 -04:00

482 lines
28 KiB
PHP

<?php
/*
* Asset quick look - the asset at a glance, opened from a ticket, the client
* overview, a contact, a credential or a document without leaving the page.
*
* A small version of the asset page (agent/asset.php), in the same look: what
* it is in the header, how to get on it and where it is in a few lines with
* no field labels, then whatever is linked to it in tabs with counts. Status,
* location and who it is assigned to change in place (js/quick_pick.js), like
* on the asset page. Purchase details, IPv6, NAT and the like stay on the
* asset page - "Open asset" is one click away.
*/
require_once '../../../includes/modal_header.php';
enforceUserPermission('module_support');
$asset_id = intval($_GET['id'] ?? 0);
$sql = mysqli_query($mysqli, "SELECT asset_archived_at, asset_client_id, asset_contact_id, asset_description, asset_favorite, asset_id,
asset_location_id, asset_make, asset_model, asset_name, asset_notes, asset_os, asset_photo, asset_physical_location,
asset_serial, asset_status, asset_type, asset_uri, asset_uri_2, asset_uri_client, asset_warranty_expire,
contact_archived_at, contact_name, contact_photo,
interface_ip, interface_mac,
location_archived_at, location_name FROM assets
LEFT JOIN contacts ON asset_contact_id = contact_id
LEFT JOIN locations ON asset_location_id = location_id
LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1
WHERE asset_id = $asset_id
LIMIT 1
");
$asset_row = mysqli_fetch_assoc($sql);
if (!$asset_row) {
echo json_encode(['error' => 'That asset no longer exists.']);
exit;
}
$client_id = intval($asset_row['asset_client_id']);
enforceClientAccess();
$can_edit = lookupUserPermission('module_support') >= 2;
$can_credentials = lookupUserPermission('module_credential') >= 1;
$asset_name = escapeHtml($asset_row['asset_name']);
$asset_type = escapeHtml($asset_row['asset_type']);
$asset_description = escapeHtml($asset_row['asset_description']);
$asset_make_model = escapeHtml(trim($asset_row['asset_make'] . ' ' . $asset_row['asset_model']));
$asset_os = escapeHtml($asset_row['asset_os']);
$asset_os_icon = assetOsIcon($asset_row['asset_os']);
$asset_serial = escapeHtml($asset_row['asset_serial']);
$asset_ip = escapeHtml($asset_row['interface_ip']);
$asset_mac = escapeHtml($asset_row['interface_mac']);
$asset_links = assetLinks($asset_row['asset_uri'], $asset_row['asset_uri_2'], $asset_row['asset_uri_client']);
$asset_warranty = assetWarranty($asset_row['asset_warranty_expire']);
$asset_photo = escapeHtml($asset_row['asset_photo']);
$asset_physical_location = escapeHtml($asset_row['asset_physical_location']);
$asset_notes = escapeHtml($asset_row['asset_notes']);
$asset_favorite = intval($asset_row['asset_favorite']);
$asset_archived = !empty($asset_row['asset_archived_at']);
$asset_contact_id = intval($asset_row['asset_contact_id']);
$asset_tone = assetTypeTone($asset_row['asset_type']);
$device_icon = getAssetIcon($asset_row['asset_type']);
$asset_tags_display = tagPills('asset', $asset_id, tagsForRecord('asset', $asset_id), "assets.php?client_id=$client_id&");
// The rack it sits in, if any
$asset_rack = rackPositionForAsset($asset_id);
// Interfaces, primary first, and what each one is plugged into
$sql_related_interfaces = mysqli_query($mysqli, "
SELECT ai.interface_id, ai.interface_name, ai.interface_mac, ai.interface_ip, ai.interface_primary,
connected_interfaces.interface_name AS connected_interface_name,
connected_assets.asset_name AS connected_asset_name,
connected_assets.asset_id AS connected_asset_id,
connected_assets.asset_type AS connected_asset_type
FROM asset_interfaces AS ai
LEFT JOIN asset_interface_links AS ail
ON (ail.interface_a_id = ai.interface_id OR ail.interface_b_id = ai.interface_id)
LEFT JOIN asset_interfaces AS connected_interfaces
ON (
(ail.interface_a_id = ai.interface_id AND ail.interface_b_id = connected_interfaces.interface_id)
OR
(ail.interface_b_id = ai.interface_id AND ail.interface_a_id = connected_interfaces.interface_id)
)
LEFT JOIN assets AS connected_assets
ON connected_assets.asset_id = connected_interfaces.interface_asset_id
WHERE ai.interface_asset_id = $asset_id
AND ai.interface_archived_at IS NULL
ORDER BY ai.interface_primary DESC, ai.interface_name ASC
");
$interface_count = mysqli_num_rows($sql_related_interfaces);
// Credentials - only for agents who may see them
$related_credentials = $can_credentials ? credentialRows("credential_asset_id = $asset_id AND credential_client_id = $client_id") : [];
$credential_count = count($related_credentials);
// Tickets - open ones first; the most recent few here, the rest on the asset page
$quick_ticket_limit = 8;
[$ticket_count, $ticket_open_count] = ticketCounts(ticketAssetSql($asset_id));
$related_tickets = ticketRows(ticketAssetSql($asset_id), $quick_ticket_limit);
// Recurring tickets
$sql_related_recurring_tickets = mysqli_query($mysqli, "SELECT recurring_tickets.recurring_ticket_id, recurring_ticket_subject,
recurring_ticket_frequency, recurring_ticket_next_run, recurring_ticket_paused_at
FROM recurring_tickets
LEFT JOIN recurring_ticket_assets ON recurring_tickets.recurring_ticket_id = recurring_ticket_assets.recurring_ticket_id
WHERE recurring_ticket_asset_id = $asset_id OR recurring_ticket_assets.asset_id = $asset_id
GROUP BY recurring_tickets.recurring_ticket_id
ORDER BY recurring_ticket_next_run ASC"
);
$recurring_ticket_count = mysqli_num_rows($sql_related_recurring_tickets);
// Licenses it holds a seat on, with the key it was given
$held_software = softwareHeldBy('asset', $asset_id);
$software_count = count($held_software);
// The services it runs - what goes down with it
$asset_services = servicesFor('asset', $asset_id);
$service_count = count($asset_services);
// Documents
$linked_documents = linkedRecords('asset', $asset_id, 'document');
$document_count = count($linked_documents);
// Files
$linked_files = linkedRecords('asset', $asset_id, 'file');
$file_count = count($linked_files);
// Notes
$sql_related_notes = mysqli_query($mysqli, "SELECT asset_note, asset_note_created_at, asset_note_type, user_name FROM asset_notes
LEFT JOIN users ON asset_note_created_by = user_id
WHERE asset_note_asset_id = $asset_id
AND asset_note_archived_at IS NULL
ORDER BY asset_note_created_at DESC"
);
$note_count = mysqli_num_rows($sql_related_notes);
$note_type_icons = [];
$sql_note_type_icons = mysqli_query($mysqli, "SELECT category_name, category_icon FROM categories WHERE category_type = 'asset_note_type'");
while ($row = mysqli_fetch_assoc($sql_note_type_icons)) {
$note_type_icons[$row['category_name']] = escapeHtml($row['category_icon']);
}
// Only the tabs with something in them - same order as the asset page
$quick_tabs = array_filter([
'interfaces' => ['label' => 'Interfaces', 'count' => $interface_count],
'credentials' => ['label' => 'Credentials', 'count' => $credential_count],
'tickets' => ['label' => 'Tickets', 'count' => $ticket_count, 'badge' => $ticket_open_count],
'recurring' => ['label' => 'Recurring', 'count' => $recurring_ticket_count],
'licenses' => ['label' => 'Licenses', 'count' => $software_count],
'services' => ['label' => 'Services', 'count' => $service_count],
'documents' => ['label' => 'Documents', 'count' => $document_count],
'files' => ['label' => 'Files', 'count' => $file_count],
'notes' => ['label' => 'Notes', 'count' => $note_count],
], function ($tab) {
return $tab['count'] > 0;
});
$first_tab = array_key_first($quick_tabs);
$has_left = $asset_ip || $asset_mac || $asset_serial || $asset_warranty;
$has_right = $can_edit || $asset_row['location_name'] || $asset_row['contact_name'] || $asset_rack;
ob_start();
?>
<!-- What it is - the asset page's header, in the modal's dark bar -->
<div class="modal-header bg-dark" data-bs-theme="dark">
<div class="d-flex align-items-center gap-3 min-w-0">
<?php if ($asset_photo) { ?>
<img class="itflow-asset-icon" src="<?= "../uploads/clients/$client_id/$asset_photo" ?>" alt="">
<?php } else { ?>
<span class="itflow-asset-icon itflow-asset-tone-<?= $asset_tone ?>" aria-hidden="true"><i class="fas fa-fw fa-<?= $device_icon ?>"></i></span>
<?php } ?>
<div class="min-w-0">
<h5 class="modal-title d-flex flex-wrap align-items-center column-gap-2 row-gap-1">
<span class="fw-semibold text-break"><?= $asset_name ?></span>
<?php if ($asset_favorite) { ?><i class="fas fa-star text-warning fs-6" title="Favorite"></i><?php } ?>
<?php if ($asset_archived) { ?><span class="badge rounded-pill text-bg-secondary fw-normal fs-6">Archived</span><?php } ?>
<span class="fs-6">
<?php if ($can_edit) { ?>
<?= quickPick('asset_status', $asset_id, (string) $asset_row['asset_status'], assetQuickHtml('asset_status', $asset_row), 'list', 0, $client_id) ?>
<?php } else { ?>
<?= assetStatusPill($asset_row['asset_status']) ?>
<?php } ?>
</span>
</h5>
<div class="small text-body-secondary d-flex flex-wrap column-gap-3 mt-1">
<span><?= $asset_type ?></span>
<?php if ($asset_make_model) { ?><span><?= $asset_make_model ?></span><?php } ?>
<?php if ($asset_os) { ?><span><i class="<?= $asset_os_icon ?> fa-fw me-1"></i><?= $asset_os ?></span><?php } ?>
</div>
</div>
</div>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<?php if ($asset_description || $asset_tags_display || $asset_links) { ?>
<div class="d-flex flex-wrap align-items-start justify-content-between gap-2 mb-3">
<div class="min-w-0">
<?php if ($asset_description) { ?><div class="text-body-secondary"><?= $asset_description ?></div><?php } ?>
<?php if ($asset_tags_display) { ?><div class="<?= $asset_description ? 'mt-1' : '' ?>"><?= $asset_tags_display ?></div><?php } ?>
</div>
<?php if ($asset_links) { ?>
<div class="d-flex flex-wrap gap-2">
<?php foreach ($asset_links as [$link_label, $link_url]) { ?>
<a class="btn btn-sm btn-outline-primary" href="<?= escapeUrl($link_url) ?>" target="_blank" rel="noopener" title="<?= $link_label ?>: <?= escapeHtml($link_url) ?>">
<i class="<?= assetLinkIcon($link_url) ?> me-1"></i><?= escapeHtml(assetLinkLabel($link_url)) ?><?= $link_label === 'Client link' ? ' (client)' : '' ?>
</a>
<?php } ?>
</div>
<?php } ?>
</div>
<?php } ?>
<!-- At a glance: how to get on it, and where it is - no labels, the icons say what each line is -->
<?php if ($has_left || $has_right) { ?>
<div class="row g-2 itflow-asset-glance">
<?php if ($has_left) { ?>
<div class="col-sm-6">
<?php if ($asset_ip) { ?>
<div class="itflow-glance-item" title="IP address">
<i class="fas fa-fw fa-globe"></i>
<span><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 } ?></span>
</div>
<?php } ?>
<?php if ($asset_mac) { ?>
<div class="itflow-glance-item" title="MAC address">
<i class="fas fa-fw fa-ethernet"></i>
<span><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></span>
</div>
<?php } ?>
<?php if ($asset_serial) { ?>
<div class="itflow-glance-item" title="Serial number">
<i class="fas fa-fw fa-barcode"></i>
<span><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></span>
</div>
<?php } ?>
<?php if ($asset_warranty) { ?>
<div class="itflow-glance-item" title="Warranty <?= escapeHtml($asset_warranty['date']) ?>">
<i class="fas fa-fw fa-shield-alt"></i>
<span class="text-<?= $asset_warranty['tone'] ?>-emphasis">Warranty <?= strtolower(escapeHtml($asset_warranty['text'])) ?></span>
</div>
<?php } ?>
</div>
<?php } ?>
<?php if ($has_right) { ?>
<div class="col-sm-6">
<?php if ($can_edit || $asset_row['location_name']) { ?>
<div class="itflow-glance-item" title="Location">
<i class="fas fa-fw fa-map-marker-alt"></i>
<div class="flex-grow-1 min-w-0">
<?php if ($can_edit) { ?>
<?= quickPick('asset_location', $asset_id, (string) intval($asset_row['asset_location_id']), assetQuickHtml('asset_location', $asset_row, 'detail'), 'detail', 0, $client_id) ?>
<?php } else { ?>
<?= assetQuickHtml('asset_location', $asset_row, 'detail') ?>
<?php } ?>
<?php if ($asset_physical_location) { ?><div class="small text-body-secondary"><?= $asset_physical_location ?></div><?php } ?>
</div>
</div>
<?php } ?>
<?php if ($can_edit || $asset_row['contact_name']) { ?>
<div class="itflow-glance-item" title="Assigned to">
<i class="fas fa-fw fa-user"></i>
<div class="flex-grow-1 min-w-0">
<?php if ($can_edit) { ?>
<?= quickPick('asset_contact', $asset_id, (string) $asset_contact_id, assetQuickHtml('asset_contact', $asset_row, 'detail'), 'detail', 0, $client_id) ?>
<?php } else { ?>
<?= assetQuickHtml('asset_contact', $asset_row, 'detail') ?>
<?php } ?>
</div>
</div>
<?php } ?>
<?php if ($asset_rack) { ?>
<div class="itflow-glance-item" title="Rack">
<i class="fas fa-fw fa-server"></i>
<span><a href="rack.php?client_id=<?= $client_id ?>&rack_id=<?= intval($asset_rack['rack_id']) ?>"><?= escapeHtml($asset_rack['rack_name']) ?></a> <span class="text-body-secondary">&middot; <?= rackPositionLabel($asset_rack['unit_start_number'], $asset_rack['unit_end_number']) ?>, <?= $asset_rack['unit_face'] === 'rear' ? 'rear' : 'front' ?></span></span>
</div>
<?php } ?>
</div>
<?php } ?>
</div>
<?php } ?>
<?php if ($can_edit || $asset_notes) { ?>
<textarea class="form-control form-control-sm mt-3 itflow-asset-quicknote" rows="2" placeholder="Quick notes - saves when you click away" aria-label="Quick notes"
data-asset-id="<?= $asset_id ?>" data-csrf="<?= escapeHtml($_SESSION['csrf_token'] ?? '') ?>" <?= $can_edit ? '' : 'readonly' ?>><?= $asset_notes ?></textarea>
<?php } ?>
<?php if ($quick_tabs) { ?>
<!-- Everything linked to it -->
<div class="itflow-asset-related itflow-asset-quick-related mt-3" data-asset-quick="<?= $asset_id ?>">
<ul class="nav nav-pills itflow-asset-pills mb-2" role="tablist">
<?php foreach ($quick_tabs as $tab_key => $tab) { ?>
<li class="nav-item" role="presentation">
<button class="nav-link text-nowrap <?= $tab_key === $first_tab ? 'active' : '' ?>" data-bs-toggle="pill" data-bs-target="#asset-quick-<?= $asset_id ?>-<?= $tab_key ?>" data-asset-quick-tab="<?= $tab_key ?>" type="button" role="tab">
<?= $tab['label'] ?> <span class="badge rounded-pill ms-1"><?= $tab['count'] ?></span>
<?php if (!empty($tab['badge'])) { ?><span class="badge rounded-pill text-bg-danger" title="Open"><?= intval($tab['badge']) ?> open</span><?php } ?>
</button>
</li>
<?php } ?>
</ul>
<div class="tab-content">
<?php if ($interface_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'interfaces' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-interfaces" role="tabpanel">
<div class="table-responsive">
<table class="table table-sm table-hover align-middle mb-0">
<thead class="text-nowrap"><tr><th>Port</th><th>IP</th><th>MAC</th><th>Connected to</th></tr></thead>
<tbody>
<?php while ($row = mysqli_fetch_assoc($sql_related_interfaces)) {
$interface_ip = escapeHtml($row['interface_ip']);
$interface_mac = escapeHtml($row['interface_mac']);
$connected_asset_id = intval($row['connected_asset_id']); ?>
<tr>
<td class="text-nowrap">
<?= escapeHtml($row['interface_name']) ?>
<?php if (intval($row['interface_primary'])) { ?><span class="badge rounded-pill text-bg-primary fw-normal ms-1">Primary</span><?php } ?>
</td>
<td class="text-nowrap"><?php if ($interface_ip) { ?><span class="font-monospace"><?= $interface_ip ?></span><?php if ($interface_ip !== 'DHCP') { ?><button type="button" class="itflow-copy clipboardjs" title="Copy IP" data-clipboard-text="<?= $interface_ip ?>"><i class="far fa-copy"></i></button><?php } ?><?php } else { ?><span class="text-body-secondary">-</span><?php } ?></td>
<td class="text-nowrap"><?php if ($interface_mac) { ?><span class="font-monospace"><?= $interface_mac ?></span><button type="button" class="itflow-copy clipboardjs" title="Copy MAC" data-clipboard-text="<?= $interface_mac ?>"><i class="far fa-copy"></i></button><?php } else { ?><span class="text-body-secondary">-</span><?php } ?></td>
<td>
<?php if ($connected_asset_id) { ?>
<a class="ajax-modal" href="#" data-modal-size="lg" data-modal-url="modals/asset/asset.php?id=<?= $connected_asset_id ?>"><i class="fas fa-fw fa-<?= getAssetIcon($row['connected_asset_type']) ?> me-1"></i><?= escapeHtml($row['connected_asset_name']) ?></a>
<span class="text-body-secondary">&middot; <?= escapeHtml($row['connected_interface_name']) ?></span>
<?php } else { ?>
<span class="text-body-secondary">-</span>
<?php } ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
<?php } ?>
<?php if ($credential_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'credentials' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-credentials" role="tabpanel">
<?= credentialListHtml($related_credentials) ?>
</div>
<?php } ?>
<?php if ($ticket_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'tickets' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-tickets" role="tabpanel">
<?= ticketTableHtml($related_tickets, ['compact' => true]) ?>
<?php if ($ticket_count > count($related_tickets)) { ?>
<div class="small mt-2"><a href="asset.php?client_id=<?= $client_id ?>&asset_id=<?= $asset_id ?>#tickets">All <?= $ticket_count ?> tickets on the asset page<i class="fas fa-arrow-right ms-1"></i></a></div>
<?php } ?>
</div>
<?php } ?>
<?php if ($recurring_ticket_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'recurring' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-recurring" role="tabpanel">
<ul class="list-group list-group-flush">
<?php while ($row = mysqli_fetch_assoc($sql_related_recurring_tickets)) { ?>
<li class="list-group-item px-0 d-flex justify-content-between gap-3">
<span><?= escapeHtml($row['recurring_ticket_subject']) ?></span>
<span class="small text-body-secondary text-nowrap"><?= escapeHtml($row['recurring_ticket_frequency']) ?> <?= recurringTicketNextRunPill($row) ?></span>
</li>
<?php } ?>
</ul>
</div>
<?php } ?>
<?php if ($software_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'licenses' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-licenses" role="tabpanel">
<!-- Each opens its quick look - seats, renewal, the key to install -->
<?= softwareHeldList($held_software) ?>
</div>
<?php } ?>
<?php if ($service_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'services' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-services" role="tabpanel">
<!-- What goes down with it - most important first -->
<?= serviceRowsHtml($asset_services) ?>
</div>
<?php } ?>
<?php if ($document_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'documents' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-documents" role="tabpanel">
<!-- The same rows as a document's Linked card - tile, name, when it changed -->
<ul class="list-unstyled mb-0 itflow-links">
<?php foreach ($linked_documents as $row) { ?>
<li class="itflow-link-row"><?= linkRowHtml('document', $row, $client_id) ?></li>
<?php } ?>
</ul>
</div>
<?php } ?>
<?php if ($file_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'files' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-files" role="tabpanel">
<!-- Each opens its quick look - preview, details, what else it is linked to -->
<ul class="list-unstyled mb-0 itflow-links">
<?php foreach ($linked_files as $row) { ?>
<li class="itflow-link-row"><?= linkRowHtml('file', $row, $client_id) ?></li>
<?php } ?>
</ul>
</div>
<?php } ?>
<?php if ($note_count) { ?>
<div class="tab-pane fade <?= $first_tab === 'notes' ? 'show active' : '' ?>" id="asset-quick-<?= $asset_id ?>-notes" role="tabpanel">
<ul class="list-group list-group-flush">
<?php while ($row = mysqli_fetch_assoc($sql_related_notes)) {
$note_icon = $note_type_icons[$row['asset_note_type']] ?? '' ?: 'fa-sticky-note'; ?>
<li class="list-group-item px-0 d-flex gap-3">
<i class="fas fa-fw <?= $note_icon ?> text-secondary mt-1" title="<?= escapeHtml($row['asset_note_type']) ?>"></i>
<div class="min-w-0">
<div><?= nl2br(escapeHtml($row['asset_note'])) ?></div>
<div class="small text-body-secondary"><?= escapeHtml($row['user_name']) ?> &middot; <span title="<?= escapeHtml($row['asset_note_created_at']) ?>"><?= timeAgo($row['asset_note_created_at']) ?></span></div>
</div>
</li>
<?php } ?>
</ul>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
</div>
<div class="modal-footer">
<a href="asset.php?client_id=<?= $client_id ?>&asset_id=<?= $asset_id ?>" class="btn btn-primary text-bold"><i class="fas fa-arrow-right me-2"></i>Open asset</a>
<?php if ($can_edit) { ?>
<button type="button" class="btn btn-outline-secondary ajax-modal" data-modal-size="lg" data-modal-url="modals/asset/asset_edit.php?id=<?= $asset_id ?>"><i class="fas fa-edit me-2"></i>Edit</button>
<?php } ?>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Close</button>
</div>
<script>
(function () {
// Quick notes save when you click away. Scoped to this modal's own
// textarea - an id here would collide with the asset page's own
// Quick notes, which is still on the page under the modal
document.querySelectorAll('.itflow-asset-quicknote:not([data-wired])').forEach(function (notes) {
notes.dataset.wired = '1';
var saved = notes.value;
notes.addEventListener('blur', function () {
if (notes.readOnly || notes.value === saved) {
return;
}
saved = notes.value;
itflowPost('ajax.php', {
asset_set_notes: 'TRUE',
csrf_token: notes.dataset.csrf,
asset_id: notes.dataset.assetId,
notes: notes.value
});
});
});
// Open on the tab used last time, if this asset has one
var key = 'itflow.assetQuickTab';
document.querySelectorAll('[data-asset-quick]:not([data-wired])').forEach(function (box) {
box.dataset.wired = '1';
var remembered = null;
try {
remembered = localStorage.getItem(key);
} catch (e) {}
var tab = remembered && box.querySelector('[data-asset-quick-tab="' + remembered + '"]');
if (tab && !tab.classList.contains('active')) {
bootstrap.Tab.getOrCreateInstance(tab).show();
}
box.addEventListener('shown.bs.tab', function (e) {
try {
localStorage.setItem(key, e.target.dataset.assetQuickTab);
} catch (err) {}
});
});
})();
</script>
<?php
require_once '../../../includes/modal_footer.php';