Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [UNRELEASED]

### Added
- Added support to choose importing/merging mobile devices as Computers or Phones

### Fixes

- SQL error when merging the Jamf device linked to a GLPI asset
Expand Down
15 changes: 8 additions & 7 deletions ajax/import.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,22 @@
if (isset($_REQUEST['import_ids']) && is_array($_REQUEST['import_ids'])) {
// Get data for each item to import
$toimport = $DB->request([
'SELECT' => ['type', 'jamf_type', 'jamf_items_id'],
'FROM' => PluginJamfImport::getTable(),
'WHERE' => [
'id' => $_REQUEST['import_ids'],
],
'SELECT' => ['id', 'type', 'jamf_type', 'jamf_items_id'],
'FROM' => PluginJamfImport::getTable(),
'WHERE' => [
'id' => $_REQUEST['import_ids']
]
]);
// Trigger extension attribute definition sync
PluginJamfMobileSync::syncExtensionAttributeDefinitions();
PluginJamfComputerSync::syncExtensionAttributeDefinitions();
// Import the requested device(s)
foreach ($toimport as $data) {
$glpi_itemtype = $_REQUEST['itemtype_overrides'][$data['id']] ?? $data['type'];
if ($data['jamf_type'] === 'MobileDevice') {
PluginJamfMobileSync::import($data['type'], $data['jamf_items_id']);
PluginJamfMobileSync::import($glpi_itemtype, $data['jamf_items_id']);
} else {
PluginJamfComputerSync::import($data['type'], $data['jamf_items_id']);
PluginJamfComputerSync::import($glpi_itemtype, $data['jamf_items_id']);
}
}
} else {
Expand Down
6 changes: 5 additions & 1 deletion ajax/merge.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
// Trigger extension attribute definition sync
PluginJamfMobileSync::syncExtensionAttributeDefinitions();
PluginJamfComputerSync::syncExtensionAttributeDefinitions();
$supported_glpi_types = [
'Computer' => PluginJamfComputerSync::getSupportedGlpiItemtypes(),
'MobileDevice' => PluginJamfMobileSync::getSupportedGlpiItemtypes()
];
// An array of item IDs is required
if (isset($_REQUEST['item_ids']) && is_array($_REQUEST['item_ids'])) {
$failures = 0;
Expand All @@ -65,7 +69,7 @@
$jamf_id = $data['jamf_id'];
$itemtype = $data['itemtype'];

if (($itemtype !== 'Computer') && ($itemtype !== 'Phone')) {
if (!in_array($itemtype, $supported_glpi_types[$data['jamf_type']])) {
// Invalid itemtype for a mobile device
throw new RuntimeException('Invalid itemtype!');
}
Expand Down
55 changes: 33 additions & 22 deletions front/merge.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,35 +64,46 @@
$linked[$data['itemtype']][] = $data;
}

$supported_glpi_types = [
'Computer' => PluginJamfComputerSync::getSupportedGlpiItemtypes(),
'MobileDevice' => PluginJamfMobileSync::getSupportedGlpiItemtypes()
];

foreach ($pending as &$data) {
$itemtype = $data['type'];
/** @var CommonDBTM $item */
$item = new $itemtype();
$jamftype = ('PluginJamf' . $data['jamf_type']);
$guesses = $DB->request([
'SELECT' => ['id'],
'FROM' => $itemtype::getTable(),
'WHERE' => [
'OR' => [
'uuid' => $data['udid'],
'name' => Sanitizer::sanitize($data['name']),
$queries = [];
foreach ($supported_glpi_types[$data['jamf_type']] as $type) {
$queries[] = [
'SELECT' => [
new QueryExpression($DB::quoteValue($type) . ' AS ' . $DB::quoteName('itemtype')),
'id'
],
'FROM' => $type::getTable(),
'WHERE' => [
'OR' => [
'uuid' => $data['udid'],
'name' => Sanitizer::sanitize($data['name'])
],
'is_deleted' => 0,
'is_template' => 0
],
'is_deleted' => 0,
'is_template' => 0,
],
'ORDER' => new QueryExpression("CASE WHEN uuid='" . $data['udid'] . "' THEN 0 ELSE 1 END"),
'LIMIT' => 1,
]);
if (count($guesses)) {
$data['guessed_item'] = $guesses->current()['id'];
} else {
$data['guessed_item'] = 0;
'ORDER' => new QueryExpression("CASE WHEN uuid='" . $data['udid'] . "' THEN 0 ELSE 1 END"),
'LIMIT' => 1
];
}
$guesses = $DB->request(new QueryUnion($queries));
$data['guessed_item'] = null;
foreach ($guesses as $guess) {
$data['guessed_item'][$guess['itemtype']] = $guess['id'];
}
}

TemplateRenderer::getInstance()->display('@jamf/merge.html.twig', [
'pending' => $pending,
'total_count' => $importcount,
'linked' => $linked,
'linked' => $linked,
'supported_glpi_types' => array_map(
static fn ($ts) => array_combine($ts, array_map(static fn ($t) => $t::getTypeName(1), $ts)),
$supported_glpi_types
)
]);
Html::footer();
26 changes: 23 additions & 3 deletions templates/import.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* -------------------------------------------------------------------------
*/
#}
{% import 'components/form/fields_macros.html.twig' as fields %}

<form>
{{ include('components/pager.html.twig', {
Expand Down Expand Up @@ -57,14 +58,26 @@
{% set import_checkbox %}
<input type="checkbox" name="import_{{ data.id }}" class="form-check-input massive_action_checkbox">
{% endset %}
<tr>
<tr data-import-id="{{ data.id }}">
<td>{{ import_checkbox }}</td>
<td>{{ data.jamf_items_id }}</td>
<td>{{ data.jamf_type }}</td>
<td>
<a href="{{ call('PluginJamf' ~ data.jamf_type ~ '::getJamfDeviceURL', [data.jamf_items_id]) }}">{{ data.name }}</a>
</td>
<td>{{ data.type }}</td>
{% if data.jamf_type == 'MobileDevice' %}
<td>
{{ fields.dropdownArrayField('glpi_type', data.type, {
'Phone': 'Phone'|itemtype_name,
'Computer': 'Computer'|itemtype_name,
}, null, {
no_label: true,
full_width: true,
}) }}
</td>
{% else %}
<td>{{ data.type|itemtype_name }}</td>
{% endif %}
<td class="{{ data.udid is empty ? 'font-italic' : '' }}">
{{ data.udid is not empty ? data.udid : _x('message', 'Not collected during discovery', 'jamf') }}
</td>
Expand All @@ -90,12 +103,19 @@
const import_ids = $(':checkbox:checked').filter(':not([name^="_checkall"])').map(function() {
return this.name.replace("import","").substring(1).split('_');
}).toArray();
const itemtype_overrides = {};
$('select[name="glpi_type"]').each((i, e) => {
const itemtype = $(e).val();
const id = $(e).closest('tr').attr('data-import-id');
itemtype_overrides[id] = itemtype;
});
$.ajax({
type: "POST",
url: "{{ get_plugin_web_dir('jamf') }}/ajax/import.php",
data: {
action: "import",
import_ids: import_ids
import_ids: import_ids,
itemtype_overrides: itemtype_overrides
},
contentType: 'application/json',
beforeSend: () => {
Expand Down
35 changes: 26 additions & 9 deletions templates/merge.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,31 @@
<td>
<a href="{{ call('PluginJamf' ~ data.jamf_type ~ '::getJamfDeviceURL', [data.jamf_items_id]) }}">{{ data.name }}</a>
</td>
<td>{{ data.type }}</td>
<td>
{% if supported_glpi_types[data.jamf_type]|length > 0 %}
{{ fields.dropdownArrayField('glpi_type', data.type, supported_glpi_types[data.jamf_type], null, {
no_label: true,
full_width: true,
}) }}
{% else %}
{{ data.type|itemtype_name }}
{% endif %}
</td>
<td>{{ data.jamf_type }}</td>
<td class="{{ data.udid is empty ? 'font-italic' : '' }}">
{{ data.udid is not empty ? data.udid : _x('message', 'Not collected during discovery', 'jamf') }}
</td>
<td>{{ data.date_discover|formatted_datetime }}</td>
<td>
{{ fields.dropdownField(data.type, 'items_id', data.guessed_item, null, {
no_label: true,
full_width: true,
used: linked[data.type]|default([])|column('items_id')
}) }}
{% for glpi_type in supported_glpi_types[data.jamf_type] %}
<span class="{{ glpi_type != data.type ? 'd-none' : '' }}" data-itemtype="{{ glpi_type }}">
{{ fields.dropdownField(glpi_type, 'items_id', data.guessed_item[glpi_type], null, {
no_label: true,
full_width: true,
used: linked[glpi_type]|default([])|column('items_id'),
}) }}
</span>
{% endfor %}
</td>
</tr>
{% endfor %}
Expand All @@ -93,12 +106,11 @@
for (let i = 1; i < row_count; i++) {
const row = table.rows[i];
const jamf_id = row.cells[0].innerText;
const itemtype = row.cells[2].innerText;
const itemtype = $(row.cells[2]).find('select')[0].value;
const jamf_type = row.cells[3].innerText;
const glpi_sel = $(row.cells[6]).find('select')[0];
const glpi_sel = $(row.cells[6]).find('span[data-itemtype]:not(.d-none) select')[0];
const glpi_id = glpi_sel.value;
if (glpi_id && glpi_id > 0) {
data = [];
post_data[glpi_id] = {'itemtype': itemtype, 'jamf_id': jamf_id, 'jamf_type': jamf_type};
}
}
Expand All @@ -115,6 +127,11 @@
}
});
}
$('select[name="glpi_type"]').on('change', (e) => {
const selection = $(e.target).val();
$(e.target).closest('tr').find('span[data-itemtype]').addClass('d-none');
$(e.target).closest('tr').find('span[data-itemtype="' + selection + '"]').removeClass('d-none');
});
</script>
</div>
</form>