Link Entra ids to the Azure portal

This commit is contained in:
2026-08-31 16:24:00 -04:00
parent 7081ea5d41
commit 46e14f457d
3 changed files with 104 additions and 2 deletions

View File

@@ -196,13 +196,35 @@
>
<h3 class="section-title">Entra Device Info</h3>
<div class="info-list">
<!-- The link sits beside the id it is built from, not in a row
of its own at the top: the pairing is counterintuitive - the
DEVICE id opens Intune and the OBJECT id opens Entra - and a
separate action row would have to re-label which is which. -->
<div v-if="computer.computer?.azuredeviceid" class="info-row">
<span class="info-label">Device ID</span>
<span class="info-value">{{ computer.computer.azuredeviceid }}</span>
<span class="info-value">
{{ computer.computer.azuredeviceid }}
<a
v-if="intuneDeviceLink"
:href="intuneDeviceLink"
target="_blank"
rel="noopener noreferrer"
class="portal-link"
>Intune</a>
</span>
</div>
<div v-if="computer.computer?.azureobjectid" class="info-row">
<span class="info-label">Object ID</span>
<span class="info-value">{{ computer.computer.azureobjectid }}</span>
<span class="info-value">
{{ computer.computer.azureobjectid }}
<a
v-if="entraDeviceLink"
:href="entraDeviceLink"
target="_blank"
rel="noopener noreferrer"
class="portal-link"
>Entra</a>
</span>
</div>
<div v-if="computer.computer?.azuredevicecategory" class="info-row">
<span class="info-label">Device Category</span>
@@ -342,6 +364,7 @@ import PluginAssetPanels from '@/components/PluginAssetPanels.vue'
import AssetDetailTabs from '@/components/AssetDetailTabs.vue'
import AssetRelationships from '@/components/AssetRelationships.vue'
import { useIdentifierFlags } from '@/composables/identifierSettings'
import { getEntraDeviceUrls } from '@/utils/siteSettings'
import { setRecordTitle } from '@/composables/pageTitle'
import { useToast } from '@/composables/toast'
import { apiError } from '@/utils/apiError'
@@ -374,6 +397,22 @@ const assetPanels = ref(null)
// which is the answer somebody arriving from a search or a link came for.
const activegroup = ref('details')
// Azure portal deep links, built from templates a site configures. Empty
// templates mean no link at all, which is the default: the host differs per
// tenant (this site is on the US Government cloud) and Microsoft moves these
// blade paths, so guessing one would send people to a dead page.
const entraUrlTemplate = ref('')
const intuneUrlTemplate = ref('')
function fillTemplate(template, token, value) {
if (!template || !value) return null
return template.split(token).join(encodeURIComponent(value))
}
const entraDeviceLink = computed(() => fillTemplate(
entraUrlTemplate.value, '{objectid}', computer.value?.computer?.azureobjectid))
const intuneDeviceLink = computed(() => fillTemplate(
intuneUrlTemplate.value, '{deviceid}', computer.value?.computer?.azuredeviceid))
// Only offer a tab that has something in it. An empty tab is worse than an
// absent one - it reads as broken rather than as "this site does not collect
// that". Details always shows because core fills it regardless of plugins.
@@ -451,6 +490,13 @@ function relativeAge(value) {
}
onMounted(async () => {
// Non-critical: a site that has not configured these just gets no link, so a
// failure here must not stop the page loading.
getEntraDeviceUrls().then(({ entraUrl, intuneUrl }) => {
entraUrlTemplate.value = entraUrl
intuneUrlTemplate.value = intuneUrl
}).catch(() => {})
try {
const response = await computersApi.get(route.params.id)
computer.value = response.data.data
@@ -606,6 +652,18 @@ async function confirmRetire() {
</script>
<style scoped>
.portal-link {
font-size: 0.78rem;
margin-left: 0.5rem;
padding: 0.05rem 0.4rem;
border: 1px solid var(--border);
border-radius: 3px;
color: var(--link, var(--primary));
text-decoration: none;
white-space: nowrap;
}
.portal-link:hover { border-color: var(--primary); }
/* PC-specific styles - shared styles are in global style.css */