Files
itflow/agent/modals/service/service.php
johnnyq 9e55d52432 Vendors overhaul for daily MSP work: new vendor page built around calling them, with Call support (dials the extension), Email, Support site, a star, and a New menu (case/ticket with them, license from them, asset bought from them, portal login, document, upload, or expense and recurring expense for our own vendors), Support card (phone, hours, SLA, email, site), Account card (account number, PIN shown as dots with show and copy, account manager name, direct line and email), blur-to-save notes, an Also used by list of the agent's other clients with the same vendor by name or template, and tabs for Cases (open first, with their ticket number), Licenses (soonest renewal first), Assets (serial, order ref, warranty status), Domains (registrar, web host, DNS, mail), Services, Logins, Documents, Files and History; our own vendors show spend this year, last year and in all, recent expenses and recurring expenses; new vendor quick look; rebuilt vendors list with starred first, initials tiles coloured by vendor name, support line, hours and SLA, account number and PIN, chips for open cases, licenses, assets, domains, services and logins, an Ours / Clients' switch with an all-clients view in All Client Docs, wider search (account manager, emails, tags), tag filter and bulk Tags/Archive/Restore/Delete; shared tabbed vendor form (Details, Support, Account, Notes) with star, tags and start from template that fills every field; Add a Template to Clients to roll one vendor out to many clients, skipping those that have it; Admin Vendor Templates shows how many clients use each template and has Add to clients; vendor tags (tag type 6) with vendor tags in the Tags starter pack; ticket vendor block shows their ticket number, support line, hours, SLA, account number and PIN with copy buttons; vendor tiles in every vendor picker and quick look links across tickets, licenses, assets, domains, services and expense lists; search opens the vendor page; CSV export adds tags, open cases, account manager and PIN; logins, documents and files can be linked to a vendor and ones made from a vendor's page are linked to it; demo vendors get tags, a star and an account manager; database update 2.8.2 adds vendor_tags and vendor_contact_phone_country_code, vendor_contact_phone, vendor_contact_email (db.sql updated) and clears references to vendors that no longer exist; ticket vendor now validated against the ticket's client and removing a vendor from a ticket is logged as such; deleting a vendor from a template no longer unlinks every other vendor from that template; deleting a vendor clears it from assets, licenses, tickets and domains, and vendors with expenses, recurring expenses or payment provider fees are archived rather than deleted; vendor websites without a scheme open correctly; domain form vendor names escaped; new vendor templates keep the phone country code and pushing a template update with nothing ticked no longer errors; vendors API accepts the account manager fields; new functions/vendor.php (VENDOR_USES, vendorCan/vendorEnforce with client vendors on module_client and ours on module_financial, tile, link, website, PIN, form, tags, counts, delete check, reference clearing, also used by, spend, history), credential|vendor and file|vendor link relations, tag module_ours for our own vendors, handlers add_vendor_template_to_clients, favorite_vendor, unfavorite_vendor and ajax vendor_set_notes, vendor_model.php removed
2026-10-14 22:49:05 -04:00

319 lines
14 KiB
PHP

