This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new 4bc6188f53 feat(#4041): Show opc ua certificate info in asset view
(#4044)
4bc6188f53 is described below
commit 4bc6188f53678d2860c61712f9e5e7f552905331
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed Dec 10 14:03:41 2025 +0100
feat(#4041): Show opc ua certificate info in asset view (#4044)
---
.../connectors/opcua/adapter/OpcUaAdapter.java | 3 +-
.../opcua/client/OpcUaClientProvider.java | 18 +++-
.../config/MiloOpcUaConfigurationProvider.java | 2 +-
.../opcua/config/OpcUaAdapterConfig.java | 1 +
.../connectors/opcua/config/OpcUaConfig.java | 29 ++++++
.../opcua/config/SpOpcUaConfigExtractor.java | 10 ++
.../security/CompositeCertificateValidator.java | 24 ++++-
.../opcua/config/security/SecurityConfig.java | 10 +-
.../opcua/utils/OpcUaCertificateUtils.java | 101 +++++++++++++++++++++
.../connectors/opcua/utils/OpcUaUtils.java | 54 +----------
.../streampipes/model/opcua/Certificate.java | 27 +++++-
.../model/opcua/CertificateBuilder.java | 10 ++
.../streampipes/model/opcua/CertificateUsage.java | 22 +----
.../streampipes/model/opcua/CertificateUtils.java | 48 ++++++++++
.../management/AdapterResourceManager.java | 19 +++-
.../management/AdapterResourceManagerTest.java | 2 +-
.../rest/impl/admin/CertificateResource.java | 14 +++
.../core/migrations/AvailableMigrations.java | 4 +-
.../ComputeCertificateThumbprintMigration.java | 68 ++++++++++++++
.../src/lib/model/gen/streampipes-model.ts | 25 ++---
ui/src/app/assets/assets.module.ts | 2 +
.../asset-details-labels.component.ts | 4 +-
.../view-assset-basics.component.ts | 8 +-
...asset-link-table-additional-data.component.html | 29 ++++++
.../asset-link-table-additional-data.component.ts | 76 ++++++++++++++++
.../asset-link-table.component.html | 9 +-
.../asset-link-table/asset-link-table.component.ts | 35 ++++++-
.../view-asset-links/view-asset-links.component.ts | 4 +-
ui/src/app/configuration/configuration.module.ts | 4 +-
...xtensions-service-details-dialog.component.html | 8 +-
...xtensions-service-details-dialog.component.scss | 11 +--
.../extensions-service-details-dialog.component.ts | 6 ++
.../certificate-configuration.component.html | 7 +-
.../certificate-configuration.component.scss} | 20 +---
.../certificate-configuration.component.ts | 3 +-
.../certificate-label.component.html | 28 ++++++
.../certificate-label.component.ts} | 24 ++---
.../certificate-details-dialog.component.html | 0
.../certificate-details-dialog.component.ts | 0
ui/src/app/core-ui/core-ui.module.ts | 3 +
ui/src/scss/sp/_spacing.scss | 44 ++++++++-
41 files changed, 650 insertions(+), 166 deletions(-)
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
index c73a8b2926..196392dd77 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
@@ -205,7 +205,8 @@ public class OpcUaAdapter implements StreamPipesAdapter,
IPullAdapter, SupportsR
this.opcUaAdapterConfig =
SpOpcUaConfigExtractor.extractAdapterConfig(
extractor.getStaticPropertyExtractor(),
- adapterRuntimeContext.getStreamPipesClient()
+ adapterRuntimeContext.getStreamPipesClient(),
+ extractor.getAdapterDescription().getElementId()
);
this.collector = collector;
this.prepareAdapter(extractor);
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/OpcUaClientProvider.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/OpcUaClientProvider.java
index b3f907bd4e..e9fe90488f 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/OpcUaClientProvider.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/OpcUaClientProvider.java
@@ -20,13 +20,16 @@ package
org.apache.streampipes.extensions.connectors.opcua.client;
import org.apache.streampipes.commons.exceptions.SpConfigurationException;
import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
+import
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaCertificateUtils;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URISyntaxException;
+import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@@ -36,6 +39,7 @@ public class OpcUaClientProvider {
private final Map<String, ConnectedOpcUaClient> clients = new
ConcurrentHashMap<>();
private final Map<String, Integer> consumers = new ConcurrentHashMap<>();
+ private final Map<String, String> serverThumbprints = new HashMap<>();
public synchronized <T extends OpcUaConfig> ConnectedOpcUaClient getClient(T
config)
throws UaException, SpConfigurationException, URISyntaxException,
ExecutionException, InterruptedException {
@@ -43,12 +47,24 @@ public class OpcUaClientProvider {
if (clients.containsKey(serverId)) {
LOG.debug("Adding new consumer to client {}", serverId);
consumers.put(serverId, consumers.get(config.getUniqueServerId()) + 1);
- return clients.get(serverId);
+ var client = clients.get(serverId);
+ var associatedResourceId = config.getAssociatedResourceId();
+ if (serverThumbprints.containsKey(serverId) &&
Objects.nonNull(associatedResourceId)) {
+ OpcUaCertificateUtils.sendUsageToCore(
+ serverThumbprints.get(serverId),
+ associatedResourceId,
+ config.getStreamPipesClient()
+ );
+ }
+ return client;
} else {
LOG.debug("Creating new client {}", serverId);
var connectedClient = new SpOpcUaClient<>(config).connect();
clients.put(serverId, connectedClient);
consumers.put(serverId, 1);
+ if (config.getCertificateThumbprint() != null) {
+ serverThumbprints.put(serverId, config.getCertificateThumbprint());
+ }
return connectedClient;
}
}
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java
index 8db41a8e11..d1f3564fb9 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java
@@ -42,7 +42,7 @@ public class MiloOpcUaConfigurationProvider {
.setApplicationName(LocalizedText.english("Apache StreamPipes"))
.setApplicationUri(applicationUri);
- spOpcConfig.getSecurityConfig().configureSecurityPolicy(opcServerUrl,
endpoints, builder);
+ spOpcConfig.getSecurityConfig().configureSecurityPolicy(spOpcConfig,
endpoints, builder);
spOpcConfig.getIdentityConfig().configureIdentity(builder);
return builder.build();
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaAdapterConfig.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaAdapterConfig.java
index 83cf333b7d..a0bac48efd 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaAdapterConfig.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaAdapterConfig.java
@@ -53,4 +53,5 @@ public class OpcUaAdapterConfig extends OpcUaConfig {
public void setNamingStrategy(OpcUaNamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
+
}
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java
index 128f1bc1f9..aec8e37342 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java
@@ -18,6 +18,7 @@
package org.apache.streampipes.extensions.connectors.opcua.config;
+import org.apache.streampipes.client.api.IStreamPipesClient;
import
org.apache.streampipes.extensions.connectors.opcua.config.identity.IdentityConfig;
import
org.apache.streampipes.extensions.connectors.opcua.config.security.SecurityConfig;
@@ -29,6 +30,9 @@ public class OpcUaConfig {
private List<String> selectedNodeNames;
private IdentityConfig identityConfig;
private SecurityConfig securityPolicyConfig;
+ private String associatedResourceId;
+ private String certificateThumbprint;
+ private IStreamPipesClient streamPipesClient;
public OpcUaConfig() {
@@ -69,4 +73,29 @@ public class OpcUaConfig {
public String getUniqueServerId() {
return String.format("%s-%s-%s", opcServerURL, securityPolicyConfig,
identityConfig);
}
+
+ public String getAssociatedResourceId() {
+ return associatedResourceId;
+ }
+
+ public void setAssociatedResourceId(String associatedResourceId) {
+ this.associatedResourceId = associatedResourceId;
+ }
+
+ public String getCertificateThumbprint() {
+ return certificateThumbprint;
+ }
+
+ public void setCertificateThumbprint(String certificateThumbprint) {
+ this.certificateThumbprint = certificateThumbprint;
+ }
+
+ public IStreamPipesClient getStreamPipesClient() {
+ return streamPipesClient;
+ }
+
+ public void setStreamPipesClient(IStreamPipesClient streamPipesClient) {
+ this.streamPipesClient = streamPipesClient;
+ }
+
}
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
index e6fea5c2d6..9363339bd8 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
@@ -49,6 +49,15 @@ import static
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabe
public class SpOpcUaConfigExtractor {
+
+ public static OpcUaAdapterConfig
extractAdapterConfig(IStaticPropertyExtractor extractor,
+ IStreamPipesClient
streamPipesClient,
+ String adapterId) {
+ var config = extractAdapterConfig(extractor, streamPipesClient);
+ config.setAssociatedResourceId(adapterId);
+ return config;
+ }
+
/**
* Creates {@link OpcUaAdapterConfig} instance in accordance with the given
* {@link org.apache.streampipes.sdk.extractor.StaticPropertyExtractor}.
@@ -116,6 +125,7 @@ public class SpOpcUaConfigExtractor {
streamPipesClient
)
);
+ config.setStreamPipesClient(streamPipesClient);
boolean useURL = selectedAlternativeConnection.equals(OPC_URL.name());
if (useURL) {
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/CompositeCertificateValidator.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/CompositeCertificateValidator.java
index f5c391ffa7..b228c8be5e 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/CompositeCertificateValidator.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/CompositeCertificateValidator.java
@@ -19,9 +19,11 @@
package org.apache.streampipes.extensions.connectors.opcua.config.security;
import org.apache.streampipes.client.api.IStreamPipesClient;
-import org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaUtils;
+import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
+import
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaCertificateUtils;
import org.apache.streampipes.model.opcua.CertificateBuilder;
import org.apache.streampipes.model.opcua.CertificateState;
+import org.apache.streampipes.model.opcua.CertificateUtils;
import org.eclipse.milo.opcua.stack.client.security.ClientCertificateValidator;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
@@ -32,6 +34,8 @@ import
org.eclipse.milo.opcua.stack.core.util.validation.ValidationCheck;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
@@ -52,15 +56,18 @@ public class CompositeCertificateValidator implements
ClientCertificateValidator
StatusCodes.Bad_SecurityChecksFailed
);
+ private final OpcUaConfig opcUaConfig;
private final TrustListManager trustListManager;
private final List<X509Certificate> trustedCerts;
private final List<ValidationCheck> validationChecks;
private final IStreamPipesClient streamPipesClient;
- public CompositeCertificateValidator(TrustListManager trustListManager,
+ public CompositeCertificateValidator(OpcUaConfig opcUaConfig,
+ TrustListManager trustListManager,
List<X509Certificate> trustedCerts,
List<ValidationCheck> validationChecks,
IStreamPipesClient streamPipesClient) {
+ this.opcUaConfig = opcUaConfig;
this.trustListManager = trustListManager;
this.trustedCerts = trustedCerts;
this.validationChecks = validationChecks;
@@ -96,6 +103,16 @@ public class CompositeCertificateValidator implements
ClientCertificateValidator
ValidationCheck.NO_OPTIONAL_CHECKS,
false
);
+
+ if (opcUaConfig.getAssociatedResourceId() != null) {
+ try {
+ var thumbprint = CertificateUtils.getThumbprint(peer);
+ opcUaConfig.setCertificateThumbprint(thumbprint);
+ OpcUaCertificateUtils.sendUsageToCore(thumbprint,
opcUaConfig.getAssociatedResourceId(), streamPipesClient);
+ } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
+ LOG.warn("Error sending certificate to opcUa", e);
+ }
+ }
}
private X509Certificate getEndEntity(List<X509Certificate> chain) {
@@ -146,7 +163,8 @@ public class CompositeCertificateValidator implements
ClientCertificateValidator
private void sendToCore(X509Certificate cert) {
try {
var certificate = CertificateBuilder.fromX509(cert,
CertificateState.REJECTED);
-
streamPipesClient.customRequest().sendPost(OpcUaUtils.getCoreCertificatePath(),
certificate);
+ opcUaConfig.setCertificateThumbprint(certificate.getThumbprint());
+
streamPipesClient.customRequest().sendPost(OpcUaCertificateUtils.getCoreCertificatePath(),
certificate);
} catch (Exception ex) {
LOG.error("Failed to report rejected certificate to API", ex);
}
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/SecurityConfig.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/SecurityConfig.java
index d4279162c9..87e624e22d 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/SecurityConfig.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/SecurityConfig.java
@@ -21,7 +21,8 @@ package
org.apache.streampipes.extensions.connectors.opcua.config.security;
import org.apache.streampipes.client.api.IStreamPipesClient;
import org.apache.streampipes.commons.environment.Environments;
import org.apache.streampipes.commons.exceptions.SpConfigurationException;
-import org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaUtils;
+import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
+import
org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaCertificateUtils;
import org.apache.streampipes.model.opcua.Certificate;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
@@ -56,11 +57,11 @@ public class SecurityConfig {
this.streamPipesClient = streamPipesClient;
}
- public void configureSecurityPolicy(String opcServerUrl,
+ public void configureSecurityPolicy(OpcUaConfig config,
List<EndpointDescription> endpoints,
OpcUaClientConfigBuilder builder)
throws SpConfigurationException, URISyntaxException {
- String host = opcServerUrl.split("://")[1].split(":")[0];
+ String host = config.getOpcServerURL().split("://")[1].split(":")[0];
EndpointDescription tmpEndpoint = endpoints
.stream()
@@ -87,6 +88,7 @@ public class SecurityConfig {
var loadedCerts = new AtomicReference<>(fetchTrustedCertsFromRest());
var compositeValidator = new CompositeCertificateValidator(
+ config,
trustListManager,
loadedCerts.get(),
List.of(),
@@ -130,7 +132,7 @@ public class SecurityConfig {
private List<X509Certificate> fetchTrustedCertsFromRest() throws
SpConfigurationException {
try {
- var response =
streamPipesClient.customRequest().getList(OpcUaUtils.getCoreTrustedCertificatePath(),
Certificate.class);
+ var response =
streamPipesClient.customRequest().getList(OpcUaCertificateUtils.getCoreTrustedCertificatePath(),
Certificate.class);
return response
.stream()
.map(res -> {
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaCertificateUtils.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaCertificateUtils.java
new file mode 100644
index 0000000000..ef4769bb7b
--- /dev/null
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaCertificateUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.utils;
+
+import org.apache.streampipes.client.api.IStreamPipesClient;
+import
org.apache.streampipes.extensions.connectors.opcua.config.security.CompositeCertificateValidator;
+import org.apache.streampipes.model.opcua.CertificateUsage;
+
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ExecutionException;
+
+public class OpcUaCertificateUtils {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OpcUaCertificateUtils.class);
+
+ public static String getCoreCertificatePath() {
+ return "/api/v2/admin/certificates";
+ }
+
+ public static String getCoreCertificateUsagePath() {
+ return getCoreCertificatePath() + "/usage";
+ }
+
+ public static String getCoreTrustedCertificatePath() {
+ return getCoreCertificatePath() + "/trusted";
+ }
+
+ public static boolean isCertificateException(ExecutionException e) {
+ Throwable cause = e.getCause();
+
+ if (cause instanceof UaException uaException) {
+ return checkAndLogCertificateException(uaException);
+ }
+
+ Throwable nestedCause = cause != null ? cause.getCause() : null;
+ if (nestedCause instanceof UaException uaException) {
+ return checkAndLogCertificateException(uaException);
+ }
+
+ return false;
+ }
+
+ private static boolean checkAndLogCertificateException(UaException e) {
+ var containsRejectedStatusCode =
CompositeCertificateValidator.REJECTED_STATUS_CODES
+ .contains(e.getStatusCode().getValue());
+
+ if (containsRejectedStatusCode) {
+ var statusCode =
CompositeCertificateValidator.REJECTED_STATUS_CODES.stream().filter(code ->
code.equals(e.getStatusCode().getValue())).findFirst();
+ statusCode.ifPresent(sc -> LOG.warn("Status Code: {}", sc));
+ }
+ return containsRejectedStatusCode;
+ }
+
+ public static String makeExceptionMessage(ExecutionException e) {
+ StringBuilder message = new StringBuilder(
+ "The provided certificate could not be trusted. Administrators can
accept this certificate in the settings. "
+ );
+ Throwable cause = e.getCause();
+ if (cause != null) {
+ message.append("Reason: ").append(cause.getMessage());
+ }
+ return message.toString();
+ }
+
+ public static void sendUsageToCore(String thumbprint,
+ String associatedResourceId,
+ IStreamPipesClient streamPipesClient) {
+ try {
+ var usage = new CertificateUsage(
+ associatedResourceId,
+ thumbprint
+ );
+
+ streamPipesClient
+ .customRequest()
+ .sendPost(OpcUaCertificateUtils.getCoreCertificateUsagePath(),
usage);
+
+ } catch (Exception ex) {
+ LOG.error("Failed to report certificate usage to API", ex);
+ }
+ }
+}
diff --git
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtils.java
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtils.java
index 5debd21414..d446168817 100644
---
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtils.java
+++
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtils.java
@@ -26,7 +26,6 @@ import
org.apache.streampipes.extensions.connectors.opcua.client.OpcUaClientProv
import
org.apache.streampipes.extensions.connectors.opcua.config.OpcUaAdapterConfig;
import
org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration;
import
org.apache.streampipes.extensions.connectors.opcua.config.SpOpcUaConfigExtractor;
-import
org.apache.streampipes.extensions.connectors.opcua.config.security.CompositeCertificateValidator;
import
org.apache.streampipes.extensions.management.client.StreamPipesClientResolver;
import
org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty;
@@ -34,8 +33,6 @@ import org.eclipse.milo.opcua.sdk.client.api.UaClient;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.net.URISyntaxException;
import java.util.List;
@@ -47,8 +44,6 @@ import java.util.concurrent.ExecutionException;
*/
public class OpcUaUtils {
- private static final Logger LOG = LoggerFactory.getLogger(OpcUaUtils.class);
-
private static final String OPC_TCP_PREFIX = "opc.tcp://";
/***
@@ -107,9 +102,9 @@ public class OpcUaUtils {
} catch (UaException e) {
throw new
SpConfigurationException(ExceptionMessageExtractor.getDescription(e), e);
} catch (ExecutionException | InterruptedException | URISyntaxException e)
{
- if (e instanceof ExecutionException &&
isCertificateException((ExecutionException) e)) {
+ if (e instanceof ExecutionException &&
OpcUaCertificateUtils.isCertificateException((ExecutionException) e)) {
throw new SpConfigurationException(
- makeExceptionMessage((ExecutionException) e)
+ OpcUaCertificateUtils.makeExceptionMessage((ExecutionException) e)
);
} else {
throw new SpConfigurationException("Could not connect to the OPC UA
server with the provided settings", e);
@@ -132,49 +127,4 @@ public class OpcUaUtils {
}
}).toList();
}
-
- public static String getCoreCertificatePath() {
- return "/api/v2/admin/certificates";
- }
-
- public static String getCoreTrustedCertificatePath() {
- return getCoreCertificatePath() + "/trusted";
- }
-
- private static boolean isCertificateException(ExecutionException e) {
- Throwable cause = e.getCause();
-
- if (cause instanceof UaException uaException) {
- return checkAndLogCertificateException(uaException);
- }
-
- Throwable nestedCause = cause != null ? cause.getCause() : null;
- if (nestedCause instanceof UaException uaException) {
- return checkAndLogCertificateException(uaException);
- }
-
- return false;
- }
-
- private static boolean checkAndLogCertificateException(UaException e) {
- var containsRejectedStatusCode =
CompositeCertificateValidator.REJECTED_STATUS_CODES
- .contains(e.getStatusCode().getValue());
-
- if (containsRejectedStatusCode) {
- var statusCode =
CompositeCertificateValidator.REJECTED_STATUS_CODES.stream().filter(code ->
code.equals(e.getStatusCode().getValue())).findFirst();
- statusCode.ifPresent(sc -> LOG.warn("Status Code: {}", sc));
- }
- return containsRejectedStatusCode;
- }
-
- private static String makeExceptionMessage(ExecutionException e) {
- StringBuilder message = new StringBuilder(
- "The provided certificate could not be trusted. Administrators can
accept this certificate in the settings. "
- );
- Throwable cause = e.getCause();
- if (cause != null) {
- message.append("Reason: ").append(cause.getMessage());
- }
- return message.toString();
- }
}
diff --git
a/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/Certificate.java
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/Certificate.java
index 85ba67e008..93c6ba1b07 100644
---
a/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/Certificate.java
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/Certificate.java
@@ -24,8 +24,10 @@ import org.apache.streampipes.model.shared.api.Storable;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.google.gson.annotations.SerializedName;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
@TsModel
public final class Certificate implements Storable {
@@ -50,11 +52,14 @@ public final class Certificate implements Storable {
private List<String> extendedKeyUsages;
private List<String> subjectAlternativeNames;
private String certificateDerBase64;
+ private String thumbprint;
+ private Set<String> associatedResourceIds;
private CertificateState state;
public Certificate() {
+ this.associatedResourceIds = new HashSet<>();
}
public Certificate(String subjectDn,
@@ -201,13 +206,33 @@ public final class Certificate implements Storable {
this.state = state;
}
+ public String getThumbprint() {
+ return thumbprint;
+ }
+
+ public void setThumbprint(String thumbprint) {
+ this.thumbprint = thumbprint;
+ }
+
+ public Set<String> getAssociatedResourceIds() {
+ return associatedResourceIds;
+ }
+
+ public void setAssociatedResourceIds(Set<String> associatedResourceIds) {
+ this.associatedResourceIds = associatedResourceIds;
+ }
+
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Certificate that = (Certificate) o;
- return Objects.equals(getSubjectDn(), that.getSubjectDn()) &&
Objects.equals(getIssuerDn(), that.getIssuerDn()) &&
Objects.equals(getSerialNumber(), that.getSerialNumber()) &&
Objects.equals(getNotBefore(), that.getNotBefore()) &&
Objects.equals(getNotAfter(), that.getNotAfter()) &&
Objects.equals(getSigAlgName(), that.getSigAlgName()) &&
Objects.equals(getAlgorithm(), that.getAlgorithm()) &&
Objects.equals(getCertificateDerBase64(), that.getCertificateDerBase64()) &&
getState() == t [...]
+ if (getThumbprint() != null && that.getThumbprint() != null) {
+ return getThumbprint().equals(that.getThumbprint());
+ } else {
+ return getCertificateDerBase64().equals(that.getCertificateDerBase64());
+ }
}
@Override
diff --git
a/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateBuilder.java
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateBuilder.java
index 716e264157..191ff612dc 100644
---
a/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateBuilder.java
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateBuilder.java
@@ -18,6 +18,9 @@
package org.apache.streampipes.model.opcua;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import javax.security.auth.x500.X500Principal;
import java.math.BigInteger;
@@ -37,6 +40,8 @@ import java.util.Objects;
public final class CertificateBuilder {
+ private static final Logger LOG =
LoggerFactory.getLogger(CertificateBuilder.class);
+
// fluent setters
public CertificateBuilder subjectDn(String v) {
cert.setSubjectDn(v);
@@ -128,6 +133,11 @@ public final class CertificateBuilder {
.build();
certificate.setState(state);
+ try {
+ certificate.setThumbprint(CertificateUtils.getThumbprint(cert));
+ } catch (Exception e) {
+ LOG.warn("Could not create thumbprint for certificate: {}",
e.getMessage());
+ }
return certificate;
}
diff --git
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateUsage.java
similarity index 70%
copy from
ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
copy to
streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateUsage.java
index 0fed18c7fa..3b43786a58 100644
---
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateUsage.java
@@ -1,4 +1,4 @@
-/*!
+/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -16,22 +16,8 @@
*
*/
-.service-tag {
- border-radius: 5px;
- margin-right: 10px;
- margin-top: 5px;
- margin-bottom: 5px;
- border: 1px solid var(--color-bg-3);
- display: inline-block;
- padding-top: 5px;
- padding-bottom: 5px;
-}
-
-.service-tag-prefix {
- padding: 5px;
-}
+package org.apache.streampipes.model.opcua;
-.service-tag-value {
- padding: 5px;
- font-weight: bold;
+public record CertificateUsage(String associatedResourceId,
+ String thumbprint) {
}
diff --git
a/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateUtils.java
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateUtils.java
new file mode 100644
index 0000000000..2ca092e2b8
--- /dev/null
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/opcua/CertificateUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.model.opcua;
+
+import java.io.ByteArrayInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.HexFormat;
+
+public class CertificateUtils {
+
+ public static String getThumbprint(X509Certificate certificate) throws
NoSuchAlgorithmException, CertificateEncodingException {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ byte[] digest = md.digest(certificate.getEncoded());
+ return HexFormat.of().withUpperCase().formatHex(digest);
+ }
+
+ public static String getThumbprint(String certificateDerBase64) throws
CertificateException, NoSuchAlgorithmException {
+ byte[] derBytes = Base64.getDecoder().decode(certificateDerBase64);
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(derBytes));
+
+ return getThumbprint(cert);
+ }
+}
diff --git
a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java
b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java
index 8e3d4d4a85..f9f99d4a9d 100644
---
a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java
+++
b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java
@@ -19,20 +19,27 @@ package org.apache.streampipes.resource.management;
import org.apache.streampipes.commons.exceptions.connect.AdapterException;
import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.opcua.Certificate;
import org.apache.streampipes.model.util.Cloner;
import org.apache.streampipes.resource.management.secret.SecretProvider;
+import org.apache.streampipes.storage.api.CRUDStorage;
import org.apache.streampipes.storage.api.IAdapterStorage;
import org.apache.streampipes.storage.management.StorageDispatcher;
public class AdapterResourceManager extends
AbstractResourceManager<IAdapterStorage> {
- public AdapterResourceManager(IAdapterStorage adapterStorage) {
+ private final CRUDStorage<Certificate> certificateStorage;
+
+ public AdapterResourceManager(IAdapterStorage adapterStorage,
+ CRUDStorage<Certificate> certificateStorage) {
super(adapterStorage);
+ this.certificateStorage = certificateStorage;
}
public AdapterResourceManager() {
super(StorageDispatcher.INSTANCE.getNoSqlStore()
.getAdapterInstanceStorage());
+ this.certificateStorage =
StorageDispatcher.INSTANCE.getNoSqlStore().getCertificateStorage();
}
/**
@@ -69,6 +76,16 @@ public class AdapterResourceManager extends
AbstractResourceManager<IAdapterStor
}
public void delete(String elementId) {
+ // Remove references from certificates
+ certificateStorage
+ .findAll()
+ .stream().filter(c ->
c.getAssociatedResourceIds().contains(elementId))
+ .forEach(c -> {
+ c.getAssociatedResourceIds().remove(elementId);
+ certificateStorage.updateElement(c);
+ });
+
+ // Then delete the adapter
db.deleteElementById(elementId);
}
diff --git
a/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java
b/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java
index d4616713da..7d9bfcda6a 100644
---
a/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java
+++
b/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java
@@ -43,7 +43,7 @@ public class AdapterResourceManagerTest {
@BeforeEach
void setUp() {
storage = mock(IAdapterStorage.class);
- adapterResourceManager = new AdapterResourceManager(storage);
+ adapterResourceManager = new AdapterResourceManager(storage, null);
}
@Test
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/CertificateResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/CertificateResource.java
index a466b52c20..d39c172afc 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/CertificateResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/CertificateResource.java
@@ -20,6 +20,7 @@ package org.apache.streampipes.rest.impl.admin;
import org.apache.streampipes.model.opcua.Certificate;
import org.apache.streampipes.model.opcua.CertificateState;
+import org.apache.streampipes.model.opcua.CertificateUsage;
import
org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
import org.apache.streampipes.rest.security.AuthConstants;
import org.apache.streampipes.storage.api.CRUDStorage;
@@ -39,6 +40,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
+import java.util.Objects;
@RestController
@PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
@@ -87,7 +89,19 @@ public class CertificateResource extends
AbstractAuthGuardedRestResource {
} else {
LOG.info("Certificate with IssuerDN {} already exists, skipping
creation", certificate.getIssuerDn());
}
+ }
+ @PostMapping(value = "usage", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
+ public void updateUsage(@RequestBody CertificateUsage certificateUsage) {
+ var certificates = certificateStorage.findAll();
+ certificates
+ .stream()
+ .filter(c -> Objects.nonNull(c.getThumbprint()) &&
c.getThumbprint().equals(certificateUsage.thumbprint()))
+ .findFirst()
+ .ifPresent(c -> {
+
c.getAssociatedResourceIds().add(certificateUsage.associatedResourceId());
+ certificateStorage.updateElement(c);
+ });
}
@DeleteMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_VALUE)
diff --git
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
index c1f72c466c..0969bf8f8e 100644
---
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
+++
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/AvailableMigrations.java
@@ -31,6 +31,7 @@ import
org.apache.streampipes.service.core.migrations.v0980.FixImportedPermissio
import
org.apache.streampipes.service.core.migrations.v0980.ModifyAssetLinkTypesMigration;
import
org.apache.streampipes.service.core.migrations.v0980.ModifyAssetLinksMigration;
import
org.apache.streampipes.service.core.migrations.v099.AddAssetManagementViewMigration;
+import
org.apache.streampipes.service.core.migrations.v099.ComputeCertificateThumbprintMigration;
import
org.apache.streampipes.service.core.migrations.v099.CreateAssetPermissionMigration;
import
org.apache.streampipes.service.core.migrations.v099.MoveAssetContentMigration;
import
org.apache.streampipes.service.core.migrations.v099.RemoveObsoletePrivilegesMigration;
@@ -70,7 +71,8 @@ public class AvailableMigrations {
new MoveAssetContentMigration(),
new CreateAssetPermissionMigration(),
new RemoveObsoletePrivilegesMigration(),
- new UniqueDashboardIdMigration()
+ new UniqueDashboardIdMigration(),
+ new ComputeCertificateThumbprintMigration()
);
}
}
diff --git
a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v099/ComputeCertificateThumbprintMigration.java
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v099/ComputeCertificateThumbprintMigration.java
new file mode 100644
index 0000000000..625c843eb7
--- /dev/null
+++
b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/migrations/v099/ComputeCertificateThumbprintMigration.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.service.core.migrations.v099;
+
+import org.apache.streampipes.model.opcua.Certificate;
+import org.apache.streampipes.model.opcua.CertificateUtils;
+import org.apache.streampipes.service.core.migrations.Migration;
+import org.apache.streampipes.storage.api.CRUDStorage;
+import org.apache.streampipes.storage.management.StorageDispatcher;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+public class ComputeCertificateThumbprintMigration implements Migration {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ComputeCertificateThumbprintMigration.class);
+
+ private CRUDStorage<Certificate> certificateStorage =
+ StorageDispatcher.INSTANCE.getNoSqlStore().getCertificateStorage();
+
+ @Override
+ public boolean shouldExecute() {
+ return true;
+ }
+
+ @Override
+ public void executeMigration() throws IOException {
+ this.certificateStorage
+ .findAll()
+ .stream()
+ .filter(certificate -> certificate.getThumbprint() == null)
+ .forEach(certificate -> {
+ try {
+
certificate.setThumbprint(CertificateUtils.getThumbprint(certificate.getCertificateDerBase64()));
+ certificateStorage.updateElement(certificate);
+ } catch (CertificateException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchAlgorithmException e) {
+ LOG.warn("Could not compute thumbprint for existing certificate:
{}", e.getMessage());
+ }
+ });
+ }
+
+ @Override
+ public String getDescription() {
+ return "Adding thumbprint to existing certificates";
+ }
+}
diff --git
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
index edb971862f..8d6dc8bd5e 100644
---
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
+++
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
@@ -20,7 +20,7 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2025-12-04
17:41:18.
+// Generated using typescript-generator version 3.2.1263 on 2025-12-10
08:38:24.
export class NamedStreamPipesEntity implements Storable {
'@class':
@@ -180,23 +180,6 @@ export class AdapterEventPreview {
}
}
-export class AdapterType {
- code: string;
- description: string;
- label: string;
-
- static fromData(data: AdapterType, target?: AdapterType): AdapterType {
- if (!data) {
- return data;
- }
- const instance = target || new AdapterType();
- instance.code = data.code;
- instance.description = data.description;
- instance.label = data.label;
- return instance;
- }
-}
-
export class TransformationRuleDescription {
'@class':
|
'org.apache.streampipes.model.connect.rules.value.ValueTransformationRuleDescription'
@@ -749,6 +732,7 @@ export class CanvasPosition {
export class Certificate implements Storable {
algorithm: string;
+ associatedResourceIds: string[];
basicConstraints: string;
certificateDerBase64: string;
elementId: string;
@@ -763,6 +747,7 @@ export class Certificate implements Storable {
state: CertificateState;
subjectAlternativeNames: string[];
subjectDn: string;
+ thumbprint: string;
static fromData(data: Certificate, target?: Certificate): Certificate {
if (!data) {
@@ -770,6 +755,9 @@ export class Certificate implements Storable {
}
const instance = target || new Certificate();
instance.algorithm = data.algorithm;
+ instance.associatedResourceIds =
__getCopyArrayFn(__identity<string>())(
+ data.associatedResourceIds,
+ );
instance.basicConstraints = data.basicConstraints;
instance.certificateDerBase64 = data.certificateDerBase64;
instance.elementId = data.elementId;
@@ -790,6 +778,7 @@ export class Certificate implements Storable {
__identity<string>(),
)(data.subjectAlternativeNames);
instance.subjectDn = data.subjectDn;
+ instance.thumbprint = data.thumbprint;
return instance;
}
}
diff --git a/ui/src/app/assets/assets.module.ts
b/ui/src/app/assets/assets.module.ts
index d0ac72f1b6..13bd455aa4 100644
--- a/ui/src/app/assets/assets.module.ts
+++ b/ui/src/app/assets/assets.module.ts
@@ -68,6 +68,7 @@ import { SpAssetSelectionMenuComponent } from
'./components/asset-details/edit-a
import { AssetLinkTableComponent } from
'./components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component';
import { AssetLinkTableTypeComponent } from
'./components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-link-type/asset-link-table-type.component';
import { AssetDetailsCustomFieldsComponent } from
'./components/asset-details/edit-asset/asset-details-panel/asset-details-basics/asset-details-custom-fields/asset-details-custom-fields.component';
+import { AssetLinkTableAdditionalDataComponent } from
'./components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-additional-data/asset-link-table-additional-data.component';
@NgModule({
imports: [
@@ -151,6 +152,7 @@ import { AssetDetailsCustomFieldsComponent } from
'./components/asset-details/ed
AssetLinkTableComponent,
AssetLinkTableTypeComponent,
AssetDetailsCustomFieldsComponent,
+ AssetLinkTableAdditionalDataComponent,
],
providers: [],
})
diff --git
a/ui/src/app/assets/components/asset-details/edit-asset/asset-details-panel/asset-details-basics/asset-details-labels/asset-details-labels.component.ts
b/ui/src/app/assets/components/asset-details/edit-asset/asset-details-panel/asset-details-basics/asset-details-labels/asset-details-labels.component.ts
index d803cb774e..2fada3c2f8 100644
---
a/ui/src/app/assets/components/asset-details/edit-asset/asset-details-panel/asset-details-basics/asset-details-labels/asset-details-labels.component.ts
+++
b/ui/src/app/assets/components/asset-details/edit-asset/asset-details-panel/asset-details-basics/asset-details-labels/asset-details-labels.component.ts
@@ -33,9 +33,9 @@ import {
import { MatChipInputEvent } from '@angular/material/chips';
import { FormControl } from '@angular/forms';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
-import { Observable, of } from 'rxjs';
+import { Observable } from 'rxjs';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
-import { filter, map, startWith } from 'rxjs/operators';
+import { map, startWith } from 'rxjs/operators';
import { SpColorizationService } from '@streampipes/shared-ui';
@Component({
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-basics/view-assset-basics.component.ts
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-basics/view-assset-basics.component.ts
index 52e7ccc2d4..8a3ae225fa 100644
---
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-basics/view-assset-basics.component.ts
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-basics/view-assset-basics.component.ts
@@ -16,13 +16,7 @@
*
*/
-import {
- Component,
- Input,
- OnChanges,
- OnInit,
- SimpleChanges,
-} from '@angular/core';
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import {
AssetSiteDesc,
Isa95TypeService,
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-additional-data/asset-link-table-additional-data.component.html
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-additional-data/asset-link-table-additional-data.component.html
new file mode 100644
index 0000000000..4cad5a6116
--- /dev/null
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-additional-data/asset-link-table-additional-data.component.html
@@ -0,0 +1,29 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+@if (showCertificateInfo) {
+ <div>
+ <button
+ mat-icon-button
+ (click)="openCertificateDetailsDialog()"
+ [disabled]="matchingCertificates.length === 0"
+ >
+ <mat-icon>approval</mat-icon>
+ </button>
+ </div>
+}
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-additional-data/asset-link-table-additional-data.component.ts
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-additional-data/asset-link-table-additional-data.component.ts
new file mode 100644
index 0000000000..1a8fb20318
--- /dev/null
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table-additional-data/asset-link-table-additional-data.component.ts
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import {
+ Component,
+ inject,
+ Input,
+ OnChanges,
+ SimpleChanges,
+} from '@angular/core';
+import { AssetLink, Certificate } from '@streampipes/platform-services';
+import { CertificateDetailsDialogComponent } from
'../../../../../../../core-ui/certificate-details/certificate-details-dialog.component';
+import { DialogService, PanelType } from '@streampipes/shared-ui';
+import { TranslateService } from '@ngx-translate/core';
+
+@Component({
+ selector: 'sp-asset-link-table-additional-data',
+ templateUrl: './asset-link-table-additional-data.component.html',
+ standalone: false,
+})
+export class AssetLinkTableAdditionalDataComponent implements OnChanges {
+ @Input()
+ assetLink: AssetLink;
+
+ @Input()
+ certificates: Certificate[] = [];
+
+ @Input()
+ isAdminUser = false;
+
+ showCertificateInfo = false;
+ matchingCertificates: Certificate[] = [];
+
+ private dialogService = inject(DialogService);
+ private translateService = inject(TranslateService);
+
+ ngOnChanges(changes: SimpleChanges) {
+ this.findAssociatedCertificates();
+ }
+
+ findAssociatedCertificates() {
+ this.matchingCertificates = this.certificates.filter(
+ c =>
+ c.associatedResourceIds.find(
+ resourceId => resourceId === this.assetLink.resourceId,
+ ) !== undefined,
+ );
+ this.showCertificateInfo = this.matchingCertificates.length > 0;
+ }
+
+ openCertificateDetailsDialog(): void {
+ this.dialogService.open(CertificateDetailsDialogComponent, {
+ title: this.translateService.instant('Certificate details'),
+ panelType: PanelType.STANDARD_PANEL,
+ width: '60vw',
+ data: {
+ certificate: this.matchingCertificates[0],
+ },
+ });
+ }
+}
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
index b3f47f1197..2b71fa7ce9 100644
---
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
@@ -66,7 +66,14 @@
</ng-container>
<ng-container matColumnDef="additionalInfo">
<th mat-header-cell *matHeaderCellDef>Info</th>
- <td mat-cell *matCellDef="let assetLink"></td>
+ <td mat-cell *matCellDef="let assetLink">
+ <sp-asset-link-table-additional-data
+ [isAdminUser]="isAdminUser"
+ [assetLink]="assetLink"
+ [certificates]="certificates"
+ >
+ </sp-asset-link-table-additional-data>
+ </td>
</ng-container>
<ng-template spTableActions let-element>
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
index 07e455c6f5..5e3c06c98c 100644
---
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
@@ -22,6 +22,7 @@ import {
inject,
Input,
OnChanges,
+ OnDestroy,
OnInit,
SimpleChanges,
ViewChild,
@@ -29,15 +30,24 @@ import {
import {
AssetLink,
AssetLinkType,
+ Certificate,
+ CertificateService,
SpAsset,
SpAssetModel,
} from '@streampipes/platform-services';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { EditAssetLinkDialogComponent } from
'../../../../../dialog/edit-asset-link/edit-asset-link-dialog.component';
-import { DialogService, PanelType } from '@streampipes/shared-ui';
+import {
+ CurrentUserService,
+ DialogService,
+ PanelType,
+} from '@streampipes/shared-ui';
import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router';
+import { AuthService } from '../../../../../../services/auth.service';
+import { UserRole } from '../../../../../../_enums/user-role.enum';
+import { Subscription } from 'rxjs';
@Component({
selector: 'sp-asset-link-table',
@@ -45,7 +55,7 @@ import { Router } from '@angular/router';
standalone: false,
})
export class AssetLinkTableComponent
- implements OnInit, AfterViewInit, OnChanges
+ implements OnInit, AfterViewInit, OnChanges, OnDestroy
{
@Input()
assetModel: SpAssetModel;
@@ -70,12 +80,29 @@ export class AssetLinkTableComponent
];
dataSource: MatTableDataSource<AssetLink> = new MatTableDataSource();
+ isAdminUser = false;
+ certificates: Certificate[] = [];
+
+ user$: Subscription;
private dialogService = inject(DialogService);
private translateService = inject(TranslateService);
private router = inject(Router);
+ private currentUserService = inject(CurrentUserService);
+ private authService = inject(AuthService);
+ private certificateService = inject(CertificateService);
ngOnInit() {
+ this.user$ = this.currentUserService.user$.subscribe(user => {
+ this.isAdminUser = this.authService.hasRole(UserRole.ROLE_ADMIN);
+ if (this.isAdminUser) {
+ this.certificateService
+ .getAllCertificates()
+ .subscribe(certificates => {
+ this.certificates = certificates;
+ });
+ }
+ });
this.dataSource.sortingDataAccessor = (link, column) => {
if (column === 'type') {
return link.linkType;
@@ -141,4 +168,8 @@ export class AssetLinkTableComponent
this.asset.assetLinks = [...this.asset.assetLinks];
this.refreshData();
}
+
+ ngOnDestroy() {
+ this.user$?.unsubscribe();
+ }
}
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/view-asset-links.component.ts
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/view-asset-links.component.ts
index 97a4bf99a0..1cf6d31c1a 100644
---
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/view-asset-links.component.ts
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/view-asset-links.component.ts
@@ -16,7 +16,7 @@
*
*/
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, inject, Input, OnInit } from '@angular/core';
import {
AssetConstants,
AssetLinkType,
@@ -36,7 +36,7 @@ export class ViewAssetLinksComponent implements OnInit {
assetLinkTypes: AssetLinkType[] = [];
- constructor(private genericStorageService: GenericStorageService) {}
+ private genericStorageService = inject(GenericStorageService);
ngOnInit() {
this.genericStorageService
diff --git a/ui/src/app/configuration/configuration.module.ts
b/ui/src/app/configuration/configuration.module.ts
index 71a8f65e77..05f5dd7137 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -98,11 +98,11 @@ import { GenericStorageItemComponent } from
'./export/export-dialog/generic-stor
import { GenericStorageItemsComponent } from
'./export/export-dialog/generic-storage-items/generic-storage-items.component';
import { TranslateModule } from '@ngx-translate/core';
import { CertificateConfigurationComponent } from
'./extensions-service-management/certificate-configuration/certificate-configuration.component';
-import { CertificateDetailsDialogComponent } from
'./dialog/certificate-details/certificate-details-dialog.component';
import { AlternateIdConfigurationComponent } from
'./security-configuration/alternate-id-configuration/alternate-id-configuration.component';
import { UserAcknowledgmentComponent } from
'./general-configuration/user-acknowledgement/user-acknowledgment.component';
import { QuillEditorComponent } from 'ngx-quill';
import { MatStepperModule } from '@angular/material/stepper';
+import { CertificateLabelComponent } from
'./extensions-service-management/certificate-configuration/certificate-label/certificate-label.component';
@NgModule({
imports: [
@@ -254,9 +254,9 @@ import { MatStepperModule } from
'@angular/material/stepper';
PipelineElementInstallationStatusFilter,
PipelineElementTypeFilter,
CertificateConfigurationComponent,
- CertificateDetailsDialogComponent,
AlternateIdConfigurationComponent,
UserAcknowledgmentComponent,
+ CertificateLabelComponent,
],
providers: [
OrderByPipe,
diff --git
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.html
b/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.html
index e079412736..8122b4f195 100644
---
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.html
+++
b/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.html
@@ -39,14 +39,14 @@
<div fxFlex="20">{{ 'Tags' | translate }}</div>
<div fxFlex="80">
<div>
- @for (tag of serviceReg.tags; track tag) {
+ @for (tag of sortedTags; track tag) {
<div class="service-tag text-sm">
<span class="service-tag-prefix">{{
tag.prefix.toLowerCase()
}}</span>
- <span class="service-tag-value">{{
- tag.value
- }}</span>
+ <span class="service-tag-value"
+ > {{ tag.value }}</span
+ >
</div>
}
</div>
diff --git
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
b/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
index 0fed18c7fa..425e13e1b2 100644
---
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
+++
b/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
@@ -17,21 +17,16 @@
*/
.service-tag {
- border-radius: 5px;
+ border-radius: 20px;
margin-right: 10px;
margin-top: 5px;
margin-bottom: 5px;
border: 1px solid var(--color-bg-3);
+ background: var(--color-bg-3);
display: inline-block;
- padding-top: 5px;
- padding-bottom: 5px;
+ padding: 5px 10px;
}
.service-tag-prefix {
- padding: 5px;
-}
-
-.service-tag-value {
- padding: 5px;
font-weight: bold;
}
diff --git
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.ts
b/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.ts
index d225e3cd1f..b450b1124c 100644
---
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.ts
+++
b/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.ts
@@ -37,4 +37,10 @@ export class SpExtensionsServiceDetailsDialogComponent {
close() {
this.dialogRef.close();
}
+
+ get sortedTags() {
+ return [...this.serviceReg.tags].sort((a, b) =>
+ a.prefix.localeCompare(b.prefix),
+ );
+ }
}
diff --git
a/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.html
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.html
index d7bd8c7823..651a1e2d6f 100644
---
a/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.html
+++
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.html
@@ -40,7 +40,10 @@
*matCellDef="let certificate"
data-cy="user-accounts-table-row"
>
- <b>{{ certificate.issuerDn }}</b>
+ <sp-certificate-label
+ style="display: block"
+ [certificate]="certificate"
+ ></sp-certificate-label>
</td>
</ng-container>
<ng-container matColumnDef="expires">
@@ -60,7 +63,7 @@
*matCellDef="let certificate"
data-cy="user-accounts-table-row"
>
- <b>{{ certificate.notAfter }}</b>
+ <span class="text-sm">{{ certificate.notAfter }}</span>
</td>
</ng-container>
<ng-container matColumnDef="actions">
diff --git
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.scss
similarity index 70%
copy from
ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
copy to
ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.scss
index 0fed18c7fa..f2c005a77d 100644
---
a/ui/src/app/configuration/dialog/extensions-service-details/extensions-service-details-dialog.component.scss
+++
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.scss
@@ -16,22 +16,6 @@
*
*/
-.service-tag {
- border-radius: 5px;
- margin-right: 10px;
- margin-top: 5px;
- margin-bottom: 5px;
- border: 1px solid var(--color-bg-3);
- display: inline-block;
- padding-top: 5px;
- padding-bottom: 5px;
-}
-
-.service-tag-prefix {
- padding: 5px;
-}
-
-.service-tag-value {
- padding: 5px;
- font-weight: bold;
+:host {
+ --mat-table-row-item-container-height: 100%;
}
diff --git
a/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.ts
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.ts
index 66187a6a03..5458282869 100644
---
a/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.ts
+++
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-configuration.component.ts
@@ -24,13 +24,14 @@ import {
} from '@streampipes/platform-services';
import { MatTableDataSource } from '@angular/material/table';
import { DialogService, PanelType } from '@streampipes/shared-ui';
-import { CertificateDetailsDialogComponent } from
'../../dialog/certificate-details/certificate-details-dialog.component';
+import { CertificateDetailsDialogComponent } from
'../../../core-ui/certificate-details/certificate-details-dialog.component';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'sp-certificate-configuration',
standalone: false,
templateUrl: './certificate-configuration.component.html',
+ styleUrls: ['./certificate-configuration.component.scss'],
})
export class CertificateConfigurationComponent implements OnInit {
private certificateService = inject(CertificateService);
diff --git
a/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-label/certificate-label.component.html
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-label/certificate-label.component.html
new file mode 100644
index 0000000000..97ffc23465
--- /dev/null
+++
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-label/certificate-label.component.html
@@ -0,0 +1,28 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<div fxLayout="column" class="pt-sm pb-sm">
+ <span class="text-sm">
+ {{ certificate.issuerDn }}
+ </span>
+ <div fxLayout="row wrap" class="mt-sm">
+ @for (dnsName of dnsNames; track $index) {
+ <sp-label size="small" [labelText]="dnsName"></sp-label>
+ }
+ </div>
+</div>
diff --git
a/ui/src/app/configuration/dialog/certificate-details/certificate-details-dialog.component.ts
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-label/certificate-label.component.ts
similarity index 65%
copy from
ui/src/app/configuration/dialog/certificate-details/certificate-details-dialog.component.ts
copy to
ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-label/certificate-label.component.ts
index 16c6b495d6..8e5afae50a 100644
---
a/ui/src/app/configuration/dialog/certificate-details/certificate-details-dialog.component.ts
+++
b/ui/src/app/configuration/extensions-service-management/certificate-configuration/certificate-label/certificate-label.component.ts
@@ -16,28 +16,24 @@
*
*/
-import { Component, inject, Input } from '@angular/core';
-import { DialogRef } from '@streampipes/shared-ui';
+import { Component, Input, OnInit } from '@angular/core';
import { Certificate } from '@streampipes/platform-services';
@Component({
- selector: 'sp-certificate-details-dialog',
- templateUrl: './certificate-details-dialog.component.html',
+ selector: 'sp-certificate-label',
standalone: false,
+ templateUrl: './certificate-label.component.html',
})
-export class CertificateDetailsDialogComponent {
- dialogRef = inject(DialogRef<CertificateDetailsDialogComponent>);
-
+export class CertificateLabelComponent implements OnInit {
@Input()
certificate: Certificate;
- isArray(value: any): boolean {
- return Array.isArray(value);
- }
+ dnsNames: string[] = [];
- close(): void {
- this.dialogRef.close();
+ ngOnInit(): void {
+ this.dnsNames = this.certificate.subjectAlternativeNames.filter(san =>
+ san.startsWith('DNS'),
+ );
+ console.log(this.dnsNames);
}
-
- protected readonly Object = Object;
}
diff --git
a/ui/src/app/configuration/dialog/certificate-details/certificate-details-dialog.component.html
b/ui/src/app/core-ui/certificate-details/certificate-details-dialog.component.html
similarity index 100%
rename from
ui/src/app/configuration/dialog/certificate-details/certificate-details-dialog.component.html
rename to
ui/src/app/core-ui/certificate-details/certificate-details-dialog.component.html
diff --git
a/ui/src/app/configuration/dialog/certificate-details/certificate-details-dialog.component.ts
b/ui/src/app/core-ui/certificate-details/certificate-details-dialog.component.ts
similarity index 100%
rename from
ui/src/app/configuration/dialog/certificate-details/certificate-details-dialog.component.ts
rename to
ui/src/app/core-ui/certificate-details/certificate-details-dialog.component.ts
diff --git a/ui/src/app/core-ui/core-ui.module.ts
b/ui/src/app/core-ui/core-ui.module.ts
index 1bec1335d0..b55ea56a43 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -107,6 +107,7 @@ import { YamlPrettyPrintPipe } from
'./pipes/yaml-pretty-print.pipe';
import { TopicsComponent } from './topics/topics.component';
import { TranslateModule } from '@ngx-translate/core';
import { TextFieldModule } from '@angular/cdk/text-field';
+import { CertificateDetailsDialogComponent } from
'./certificate-details/certificate-details-dialog.component';
@NgModule({
imports: [
@@ -203,6 +204,7 @@ import { TextFieldModule } from '@angular/cdk/text-field';
PipelineOperationStatusComponent,
JsonPrettyPrintPipe,
YamlPrettyPrintPipe,
+ CertificateDetailsDialogComponent,
],
providers: [MatDatepickerModule, DisplayRecommendedPipe],
exports: [
@@ -237,6 +239,7 @@ import { TextFieldModule } from '@angular/cdk/text-field';
SingleMarkerMapComponent,
JsonPrettyPrintPipe,
YamlPrettyPrintPipe,
+ CertificateDetailsDialogComponent,
],
})
export class CoreUiModule {}
diff --git a/ui/src/scss/sp/_spacing.scss b/ui/src/scss/sp/_spacing.scss
index 06eeac4fe9..fa56d6869e 100644
--- a/ui/src/scss/sp/_spacing.scss
+++ b/ui/src/scss/sp/_spacing.scss
@@ -190,4 +190,46 @@
padding: var(--space-2xl);
}
-/* More padding directions available on request */
+.pt-2xs {
+ padding-top: var(--space-2xs);
+}
+.pt-xs {
+ padding-top: var(--space-xs);
+}
+.pt-sm {
+ padding-top: var(--space-sm);
+}
+.pt-md {
+ padding-top: var(--space-md);
+}
+.pt-lg {
+ padding-top: var(--space-lg);
+}
+.pt-xl {
+ padding-top: var(--space-xl);
+}
+.pt-2xl {
+ padding-top: var(--space-2xl);
+}
+
+.pb-2xs {
+ padding-bottom: var(--space-2xs);
+}
+.pb-xs {
+ padding-bottom: var(--space-xs);
+}
+.pb-sm {
+ padding-bottom: var(--space-sm);
+}
+.pb-md {
+ padding-bottom: var(--space-md);
+}
+.pb-lg {
+ padding-bottom: var(--space-lg);
+}
+.pb-xl {
+ padding-bottom: var(--space-xl);
+}
+.pb-2xl {
+ padding-bottom: var(--space-2xl);
+}