diff --git a/aas-web-ui/public/config/basyx-infra.yml b/aas-web-ui/public/config/basyx-infra.yml
index f3aff823e..4dc2c9314 100644
--- a/aas-web-ui/public/config/basyx-infra.yml
+++ b/aas-web-ui/public/config/basyx-infra.yml
@@ -8,6 +8,9 @@
# The configuration will be loaded at application startup and merged with user settings.
# When VITE_ENDPOINT_CONFIG_AVAILABLE=false, this configuration takes full precedence.
# When VITE_ENDPOINT_CONFIG_AVAILABLE=true, users can edit these infrastructures.
+# Optional component flags:
+# - hasRegistryIntegration: Backend repository creates/updates/deletes descriptors automatically.
+# - hasDiscoveryIntegration: Backend AAS registry creates/updates/deletes discovery asset links automatically.
infrastructures:
# Specify which infrastructure should be selected by default
@@ -21,12 +24,15 @@ infrastructures:
baseUrl: "http://localhost:9084"
aasRegistry:
baseUrl: "http://localhost:9082"
+ hasDiscoveryIntegration: true
submodelRegistry:
baseUrl: "http://localhost:9083"
aasRepository:
baseUrl: "http://localhost:9081"
+ hasRegistryIntegration: true
submodelRepository:
baseUrl: "http://localhost:9081"
+ hasRegistryIntegration: true
conceptDescriptionRepository:
baseUrl: "http://localhost:9081"
security:
diff --git a/aas-web-ui/src/components/AppNavigation/DeleteAAS.vue b/aas-web-ui/src/components/AppNavigation/DeleteAAS.vue
index 696aad843..c5bcacea2 100644
--- a/aas-web-ui/src/components/AppNavigation/DeleteAAS.vue
+++ b/aas-web-ui/src/components/AppNavigation/DeleteAAS.vue
@@ -37,11 +37,15 @@
diff --git a/aas-web-ui/src/components/AppNavigation/Settings/InfrastructureManagement.vue b/aas-web-ui/src/components/AppNavigation/Settings/InfrastructureManagement.vue
index 63e313565..f0c141ab1 100644
--- a/aas-web-ui/src/components/AppNavigation/Settings/InfrastructureManagement.vue
+++ b/aas-web-ui/src/components/AppNavigation/Settings/InfrastructureManagement.vue
@@ -60,7 +60,9 @@
:component-testing-loading="componentTestingLoading"
@test-connection="testComponentConnection"
@update:component-url="handleComponentUrlUpdate"
- @update:connection-status="handleConnectionStatusUpdate" />
+ @update:connection-status="handleConnectionStatusUpdate"
+ @update:registry-integration="handleRegistryIntegrationUpdate"
+ @update:discovery-integration="handleDiscoveryIntegrationUpdate" />
Security Configuration
@@ -368,4 +370,12 @@
function handleConnectionStatusUpdate(componentKey: BaSyxComponentKey, status: boolean | null): void {
componentConnectionStatus.value[componentKey] = status;
}
+
+ function handleRegistryIntegrationUpdate(componentKey: BaSyxComponentKey, enabled: boolean): void {
+ editingInfrastructure.value.components[componentKey].hasRegistryIntegration = enabled;
+ }
+
+ function handleDiscoveryIntegrationUpdate(componentKey: BaSyxComponentKey, enabled: boolean): void {
+ editingInfrastructure.value.components[componentKey].hasDiscoveryIntegration = enabled;
+ }
diff --git a/aas-web-ui/src/components/AppNavigation/UploadAAS.vue b/aas-web-ui/src/components/AppNavigation/UploadAAS.vue
index cdbbbb427..78ffd010f 100644
--- a/aas-web-ui/src/components/AppNavigation/UploadAAS.vue
+++ b/aas-web-ui/src/components/AppNavigation/UploadAAS.vue
@@ -27,13 +27,20 @@
label="Ignore Duplicates"
hide-details
class="mt-3">
-
- Descriptor creation is unavailable because AAS and Submodel registries are not connected.
+ Manual descriptor sync is required by infrastructure settings, but AAS and Submodel registries are
+ not connected.
+
+
+ Manual discovery sync is required by infrastructure settings, but AAS discovery is not connected.
infrastructureStore.getAASRepoURL);
- const smRepositoryUrl = computed(() => infrastructureStore.getSubmodelRepoURL);
-
// Composables
- const { fetchAas, uploadAas } = useAASRepositoryClient();
+ const { fetchAas, uploadAas, getAasEndpointById } = useAASRepositoryClient();
+ const { getSmEndpointById } = useSMRepositoryClient();
const { fetchSm } = useSMHandling();
const { importAasxFileClient, importEnvironmentFileClient } = useAASXImport();
+ const { createAssetLinksFromAssetInformation, upsertAssetLinksForAas } = useAASDiscoveryClient();
const { postAasDescriptor, putAasDescriptor, createDescriptorFromAAS } = useAASRegistryClient();
const { postSubmodelDescriptor, putSubmodelDescriptor, createDescriptorFromSubmodel } = useSMRegistryClient();
@@ -109,7 +115,6 @@
const aasFiles = ref([]);
const loadingUpload = ref(false);
const ignoreDuplicates = ref(true);
- const createDescriptors = ref(false);
const uploadProgress = ref(0);
const currentFileLabel = ref('');
const uploadMode = ref<'client' | 'server'>('client');
@@ -133,6 +138,30 @@
const submodelRegistryConnected = isConnected(components?.SubmodelRegistry?.connected);
return aasRegistryConnected && submodelRegistryConnected;
});
+ const discoveryAvailable = computed(() => {
+ const components = infrastructureStore.getBasyxComponents as Record;
+ const connected = components?.AASDiscovery?.connected;
+
+ if (connected === true) return true;
+ if (!connected || typeof connected !== 'object') return false;
+ const maybeRef = connected as { value?: unknown };
+ return maybeRef.value === true;
+ });
+
+ const selectedInfrastructure = computed(() => infrastructureStore.getSelectedInfrastructure);
+ const aasRepoHasRegistryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.AASRepo?.hasRegistryIntegration ?? true
+ );
+ const submodelRepoHasRegistryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.SubmodelRepo?.hasRegistryIntegration ?? true
+ );
+ const aasRegistryHasDiscoveryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.AASRegistry?.hasDiscoveryIntegration ?? true
+ );
+ const manualDescriptorSyncRequired = computed(
+ () => !aasRepoHasRegistryIntegration.value || !submodelRepoHasRegistryIntegration.value
+ );
+ const manualDiscoverySyncRequired = computed(() => !aasRegistryHasDiscoveryIntegration.value);
watch(
() => props.modelValue,
@@ -176,9 +205,17 @@
}
try {
- if (createDescriptors.value && !descriptorsAvailable.value) {
- summary.warnings.push('Descriptor creation skipped because registries are not connected.');
+ if (manualDescriptorSyncRequired.value && !descriptorsAvailable.value) {
+ summary.warnings.push('Manual descriptor sync skipped because registries are not connected.');
}
+ if (manualDiscoverySyncRequired.value && !discoveryAvailable.value) {
+ summary.warnings.push('Manual discovery sync skipped because AAS discovery is not connected.');
+ }
+
+ const shouldSyncAasDescriptor = !aasRepoHasRegistryIntegration.value && descriptorsAvailable.value;
+ const shouldSyncSubmodelDescriptor =
+ !submodelRepoHasRegistryIntegration.value && descriptorsAvailable.value;
+ const shouldSyncDiscovery = !aasRegistryHasDiscoveryIntegration.value && discoveryAvailable.value;
for (let index = 0; index < aasFiles.value.length; index++) {
const aasFile = aasFiles.value[index];
@@ -191,9 +228,14 @@
summary.failed.push(`${aasFile.name}: upload failed.`);
} else {
summary.succeeded++;
- if (createDescriptors.value && descriptorsAvailable.value) {
+ if (shouldSyncAasDescriptor || shouldSyncSubmodelDescriptor || shouldSyncDiscovery) {
const createdWarnings = await createAndPostDescriptorsFromAasIds(
- Array.isArray(response?.data?.aasIds) ? response.data.aasIds : []
+ Array.isArray(response?.data?.aasIds) ? response.data.aasIds : [],
+ {
+ syncAasDescriptor: shouldSyncAasDescriptor,
+ syncSubmodelDescriptor: shouldSyncSubmodelDescriptor,
+ syncDiscovery: shouldSyncDiscovery,
+ }
);
summary.warnings.push(...createdWarnings.map((warning) => `${aasFile.name}: ${warning}`));
}
@@ -221,10 +263,15 @@
...result.warnings.map((warning: string) => `${aasFile.name}: ${warning}`)
);
- if (createDescriptors.value && descriptorsAvailable.value) {
+ if (shouldSyncAasDescriptor || shouldSyncSubmodelDescriptor || shouldSyncDiscovery) {
const descriptorWarnings = await createAndPostDescriptorsFromPayload(
result.importedAas,
- result.importedSubmodels
+ result.importedSubmodels,
+ {
+ syncAasDescriptor: shouldSyncAasDescriptor,
+ syncSubmodelDescriptor: shouldSyncSubmodelDescriptor,
+ syncDiscovery: shouldSyncDiscovery,
+ }
);
summary.warnings.push(
...descriptorWarnings.map((warning: string) => `${aasFile.name}: ${warning}`)
@@ -290,7 +337,6 @@
aasFiles.value = [];
uploadAASDialog.value = false;
loadingUpload.value = false;
- createDescriptors.value = false;
uploadProgress.value = 0;
currentFileLabel.value = '';
uploadMode.value = 'client';
@@ -315,12 +361,15 @@
return typeof value === 'string' ? value : '';
}
- async function createAndPostDescriptorsFromAasIds(aasIds: string[]): Promise {
+ async function createAndPostDescriptorsFromAasIds(
+ aasIds: string[],
+ sync: { syncAasDescriptor: boolean; syncSubmodelDescriptor: boolean; syncDiscovery: boolean }
+ ): Promise {
const warnings: string[] = [];
for (const aasId of aasIds) {
try {
- const href = aasRepositoryUrl.value + '/' + base64Encode(aasId);
+ const href = getAasEndpointById(aasId);
const fetchedShell = await fetchAas(href);
if (!fetchedShell || !fetchedShell.id) {
@@ -328,26 +377,43 @@
continue;
}
- const aasEndpoints = createEndpoints(href, 'AAS-3.0');
- const aasDescriptor = createDescriptorFromAAS(fetchedShell, aasEndpoints);
-
- const submodelRefs = Array.isArray(fetchedShell.submodels) ? fetchedShell.submodels : [];
- for (const submodelRef of submodelRefs) {
- const submodelId = submodelRef?.keys?.[0]?.value;
- if (!submodelId) continue;
+ if (sync.syncSubmodelDescriptor) {
+ const submodelRefs = Array.isArray(fetchedShell.submodels) ? fetchedShell.submodels : [];
+ for (const submodelRef of submodelRefs) {
+ const submodelId = submodelRef?.keys?.[0]?.value;
+ if (!submodelId) continue;
+
+ const submodelDescriptor = await createSubmodelDescriptor(submodelId);
+ const smSuccess =
+ (await postSubmodelDescriptor(submodelDescriptor)) ||
+ (await putSubmodelDescriptor(submodelDescriptor));
+ if (!smSuccess) {
+ warnings.push(`Failed to create Submodel Descriptor '${submodelId}'.`);
+ }
+ }
+ }
- const submodelDescriptor = await createSubmodelDescriptor(submodelId);
- const smSuccess =
- (await postSubmodelDescriptor(submodelDescriptor)) ||
- (await putSubmodelDescriptor(submodelDescriptor));
- if (!smSuccess) {
- warnings.push(`Failed to create Submodel Descriptor '${submodelId}'.`);
+ if (sync.syncAasDescriptor) {
+ const aasEndpoints = createEndpoints(href, 'AAS-3.0');
+ const aasDescriptor = createDescriptorFromAAS(fetchedShell, aasEndpoints);
+ const aasSuccess =
+ (await postAasDescriptor(aasDescriptor)) || (await putAasDescriptor(aasDescriptor));
+ if (!aasSuccess) {
+ warnings.push(`Failed to create AAS Descriptor '${aasId}'.`);
}
}
- const aasSuccess = (await postAasDescriptor(aasDescriptor)) || (await putAasDescriptor(aasDescriptor));
- if (!aasSuccess) {
- warnings.push(`Failed to create AAS Descriptor '${aasId}'.`);
+ if (sync.syncDiscovery) {
+ const links = createAssetLinksFromAssetInformation(
+ fetchedShell?.assetInformation?.globalAssetId,
+ fetchedShell?.assetInformation?.specificAssetIds
+ );
+ if (links.length > 0) {
+ const discoverySuccess = await upsertAssetLinksForAas(aasId, links);
+ if (!discoverySuccess) {
+ warnings.push(`Failed to synchronize discovery asset links for '${aasId}'.`);
+ }
+ }
}
} catch (error) {
warnings.push(`Error while creating descriptors for AAS '${aasId}': ${stringifyUnknown(error)}`);
@@ -359,7 +425,8 @@
async function createAndPostDescriptorsFromPayload(
aasList: Array>,
- submodels: Array>
+ submodels: Array>,
+ sync: { syncAasDescriptor: boolean; syncSubmodelDescriptor: boolean; syncDiscovery: boolean }
): Promise {
const warnings: string[] = [];
const submodelById = new Map>();
@@ -374,39 +441,60 @@
const aasId = asString(aas.id).trim();
if (aasId === '') continue;
- const aasHref = aasRepositoryUrl.value + '/' + base64Encode(aasId);
- const aasDescriptor = createDescriptorFromAAS(
- aas as unknown as jsonization.JsonObject,
- createEndpoints(aasHref, 'AAS-3.0')
- );
-
- const submodelRefs = Array.isArray(aas.submodels) ? aas.submodels : [];
- for (const submodelRef of submodelRefs) {
- const submodelId = submodelRef?.keys?.[0]?.value;
- if (!submodelId) continue;
-
- const submodel = submodelById.get(submodelId);
- if (!submodel) {
- warnings.push(`Submodel '${submodelId}' not found for descriptor creation.`);
- continue;
+ const aasHref = getAasEndpointById(aasId);
+
+ if (sync.syncSubmodelDescriptor) {
+ const submodelRefs = Array.isArray(aas.submodels) ? aas.submodels : [];
+ for (const submodelRef of submodelRefs) {
+ const submodelId = submodelRef?.keys?.[0]?.value;
+ if (!submodelId) continue;
+
+ const submodel = submodelById.get(submodelId);
+ if (!submodel) {
+ warnings.push(`Submodel '${submodelId}' not found for descriptor creation.`);
+ continue;
+ }
+
+ const submodelHref = getSmEndpointById(submodelId);
+ const submodelDescriptor = createDescriptorFromSubmodel(
+ submodel as unknown as jsonization.JsonObject,
+ createEndpoints(submodelHref, 'SUBMODEL-3.0')
+ );
+ const smSuccess =
+ (await postSubmodelDescriptor(submodelDescriptor)) ||
+ (await putSubmodelDescriptor(submodelDescriptor));
+ if (!smSuccess) {
+ warnings.push(`Failed to create Submodel Descriptor '${submodelId}'.`);
+ }
}
+ }
- const submodelHref = smRepositoryUrl.value + '/' + base64Encode(submodelId);
- const submodelDescriptor = createDescriptorFromSubmodel(
- submodel as unknown as jsonization.JsonObject,
- createEndpoints(submodelHref, 'SUBMODEL-3.0')
+ if (sync.syncAasDescriptor) {
+ const aasDescriptor = createDescriptorFromAAS(
+ aas as unknown as jsonization.JsonObject,
+ createEndpoints(aasHref, 'AAS-3.0')
);
- const smSuccess =
- (await postSubmodelDescriptor(submodelDescriptor)) ||
- (await putSubmodelDescriptor(submodelDescriptor));
- if (!smSuccess) {
- warnings.push(`Failed to create Submodel Descriptor '${submodelId}'.`);
+ const aasSuccess =
+ (await postAasDescriptor(aasDescriptor)) || (await putAasDescriptor(aasDescriptor));
+ if (!aasSuccess) {
+ warnings.push(`Failed to create AAS Descriptor '${aasId}'.`);
}
}
- const aasSuccess = (await postAasDescriptor(aasDescriptor)) || (await putAasDescriptor(aasDescriptor));
- if (!aasSuccess) {
- warnings.push(`Failed to create AAS Descriptor '${aasId}'.`);
+ if (sync.syncDiscovery) {
+ const assetInformation = aas.assetInformation as
+ | { globalAssetId?: string; specificAssetIds?: Array<{ name?: string; value?: string }> }
+ | undefined;
+ const links = createAssetLinksFromAssetInformation(
+ assetInformation?.globalAssetId,
+ assetInformation?.specificAssetIds
+ );
+ if (links.length > 0) {
+ const discoverySuccess = await upsertAssetLinksForAas(aasId, links);
+ if (!discoverySuccess) {
+ warnings.push(`Failed to synchronize discovery asset links for '${aasId}'.`);
+ }
+ }
}
} catch (error) {
warnings.push(`Error while creating descriptors from imported payload: ${stringifyUnknown(error)}`);
@@ -418,9 +506,8 @@
async function createSubmodelDescriptor(submodelId: string): Promise {
try {
- let submodelId64 = base64Encode(submodelId);
- let href = smRepositoryUrl.value + '/' + submodelId64;
- let submodel = await fetchSm(href);
+ const href = getSmEndpointById(submodelId);
+ const submodel = await fetchSm(href);
const endpoints = createEndpoints(href, 'SUBMODEL-3.0');
diff --git a/aas-web-ui/src/components/EditorComponents/AASForm.vue b/aas-web-ui/src/components/EditorComponents/AASForm.vue
index 949fd0dd8..599ba0f57 100644
--- a/aas-web-ui/src/components/EditorComponents/AASForm.vue
+++ b/aas-web-ui/src/components/EditorComponents/AASForm.vue
@@ -191,12 +191,15 @@
import { computed, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAASHandling } from '@/composables/AAS/AASHandling';
+ import { useAASDiscoveryClient } from '@/composables/Client/AASDiscoveryClient';
import { useAASRegistryClient } from '@/composables/Client/AASRegistryClient';
import { useAASRepositoryClient } from '@/composables/Client/AASRepositoryClient';
import { useIDUtils } from '@/composables/IDUtils';
import { buildVerificationSummary, verifyForEditor } from '@/composables/MetamodelVerification';
import { useAASStore } from '@/store/AASDataStore';
+ import { useInfrastructureStore } from '@/store/InfrastructureStore';
import { useNavigationStore } from '@/store/NavigationStore';
+ import { Endpoint, ProtocolInformation } from '@/types/Descriptors';
const props = defineProps<{
modelValue: boolean;
@@ -214,14 +217,24 @@
// Stores
const aasStore = useAASStore();
+ const infrastructureStore = useInfrastructureStore();
const navigationStore = useNavigationStore();
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void;
}>();
- const { fetchAasById, postAas, putAas, putThumbnail } = useAASRepositoryClient();
- const { fetchAasDescriptorById, putAasDescriptor, createDescriptorFromAAS } = useAASRegistryClient();
+ const {
+ fetchAasById,
+ postAas,
+ putAas,
+ putThumbnail,
+ getAasEndpointById: getAasRepoEndpointById,
+ } = useAASRepositoryClient();
+ const { fetchAasDescriptorById, postAasDescriptor, putAasDescriptor, createDescriptorFromAAS } =
+ useAASRegistryClient();
+ const { createAssetLinksFromAssetInformation, upsertAssetLinksForAas, deleteAssetLinksForAas } =
+ useAASDiscoveryClient();
const editAASDialog = ref(false);
const AASObject = ref(undefined);
@@ -247,9 +260,17 @@
const embeddedDataSpecifications = ref | null>(null);
const fileThumbnail = ref(undefined);
+ const initialDiscoveryAssetLinks = ref>([]);
// Computed Properties
const selectedAAS = computed(() => aasStore.getSelectedAAS); // Get the selected AAS from Store
+ const selectedInfrastructure = computed(() => infrastructureStore.getSelectedInfrastructure);
+ const aasRepoHasRegistryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.AASRepo?.hasRegistryIntegration ?? true
+ );
+ const aasRegistryHasDiscoveryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.AASRegistry?.hasDiscoveryIntegration ?? true
+ );
const bordersToShow = computed(() => (panel: number) => {
let border = '';
switch (panel) {
@@ -344,6 +365,12 @@
defaultThumbnail.value = AASObject.value.assetInformation.defaultThumbnail ?? null;
}
embeddedDataSpecifications.value = AASObject.value.embeddedDataSpecifications ?? null;
+
+ // Keep a stable snapshot from initial load so update detection is not affected by in-place form mutations.
+ initialDiscoveryAssetLinks.value = createAssetLinksFromAssetInformation(
+ AASObject.value.assetInformation?.globalAssetId,
+ AASObject.value.assetInformation?.specificAssetIds
+ );
}
}
@@ -401,6 +428,8 @@
async function saveAAS(): Promise {
if (AASId.value === null) return;
+ const previousAssetLinks = props.newShell ? [] : [...initialDiscoveryAssetLinks.value];
+
const assetInformation = createAssetInformation();
const administrativeInformation = createAdministrativeInformation();
@@ -463,6 +492,7 @@
if (props.newShell) {
// Create new AAS
await postAas(AASObject.value);
+ await syncAasDescriptorAndDiscovery(AASObject.value, true, previousAssetLinks);
// Upload default thumbnail
if (fileThumbnail.value !== undefined) {
await putThumbnail(fileThumbnail.value, AASObject.value.id);
@@ -472,18 +502,12 @@
query.aas = await getAasEndpointById(AASObject.value.id);
if (Object.hasOwn(query, 'path')) delete query.path;
- router.push({ query: query });
+ await router.push({ query: query });
navigationStore.dispatchTriggerAASListReload(); // Reload AAS List
} else {
// Update existing AAS
await putAas(AASObject.value);
- // Update AAS Descriptor
- const jsonAAS = jsonization.toJsonable(AASObject.value);
- // Fetch existing descriptor to preserve endpoints
- const existingDescriptor = await fetchAasDescriptorById(AASObject.value.id);
- const endpoints = existingDescriptor?.endpoints ?? [];
- const descriptor = createDescriptorFromAAS(jsonAAS, endpoints);
- await putAasDescriptor(descriptor);
+ await syncAasDescriptorAndDiscovery(AASObject.value, false, previousAssetLinks);
// Upload default thumbnail
if (fileThumbnail.value !== undefined) {
await putThumbnail(fileThumbnail.value, AASObject.value.id);
@@ -497,6 +521,115 @@
editAASDialog.value = false;
}
+ async function syncAasDescriptorAndDiscovery(
+ aas: aasTypes.AssetAdministrationShell,
+ isCreate: boolean,
+ previousAssetLinks: Array<{ name: string; value: string }>
+ ): Promise {
+ const warnings: string[] = [];
+
+ if (!aasRepoHasRegistryIntegration.value) {
+ try {
+ const jsonAAS = jsonization.toJsonable(aas);
+ const existingDescriptor = await fetchAasDescriptorById(aas.id);
+ const fallbackAasEndpoint = getAasRepoEndpointById(aas.id);
+ const endpoints =
+ Array.isArray(existingDescriptor?.endpoints) && existingDescriptor.endpoints.length > 0
+ ? existingDescriptor.endpoints
+ : createEndpoints(fallbackAasEndpoint, 'AAS-3.0');
+ const descriptor = createDescriptorFromAAS(jsonAAS, endpoints);
+ const success = isCreate
+ ? (await postAasDescriptor(descriptor)) || (await putAasDescriptor(descriptor))
+ : (await putAasDescriptor(descriptor)) || (await postAasDescriptor(descriptor));
+
+ if (!success) {
+ warnings.push(`Failed to synchronize AAS descriptor for '${aas.id}'.`);
+ }
+ } catch (error) {
+ warnings.push(
+ `Failed to synchronize AAS descriptor for '${aas.id}': ${error instanceof Error ? error.message : String(error)}`
+ );
+ }
+ }
+
+ if (!aasRegistryHasDiscoveryIntegration.value) {
+ try {
+ const assetLinks = createAssetLinksFromAssetInformation(
+ aas.assetInformation?.globalAssetId,
+ aas.assetInformation?.specificAssetIds
+ );
+
+ if (isCreate) {
+ if (assetLinks.length > 0) {
+ const success = await upsertAssetLinksForAas(aas.id, assetLinks);
+ if (!success) {
+ warnings.push(`Failed to synchronize discovery asset links for '${aas.id}'.`);
+ }
+ }
+ } else {
+ const linksChanged = !areAssetLinksEqual(previousAssetLinks, assetLinks);
+
+ if (linksChanged) {
+ if (assetLinks.length === 0) {
+ const deleteSuccess = await deleteAssetLinksForAas(aas.id);
+ if (!deleteSuccess) {
+ warnings.push(`Failed to remove discovery asset links for '${aas.id}'.`);
+ }
+ } else {
+ const upsertSuccess = await upsertAssetLinksForAas(aas.id, assetLinks);
+ if (!upsertSuccess) {
+ warnings.push(`Failed to synchronize discovery asset links for '${aas.id}'.`);
+ }
+ }
+ }
+ }
+ } catch (error) {
+ warnings.push(
+ `Failed to synchronize discovery asset links for '${aas.id}': ${error instanceof Error ? error.message : String(error)}`
+ );
+ }
+ }
+
+ if (warnings.length > 0) {
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 10000,
+ color: 'warning',
+ btnColor: 'buttonText',
+ baseError: 'AAS saved with synchronization warnings.',
+ extendedError: warnings.join('\n'),
+ });
+ }
+ }
+
+ function areAssetLinksEqual(
+ left: Array<{ name: string; value: string }>,
+ right: Array<{ name: string; value: string }>
+ ): boolean {
+ if (left.length !== right.length) return false;
+
+ const normalize = (links: Array<{ name: string; value: string }>): Array =>
+ links.map((link) => `${link.name}\u0000${link.value}`).sort((a, b) => a.localeCompare(b));
+
+ const leftNormalized = normalize(left);
+ const rightNormalized = normalize(right);
+
+ return leftNormalized.every((entry, index) => entry === rightNormalized[index]);
+ }
+
+ function createEndpoints(href: string, type: string): Array {
+ let protocol: string | null = null;
+ try {
+ const url = new URL(href);
+ protocol = url.protocol.replace(/:$/, '');
+ } catch {
+ // If href is not a valid absolute URL, keep protocol null.
+ }
+
+ const protocolInformation = new ProtocolInformation(href, null, protocol);
+ return [new Endpoint(type, protocolInformation)];
+ }
+
function closeDialog(): void {
clearForm();
editAASDialog.value = false;
@@ -520,6 +653,7 @@
assetType.value = null;
defaultThumbnail.value = null;
embeddedDataSpecifications.value = null;
+ initialDiscoveryAssetLinks.value = [];
// Reset state of expansion panels
openPanels.value = [0, 3];
}
diff --git a/aas-web-ui/src/components/EditorComponents/DeleteDialog.vue b/aas-web-ui/src/components/EditorComponents/DeleteDialog.vue
index 3512231f6..44c7fed42 100644
--- a/aas-web-ui/src/components/EditorComponents/DeleteDialog.vue
+++ b/aas-web-ui/src/components/EditorComponents/DeleteDialog.vue
@@ -34,12 +34,16 @@
import { useRoute, useRouter } from 'vue-router';
import { useSMHandling } from '@/composables/AAS/SMHandling';
import { useAASRepositoryClient } from '@/composables/Client/AASRepositoryClient';
+ import { useSMRegistryClient } from '@/composables/Client/SMRegistryClient';
+ import { useSMRepositoryClient } from '@/composables/Client/SMRepositoryClient';
import { useRequestHandling } from '@/composables/RequestHandling';
import { useAASStore } from '@/store/AASDataStore';
+ import { useInfrastructureStore } from '@/store/InfrastructureStore';
import { useNavigationStore } from '@/store/NavigationStore';
import { extractEndpointHref } from '@/utils/AAS/DescriptorUtils';
const aasStore = useAASStore();
+ const infrastructureStore = useInfrastructureStore();
const navigationStore = useNavigationStore();
const router = useRouter();
@@ -47,7 +51,9 @@
const { deleteRequest } = useRequestHandling();
const { fetchSmDescriptor } = useSMHandling();
- const { deleteSubmodelRef } = useAASRepositoryClient();
+ const { deleteSubmodelDescriptor } = useSMRegistryClient();
+ const { deleteSubmodelRef, getAasEndpointById } = useAASRepositoryClient();
+ const { getSmEndpointById } = useSMRepositoryClient();
const props = defineProps<{
modelValue: boolean;
@@ -62,6 +68,10 @@
const deleteLoading = ref(false); // Variable to store if the AAS is being deleted
const selectedAAS = computed(() => aasStore.getSelectedAAS); // get selected AAS from Store
+ const selectedInfrastructure = computed(() => infrastructureStore.getSelectedInfrastructure);
+ const submodelRepoHasRegistryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.SubmodelRepo?.hasRegistryIntegration ?? true
+ );
watch(
() => props.modelValue,
@@ -80,20 +90,54 @@
async function confirmDelete(): Promise {
deleteLoading.value = true;
if (props.element.modelType === 'Submodel') {
- // Fetch the submodel from the submodel registry
- const smDescriptor = await fetchSmDescriptor(props.element.id);
- // extract the submodel endpoint
- const smEndpoint = extractEndpointHref(smDescriptor, 'SUBMODEL-3.0');
+ let smEndpoint = '';
+ if (submodelRepoHasRegistryIntegration.value) {
+ const smDescriptor = await fetchSmDescriptor(props.element.id);
+ smEndpoint = extractEndpointHref(smDescriptor, 'SUBMODEL-3.0');
+ if (!smEndpoint) {
+ smEndpoint = getSmEndpointById(props.element.id);
+ }
+ } else {
+ smEndpoint = getSmEndpointById(props.element.id);
+ }
+
+ if (!smEndpoint) {
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 6000,
+ color: 'error',
+ btnColor: 'buttonText',
+ text: 'Unable to resolve Submodel endpoint for deletion.',
+ });
+ deleteLoading.value = false;
+ return;
+ }
+
try {
// delete the submodel
await deleteRequest(smEndpoint, 'removing Submodel', false);
// extract the AAS endpoint
- const aasEndpoint = extractEndpointHref(selectedAAS.value, 'AAS-3.0');
+ const aasEndpoint =
+ extractEndpointHref(selectedAAS.value, 'AAS-3.0') || getAasEndpointById(selectedAAS.value.id);
// delete the submodel reference from the AAS
await deleteSubmodelRef(aasEndpoint, props.element.id);
+ if (!submodelRepoHasRegistryIntegration.value) {
+ const descriptorDeleted = await deleteSubmodelDescriptor(props.element.id);
+ if (!descriptorDeleted) {
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 6000,
+ color: 'warning',
+ btnColor: 'buttonText',
+ baseError: 'Submodel deleted with synchronization warning.',
+ extendedError: `Failed to delete Submodel descriptor '${props.element.id}'.`,
+ });
+ }
+ }
+
// delete the submodel reference from the local AAS
const localAAS = { ...selectedAAS.value };
const submodelRefs = localAAS.submodels;
diff --git a/aas-web-ui/src/components/EditorComponents/JsonInsert.vue b/aas-web-ui/src/components/EditorComponents/JsonInsert.vue
index 9d4638d0b..9205cfa74 100644
--- a/aas-web-ui/src/components/EditorComponents/JsonInsert.vue
+++ b/aas-web-ui/src/components/EditorComponents/JsonInsert.vue
@@ -30,10 +30,12 @@
import { computed, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAASRepositoryClient } from '@/composables/Client/AASRepositoryClient';
+ import { useSMRegistryClient } from '@/composables/Client/SMRegistryClient';
import { useSMRepositoryClient } from '@/composables/Client/SMRepositoryClient';
import { useAASStore } from '@/store/AASDataStore';
import { useInfrastructureStore } from '@/store/InfrastructureStore';
import { useNavigationStore } from '@/store/NavigationStore';
+ import { Endpoint, ProtocolInformation } from '@/types/Descriptors';
import { getCreatedSubmodelElementPath, isDataElementModelType } from '@/utils/AAS/SubmodelElementPathUtils';
import { base64Decode, base64Encode } from '@/utils/EncodeDecodeUtils';
@@ -58,6 +60,7 @@
// Composables
const { postSubmodel, postSubmodelElement } = useSMRepositoryClient();
+ const { postSubmodelDescriptor, putSubmodelDescriptor, createDescriptorFromSubmodel } = useSMRegistryClient();
const { putAas } = useAASRepositoryClient();
// Data
@@ -67,7 +70,11 @@
// Computed Properties
const selectedAAS = computed(() => aasStore.getSelectedAAS); // Get the selected AAS from Store
+ const selectedInfrastructure = computed(() => infrastructureStore.getSelectedInfrastructure);
const submodelRepoUrl = computed(() => infrastructureStore.getSubmodelRepoURL);
+ const submodelRepoHasRegistryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.SubmodelRepo?.hasRegistryIntegration ?? true
+ );
watch(
() => props.modelValue,
@@ -117,6 +124,7 @@
await postSubmodel(submodel);
// Add Submodel Reference to AAS
await addSubmodelReferenceToAas(submodel);
+ await syncSubmodelDescriptor(submodel);
// Fetch and dispatch Submodel
const query = structuredClone(route.query);
query.path = submodelRepoUrl.value + '/' + base64Encode(submodel.id);
@@ -218,6 +226,43 @@
aasStore.dispatchSelectedAAS(localAAS);
}
+ async function syncSubmodelDescriptor(submodel: aasTypes.Submodel): Promise {
+ if (submodelRepoHasRegistryIntegration.value) {
+ return;
+ }
+
+ const submodelHref = `${submodelRepoUrl.value}/submodels/${base64Encode(submodel.id)}`;
+ const descriptor = createDescriptorFromSubmodel(
+ jsonization.toJsonable(submodel),
+ createEndpoints(submodelHref, 'SUBMODEL-3.0')
+ );
+
+ const success = (await putSubmodelDescriptor(descriptor)) || (await postSubmodelDescriptor(descriptor));
+ if (!success) {
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 8000,
+ color: 'warning',
+ btnColor: 'buttonText',
+ baseError: 'Submodel inserted with synchronization warning.',
+ extendedError: `Failed to synchronize Submodel descriptor for '${submodel.id}'.`,
+ });
+ }
+ }
+
+ function createEndpoints(href: string, type: string): Array {
+ let protocol: string | null = null;
+ try {
+ const url = new URL(href);
+ protocol = url.protocol.replace(/:$/, '');
+ } catch {
+ // If href is not a valid absolute URL, keep protocol null.
+ }
+
+ const protocolInformation = new ProtocolInformation(href, null, protocol);
+ return [new Endpoint(type, protocolInformation)];
+ }
+
function isValidJson(jsonString: string): boolean {
try {
JSON.parse(jsonString);
diff --git a/aas-web-ui/src/components/EditorComponents/SubmodelForm.vue b/aas-web-ui/src/components/EditorComponents/SubmodelForm.vue
index 64b2ede55..94fa6cb49 100644
--- a/aas-web-ui/src/components/EditorComponents/SubmodelForm.vue
+++ b/aas-web-ui/src/components/EditorComponents/SubmodelForm.vue
@@ -170,6 +170,7 @@
import { useAASStore } from '@/store/AASDataStore';
import { useInfrastructureStore } from '@/store/InfrastructureStore';
import { useNavigationStore } from '@/store/NavigationStore';
+ import { Endpoint, ProtocolInformation } from '@/types/Descriptors';
import { base64Encode } from '@/utils/EncodeDecodeUtils';
const props = defineProps<{
@@ -195,7 +196,7 @@
}>();
const { postSubmodel, putSubmodel } = useSMRepositoryClient();
- const { putSubmodelDescriptor, createDescriptorFromSubmodel } = useSMRegistryClient();
+ const { postSubmodelDescriptor, putSubmodelDescriptor, createDescriptorFromSubmodel } = useSMRegistryClient();
const { fetchSmById, fetchSmDescriptor, fetchAndDispatchSm } = useSMHandling();
const { putAas } = useAASRepositoryClient();
@@ -222,7 +223,11 @@
// Computed Properties
const selectedNode = computed(() => aasStore.getSelectedNode); // Get the selected AAS from Store
const selectedAAS = computed(() => aasStore.getSelectedAAS); // Get the selected AAS from Store
+ const selectedInfrastructure = computed(() => infrastructureStore.getSelectedInfrastructure);
const submodelRepoUrl = computed(() => infrastructureStore.getSubmodelRepoURL);
+ const submodelRepoHasRegistryIntegration = computed(
+ () => selectedInfrastructure.value?.components?.SubmodelRepo?.hasRegistryIntegration ?? true
+ );
const bordersToShow = computed(() => (panel: number) => {
let border = '';
switch (panel) {
@@ -414,6 +419,7 @@
await postSubmodel(submodelObject.value);
// Add Submodel Reference to AAS
await addSubmodelReferenceToAas(submodelObject.value);
+ await syncSubmodelDescriptor(submodelObject.value);
// Fetch and dispatch Submodel
const query = structuredClone(route.query);
query.path = submodelRepoUrl.value + '/submodels/' + base64Encode(submodelObject.value.id);
@@ -422,12 +428,7 @@
} else {
// Update existing Submodel
await putSubmodel(submodelObject.value);
- const jsonSubmodel = jsonization.toJsonable(submodelObject.value);
- // Fetch the current desciptor from the registry
- const fetchedDescriptor = await fetchSmDescriptor(submodelObject.value.id);
- const descriptor = createDescriptorFromSubmodel(jsonSubmodel, fetchedDescriptor.endpoints);
- // Update AAS Descriptor
- await putSubmodelDescriptor(descriptor);
+ await syncSubmodelDescriptor(submodelObject.value);
if (submodelObject.value.id === selectedNode.value.id) {
const path = submodelRepoUrl.value + '/submodels/' + base64Encode(submodelObject.value.id);
fetchAndDispatchSm(path);
@@ -438,6 +439,52 @@
editSMDialog.value = false;
}
+ async function syncSubmodelDescriptor(submodel: aasTypes.Submodel): Promise {
+ if (submodelRepoHasRegistryIntegration.value) {
+ return;
+ }
+
+ const jsonSubmodel = jsonization.toJsonable(submodel);
+ let descriptorSuccess = false;
+
+ try {
+ const fetchedDescriptor = await fetchSmDescriptor(submodel.id);
+ const fallbackEndpoint = `${submodelRepoUrl.value}/submodels/${base64Encode(submodel.id)}`;
+ const endpoints =
+ Array.isArray(fetchedDescriptor?.endpoints) && fetchedDescriptor.endpoints.length > 0
+ ? fetchedDescriptor.endpoints
+ : createEndpoints(fallbackEndpoint, 'SUBMODEL-3.0');
+ const descriptor = createDescriptorFromSubmodel(jsonSubmodel, endpoints);
+ descriptorSuccess = (await putSubmodelDescriptor(descriptor)) || (await postSubmodelDescriptor(descriptor));
+ } catch (error) {
+ console.error('Failed to synchronize Submodel descriptor:', error);
+ }
+
+ if (!descriptorSuccess) {
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 8000,
+ color: 'warning',
+ btnColor: 'buttonText',
+ baseError: 'Submodel saved with synchronization warning.',
+ extendedError: `Failed to synchronize Submodel descriptor for '${submodel.id}'.`,
+ });
+ }
+ }
+
+ function createEndpoints(href: string, type: string): Array {
+ let protocol: string | null = null;
+ try {
+ const url = new URL(href);
+ protocol = url.protocol.replace(/:$/, '');
+ } catch {
+ // If href is not a valid absolute URL, keep protocol null.
+ }
+
+ const protocolInformation = new ProtocolInformation(href, null, protocol);
+ return [new Endpoint(type, protocolInformation)];
+ }
+
async function addSubmodelReferenceToAas(submodel: aasTypes.Submodel): Promise {
if (selectedAAS.value === null) return;
const localAAS = { ...selectedAAS.value };
diff --git a/aas-web-ui/src/composables/Client/AASDiscoveryClient.ts b/aas-web-ui/src/composables/Client/AASDiscoveryClient.ts
index 894813e0e..d933c88f4 100644
--- a/aas-web-ui/src/composables/Client/AASDiscoveryClient.ts
+++ b/aas-web-ui/src/composables/Client/AASDiscoveryClient.ts
@@ -9,7 +9,7 @@ export function useAASDiscoveryClient() {
const infrastructureStore = useInfrastructureStore();
// Composables
- const { getRequest } = useRequestHandling();
+ const { getRequest, postRequest, deleteRequest } = useRequestHandling();
const endpointPath = '/lookup/shells';
@@ -68,6 +68,85 @@ export function useAASDiscoveryClient() {
return failResponse;
}
+ function resolveDiscoveryBaseUrl(endpoint?: string): string {
+ let aasDiscUrl = endpoint ? endpoint.trim() : aasDiscoveryUrl.value.trim();
+ if (aasDiscUrl === '') return '';
+ if (aasDiscUrl.endsWith('/')) aasDiscUrl = stripLastCharacter(aasDiscUrl);
+ if (!aasDiscUrl.endsWith(endpointPath)) aasDiscUrl += endpointPath;
+
+ return aasDiscUrl;
+ }
+
+ function createAssetLinksFromAssetInformation(
+ globalAssetId?: string | null,
+ specificAssetIds?: Array<{ name?: string; value?: string }> | null
+ ): Array<{ name: string; value: string }> {
+ const links: Array<{ name: string; value: string }> = [];
+
+ if (typeof globalAssetId === 'string' && globalAssetId.trim() !== '') {
+ links.push({ name: 'globalAssetId', value: globalAssetId.trim() });
+ }
+
+ if (Array.isArray(specificAssetIds)) {
+ for (const specificAssetId of specificAssetIds) {
+ const name = typeof specificAssetId?.name === 'string' ? specificAssetId.name.trim() : '';
+ const value = typeof specificAssetId?.value === 'string' ? specificAssetId.value.trim() : '';
+ if (name !== '' && value !== '') {
+ links.push({ name, value });
+ }
+ }
+ }
+
+ return links;
+ }
+
+ async function upsertAssetLinksForAas(
+ aasId: string,
+ assetLinks: Array<{ name: string; value: string }>,
+ endpoint?: string
+ ): Promise {
+ const failResponse = false;
+
+ if (!aasId) return failResponse;
+
+ aasId = aasId.trim();
+
+ if (aasId === '' || !Array.isArray(assetLinks) || assetLinks.length === 0) return failResponse;
+
+ const discoveryBaseUrl = resolveDiscoveryBaseUrl(endpoint);
+ if (discoveryBaseUrl === '') return failResponse;
+
+ const path = `${discoveryBaseUrl}/${base64Encode(aasId)}`;
+ const context = 'updating AAS discovery asset links';
+ const disableMessage = false;
+ const headers = new Headers();
+ headers.append('Content-Type', 'application/json');
+ const body = JSON.stringify(assetLinks);
+
+ const response = await postRequest(path, body, headers, context, disableMessage);
+ return response.success;
+ }
+
+ async function deleteAssetLinksForAas(aasId: string, endpoint?: string): Promise {
+ const failResponse = false;
+
+ if (!aasId) return failResponse;
+
+ aasId = aasId.trim();
+
+ if (aasId === '') return failResponse;
+
+ const discoveryBaseUrl = resolveDiscoveryBaseUrl(endpoint);
+ if (discoveryBaseUrl === '') return failResponse;
+
+ const path = `${discoveryBaseUrl}/${base64Encode(aasId)}`;
+ const context = 'deleting AAS discovery asset links';
+ const disableMessage = false;
+ const response = await deleteRequest(path, context, disableMessage);
+
+ return response.success;
+ }
+
/**
* Checks the availability of a global asset by its ID.
*
@@ -98,6 +177,9 @@ export function useAASDiscoveryClient() {
endpointPath,
getAasId,
getAasIds,
+ createAssetLinksFromAssetInformation,
+ upsertAssetLinksForAas,
+ deleteAssetLinksForAas,
isAvailableById,
};
}
diff --git a/aas-web-ui/src/composables/Client/AASRegistryClient.ts b/aas-web-ui/src/composables/Client/AASRegistryClient.ts
index 38af5be62..dabf6c486 100644
--- a/aas-web-ui/src/composables/Client/AASRegistryClient.ts
+++ b/aas-web-ui/src/composables/Client/AASRegistryClient.ts
@@ -13,7 +13,7 @@ export function useAASRegistryClient() {
const infrastructureStore = useInfrastructureStore();
//Composables
- const { getRequest, postRequest, putRequest } = useRequestHandling();
+ const { getRequest, postRequest, putRequest, deleteRequest } = useRequestHandling();
const endpointPath = '/shell-descriptors';
@@ -181,6 +181,28 @@ export function useAASRegistryClient() {
return response.success;
}
+ async function deleteAasDescriptor(aasId: string): Promise {
+ const failResponse = false;
+
+ if (!aasId) return failResponse;
+
+ aasId = aasId.trim();
+
+ if (aasId === '') return failResponse;
+
+ let aasRegUrl = aasRegistryUrl.value.trim();
+ if (aasRegUrl === '') return failResponse;
+ if (aasRegUrl.endsWith('/')) aasRegUrl = stripLastCharacter(aasRegUrl);
+ if (!aasRegUrl.endsWith(endpointPath)) aasRegUrl += endpointPath;
+
+ const context = 'deleting AAS Descriptor';
+ const disableMessage = false;
+ const path = aasRegUrl + '/' + base64Encode(aasId);
+ const response = await deleteRequest(path, context, disableMessage);
+
+ return response.success;
+ }
+
function createDescriptorFromAAS(
aas: jsonization.JsonObject,
endpoints: Array
@@ -212,6 +234,7 @@ export function useAASRegistryClient() {
aasDescriptorIsAvailableById,
putAasDescriptor,
postAasDescriptor,
+ deleteAasDescriptor,
createDescriptorFromAAS,
};
}
diff --git a/aas-web-ui/src/composables/Client/SMRegistryClient.ts b/aas-web-ui/src/composables/Client/SMRegistryClient.ts
index 6574e625c..ae12a6c0f 100644
--- a/aas-web-ui/src/composables/Client/SMRegistryClient.ts
+++ b/aas-web-ui/src/composables/Client/SMRegistryClient.ts
@@ -13,7 +13,7 @@ export function useSMRegistryClient() {
const infrastructureStore = useInfrastructureStore();
// Composables
- const { getRequest, postRequest, putRequest } = useRequestHandling();
+ const { getRequest, postRequest, putRequest, deleteRequest } = useRequestHandling();
const endpointPath = '/submodel-descriptors';
@@ -181,6 +181,28 @@ export function useSMRegistryClient() {
return response.success;
}
+ async function deleteSubmodelDescriptor(smId: string): Promise {
+ const failResponse = false;
+
+ if (!smId) return failResponse;
+
+ smId = smId.trim();
+
+ if (smId === '') return failResponse;
+
+ let smRegistryUrl = submodelRegistryUrl.value.trim();
+ if (smRegistryUrl === '') return failResponse;
+ if (smRegistryUrl.endsWith('/')) smRegistryUrl = stripLastCharacter(smRegistryUrl);
+ if (!smRegistryUrl.endsWith(endpointPath)) smRegistryUrl += endpointPath;
+
+ const context = 'deleting Submodel Descriptor';
+ const disableMessage = false;
+ const path = smRegistryUrl + '/' + base64Encode(smId);
+ const response = await deleteRequest(path, context, disableMessage);
+
+ return response.success;
+ }
+
function createDescriptorFromSubmodel(
submodel: jsonization.JsonObject,
endpoints: Array
@@ -194,7 +216,9 @@ export function useSMRegistryClient() {
parsedSubmodel.description,
parsedSubmodel.displayName,
parsedSubmodel.extensions,
- parsedSubmodel.idShort
+ parsedSubmodel.idShort,
+ parsedSubmodel.semanticId,
+ parsedSubmodel.supplementalSemanticIds
);
descriptor = removeNullValues(descriptor);
return descriptor;
@@ -208,6 +232,7 @@ export function useSMRegistryClient() {
isAvailableById,
postSubmodelDescriptor,
putSubmodelDescriptor,
+ deleteSubmodelDescriptor,
createDescriptorFromSubmodel,
};
}
diff --git a/aas-web-ui/src/composables/Infrastructure/useInfrastructureStorage.ts b/aas-web-ui/src/composables/Infrastructure/useInfrastructureStorage.ts
index dfe97be71..344702265 100644
--- a/aas-web-ui/src/composables/Infrastructure/useInfrastructureStorage.ts
+++ b/aas-web-ui/src/composables/Infrastructure/useInfrastructureStorage.ts
@@ -168,15 +168,18 @@ export function useInfrastructureStorage(): {
},
AASRegistry: {
url: '',
+ hasDiscoveryIntegration: true,
},
SubmodelRegistry: {
url: '',
},
AASRepo: {
url: '',
+ hasRegistryIntegration: true,
},
SubmodelRepo: {
url: '',
+ hasRegistryIntegration: true,
},
ConceptDescriptionRepo: {
url: '',
diff --git a/aas-web-ui/src/composables/Infrastructure/useInfrastructureYamlParser.ts b/aas-web-ui/src/composables/Infrastructure/useInfrastructureYamlParser.ts
index a6911be43..2160d89c5 100644
--- a/aas-web-ui/src/composables/Infrastructure/useInfrastructureYamlParser.ts
+++ b/aas-web-ui/src/composables/Infrastructure/useInfrastructureYamlParser.ts
@@ -155,6 +155,14 @@ export function useInfrastructureYamlParser(): {
const yamlComponent = yamlConfig.components[yamlKey as keyof typeof yamlConfig.components];
if (yamlComponent?.baseUrl) {
infrastructure.components[internalKey].url = yamlComponent.baseUrl;
+ if (yamlComponent.hasRegistryIntegration !== undefined) {
+ infrastructure.components[internalKey].hasRegistryIntegration =
+ yamlComponent.hasRegistryIntegration;
+ }
+ if (yamlComponent.hasDiscoveryIntegration !== undefined) {
+ infrastructure.components[internalKey].hasDiscoveryIntegration =
+ yamlComponent.hasDiscoveryIntegration;
+ }
}
}
diff --git a/aas-web-ui/src/composables/RequestHandling.ts b/aas-web-ui/src/composables/RequestHandling.ts
index f89230eff..668643d81 100644
--- a/aas-web-ui/src/composables/RequestHandling.ts
+++ b/aas-web-ui/src/composables/RequestHandling.ts
@@ -68,6 +68,12 @@ export function useRequestHandling() {
return { success: false };
}
+ async function parseJsonIfPresent(response: Response): Promise {
+ const bodyText = await response.text();
+ if (bodyText.trim() === '') return undefined;
+ return JSON.parse(bodyText);
+ }
+
function getRequest(path: string, context: string, disableMessage: boolean, headers: Headers = new Headers()): any {
if (shouldAddAuthorizationHeader(path)) {
// No Authorization needed for the /description endpoint.
@@ -80,7 +86,7 @@ export function useRequestHandling() {
response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
response.headers.get('Content-Length') !== '0'
) {
- return { response: response, data: await response.json() }; // Return the response as JSON
+ return { response: response, data: await parseJsonIfPresent(response) }; // Return the response as JSON
} else if (
response.headers.get('Content-Type')?.split(';')[0] ===
'application/asset-administration-shell-package+xml' &&
@@ -126,6 +132,9 @@ export function useRequestHandling() {
} else if (data) {
// Successful response from the server
return { success: true, data: data, status: response.status, raw: response };
+ } else if (response.ok && response.status >= 200 && response.status < 300) {
+ // Empty successful response
+ return { success: true, data: {}, status: response.status, raw: response };
} else {
// Unexpected response format
throw new Error('Unexpected response format');
@@ -152,7 +161,7 @@ export function useRequestHandling() {
response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
response.headers.get('Content-Length') !== '0'
) {
- return response.json(); // Return the response as JSON
+ return parseJsonIfPresent(response); // Return the response as JSON
} else if (
response.headers.get('Content-Type')?.split(';')[0] === 'text/csv' &&
response.headers.get('Content-Length') !== '0'
@@ -200,7 +209,7 @@ export function useRequestHandling() {
response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
response.headers.get('Content-Length') !== '0'
) {
- return response.json(); // Return the response as JSON
+ return parseJsonIfPresent(response); // Return the response as JSON
} else if (!response.ok) {
// No content but received an HTTP error status
throw new Error('Error status: ' + response.status);
@@ -237,7 +246,7 @@ export function useRequestHandling() {
response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
response.headers.get('Content-Length') !== '0'
) {
- return response.json(); // Return the response as JSON
+ return parseJsonIfPresent(response); // Return the response as JSON
} else if (!response.ok) {
// No content but received an HTTP error status
throw new Error('Error status: ' + response.status);
@@ -273,7 +282,7 @@ export function useRequestHandling() {
response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
response.headers.get('Content-Length') !== '0'
) {
- return response.json(); // Return the response as JSON
+ return parseJsonIfPresent(response); // Return the response as JSON
} else if (!response.ok) {
// No content but received an HTTP error status
throw new Error('Error status: ' + response.status);
diff --git a/aas-web-ui/src/pages/modules/DPPDemo/index.vue b/aas-web-ui/src/pages/modules/DPPDemo/index.vue
index 96fd9288e..870b28c89 100644
--- a/aas-web-ui/src/pages/modules/DPPDemo/index.vue
+++ b/aas-web-ui/src/pages/modules/DPPDemo/index.vue
@@ -15,7 +15,7 @@
:aspect-ratio="productImageAspectRatio"
class="w-100 mx-auto border rounded-lg"
style="max-width: 300px"
- cover
+ contain
@error="onProductImageError" />
| null;
globalAssetId?: string | null;
idShort?: string | null;
- specificAssetId?: Array | null;
+ specificAssetIds?: Array | null;
submodelDescriptors?: Array | null;
constructor(
@@ -27,7 +27,7 @@ export class AASDescriptor {
extensions?: Array | null,
globalAssetId?: string | null,
idShort?: string | null,
- specificAssetId?: Array | null,
+ specificAssetIds?: Array | null,
submodelDescriptors?: Array | null
) {
this.id = id;
@@ -40,7 +40,7 @@ export class AASDescriptor {
this.extensions = extensions;
this.globalAssetId = globalAssetId;
this.idShort = idShort;
- this.specificAssetId = specificAssetId;
+ this.specificAssetIds = specificAssetIds;
this.submodelDescriptors = submodelDescriptors;
}
}
diff --git a/aas-web-ui/src/types/Infrastructure.ts b/aas-web-ui/src/types/Infrastructure.ts
index 4bb5838f8..486f907d8 100644
--- a/aas-web-ui/src/types/Infrastructure.ts
+++ b/aas-web-ui/src/types/Infrastructure.ts
@@ -58,6 +58,8 @@ export interface TokenData {
*/
export interface ComponentConfig {
url: string;
+ hasRegistryIntegration?: boolean;
+ hasDiscoveryIntegration?: boolean;
}
/**
@@ -152,6 +154,8 @@ export type ComponentTestingLoading = Record;
*/
export interface YamlComponentConfig {
baseUrl: string;
+ hasRegistryIntegration?: boolean;
+ hasDiscoveryIntegration?: boolean;
}
/**