<?php
require_once '../../../includes/modal_header.php';
enforceUserPermission('module_support');
$service_id = intval($_GET['id']);
$sql = mysqli_query($mysqli, "SELECT service_backup, service_category, service_client_id, service_created_at,
service_description, service_importance, service_name, service_notes, service_review_due,
service_updated_at FROM services WHERE service_id = $service_id LIMIT 1");
$row = mysqli_fetch_assoc($sql);
$service_name = escapeHtml($row['service_name']);
$service_description = escapeHtml($row['service_description']);
$service_category = escapeHtml($row['service_category']);
$service_importance = escapeHtml($row['service_importance']);
$service_backup = escapeHtml($row['service_backup']);
$service_notes = escapeHtml($row['service_notes']);
$service_created_at = escapeHtml($row['service_created_at']);
$service_updated_at = escapeHtml($row['service_updated_at']);
$service_review_due = escapeHtml($row['service_review_due']);
$client_id = intval($row['service_client_id']);
enforceClientAccess();
// Service Importance
if ($service_importance == "High") {
$service_importance_display = "<span class='p-2 badge bg-danger'>$service_importance</span>";
} elseif ($service_importance == "Medium") {
$service_importance_display = "<span class='p-2 badge bg-warning text-dark'>$service_importance</span>";
} elseif ($service_importance == "Low") {
$service_importance_display = "<span class='p-2 badge bg-info text-dark'>$service_importance</span>";
} else {
$service_importance_display = "-";
}
// Associated Assets (and their credentials/networks/locations)
$sql_assets = mysqli_query(
$mysqli,
"SELECT * FROM service_assets
LEFT JOIN assets ON service_assets.asset_id = assets.asset_id
LEFT JOIN asset_interfaces ON interface_asset_id = assets.asset_id AND interface_primary = 1
LEFT JOIN credentials ON service_assets.asset_id = credentials.credential_asset_id
LEFT JOIN networks ON interface_network_id = networks.network_id
LEFT JOIN locations ON assets.asset_location_id = locations.location_id
WHERE service_id = $service_id"
);
// Associated credentials
$sql_credentials = mysqli_query(
$mysqli,
"SELECT * FROM service_credentials
LEFT JOIN credentials ON service_credentials.credential_id = credentials.credential_id
WHERE service_id = $service_id"
);
// Associated Domains
$sql_domains = mysqli_query(
$mysqli,
"SELECT domain_name FROM service_domains
LEFT JOIN domains ON service_domains.domain_id = domains.domain_id
WHERE service_id = $service_id"
);
// Associated Certificates
$sql_certificates = mysqli_query(
$mysqli,
"SELECT certificate_domain, certificate_name FROM service_certificates
LEFT JOIN certificates ON service_certificates.certificate_id = certificates.certificate_id
WHERE service_id = $service_id"
);
// Associated Vendors
$sql_vendors = mysqli_query(
$mysqli,
"SELECT vendors.vendor_id, vendor_name FROM service_vendors
LEFT JOIN vendors ON service_vendors.vendor_id = vendors.vendor_id
WHERE service_id = $service_id"
);
// Associated Contacts
$sql_contacts = mysqli_query(
$mysqli,
"SELECT service_contacts.contact_id, contact_client_id, contact_name, contact_photo FROM service_contacts
LEFT JOIN contacts ON service_contacts.contact_id = contacts.contact_id
WHERE service_id = $service_id"
);
// Associated Documents
$sql_docs = mysqli_query(
$mysqli,
"SELECT documents.document_id AS id, document_name AS name, document_description, document_created_at, document_updated_at
FROM service_documents
JOIN documents ON service_documents.document_id = documents.document_id
WHERE service_id = $service_id AND document_archived_at IS NULL
ORDER BY document_name ASC"
);
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title text-white"><i class="fa fa-fw fa-stream me-2"></i><?= $service_name ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- Main/Left side -->
<div class="col-8 border-end">
<div class="col-12">
<h4>Service Overview: <?= "$service_name $service_importance_display" ?></h4>
<b>Description:</b> <?= $service_description ?> <br>
<b>Backup Info:</b> <?= $service_backup ?> <br><br>
<h5><i class="fas fa-fw fa-sticky-note me-2"></i>Notes</h5>
<div style="white-space: pre-line"><?= $service_notes ?></div>
<hr>
<!-- Assets -->
<?php
if (mysqli_num_rows($sql_assets) > 0) {
echo "<h5><i class='fas fa-fw fa-desktop me-2'></i>Assets</h5><ul>";
mysqli_data_seek($sql_assets, 0);
while ($row = mysqli_fetch_assoc($sql_assets)) {
$asset_id = intval($row['asset_id']);
$asset_name = escapeHtml($row['asset_name']);
$ip = !empty($row['interface_ip']) ? '(' . escapeHtml($row['interface_ip']) . ')' : '';
echo "<li><a class='ajax-modal' href='#' data-modal-size='lg' data-modal-url='modals/asset/asset.php?id=$asset_id'>$asset_name</a>$ip</li>";
}
echo "</ul>";
}
?>
<!-- Networks -->
<?php
$networks = [];
if ($sql_assets) {
mysqli_data_seek($sql_assets, 0);
while ($row = mysqli_fetch_assoc($sql_assets)) {
if (!empty($row['network_name'])) {
$network_data = escapeHtml($row["network_name"]) . ':' . escapeHtml($row["network_vlan"]);
$networks[] = $network_data;
}
}
$networks = array_unique($networks);
if (!empty($networks)) {
echo "<h5><i class='fas fa-fw fa-network-wired me-2'></i>Networks</h5><ul>";
foreach ($networks as $network) {
$network_parts = explode(":", $network);
$network_name = $network_parts[0];
$network_vlan = $network_parts[1] ?? '';
echo "<li><a href='networks.php?client_id=$client_id&q=$network_name'>$network_name</a> (VLAN <span class='font-monospace'>$network_vlan</span>)</li>";
}
echo "</ul>";
}
}
?>
<!-- Locations -->
<?php
$location_names = [];
if ($sql_assets) {
mysqli_data_seek($sql_assets, 0);
while ($row = mysqli_fetch_assoc($sql_assets)) {
if (!empty($row['location_name'])) {
$location_names[] = escapeHtml($row['location_name']);
}
}
$location_names = array_unique($location_names);
if (!empty($location_names)) {
echo "<h5><i class='fas fa-fw fa-map-marker-alt me-2'></i>Locations</h5><ul>";
foreach ($location_names as $location) {
echo "<li><a href='locations.php?client_id=$client_id&q=$location'>$location</a></li>";
}
echo "</ul>";
}
}
?>
<!-- Domains -->
<?php
if (mysqli_num_rows($sql_domains) > 0) {
echo "<h5><i class='fas fa-fw fa-globe me-2'></i>Domains</h5><ul>";
mysqli_data_seek($sql_domains, 0);
while ($row = mysqli_fetch_assoc($sql_domains)) {
if (!empty($row['domain_name'])) {
$domain_name = escapeHtml($row['domain_name']);
echo "<li><a href='domains.php?client_id=$client_id&q=$domain_name'>$domain_name</a></li>";
}
}
echo "</ul>";
}
?>
<!-- Certificates -->
<?php
if (mysqli_num_rows($sql_certificates) > 0) {
echo "<h5><i class='fas fa-fw fa-lock me-2'></i>Certificates</h5><ul>";
mysqli_data_seek($sql_certificates, 0);
while ($row = mysqli_fetch_assoc($sql_certificates)) {
if (!empty($row['certificate_name'])) {
$certificate_name = escapeHtml($row['certificate_name']);
$certificate_domain = escapeHtml($row['certificate_domain']);
echo "<li><a href='certificates.php?client_id=$client_id&q=$certificate_name'>$certificate_name ($certificate_domain)</a></li>";
}
}
echo "</ul>";
}
?>
</div>
</div>
<!-- Right side -->
<div class="col-4">
<div class="col-12">
<h4>Additional Related Items</h4>
<br>
<!-- Vendors -->
<?php
if (mysqli_num_rows($sql_vendors) > 0) {
echo "<h5><i class='fas fa-fw fa-building me-2'></i>Vendors</h5><ul>";
mysqli_data_seek($sql_vendors, 0);
while ($row = mysqli_fetch_assoc($sql_vendors)) {
$vendor_id = intval($row['vendor_id']);
$vendor_name = escapeHtml($row['vendor_name']);
echo "<li class='mb-1'>" . vendorLink($vendor_id, $row['vendor_name']) . "</li>";
}
echo "</ul>";
}
?>
<!-- Contacts -->
<?php
if (mysqli_num_rows($sql_contacts) > 0) {
// No bullets - each contact leads with their avatar instead
echo "<h5><i class='fas fa-fw fa-users me-2'></i>Contacts</h5><ul class='list-unstyled ms-1'>";
mysqli_data_seek($sql_contacts, 0);
while ($row = mysqli_fetch_assoc($sql_contacts)) {
echo '<li class="mb-1">' . contactLink($row['contact_client_id'], $row['contact_id'], $row['contact_name'], $row['contact_photo']) . '</li>';
}
echo "</ul>";
}
?>
<!-- Credentials -->
<?php
if (mysqli_num_rows($sql_assets) > 0 || mysqli_num_rows($sql_credentials) > 0) {
echo "<h5><i class='fas fa-fw fa-key me-2'></i>Credentials</h5><ul>";
// Credentials linked to assets
mysqli_data_seek($sql_assets, 0);
while ($row = mysqli_fetch_assoc($sql_assets)) {
$credential_name = escapeHtml($row['credential_name']);
if (!empty($credential_name)) {
echo "<li><a href='credentials.php?client_id=$client_id&q=$credential_name'>$credential_name</a></li>";
}
}
// Explicitly linked credentials
mysqli_data_seek($sql_credentials, 0);
while ($row = mysqli_fetch_assoc($sql_credentials)) {
$credential_name = escapeHtml($row['credential_name']);
if (!empty($credential_name)) {
echo "<li><a href='credentials.php?client_id=$client_id&q=$credential_name'>$credential_name</a></li>";
}
}
echo "</ul>";
}
?>
<!-- URLs -->
<?php
$urls = [];
mysqli_data_seek($sql_credentials, 0);
while ($row = mysqli_fetch_assoc($sql_credentials)) {
if (!empty($row['credential_uri'])) {
$urls[] = escapeUrl($row['credential_uri']);
}
}
mysqli_data_seek($sql_assets, 0);
while ($row = mysqli_fetch_assoc($sql_assets)) {
if (!empty($row['asset_uri'])) {
$urls[] = escapeUrl($row['asset_uri']);
}
}
$urls = array_unique($urls);
if (!empty($urls)) {
echo "<h5><i class='fas fa-fw fa-link me-2'></i>URLs</h5><ul>";
foreach ($urls as $url) {
$label = htmlspecialchars(parse_url($url, PHP_URL_HOST) ?: $url);
echo "<li><a href='$url' target='_blank'>$label</a></li>";
}
echo "</ul>";
}
?>
<!-- Documents -->
<?php
if (mysqli_num_rows($sql_docs) > 0) {
// The same rows as a document's Linked card
echo "<h5><i class='fas fa-fw fa-file-alt me-2'></i>Documents</h5><ul class='list-unstyled itflow-links'>";
mysqli_data_seek($sql_docs, 0);
while ($row = mysqli_fetch_assoc($sql_docs)) {
echo '<li class="itflow-link-row">' . linkRowHtml('document', $row, $client_id) . '</li>';
}
echo "</ul>";
}
?>
</div>
</div>
</div>
</div>
<?php
require_once '../../../includes/modal_footer.php';