diff --git a/aas-web-ui/src/App.vue b/aas-web-ui/src/App.vue
index 8317a29..bc90050 100644
--- a/aas-web-ui/src/App.vue
+++ b/aas-web-ui/src/App.vue
@@ -13,161 +13,150 @@
-
-
+
diff --git a/aas-web-ui/src/composables/ChartHandling.ts b/aas-web-ui/src/composables/ChartHandling.ts
new file mode 100644
index 0000000..d4377df
--- /dev/null
+++ b/aas-web-ui/src/composables/ChartHandling.ts
@@ -0,0 +1,41 @@
+import { useConceptDescriptionHandling } from '@/composables/ConceptDescriptionHandling';
+
+export function useChartHandling() {
+ const { unitSuffix } = useConceptDescriptionHandling();
+
+ function prepareYValueTooltip(chartData: any, yVariables: any) {
+ return chartData.map((_series: any, index: number) => {
+ // Use optional chaining and nullish coalescing to simplify the retrieval of the unit
+ let unit = '';
+ if (yVariables[index]) {
+ unit = unitSuffix(yVariables[index]);
+ }
+ return {
+ formatter: (value: any) => `${value} ${unit}`,
+ };
+ });
+ }
+
+ function prepareLegend(yVariables: any) {
+ return {
+ formatter: (seriesName: any, opts: any) => {
+ let unit = '';
+ const index = opts.seriesIndex;
+
+ // check if the yVariable exists
+ if (yVariables.length > index) {
+ // check if the yVariable has an unit (embeddedDataSpecification) -> take the first one (TODO: make this more generic in the future)
+ if (yVariables[index]) {
+ unit = '[' + unitSuffix(yVariables[index]) + ']';
+ }
+ }
+ return seriesName + ' ' + unit;
+ },
+ };
+ }
+
+ return {
+ prepareYValueTooltip,
+ prepareLegend,
+ };
+}
diff --git a/aas-web-ui/src/composables/Client/AASRegistryClient.ts b/aas-web-ui/src/composables/Client/AASRegistryClient.ts
new file mode 100644
index 0000000..104bef5
--- /dev/null
+++ b/aas-web-ui/src/composables/Client/AASRegistryClient.ts
@@ -0,0 +1,76 @@
+import { computed } from 'vue';
+import { useRequestHandling } from '@/composables/RequestHandling';
+import { useNavigationStore } from '@/store/NavigationStore';
+import { URLEncode } from '@/utils/EncodeDecodeUtils';
+
+export function useAASRegistryClient() {
+ const { getRequest } = useRequestHandling();
+
+ const navigationStore = useNavigationStore();
+
+ const aasRegistryUrl = computed(() => navigationStore.getAASRegistryURL);
+
+ // Fetch List of all available AAS Descriptors
+ async function fetchAasDescriptorList(): Promise> {
+ const failResponse = [] as Array;
+
+ let aasRegUrl = aasRegistryUrl.value;
+ if (aasRegUrl.trim() === '') return failResponse;
+ if (!aasRegUrl.includes('/shell-descriptors')) {
+ aasRegUrl += '/shell-descriptors';
+ }
+
+ const aasRegistryPath = aasRegUrl;
+ const aasRegistryContext = 'retrieving all AAS Descriptors';
+ const disableMessage = false;
+ try {
+ const aasRegistryResponse = await getRequest(aasRegistryPath, aasRegistryContext, disableMessage);
+ if (
+ aasRegistryResponse.success &&
+ aasRegistryResponse.data.result &&
+ aasRegistryResponse.data.result.length > 0
+ ) {
+ return aasRegistryResponse.data.result;
+ }
+ } catch {
+ // handle error
+ return failResponse;
+ }
+
+ return failResponse;
+ }
+
+ // Fetch AAS Descriptor by AAS ID with AAS Registry
+ async function fetchAasDescriptorById(aasId: string): Promise {
+ const failResponse = {} as any;
+
+ let aasRegUrl = aasRegistryUrl.value;
+ if (aasRegUrl.trim() === '') return failResponse;
+ if (!aasRegUrl.includes('/shell-descriptors')) {
+ aasRegUrl += '/shell-descriptors';
+ }
+
+ const aasRegistryPath = aasRegUrl + '/' + URLEncode(aasId);
+ const aasRegistryContext = 'retrieving AAS Descriptor';
+ const disableMessage = false;
+ try {
+ const aasRegistryResponse = await getRequest(aasRegistryPath, aasRegistryContext, disableMessage);
+ if (
+ aasRegistryResponse?.success &&
+ aasRegistryResponse?.data &&
+ Object.keys(aasRegistryResponse?.data).length > 0
+ ) {
+ return aasRegistryResponse.data;
+ }
+ } catch {
+ // handle error
+ return failResponse;
+ }
+ return failResponse;
+ }
+
+ return {
+ fetchAasDescriptorList,
+ fetchAasDescriptorById,
+ };
+}
diff --git a/aas-web-ui/src/composables/Client/AASRepositoryClient.ts b/aas-web-ui/src/composables/Client/AASRepositoryClient.ts
new file mode 100644
index 0000000..e762f9d
--- /dev/null
+++ b/aas-web-ui/src/composables/Client/AASRepositoryClient.ts
@@ -0,0 +1,102 @@
+import { computed } from 'vue';
+import { useAASRegistryClient } from '@/composables/Client/AASRegistryClient';
+import { useRequestHandling } from '@/composables/RequestHandling';
+import { useAASStore } from '@/store/AASDataStore';
+import { useNavigationStore } from '@/store/NavigationStore';
+import { extractEndpointHref } from '@/utils/DescriptorUtils';
+
+export function useAASRepositoryClient() {
+ const { getRequest } = useRequestHandling();
+ const { fetchAasDescriptorById } = useAASRegistryClient();
+
+ const aasStore = useAASStore();
+ const navigationStore = useNavigationStore();
+
+ const aasRepositoryUrl = computed(() => navigationStore.getAASRepoURL);
+
+ // Fetch List of all available AAS
+ async function fetchAasList(): Promise> {
+ const failResponse = [] as Array;
+
+ let aasRepoUrl = aasRepositoryUrl.value;
+ if (aasRepoUrl.trim() === '') return failResponse;
+ if (!aasRepoUrl.includes('/shells')) {
+ aasRepoUrl += '/shells';
+ }
+
+ const aasRepoPath = aasRepoUrl;
+ const aasRepoContext = 'retrieving all AAS';
+ const disableMessage = false;
+ try {
+ const aasRepoResponse = await getRequest(aasRepoPath, aasRepoContext, disableMessage);
+ if (aasRepoResponse.success && aasRepoResponse.data.result && aasRepoResponse.data.result.length > 0) {
+ return aasRepoResponse.data.result;
+ }
+ } catch {
+ // handle error
+ return failResponse;
+ }
+ return failResponse;
+ }
+
+ // Fetch AAS from AAS Repo (with the help of the AAS Registry)
+ async function fetchAasById(aasId: string): Promise {
+ const failResponse = {} as any;
+
+ if (aasId.trim() === '') return failResponse;
+
+ const aasDescriptor = await fetchAasDescriptorById(aasId);
+
+ if (aasDescriptor && Object.keys(aasDescriptor).length > 0) {
+ const aasEndpoint = extractEndpointHref(aasDescriptor, 'AAS-3.0');
+ return fetchAas(aasEndpoint);
+ }
+
+ return failResponse;
+ }
+
+ // Fetch AAS from (AAS Repo) Endpoint
+ async function fetchAas(aasEndpoint: string): Promise {
+ // console.log('fetchAas()', aasEndpoint);
+ const failResponse = {} as any;
+
+ if (aasEndpoint.trim() === '') return failResponse;
+
+ const aasRepoPath = aasEndpoint;
+ const aasRepoContext = 'retrieving AAS Data';
+ const disableMessage = true;
+ try {
+ const aasRepoResponse = await getRequest(aasRepoPath, aasRepoContext, disableMessage);
+ if (aasRepoResponse?.success && aasRepoResponse?.data && Object.keys(aasRepoResponse?.data).length > 0) {
+ const aas = aasRepoResponse.data;
+ // console.log('fetchAas()', aasEndpoint, 'aas', aas);
+
+ // Add endpoint to AAS
+ aas.endpoints = [{ protocolInformation: { href: aasEndpoint }, interface: 'AAS-3.0' }];
+
+ return aas;
+ }
+ } catch {
+ return failResponse;
+ }
+
+ return failResponse;
+ }
+
+ // Fetch and Dispatch AAS from (AAS Repo) Endpoint
+ async function fetchAndDispatchAas(aasEndpoint: string) {
+ if (aasEndpoint.trim() === '') return;
+
+ const aas = await fetchAas(aasEndpoint);
+ // console.log('fetchAndDispatchAas()', aasEndpoint, 'aas', aas);
+
+ aasStore.dispatchSelectedAAS(aas);
+ }
+
+ return {
+ fetchAasList,
+ fetchAasById,
+ fetchAas,
+ fetchAndDispatchAas,
+ };
+}
diff --git a/aas-web-ui/src/composables/Client/SMRegistryClient.ts b/aas-web-ui/src/composables/Client/SMRegistryClient.ts
new file mode 100644
index 0000000..4d86b04
--- /dev/null
+++ b/aas-web-ui/src/composables/Client/SMRegistryClient.ts
@@ -0,0 +1,75 @@
+import { computed } from 'vue';
+import { useRequestHandling } from '@/composables/RequestHandling';
+import { useNavigationStore } from '@/store/NavigationStore';
+import { URLEncode } from '@/utils/EncodeDecodeUtils';
+
+export function useSMRegistryClient() {
+ const { getRequest } = useRequestHandling();
+
+ const navigationStore = useNavigationStore();
+
+ const submodelRegistryUrl = computed(() => navigationStore.getSubmodelRegistryURL);
+
+ // Fetch List of all available SM Descriptors
+ async function fetchSmDescriptorList(): Promise> {
+ const failResponse = [] as Array;
+
+ let smRegistryUrl = submodelRegistryUrl.value;
+ if (smRegistryUrl.trim() === '') return failResponse;
+ if (!smRegistryUrl.includes('/submodel-descriptors')) {
+ smRegistryUrl += '/submodel-descriptors';
+ }
+
+ const smRegistryPath = smRegistryUrl;
+ const smRegistryContext = 'retrieving all SM Descriptors';
+ const disableMessage = false;
+ try {
+ const smRegistryResponse = await getRequest(smRegistryPath, smRegistryContext, disableMessage);
+ if (
+ smRegistryResponse.success &&
+ smRegistryResponse.data.result &&
+ smRegistryResponse.data.result.length > 0
+ ) {
+ return smRegistryResponse.data.result;
+ }
+ } catch {
+ // handle error
+ return failResponse;
+ }
+ return failResponse;
+ }
+
+ // Fetch SM Descriptor by SM ID with SM Registry
+ async function fetchSmDescriptorById(smId: string): Promise {
+ const failResponse = {} as any;
+
+ let smRegistryUrl = submodelRegistryUrl.value;
+ if (smRegistryUrl.trim() === '') return failResponse;
+ if (!smRegistryUrl.includes('/shell-descriptors')) {
+ smRegistryUrl += '/shell-descriptors';
+ }
+
+ const smRegistryPath = smRegistryUrl + '/' + URLEncode(smId);
+ const smRegistryContext = 'retrieving SM Descriptor';
+ const disableMessage = false;
+ try {
+ const smRegistryResponse = await getRequest(smRegistryPath, smRegistryContext, disableMessage);
+ if (
+ smRegistryResponse?.success &&
+ smRegistryResponse?.data &&
+ Object.keys(smRegistryResponse?.data).length > 0
+ ) {
+ return smRegistryResponse.data;
+ }
+ } catch {
+ // handle error
+ return failResponse;
+ }
+ return failResponse;
+ }
+
+ return {
+ fetchSmDescriptorList,
+ fetchSmDescriptorById,
+ };
+}
diff --git a/aas-web-ui/src/composables/Client/SMRepositoryClient.ts b/aas-web-ui/src/composables/Client/SMRepositoryClient.ts
new file mode 100644
index 0000000..9947031
--- /dev/null
+++ b/aas-web-ui/src/composables/Client/SMRepositoryClient.ts
@@ -0,0 +1,90 @@
+import { computed } from 'vue';
+import { useSMRegistryClient } from '@/composables/Client/SMRegistryClient';
+import { useRequestHandling } from '@/composables/RequestHandling';
+import { useNavigationStore } from '@/store/NavigationStore';
+import { extractEndpointHref } from '@/utils/DescriptorUtils';
+
+export function useSMRepositoryClient() {
+ const { getRequest } = useRequestHandling();
+ const { fetchSmDescriptorById } = useSMRegistryClient();
+
+ const navigationStore = useNavigationStore();
+
+ const submodelRepoUrl = computed(() => navigationStore.getSubmodelRepoURL);
+
+ // Fetch List of all available SM
+ async function fetchSmList(): Promise> {
+ const failResponse = [] as Array;
+
+ let smRepoUrl = submodelRepoUrl.value;
+ if (smRepoUrl.trim() === '') return failResponse;
+ if (!smRepoUrl.includes('/shells')) {
+ smRepoUrl += '/shells';
+ }
+
+ const smRepoPath = smRepoUrl;
+ const smRepoContext = 'retrieving all SMs';
+ const disableMessage = false;
+ try {
+ const smRepoResponse = await getRequest(smRepoPath, smRepoContext, disableMessage);
+ if (smRepoResponse.success && smRepoResponse.data.result && smRepoResponse.data.result.length > 0) {
+ return smRepoResponse.data.result;
+ }
+ } catch {
+ // handle error
+ return failResponse;
+ }
+ return failResponse;
+ }
+
+ // Fetch SM from SM Repo (with the help of the SM Registry)
+ async function fetchSmById(smId: string): Promise {
+ // console.log('fetchAasById()', aasId);
+ const failResponse = {} as any;
+
+ if (smId.trim() === '') return failResponse;
+
+ const smDescriptor = await fetchSmDescriptorById(smId);
+
+ if (smDescriptor && Object.keys(smDescriptor).length > 0) {
+ const smEndpoint = extractEndpointHref(smDescriptor, 'SUBMODEL-3.0');
+ return fetchSm(smEndpoint);
+ }
+
+ return failResponse;
+ }
+
+ // Fetch SM from (SM Repo) Endpoint
+ async function fetchSm(smEndpoint: string): Promise {
+ // console.log('fetchSm()', aasEndpoint);
+ const failResponse = {} as any;
+
+ if (smEndpoint.trim() === '') return failResponse;
+
+ const smRepoPath = smEndpoint;
+ const smRepoContext = 'retrieving SM Data';
+ const disableMessage = true;
+ try {
+ const smRepoResponse = await getRequest(smRepoPath, smRepoContext, disableMessage);
+ if (smRepoResponse?.success && smRepoResponse?.data && Object.keys(smRepoResponse?.data).length > 0) {
+ const sm = smRepoResponse.data;
+ // console.log('fetchSm()', smEndpoint, 'sm', sm);
+
+ // Add endpoint to AAS
+ sm.endpoints = [{ protocolInformation: { href: smEndpoint }, interface: 'SUBMODEL-3.0' }];
+
+ return sm;
+ }
+ } catch {
+ return failResponse;
+ }
+
+ return failResponse;
+ }
+
+ return {
+ fetchSmList,
+ fetchSmById,
+ fetchSm,
+ };
+}
diff --git a/aas-web-ui/src/composables/ConceptDescriptionHandling.ts b/aas-web-ui/src/composables/ConceptDescriptionHandling.ts
new file mode 100644
index 0000000..4b9bba4
--- /dev/null
+++ b/aas-web-ui/src/composables/ConceptDescriptionHandling.ts
@@ -0,0 +1,149 @@
+import { computed } from 'vue';
+import { useRequestHandling } from '@/composables/RequestHandling';
+import { useNavigationStore } from '@/store/NavigationStore';
+import { URLEncode } from '@/utils/EncodeDecodeUtils';
+import { getEquivalentEclassSemanticIds, getEquivalentIriSemanticIds } from '@/utils/SemanticIdUtils';
+
+export function useConceptDescriptionHandling() {
+ const { getRequest } = useRequestHandling();
+
+ const navigationStore = useNavigationStore();
+
+ const CDRepoURL = computed(() => {
+ return navigationStore.getConceptDescriptionRepoURL;
+ });
+
+ // Get the Unit from the EmbeddedDataSpecification of the ConceptDescription of the Property (if available)
+ function unitSuffix(prop: any) {
+ if (!prop.conceptDescriptions) {
+ getConceptDescriptions(prop).then((conceptDescriptions) => {
+ prop.conceptDescriptions = conceptDescriptions;
+ });
+ }
+ if (!prop.conceptDescriptions || prop.conceptDescriptions.length == 0) {
+ return '';
+ }
+ for (const conceptDescription of prop.conceptDescriptions) {
+ if (!conceptDescription.embeddedDataSpecifications) {
+ continue;
+ }
+ for (const embeddedDataSpecification of conceptDescription.embeddedDataSpecifications) {
+ if (
+ embeddedDataSpecification.dataSpecificationContent &&
+ embeddedDataSpecification.dataSpecificationContent.unit
+ ) {
+ return embeddedDataSpecification.dataSpecificationContent.unit;
+ }
+ }
+ }
+ return '';
+ }
+
+ // Get all ConceptDescriptions for the SubmodelElement from the ConceptDescription Repository
+ async function getConceptDescriptions(SelectedNode: any) {
+ let conceptDescriptionRepoURL = '';
+ if (CDRepoURL.value && CDRepoURL.value != '') {
+ conceptDescriptionRepoURL = CDRepoURL.value;
+ } else {
+ return Promise.resolve([]); // Return an empty object wrapped in a resolved promise
+ }
+
+ // return if no SemanticID is available
+ if (!SelectedNode.semanticId || !SelectedNode.semanticId.keys || SelectedNode.semanticId.keys.length == 0) {
+ return Promise.resolve([]);
+ }
+
+ const semanticIdsToFetch = SelectedNode.semanticId.keys.map((key: any) => {
+ return key.value;
+ });
+
+ semanticIdsToFetch.forEach((semanticId: string) => {
+ if (
+ semanticId.startsWith('0173-1#') ||
+ semanticId.startsWith('0173/1///') ||
+ semanticId.startsWith('https://api.eclass-cdp.com/0173-1')
+ ) {
+ semanticIdsToFetch.push(...getEquivalentEclassSemanticIds(semanticId));
+ } else if (semanticId.startsWith('http://') || semanticId.startsWith('https://')) {
+ semanticIdsToFetch.push(...getEquivalentIriSemanticIds(semanticId));
+ }
+ });
+
+ const semanticIdsUniqueToFetch = semanticIdsToFetch.filter(
+ (value: string, index: number, self: string) => self.indexOf(value) === index
+ );
+
+ const cdPromises = semanticIdsUniqueToFetch.map((semanticId: string) => {
+ const path = conceptDescriptionRepoURL + '/' + URLEncode(semanticId);
+ const context = 'retrieving ConceptDescriptions';
+ const disableMessage = true;
+
+ return getRequest(path, context, disableMessage).then((response: any) => {
+ if (response.success) {
+ // console.log('ConceptDescription Data: ', response.data);
+ const conceptDescription = response.data;
+ conceptDescription.path = path;
+ // Check if ConceptDescription has data to be displayed
+ if (
+ (conceptDescription.displayName && conceptDescription.displayName.length > 0) ||
+ (conceptDescription.description && conceptDescription.description.length > 0) ||
+ (conceptDescription.embeddedDataSpecifications &&
+ conceptDescription.embeddedDataSpecifications.length > 0)
+ ) {
+ return conceptDescription;
+ }
+ return {};
+ } else {
+ return {};
+ }
+ });
+ });
+
+ let conceptDescriptions = await Promise.all(cdPromises);
+ conceptDescriptions = conceptDescriptions.filter(
+ (conceptDescription: any) => Object.keys(conceptDescription).length !== 0
+ ); // Filter empty Objects
+ return conceptDescriptions;
+ }
+
+ // Get the Definition from the EmbeddedDataSpecification of the ConceptDescription of the Property (if available)
+ function cdDefinition(prop: any) {
+ if (!prop.conceptDescriptions) {
+ getConceptDescriptions(prop).then((conceptDescriptions) => {
+ prop.conceptDescriptions = conceptDescriptions;
+ });
+ }
+ if (!prop.conceptDescriptions || prop.conceptDescriptions.length == 0) {
+ return '';
+ }
+ for (const conceptDescription of prop.conceptDescriptions) {
+ if (!conceptDescription.embeddedDataSpecifications) {
+ continue;
+ }
+ for (const embeddedDataSpecification of conceptDescription.embeddedDataSpecifications) {
+ if (
+ embeddedDataSpecification.dataSpecificationContent &&
+ embeddedDataSpecification.dataSpecificationContent.definition
+ ) {
+ const definitionEn = embeddedDataSpecification.dataSpecificationContent.definition.find(
+ (definition: any) => {
+ return definition.language === 'en' && definition.text !== '';
+ }
+ );
+ if (definitionEn && definitionEn.text) {
+ return definitionEn.text;
+ }
+ } else {
+ return '';
+ }
+ }
+ }
+ return '';
+ }
+
+ return {
+ unitSuffix,
+ getConceptDescriptions,
+ cdDefinition,
+ };
+}
diff --git a/aas-web-ui/src/composables/DashboardHandling.ts b/aas-web-ui/src/composables/DashboardHandling.ts
new file mode 100644
index 0000000..55f7fdd
--- /dev/null
+++ b/aas-web-ui/src/composables/DashboardHandling.ts
@@ -0,0 +1,160 @@
+import { computed } from 'vue';
+import { useRequestHandling } from '@/composables/RequestHandling';
+import { useAASStore } from '@/store/AASDataStore';
+import { useEnvStore } from '@/store/EnvironmentStore';
+import { extractEndpointHref } from '@/utils/DescriptorUtils';
+import { URLEncode } from '@/utils/EncodeDecodeUtils';
+import { UUID } from '@/utils/IDUtils';
+
+export function useDashboardHandling() {
+ const { getRequest, postRequest, putRequest, deleteRequest } = useRequestHandling();
+
+ const aasStore = useAASStore();
+
+ const SelectedNode = computed(() => {
+ return aasStore.getSelectedNode;
+ });
+
+ const SelectedAAS = computed(() => {
+ return aasStore.getSelectedAAS;
+ });
+
+ const dashboardServicePath = computed(() => {
+ return useEnvStore().getEnvDashboardServicePath;
+ });
+
+ async function dashboardAdd(item: any) {
+ // console.log(item)
+ const group = await getAAS();
+ // console.log(group);
+ const dashboardObj = {
+ title: item.title,
+ endpoint: SelectedNode.value.path,
+ group: {
+ groupName: group.idShort,
+ groupId: group.id,
+ },
+ configObject: {
+ semanticId: SelectedNode.value.semanticId,
+ chartType: item.chartType,
+ chartOptions: item.chartOptions,
+ timeVal: item.timeValue,
+ yvals: item.yValues,
+ segment: item.segment,
+ apiToken: item.apiToken,
+ },
+ visibility: true,
+ };
+ // console.log('Add Element to Dasboard: ', dashboardObj);
+ // construct the request
+ const path = dashboardServicePath.value + '/addElement';
+ const headers: Headers = new Headers();
+ headers.append('Content-Type', 'application/json');
+ headers.append('accept', '*/*');
+ const content = JSON.stringify(dashboardObj);
+ const context = 'adding element to dashboard';
+ const disableMessage = false;
+ // send the request
+ postRequest(path, content, headers, context, disableMessage).then((response: any) => {
+ if (response.success) {
+ // console.log('Successfully added Element to Dashboard: ', response.data);
+ }
+ });
+ }
+
+ async function getGroups() {
+ const path = dashboardServicePath.value + '/groups/summary';
+ const context = 'fetching all groups';
+ const disableMessage = false;
+ const response: any = await getRequest(path, context, disableMessage);
+ if (response.success) {
+ // console.log(response.data)
+ return response.data;
+ }
+ }
+
+ async function getElements(group: any) {
+ const pathGroup = URLEncode(group);
+ const path = dashboardServicePath.value + '/findGroup/' + pathGroup;
+ const context = 'fetching all elements of a group';
+ const disableMessage = false;
+ const response: any = await getRequest(path, context, disableMessage);
+ if (response.success) {
+ // console.log(response);
+ return response.data;
+ }
+ }
+
+ async function deleteGroup(groups: any, groupId: any): Promise {
+ // console.log(groups)
+ const pathGroup = URLEncode(groupId);
+ const path = dashboardServicePath.value + '/deleteGroup/' + pathGroup;
+ const context = 'deleting all elements of a group';
+ const disableMessage = false;
+ const response: any = await deleteRequest(path, context, disableMessage);
+ if (response.success) {
+ // console.log(response);
+ const index = groups.findIndex((element: any) => element.groupId === groupId);
+ // console.log(index)
+ groups = groups.filter((item: any) => item !== groups[index]);
+ // console.log(groups)
+ return groups;
+ }
+ return groups;
+ }
+
+ async function deleteSingle(elementId: any): Promise {
+ const path = dashboardServicePath.value + '/deleteElement/' + elementId;
+ const context = 'deleting one element of a group';
+ const disableMessage = false;
+ const response: any = await deleteRequest(path, context, disableMessage);
+ if (response.success) {
+ return elementId;
+ }
+ return undefined;
+ }
+
+ async function updateElement(element: any): Promise {
+ // console.log(element)
+ const path = dashboardServicePath.value + '/updateElement/' + element.id;
+ const headers: Headers = new Headers();
+ headers.append('Content-Type', 'application/json');
+ headers.append('accept', '*/*');
+ const content = JSON.stringify(element);
+ const context = 'adding element to dashboard';
+ const disableMessage = false;
+ // send the request
+ const response: any = await putRequest(path, content, headers, context, disableMessage);
+ if (response.success) {
+ return response.data;
+ }
+ return undefined;
+ }
+
+ async function getAAS(): Promise {
+ if ((SelectedAAS.value.idShort && SelectedAAS.value.id) != null) {
+ // console.log(this.SelectedAAS)
+ return SelectedAAS.value;
+ } else {
+ const shellHref = extractEndpointHref(SelectedAAS.value, 'AAS-3.0');
+ const path = shellHref;
+ const context = 'getting aas from endpoint';
+ const disableMessage = false;
+ const response = await getRequest(path, context, disableMessage);
+ if (response && response.success) {
+ return response.data;
+ }
+ return { idShort: 'new Group', id: UUID() };
+ }
+ }
+
+ return {
+ dashboardAdd,
+ getGroups,
+ getElements,
+ deleteGroup,
+ deleteSingle,
+ updateElement,
+ getAAS,
+ };
+}
diff --git a/aas-web-ui/src/composables/RequestHandling.ts b/aas-web-ui/src/composables/RequestHandling.ts
new file mode 100644
index 0000000..92e28cf
--- /dev/null
+++ b/aas-web-ui/src/composables/RequestHandling.ts
@@ -0,0 +1,332 @@
+import { useAuthStore } from '@/store/AuthStore';
+import { useNavigationStore } from '@/store/NavigationStore';
+
+export function useRequestHandling() {
+ const authStore = useAuthStore();
+ const navigationStore = useNavigationStore();
+
+ function getRequest(path: string, context: string, disableMessage: boolean, headers: Headers = new Headers()): any {
+ headers = addAuthorizationHeader(headers); // Add the Authorization header
+ return fetch(path, { method: 'GET', headers: headers })
+ .then((response) => {
+ // Check if the Server responded with content
+ if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.json(); // Return the response as JSON
+ } else if (
+ response.headers.get('Content-Type')?.split(';')[0] ===
+ 'application/asset-administration-shell-package+xml' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.blob(); // Return the response as Blob}
+ } else if (
+ response.headers.get('Content-Type')?.split(';')[0].includes('image') &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.blob(); // Return the response as Blob
+ } else if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'text/csv' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.text(); // Return the response as text
+ } else if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'text/plain' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.text(); // Return the response as text
+ } else if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'application/pdf' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.blob(); // Return the response as Blob
+ } else if (!response.ok) {
+ // No content but received an HTTP error status
+ throw new Error('Error status: ' + response.status);
+ } else if (response.ok && response.status >= 200 && response.status < 300) {
+ return response.blob(); // Return the response as Blob
+ } else {
+ // Unexpected HTTP status
+ throw new Error('Unexpected HTTP status: ' + response.status);
+ }
+ })
+ .then((data) => {
+ // Check if the Server responded with an error
+ if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) {
+ // Error response from the server
+ if (!disableMessage) errorHandler(data, context); // Call the error handler
+ return { success: false };
+ } else if (data) {
+ // Successful response from the server
+ return { success: true, data: data };
+ } else {
+ // Unexpected response format
+ throw new Error('Unexpected response format');
+ }
+ })
+ .catch((error) => {
+ // Catch any errors
+ // console.error('Error: ', error); // Log the error
+ if (!disableMessage)
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 60000,
+ color: 'error',
+ btnColor: 'buttonText',
+ text: 'Error! Server responded with: ' + error,
+ });
+ return { success: false };
+ });
+ }
+
+ function postRequest(
+ path: string,
+ body: any,
+ headers: Headers,
+ context: string,
+ disableMessage: boolean,
+ isTSRequest: boolean = false
+ ): any {
+ if (!isTSRequest) {
+ headers = addAuthorizationHeader(headers); // Add the Authorization header
+ }
+ return fetch(path, { method: 'POST', body: body, headers: headers })
+ .then((response) => {
+ // Check if the Server responded with content
+ if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.json(); // Return the response as JSON
+ } else if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'text/csv' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.text(); // Return the response as text
+ } else if (!response.ok) {
+ // No content but received an HTTP error status
+ throw new Error('Error status: ' + response.status);
+ } else {
+ return; // Return without content
+ }
+ })
+ .then((data) => {
+ // Check if the Server responded with an error
+ if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) {
+ // Error response from the server
+ if (!disableMessage) errorHandler(data, context); // Call the error handler
+ return { success: false };
+ } else if (data) {
+ // Successful response from the server
+ return { success: true, data: data };
+ } else if (data === null || data === undefined) {
+ // in this case no content is expected
+ return { success: true };
+ } else {
+ // Unexpected response format
+ throw new Error('Unexpected response format');
+ }
+ })
+ .catch((error) => {
+ // Catch any errors
+ // console.error('Error: ', error); // Log the error
+ if (!disableMessage)
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 60000,
+ color: 'error',
+ btnColor: 'buttonText',
+ text: 'Error! Server responded with: ' + error,
+ });
+ return { success: false };
+ });
+ }
+
+ function putRequest(path: string, body: any, headers: Headers, context: string, disableMessage: boolean): any {
+ headers = addAuthorizationHeader(headers); // Add the Authorization header
+ return fetch(path, { method: 'PUT', body: body, headers: headers })
+ .then((response) => {
+ // Check if the Server responded with content
+ if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.json(); // Return the response as JSON
+ } else if (!response.ok) {
+ // No content but received an HTTP error status
+ throw new Error('Error status: ' + response.status);
+ } else {
+ return; // Return without content
+ }
+ })
+ .then((data) => {
+ // Check if the Server responded with an error
+ if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) {
+ // Error response from the server
+ if (!disableMessage) errorHandler(data, context); // Call the error handler
+ return { success: false };
+ } else if (data) {
+ // Successful response from the server
+ return { success: true, data: data };
+ } else if (data === null || data === undefined) {
+ // in this case no content is expected
+ return { success: true };
+ } else {
+ // Unexpected response format
+ throw new Error('Unexpected response format');
+ }
+ })
+ .catch((error) => {
+ // Catch any errors
+ // console.error('Error: ', error); // Log the error
+ if (!disableMessage)
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 60000,
+ color: 'error',
+ btnColor: 'buttonText',
+ text: 'Error! Server responded with: ' + error,
+ });
+ return { success: false };
+ });
+ }
+
+ function patchRequest(path: string, body: any, headers: Headers, context: string, disableMessage: boolean): any {
+ headers = addAuthorizationHeader(headers); // Add the Authorization header
+ return fetch(path, { method: 'PATCH', body: body, headers: headers })
+ .then((response) => {
+ // Check if the Server responded with content
+ if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.json(); // Return the response as JSON
+ } else if (!response.ok) {
+ // No content but received an HTTP error status
+ throw new Error('Error status: ' + response.status);
+ } else {
+ return; // Return without content
+ }
+ })
+ .then((data) => {
+ // Check if the Server responded with an error
+ if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) {
+ // Error response from the server
+ if (!disableMessage) errorHandler(data, context); // Call the error handler
+ return { success: false };
+ } else if (data) {
+ // Successful response from the server
+ return { success: true, data: data };
+ } else if (data === null || data === undefined) {
+ // in this case no content is expected
+ return { success: true };
+ } else {
+ // Unexpected response format
+ throw new Error('Unexpected response format');
+ }
+ })
+ .catch((error) => {
+ // Catch any errors
+ // console.error('Error: ', error); // Log the error
+ if (!disableMessage)
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 60000,
+ color: 'error',
+ btnColor: 'buttonText',
+ text: 'Error! Server responded with: ' + error,
+ });
+ return { success: false };
+ });
+ }
+
+ function deleteRequest(path: string, context: string, disableMessage: boolean): any {
+ return fetch(path, { method: 'DELETE', headers: addAuthorizationHeader(new Headers()) })
+ .then((response) => {
+ // Check if the Server responded with content
+ if (
+ response.headers.get('Content-Type')?.split(';')[0] === 'application/json' &&
+ response.headers.get('Content-Length') !== '0'
+ ) {
+ return response.json(); // Return the response as JSON
+ } else if (!response.ok) {
+ // No content but received an HTTP error status
+ throw new Error('Error status: ' + response.status);
+ } else {
+ return; // Return without content
+ }
+ })
+ .then((data) => {
+ // Check if the Server responded with an error
+ if (data && Object.prototype.hasOwnProperty.call(data, 'status') && data.status >= 400) {
+ // Error response from the server
+ if (!disableMessage) errorHandler(data, context); // Call the error handler
+ return { success: false };
+ } else if (data) {
+ // Successful response from the server
+ return { success: true, data: data };
+ } else {
+ // in this case no content is expected
+ return { success: true };
+ }
+ })
+ .catch((error) => {
+ // Catch any errors
+ // console.error('Error: ', error); // Log the error
+ if (!disableMessage)
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 60000,
+ color: 'error',
+ btnColor: 'buttonText',
+ text: 'Error! Server responded with: ' + error,
+ });
+ return { success: false };
+ });
+ }
+
+ function addAuthorizationHeader(headers: Headers): Headers {
+ if (!authStore.getAuthStatus) return headers;
+ headers.set('Authorization', 'Bearer ' + authStore.getToken);
+ return headers;
+ }
+
+ function errorHandler(errorData: any, context: string) {
+ // console.log('Error: ', errorData, 'Context: ', context)
+ const initialErrorMessage = 'Error ' + context + '!';
+ let errorMessage = '';
+
+ // Building error message based on the new error response structure
+ if (errorData.status) {
+ errorMessage += '\nStatus: ' + errorData.status;
+ }
+ if (errorData.error) {
+ errorMessage += '\nError: ' + errorData.error;
+ }
+ if (errorData.timestamp) {
+ const errorDate = new Date(errorData.timestamp).toLocaleString();
+ errorMessage += '\nTimestamp: ' + errorDate;
+ }
+ if (errorData.path) {
+ errorMessage += '\nPath: ' + errorData.path;
+ }
+
+ navigationStore.dispatchSnackbar({
+ status: true,
+ timeout: 60000,
+ color: 'error',
+ btnColor: 'buttonText',
+ baseError: initialErrorMessage,
+ extendedError: errorMessage,
+ });
+ }
+
+ return {
+ getRequest,
+ postRequest,
+ putRequest,
+ patchRequest,
+ deleteRequest,
+ };
+}
diff --git a/aas-web-ui/src/utils/ChartUtils.ts b/aas-web-ui/src/utils/ChartUtils.ts
new file mode 100644
index 0000000..768476f
--- /dev/null
+++ b/aas-web-ui/src/utils/ChartUtils.ts
@@ -0,0 +1,55 @@
+export function prepareSeriesValues(chartData: any, yVariables: any) {
+ const newSeries = chartData.map((series: any, index: number) => {
+ const chartValues = series.map((element: any) => ({
+ x: new Date(element.time),
+ y: Number(element.value).toFixed(2),
+ }));
+ let name = 'Value ' + Number(index + 1);
+ // check if the yVariable exists
+ if (yVariables.length > index) {
+ // check if the yVariable has an idShort
+ if (yVariables[index] && yVariables[index].idShort) {
+ name = yVariables[index].idShort;
+ }
+ }
+ return {
+ name: name,
+ data: chartValues,
+ };
+ });
+ return newSeries;
+}
+
+export function prepareHistogramData(chartData: any, numberOfCategories: any) {
+ const bins = Number(numberOfCategories);
+ // Flatten the array and sort values
+ const allValues = chartData.flat().map((item: any) => Number(item.value));
+ allValues.sort((a: any, b: any) => a - b);
+
+ // Determine range and interval
+ const minValue = allValues[0];
+ const maxValue = allValues[allValues.length - 1];
+ const range = maxValue - minValue;
+ const interval = range / bins;
+
+ // Create bins for histogram for each series
+ const histograms = chartData.map((series: any) => {
+ const histogram = new Array(bins).fill(0);
+ series.forEach((item: any) => {
+ const value = Number(item.value);
+ const index = Math.min(Math.floor((value - minValue) / interval), bins - 1);
+ histogram[index]++;
+ });
+ return histogram;
+ });
+
+ // Prepare categories array
+ const categories = new Array(bins);
+ for (let i = 0; i < bins; i++) {
+ const rangeStart = minValue + i * interval;
+ const rangeEnd = rangeStart + interval;
+ categories[i] = `${rangeStart.toFixed(2)} - ${rangeEnd.toFixed(2)}`;
+ }
+
+ return { histograms, categories };
+}
diff --git a/aas-web-ui/src/utils/DateUtils.ts b/aas-web-ui/src/utils/DateUtils.ts
new file mode 100644
index 0000000..e2bb2ac
--- /dev/null
+++ b/aas-web-ui/src/utils/DateUtils.ts
@@ -0,0 +1,13 @@
+// convert js date object to string (format: yyyy-MM-dd HH:mm:ss)
+export function formatDate(date: Date) {
+ return (
+ [date.getFullYear(), padTo2Digits(date.getMonth() + 1), padTo2Digits(date.getDate())].join('-') +
+ ' ' +
+ [padTo2Digits(date.getHours()), padTo2Digits(date.getMinutes()), padTo2Digits(date.getSeconds())].join(':')
+ );
+}
+
+// convert date element to digits
+function padTo2Digits(num: number) {
+ return num.toString().padStart(2, '0');
+}
diff --git a/aas-web-ui/src/utils/DescriptorUtils.ts b/aas-web-ui/src/utils/DescriptorUtils.ts
new file mode 100644
index 0000000..05fc062
--- /dev/null
+++ b/aas-web-ui/src/utils/DescriptorUtils.ts
@@ -0,0 +1,28 @@
+// Extract the right endpoints href from a descriptor
+export function extractEndpointHref(descriptor: any, interfaceShortName: string): string {
+ const interfaceShortNames = [
+ 'AAS',
+ 'SUBMODEL',
+ 'SERIALIZE',
+ 'DESCRIPTION',
+ 'AASX-FILE',
+ 'AAS-REGISTRY',
+ 'SUBMODEL-REGISTRY',
+ 'AAS-REPOSITORY',
+ 'SUBMODEL-REPOSITORY',
+ 'CD-REPOSITORY',
+ 'AAS-DISCOVERY',
+ ];
+ if (!interfaceShortNames.some((iShortName) => interfaceShortName.startsWith(`${iShortName}-`))) {
+ return '';
+ }
+ if (!Array.isArray(descriptor?.endpoints) || descriptor?.endpoints.length === 0 || interfaceShortName === '') {
+ return '';
+ }
+ const endpoints = descriptor.endpoints;
+ // find the right endpoint based on the interfaceShortName (has to match endpoint.interface)
+ const endpoint = endpoints.find((endpoint: any) => {
+ return endpoint?.interface === interfaceShortName;
+ });
+ return endpoint?.protocolInformation?.href ? endpoint.protocolInformation.href : '';
+}
diff --git a/aas-web-ui/src/utils/EncodeDecodeUtils.ts b/aas-web-ui/src/utils/EncodeDecodeUtils.ts
new file mode 100644
index 0000000..efa934c
--- /dev/null
+++ b/aas-web-ui/src/utils/EncodeDecodeUtils.ts
@@ -0,0 +1,5 @@
+export function URLEncode(aasId: string) {
+ const base64Id = btoa(unescape(encodeURIComponent(aasId)));
+ const urlSafeBase64Id = base64Id.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '%3D');
+ return urlSafeBase64Id;
+}
diff --git a/aas-web-ui/src/utils/IDUtils.ts b/aas-web-ui/src/utils/IDUtils.ts
new file mode 100644
index 0000000..ca91a17
--- /dev/null
+++ b/aas-web-ui/src/utils/IDUtils.ts
@@ -0,0 +1,50 @@
+import md5 from 'md5';
+import { v4 as uuidv4 } from 'uuid';
+
+export function UUID(): string {
+ return uuidv4();
+}
+
+export function generateUUIDFromString(str: any): string {
+ // create md5 hash from string
+ const hash = md5(str);
+ // create UUID from hash
+ const guid =
+ hash.substring(0, 8) +
+ '-' +
+ hash.substring(8, 12) +
+ '-' +
+ hash.substring(12, 16) +
+ '-' +
+ hash.substring(16, 20) +
+ '-' +
+ hash.substring(20, 32);
+ return guid;
+}
+
+// Function to check if the idShort of a SubmodelElement matches the given idShort
+export function checkIdShort(
+ referable: any,
+ idShort: string,
+ startsWith: boolean = false,
+ strict: boolean = false
+): boolean {
+ if (idShort.trim() === '') return false;
+
+ if (!referable || !referable.idShort || referable.idShort.length === 0) return false;
+
+ if (startsWith) {
+ // For matching e.g. ProductImage{00} with idShort ProductImage
+ if (strict) {
+ return referable.idShort.startsWith(idShort);
+ } else {
+ return referable.idShort.toLowerCase().startsWith(idShort.toLowerCase());
+ }
+ } else {
+ if (strict) {
+ return referable.idShort === idShort;
+ } else {
+ return referable.idShort.toLowerCase() === idShort.toLowerCase();
+ }
+ }
+}
diff --git a/aas-web-ui/src/utils/SemanticIdUtils.ts b/aas-web-ui/src/utils/SemanticIdUtils.ts
new file mode 100644
index 0000000..be76eb9
--- /dev/null
+++ b/aas-web-ui/src/utils/SemanticIdUtils.ts
@@ -0,0 +1,179 @@
+// Function to check if the SemanticID of a SubmodelElement matches the given SemanticID
+export function checkSemanticId(submodelElement: any, semanticId: string): boolean {
+ // console.log('checkSemanticId', 'submodelElement', submodelElement, 'semanticId', semanticId);
+ if (semanticId.trim() == '') return false;
+
+ if (!Array.isArray(submodelElement?.semanticId?.keys) || submodelElement.semanticId.keys.length == 0) return false;
+
+ for (const key of submodelElement.semanticId.keys) {
+ // console.log('checkSemanticId: ', 'key of submodelElement', key.value, 'semanticId', semanticId);
+ if (key.value.startsWith('0112/')) {
+ return checkSemanticIdIecCdd(key.value, semanticId);
+ } else if (key.value.startsWith('0173-1#') || key.value.startsWith('0173/1///')) {
+ return checkSemanticIdEclassIrdi(key.value, semanticId);
+ } else if (key.value.startsWith('https://api.eclass-cdp.com/0173-1')) {
+ return checkSemanticIdEclassIrdiUrl(key.value, semanticId);
+ } else if (key.value.startsWith('http://') || key.value.startsWith('https://')) {
+ return checkSemanticIdIri(key.value, semanticId);
+ } else {
+ if (key.value === semanticId) return true;
+ }
+ }
+
+ return false;
+}
+
+export function checkSemanticIdEclassIrdi(keyValue: string, semanticId: string): boolean {
+ if (semanticId.trim() == '') return false;
+
+ if (!keyValue.startsWith('0173-1#') && !keyValue.startsWith('0173/1///')) return false;
+
+ if (keyValue.startsWith('0173-1#')) {
+ // Eclass IRDI like 0173-1#01-AHF578#001
+ if (new RegExp(/\*\d{2}$/).test(keyValue)) {
+ keyValue = keyValue.slice(0, -3);
+ semanticId = semanticId.slice(0, -3);
+ }
+ if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId) || new RegExp(/[#-]{1}\d{3}\*\d{1,}$/).test(semanticId)) {
+ return getEquivalentEclassSemanticIds(keyValue).includes(semanticId);
+ }
+
+ // Eclass IRDI without version; like 0173-1#01-AHF578
+ return (
+ getEquivalentEclassSemanticIds(keyValue).findIndex((equivalentSemanticId) => {
+ return equivalentSemanticId.startsWith(semanticId);
+ }, semanticId) != -1
+ );
+ } else if (keyValue.startsWith('0173/1///')) {
+ if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId) || new RegExp(/[#-]{1}\d{3}\*\d{1,}$/).test(semanticId)) {
+ // Eclass IRDI with version; like 0173/1///01#AHF578#001
+ return getEquivalentEclassSemanticIds(keyValue).includes(semanticId);
+ }
+
+ // Eclass IRDI without version; like 0173/1///01#AHF578
+ return (
+ getEquivalentEclassSemanticIds(keyValue).findIndex((equivalentSemanticId) => {
+ return equivalentSemanticId.startsWith(semanticId);
+ }, semanticId) != -1
+ );
+ }
+
+ return false;
+}
+
+export function checkSemanticIdEclassIrdiUrl(keyValue: string, semanticId: string): boolean {
+ if (semanticId.trim() == '') return false;
+
+ if (!keyValue.startsWith('https://api.eclass-cdp.com/0173-1')) return false;
+
+ // Eclass URL like https://api.eclass-cdp.com/0173-1-01-AHF578-001
+ if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId) || new RegExp(/[#-]{1}\d{3}~\d{1,}$/).test(semanticId)) {
+ // Eclass URL with version (like https://api.eclass-cdp.com/0173-1-01-AHF578-001)
+ return getEquivalentEclassSemanticIds(semanticId).includes(keyValue);
+ }
+
+ // Eclass URL without version (like https://api.eclass-cdp.com/0173-1-01-AHF578)
+ return (
+ getEquivalentEclassSemanticIds(keyValue).findIndex((equivalentSemanticId) => {
+ return equivalentSemanticId.startsWith(semanticId);
+ }, semanticId) != -1
+ );
+}
+
+export function checkSemanticIdIecCdd(keyValue: string, semanticId: string): boolean {
+ if (semanticId.trim() == '') return false;
+
+ if (!semanticId.startsWith('0112/')) return false;
+ if (!keyValue.startsWith('0112/')) return false;
+
+ // IEC CDD like 0112/2///61987#ABN590#002
+ if (new RegExp(/[#-]{1}\d{3}$/).test(semanticId)) {
+ // IEC CDD with version; like 0112/2///61987#ABN590#002
+ if (keyValue === semanticId) {
+ return true;
+ }
+ }
+
+ // IEC CDD without version; like 0112/2///61987#ABN590
+ return keyValue.startsWith(semanticId);
+}
+
+export function checkSemanticIdIri(keyValue: string, semanticId: string): boolean {
+ // console.log('checkSemanticIdIri: ', 'keyValue', keyValue, 'semanticId', semanticId);
+ if (semanticId.trim() == '') return false;
+
+ if (!semanticId.startsWith('http://') && !semanticId.startsWith('https://')) return false;
+ if (!keyValue.startsWith('http://') && !keyValue.startsWith('https://')) return false;
+
+ if (keyValue.endsWith('/')) keyValue = keyValue.substring(0, keyValue.length - 1);
+ if (semanticId.endsWith('/')) semanticId = semanticId.substring(0, semanticId.length - 1);
+
+ if (new RegExp(/\/\d{1,}\/\d{1,}$/).test(semanticId)) {
+ // IRI with version like https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprint/0/9/
+ return getEquivalentIriSemanticIds(semanticId).includes(keyValue);
+ }
+
+ // IRI without version like https://admin-shell.io/idta/CarbonFootprint/ProductCarbonFootprint/
+ return (
+ getEquivalentIriSemanticIds(keyValue).findIndex((equivalentSemanticId) => {
+ return equivalentSemanticId.startsWith(semanticId);
+ }, semanticId) != -1
+ );
+}
+
+export function getEquivalentEclassSemanticIds(semanticId: string): any[] {
+ if (
+ semanticId.trim() === '' ||
+ (!semanticId.startsWith('0173-1#') &&
+ !semanticId.startsWith('0173/1///') &&
+ !semanticId.startsWith('https://api.eclass-cdp.com/0173-1'))
+ )
+ return [];
+
+ const semanticIds: any[] = [semanticId];
+
+ if (semanticId.startsWith('0173-1#')) {
+ // e.g. 0173-1#01-AHF578#001
+ semanticIds.push(semanticId.replace(/-1#(\d{2})-/, '/1///$1#')); // 0173-1#01-AHF578#001 --> 0173/1///01#AHF578#001
+ semanticIds.push('https://api.eclass-cdp.com/' + semanticId.replaceAll('#', '-')); // 0173-1#01-AHF578#001 --> https://api.eclass-cdp.com/0173-1-01-AHF578-001
+ } else if (semanticId.startsWith('0173/1///')) {
+ // e.g. 0173/1///01#AHF578#001
+ semanticIds.push(semanticId.replace(/\/1\/\/\/(\d{2})#/, '-1#$1-')); // 0173/1///01#AHF578#001 --> 0173-1#01-AHF578#001
+ semanticIds.push(
+ 'https://api.eclass-cdp.com/' + semanticId.replace(/\/1\/\/\/(\d{2})#/, '-1-$1-').replaceAll('#', '-') // 0173/1///01#AHF578#001 --> https://api.eclass-cdp.com/0173-1-01-AHF578-001
+ );
+ } else if (semanticId.startsWith('https://api.eclass-cdp.com/0173-1')) {
+ // e.g. https://api.eclass-cdp.com/0173-1-01-AHF578-001
+ semanticIds.push(
+ semanticId
+ .replaceAll('https://api.eclass-cdp.com/', '')
+ .replace(/-1-(\d{2})-/, '-1#$1-')
+ .replace(/-(\d{3})$/, '#$1') // https://api.eclass-cdp.com/0173-1-01-AHF578-001 --> 0173-1#01-AHF578#001
+ );
+ semanticIds.push(
+ semanticId
+ .replaceAll('https://api.eclass-cdp.com/', '')
+ .replace(/-1-(\d{2})-/, '/1///$1#')
+ .replace(/-(\d{3})$/, '#$1') // https://api.eclass-cdp.com/0173-1-01-AHF578-001 --> 0173/1///01#AHF578#001
+ );
+ }
+
+ // console.log('getEquivalentEclassSemanticIds', 'semanticId', semanticId, 'semanticIds', semanticIds);
+ return semanticIds;
+}
+
+export function getEquivalentIriSemanticIds(semanticId: string): any[] {
+ if (semanticId.trim() === '' || !(semanticId.startsWith('http://') || semanticId.startsWith('https://'))) return [];
+
+ const semanticIds: any[] = [semanticId];
+
+ // e.g. IRI
+ if (semanticId.endsWith('/')) {
+ semanticIds.push(semanticId.substring(0, semanticId.length - 1));
+ } else {
+ semanticIds.push(semanticId + '/');
+ }
+
+ // console.log('getEquivalentIriSemanticIds', 'semanticId', semanticId, 'semanticIds', semanticIds);
+ return semanticIds;
+}
diff --git a/aas-web-ui/src/utils/generalUtils.ts b/aas-web-ui/src/utils/generalUtils.ts
new file mode 100644
index 0000000..b951a7d
--- /dev/null
+++ b/aas-web-ui/src/utils/generalUtils.ts
@@ -0,0 +1,62 @@
+// Function to capitalize the first letter of a string
+export function capitalizeFirstLetter(string: string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+// Function to check if the valueType is a number
+export function isNumber(valueType: string) {
+ if (!valueType) return false;
+ // List of all number types
+ const numberTypes = [
+ 'double',
+ 'float',
+ 'integer',
+ 'int',
+ 'nonNegativeInteger',
+ 'positiveInteger',
+ 'unsignedLong',
+ 'unsignedInt',
+ 'unsignedShort',
+ 'unsignedByte',
+ 'nonPositiveInteger',
+ 'negativeInteger',
+ 'long',
+ 'short',
+ 'decimal',
+ 'byte',
+ ];
+ // strip xs: from the property if it exists
+ if (valueType.includes('xs:')) {
+ valueType = valueType.replace('xs:', '');
+ }
+ // check if the property is a number
+ if (numberTypes.includes(valueType)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Function to download a JSON File
+export function downloadJson(obj: any, fileName: string) {
+ const jsonStr = JSON.stringify(obj, null, 4);
+ const blob = new Blob([jsonStr], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = fileName;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+}
+
+// Function to download a binary File
+export function downloadFile(filename: string, fileContent: Blob) {
+ const link = document.createElement('a');
+ link.href = window.URL.createObjectURL(fileContent);
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+}