Repository: kiali/kiali
Pull request #7854 (2024-10-22 12:07:19)

[Tempo] Add Tempo cache
### Describe the change

Adds a TTL cache for Tempo to minimize the access to the Tempo backend. 

It creates a couple of structures: 
- One that saves the results for Tempo queries
- Other that saves the result for Tempo individual traces

The cache for queries save the results by minutes granularity, trying to increase the cache effectiveness. 

As a TTL cache, every entry has a expiration time that removes the entry once is expired. 

This expiration time is defined globally for all the elements (CacheExpiration) and it is set to 300s by default. It will be checked by a watcher every minute, as the watcher needs to check all the entries so decrease the watcher time, will probably have a higher cost. 

Every time the watcher runs, it will display the hit rate (Since the cache was created) and the cache size. So it might help to adjust the expiration time. 

This cache can be useful, for example, when the user is looking at the traces, and click on the same trace twice (It can be done for comparison). Also, when it clicks on the traffic graph node, in the traces tab, and click back on the same node. Here it can save 10 calls to the backend.

### Steps to test the PR

Run the environment with: 
- `hack/istio/tempo/install-tempo-env.sh -c kubectl`
- Install Kiali
- Tempo cache should be enabled 

### Automation testing
Covered in Tempo tracing CI pipeline

### Issue reference

Ref. #7769



jshaughn: I like that the caching is optional, I can imagine this cache could get large quickly.  I guess the prom caching would be similar.  We don't hold onto that information very long, it's mainly to handle when the same query is executed repeatedly in a small time window.
josunect: > I like that the caching is optional, I can imagine this cache could get large quickly. I guess the prom caching would be similar. We don't hold onto that information very long, it's mainly to handle when the same query is executed repeatedly in a small time window.

Right. So maybe the default value that I've added was very large (600 seconds). Maybe set to 300 seconds or less?
I have added these stats so we can see the size/hit rate:

```
2024-10-22T15:50:49+01:00 INF [Tempo Cache] Cache hit rate: 41.94%
2024-10-22T15:50:49+01:00 INF [Tempo Cache] Cache size: 7611 bytes
```

Probably better to be as a debug info
jshaughn: If I understand the code, it looks like we use the cached information if we re-query during the TTL period.  So the longer the TTL, the longer we go without fetching for new traces.  And then we throw out the cache and reload.  So the pattern would be: fullFetch, staleCachedFetches, fullFetch.  Considering that we have a "sliding window" when talking about the scatterplot graph, I wonder if this is the best approach?  Wouldn't this leave gaps in the sliding window?  If TTL was 5m then would we see a 5m period with no traces?  What happens if the time window is <= 5m, could it go completely empty?

I wonder if, on each query, we should try to re-use applicable cached data, and fetch for new "head" traces?  In other words, treat the cache more like a fifo list, with old entries falling off the tail, and new entries being added to the head, while much of the data could be re-used?  For example, what if we are fetching every minute for 15 minutes of traces?  The first fetch could pull all 15m, then afterwards it would pull only 1m and re-use the prior 14m from the cache.

josunect: > e, it looks like we use the cached information if we re-query dur

- Right, the cached information is used if we re query during the TTL
- Every minute, there is a clean up for the expired records (Not the full cache). There is not a fullFetch, just we save in the cache the queries done.
- It cannot go completely empty because when the data is not in the cache, it is checked in the backend.

The cache is not very smart, because if you look for start 1.12 to 1.18 it saves one result and if you look for 1.14 to 1.20 y will save another record, with the full list of resources. So, you mean by saving all the separated traces? I guess the problem is that we use another query data like the cluster, the min duration, tags, that could make also this list to increase. 
 
This helps on this case: The user checks the traces on a node in the graphs and see the traces, then clicks in another node, then clicks again in that node --> It saves 10 queries to the backend. 
Also if the use clicks in the same trace again in the graph, it saves one query in the backend.


nrfox: How does this compare to the caching support tempo has: https://grafana.com/docs/tempo/latest/operations/caching/? If someone is already using caching through tempo, would they need this?

I don't think there's much we can do in Kiali to be more efficient with how we are querying tempo but maybe users should focus on tuning their tempo instance or adding caching there before we add it to Kiali. We can even provide some guidance for what knobs they can tune similar to the metric thinning documentation we have for prom.
josunect: > How does this compare to the caching support tempo has: https://grafana.com/docs/tempo/latest/operations/caching/? If someone is already using caching through tempo, would they need this?
> 
> I don't think there's much we can do in Kiali to be more efficient with how we are querying tempo but maybe users should focus on tuning their tempo instance or adding caching there before we add it to Kiali. We can even provide some guidance for what knobs they can tune similar to the metric thinning documentation we have for prom.

Right. I think from Kiali, if we just minimize the number of queries that hit Tempo, it could help minimizing the resources used by Tempo - Even when using caching, we would save that request. 
I think this cache could be more helpful in the scenarios where there is no tune in the backend, and the user just thinks that Kiali is not working fine. 
I'm agree with the tune section also.
josunect: > I agree with Nick that we should investigate what the Tempo-specific caching can do, and whether RH Observability already has docs or recommended configuration. But I think some relatively simple caching on our end could also be beneficial, both when the server-side caching isn't enabled, and because we could potentially cut down on data retrievals. The trace cache probably wouldn't add much if the tempo caching was enabled, but it's simple and wouldn't be particularly costly for us to do and it's not going to be a resource-hog. So, I think I like the trace cache. I can see how the same trace might be accessed repeatedly in a short time window. Because this is a low-volume cache, you could probably ignore the timed reaping in favor of just holding some small N of traces.
> 
> My problem is more with the query/app cache. Maybe I don't see the use case, but when do we expect a cache hit for the app cache? As I understand it, we would have to repeat the same query for the same time period. Is that right? I think our use case is typically the same query variables but for a sliding time window. If I sit on the Trace tab and the scatterplot refreshes, we don't get a cache hit, even though we are overlapping `(duration - refreshTime)` results. For example, if I fetch 30m of traces every 10s, only the latest 10s is new, the rest we already had. (Actually, due to sampling rates we may actually see different traces, but we don't actually have to do that, in fact it could be confusing). Reducing those subsequent queries to just the time-period "head" seems like the best opportunity for client-side caching. In the example above, you could maybe have a fifo-style cache that dropped the oldest 10s from the tail and added the newest 10s to the head. If the query tags changed you could drop the whole thing and start over. Again, I don't think you'd necessarily have to reap this cache, you'd have at most one query's worth of data at any time. Thoughts?

So, specially for this use case:

![image](https://github.com/user-attachments/assets/768c878e-cf2d-4238-93bd-63125c635231)

When you click on a node, it usually generates 10 queries in the backend: 

![image](https://github.com/user-attachments/assets/e3b1f4bf-d16c-407d-9e93-482a4416b6be)

It is mainly the same query but it splits the time to get results spread over a period of time, and not just the last X ones. 
This is done here, independent of the client: 
https://github.com/kiali/kiali/blob/master/business/tracing.go#L146

If you click in one then, and you click back on the previous, the 10 queries will be repeated. And, I think that would be the best scenario for this cache...
jshaughn: > If you click in one then, and you click back on the previous, the 10 queries will be repeated. And, I think that would be the best scenario for this cache...

Nothing in tracing, or querying temp makes sense to me.  I would never guess that clicking one node in the graph would result in 10 queries to the back-end. Before we continue talking about caching maybe we need to ensure we can't improve the querying.
josunect: > > If you click in one then, and you click back on the previous, the 10 queries will be repeated. And, I think that would be the best scenario for this cache...
> 
> Nothing in tracing, or querying temp makes sense to me. I would never guess that clicking one node in the graph would result in 10 queries to the back-end. Before we continue talking about caching maybe we need to ensure we can't improve the querying.

If you want to see 100 traces for the last hour, and the search has 1000 traces without the limit, the query will return the last 100 results. If you want to display the last 1 hour graph, you will be probably visualizing 100 traces in the last 10 minutes. 

That's the reason why it makes 10 queries, each one querying for smaller ranges of time for a limit of 10, to get results spread over the last hour. 

That is what happens in the Jaeger UI, for example. Setting a limit of 1500 and requesting the "Last hour" we get traces from 3.30pm to 3.56pm:
![image](https://github.com/user-attachments/assets/f7ed51dc-f2f4-4df0-ac0b-230bdd21a421)

If we change the limit to 100, we get results from 3.55pm to 3.56pm:

![image](https://github.com/user-attachments/assets/26b9d8c6-f766-4b4d-892e-019b18548661)

We would optimize the number of queries doing just 1 request. But do we want this? This way, we will request the last 1 hour and we will probably see just the last 5 minutes.

This is the current behavior in Kiali:

![image](https://github.com/user-attachments/assets/2e9fa021-e003-4a26-8ff8-43d3025b8761)

Without this code, requesting 1 hour, returns the last 2 minutes:
![image](https://github.com/user-attachments/assets/24cb0bae-14dd-4d72-aa98-61b837091d45)

jshaughn: > If you want to see 100 traces for the last hour, and the search has 1000 traces without the limit, the query will return the last 100 results. If you want to display the last 1 hour graph, you will be probably visualizing 100 traces in the last 10 minutes.

Wow, OK.  This is something I never knew, and it's super important.  I had always assumed that the returned traces would be distributed randomly over the time period.  And now after reading a bit more on the Grafana site, I think it's not even guaranteed which results you will get back. In fact it seems you may even [get the oldest traces](https://community.grafana.com/t/tempo-in-grafana-search-limited-to-n-rows-returns-first-n-rows-rather-than-last-n-rows/113617)!

> We would optimize the number of queries doing just 1 request. But do we want this? 

This all makes me wonder whether we should pivot... We are trying to force an API to do something it's not designed to do, at the expense of poor performance. So, I'd say yes, I propose we STOP performing multiple queries to try and fill out a time period.  I think a single query is sufficient and we can let users shrink the time period and/or increase the limit.  I think we could probably add some "info" icons in applicable places, briefly explaining this.  I don't think Kiali needs to be the end-all for tracing, we do a good job of correlation with metrics/logs/graph.  We'd still work well with short time periods, and I think OK with larger time periods.  And we can always link to the full tracing UI, when configured.

We may need to figure out how to set a limit in all of the places we offer traces. The graph side panel, as well as the log and metric tab's `spans` option don't have this.

To limit our exposure we may even want to introduce a new config option for tracing, something like,`max_duration`. If the user's time period is < max, use the time period, otherwise the max. It would protect against large time period queries that could be slow, even with a low limit I think Tempo will still look at the whole time period.

Note that in the link above, a later comment mentions that Tempo *may* look into an option to retrieve newest spans, as opposed to any (or typically older) traces.


josunect: > > If you want to see 100 traces for the last hour, and the search has 1000 traces without the limit, the query will return the last 100 results. If you want to display the last 1 hour graph, you will be probably visualizing 100 traces in the last 10 minutes.
> 
> Wow, OK. This is something I never knew, and it's super important. I had always assumed that the returned traces would be distributed randomly over the time period. And now after reading a bit more on the Grafana site, I think it's not even guaranteed which results you will get back. In fact it seems you may even [get the oldest traces](https://community.grafana.com/t/tempo-in-grafana-search-limited-to-n-rows-returns-first-n-rows-rather-than-last-n-rows/113617)!
> 
> > We would optimize the number of queries doing just 1 request. But do we want this?
> 
> This all makes me wonder whether we should pivot... We are trying to force an API to do something it's not designed to do, at the expense of poor performance. So, I'd say yes, I propose we STOP performing multiple queries to try and fill out a time period. I think a single query is sufficient and we can let users shrink the time period and/or increase the limit. I think we could probably add some "info" icons in applicable places, briefly explaining this. I don't think Kiali needs to be the end-all for tracing, we do a good job of correlation with metrics/logs/graph. We'd still work well with short time periods, and I think OK with larger time periods. And we can always link to the full tracing UI, when configured.
> 
> We may need to figure out how to set a limit in all of the places we offer traces. The graph side panel, as well as the log and metric tab's `spans` option don't have this.
> 
> To limit our exposure we may even want to introduce a new config option for tracing, something like,`max_duration`. If the user's time period is < max, use the time period, otherwise the max. It would protect against large time period queries that could be slow, even with a low limit I think Tempo will still look at the whole time period.
> 
> Note that in the link above, a later comment mentions that Tempo _may_ look into an option to retrieve newest spans, as opposed to any (or typically older) traces.

Could we still leave the current behavior as an option (Spread the results over the period of time)? This was like this for a long time, and I don't think no one complained about the performance. 

Also, we still want the cache? If we don't perform many search calls probably the query cache is not needed, but I think we can save some calls to the backend with the traceID cache. 

A limit is always specified. In the graph is 15, in the traces tab by default is 100.

Right, for what I've read the results returned in Tempo are not deterministic (https://github.com/grafana/tempo/issues/2659#issuecomment-1638794014), when I've tried, it looks to be returning the latest ones (Maybe because they are more accessible, not sure)
jshaughn: > Could we still leave the current behavior as an option (Spread the results over the period of time), even if this is not default? This was like this for a long time, and I don't think no one complained about the performance. IMO, it improves the user experience providing results over the specified period of time.

My feeling is that we should not keep this behavior as an option. To me it increases risk of a performance issue and still doesn't really guarantee behavior that is easy to understand.  When I see those "stovepipe" scatter-plot charts, I really don't understand what I'm looking at.  I think this reduces risk, simplifies both the code and UX, and reduces load on the Tempo back-end.

I don't really have a problem with users seeing a 2m period of traces, even when the current time period may be 1h.  We offer two ways to solve this problem: change the limit and/or change the time period.   I do think we should have a maximum limit, to protect against fetching too many spans. If the max limit doesn't solve the problem, then the user should change the time period.  **Remember**, we do support custom time ranges!  And so if a user really wants to see older traces, they can set the time period to be shorter and in the past.

There are some things we can do to help users understand what they are looking at. Some ideas:
* We can add more info icons.
* We can suggest increasing Limit when retrieved traces cover only a small portion of the current time period.
* We can enhance the "Spans" checkbox on Logs and Metrics pages to show a Limit dropdown when enabled.
* And as always, we can launch the native UI if they configure the external_url.

This is my opinion, but this is a team effort and if you feel strongly, let's open this up to a wider audience for a discussion and decision.

> Also, we still want the cache? If we don't perform many search calls probably the query cache is not needed, but I think we can save some calls to the backend with the traceID cache.

After we arrive on an approach we can revisit what sort of caching, if any, will benefit the users and/or reduce load on the Tempo back-end.  I'm not opposed to client-side caching, even if it is in addition to server-side tuning.

> A limit is always specified. In the graph is 15, in the traces tab by default is 100.

Definitely we always want a default limit. As I said above, I think we also want a max limit, and I think we should make it user-selectable.
josunect: > > Could we still leave the current behavior as an option (Spread the results over the period of time), even if this is not default? This was like this for a long time, and I don't think no one complained about the performance. IMO, it improves the user experience providing results over the specified period of time.
> 
> My feeling is that we should not keep this behavior as an option. To me it increases risk of a performance issue and still doesn't really guarantee behavior that is easy to understand. When I see those "stovepipe" scatter-plot charts, I really don't understand what I'm looking at. I think this reduces risk, simplifies both the code and UX, and reduces load on the Tempo back-end.
> 
> I don't really have a problem with users seeing a 2m period of traces, even when the current time period may be 1h. We offer two ways to solve this problem: change the limit and/or change the time period. I do think we should have a maximum limit, to protect against fetching too many spans. If the max limit doesn't solve the problem, then the user should change the time period. **Remember**, we do support custom time ranges! And so if a user really wants to see older traces, they can set the time period to be shorter and in the past.
> 
> There are some things we can do to help users understand what they are looking at. Some ideas:
> 
>     * We can add more info icons.
> 
>     * We can suggest increasing Limit when retrieved traces cover only a small portion of the current time period.
> 
>     * We can enhance the "Spans" checkbox on Logs and Metrics pages to show a Limit dropdown when enabled.
> 
>     * And as always, we can launch the native UI if they configure the external_url.
> 
> 
> This is my opinion, but this is a team effort and if you feel strongly, let's open this up to a wider audience for a discussion and decision.
> 
> > Also, we still want the cache? If we don't perform many search calls probably the query cache is not needed, but I think we can save some calls to the backend with the traceID cache.
> 
> After we arrive on an approach we can revisit what sort of caching, if any, will benefit the users and/or reduce load on the Tempo back-end. I'm not opposed to client-side caching, even if it is in addition to server-side tuning.
> 
> > A limit is always specified. In the graph is 15, in the traces tab by default is 100.
> 
> Definitely we always want a default limit. As I said above, I think we also want a max limit, and I think we should make it user-selectable.

@jshaughn I've created this related PR for this change. 
Should we close this PR or revisit after [the linked](https://github.com/kiali/kiali/pull/7891) PR? 
jshaughn: > Should we close this PR or revisit after https://github.com/kiali/kiali/pull/7891 PR?

Let's leave this open and revisit the need for caching when we are done with the other changes.
jshaughn: > Although I have some doubts if the span data might be added progressively, we might lost some spans?

Hmm, that's interesting.  I suppose it's possible that we could cache a trace and then not pick up spans added after that initial fetch. I guess if you wanted to add a TTL you still could, but instead of having a separate reaper, you could just clear dead entries when checking the cache.

Patch:
diff --git a/Makefile b/Makefile
index 7076a7e40..a65e51e33 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ TARGET_ARCHS ?= amd64 arm64 s390x ppc64le
 
 # Identifies the current build.
 # These will be embedded in the app and displayed when it starts.
-VERSION ?= v2.2.0-SNAPSHOT
+VERSION ?= v2.3.0-SNAPSHOT
 COMMIT_HASH ?= $(shell git rev-parse HEAD)
 
 # The path where the UI project has been git cloned. The UI should
diff --git a/business/checkers/common/multi_match_selector_checker.go b/business/checkers/common/multi_match_selector_checker.go
index e8ce9c9de..59429ee3a 100644
--- a/business/checkers/common/multi_match_selector_checker.go
+++ b/business/checkers/common/multi_match_selector_checker.go
@@ -232,8 +232,7 @@ func (m GenericMultiMatchChecker) multiMatchSubjects() ReferenceMap {
 				if !selector.Matches(labels.Set(w.Labels)) {
 					continue
 				}
-				// TODO for now a hacky way, for a workload type we only have a Kind, will be checked on frontend side
-				workloadKey := models.BuildKey(schema.GroupVersionKind{Group: "", Version: "", Kind: w.Type}, w.Name, wls.Namespace, m.Cluster)
+				workloadKey := models.BuildKey(w.WorkloadGVK, w.Name, wls.Namespace, m.Cluster)
 				workloadSubjects.Add(workloadKey, subjectKey)
 			}
 		}
diff --git a/business/istio_config.go b/business/istio_config.go
index ae426b3f1..af8dae77d 100644
--- a/business/istio_config.go
+++ b/business/istio_config.go
@@ -482,116 +482,116 @@ func (in *IstioConfigService) GetIstioConfigDetails(ctx context.Context, cluster
 
 	getOpts := meta_v1.GetOptions{}
 
-	switch objectGVK.String() {
-	case kubernetes.DestinationRules.String():
+	switch objectGVK {
+	case kubernetes.DestinationRules:
 		istioConfigDetail.DestinationRule, err = in.userClients[cluster].Istio().NetworkingV1().DestinationRules(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.DestinationRule.Kind = kubernetes.DestinationRules.Kind
 			istioConfigDetail.DestinationRule.APIVersion = kubernetes.DestinationRules.GroupVersion().String()
 		}
-	case kubernetes.EnvoyFilters.String():
+	case kubernetes.EnvoyFilters:
 		istioConfigDetail.EnvoyFilter, err = in.userClients[cluster].Istio().NetworkingV1alpha3().EnvoyFilters(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.EnvoyFilter.Kind = kubernetes.EnvoyFilters.Kind
 			istioConfigDetail.EnvoyFilter.APIVersion = kubernetes.EnvoyFilters.GroupVersion().String()
 		}
-	case kubernetes.Gateways.String():
+	case kubernetes.Gateways:
 		istioConfigDetail.Gateway, err = in.userClients[cluster].Istio().NetworkingV1().Gateways(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.Gateway.Kind = kubernetes.Gateways.Kind
 			istioConfigDetail.Gateway.APIVersion = kubernetes.Gateways.GroupVersion().String()
 		}
-	case kubernetes.K8sGateways.String():
+	case kubernetes.K8sGateways:
 		istioConfigDetail.K8sGateway, err = in.userClients[cluster].GatewayAPI().GatewayV1().Gateways(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.K8sGateway.Kind = kubernetes.K8sGateways.Kind
 			istioConfigDetail.K8sGateway.APIVersion = kubernetes.K8sGateways.GroupVersion().String()
 		}
-	case kubernetes.K8sGRPCRoutes.String():
+	case kubernetes.K8sGRPCRoutes:
 		istioConfigDetail.K8sGRPCRoute, err = in.userClients[cluster].GatewayAPI().GatewayV1().GRPCRoutes(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.K8sGRPCRoute.Kind = kubernetes.K8sGRPCRoutes.Kind
 			istioConfigDetail.K8sGRPCRoute.APIVersion = kubernetes.K8sGRPCRoutes.GroupVersion().String()
 		}
-	case kubernetes.K8sHTTPRoutes.String():
+	case kubernetes.K8sHTTPRoutes:
 		istioConfigDetail.K8sHTTPRoute, err = in.userClients[cluster].GatewayAPI().GatewayV1().HTTPRoutes(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.K8sHTTPRoute.Kind = kubernetes.K8sHTTPRoutes.Kind
 			istioConfigDetail.K8sHTTPRoute.APIVersion = kubernetes.K8sHTTPRoutes.GroupVersion().String()
 		}
-	case kubernetes.K8sReferenceGrants.String():
+	case kubernetes.K8sReferenceGrants:
 		istioConfigDetail.K8sReferenceGrant, err = in.userClients[cluster].GatewayAPI().GatewayV1beta1().ReferenceGrants(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.K8sReferenceGrant.Kind = kubernetes.K8sReferenceGrants.Kind
 			istioConfigDetail.K8sReferenceGrant.APIVersion = kubernetes.K8sReferenceGrants.GroupVersion().String()
 		}
-	case kubernetes.K8sTCPRoutes.String():
+	case kubernetes.K8sTCPRoutes:
 		istioConfigDetail.K8sTCPRoute, err = in.userClients[cluster].GatewayAPI().GatewayV1alpha2().TCPRoutes(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.K8sTCPRoute.Kind = kubernetes.K8sTCPRoutes.Kind
 			istioConfigDetail.K8sTCPRoute.APIVersion = kubernetes.K8sTCPRoutes.GroupVersion().String()
 		}
-	case kubernetes.K8sTLSRoutes.String():
+	case kubernetes.K8sTLSRoutes:
 		istioConfigDetail.K8sTLSRoute, err = in.userClients[cluster].GatewayAPI().GatewayV1alpha2().TLSRoutes(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.K8sTLSRoute.Kind = kubernetes.K8sTLSRoutes.Kind
 			istioConfigDetail.K8sTLSRoute.APIVersion = kubernetes.K8sTLSRoutes.GroupVersion().String()
 		}
-	case kubernetes.ServiceEntries.String():
+	case kubernetes.ServiceEntries:
 		istioConfigDetail.ServiceEntry, err = in.userClients[cluster].Istio().NetworkingV1().ServiceEntries(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.ServiceEntry.Kind = kubernetes.ServiceEntries.Kind
 			istioConfigDetail.ServiceEntry.APIVersion = kubernetes.ServiceEntries.GroupVersion().String()
 		}
-	case kubernetes.Sidecars.String():
+	case kubernetes.Sidecars:
 		istioConfigDetail.Sidecar, err = in.userClients[cluster].Istio().NetworkingV1().Sidecars(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.Sidecar.Kind = kubernetes.Sidecars.Kind
 			istioConfigDetail.Sidecar.APIVersion = kubernetes.Sidecars.GroupVersion().String()
 		}
-	case kubernetes.VirtualServices.String():
+	case kubernetes.VirtualServices:
 		istioConfigDetail.VirtualService, err = in.userClients[cluster].Istio().NetworkingV1().VirtualServices(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.VirtualService.Kind = kubernetes.VirtualServices.Kind
 			istioConfigDetail.VirtualService.APIVersion = kubernetes.VirtualServices.GroupVersion().String()
 		}
-	case kubernetes.WorkloadEntries.String():
+	case kubernetes.WorkloadEntries:
 		istioConfigDetail.WorkloadEntry, err = in.userClients[cluster].Istio().NetworkingV1().WorkloadEntries(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.WorkloadEntry.Kind = kubernetes.WorkloadEntries.Kind
 			istioConfigDetail.WorkloadEntry.APIVersion = kubernetes.WorkloadEntries.GroupVersion().String()
 		}
-	case kubernetes.WorkloadGroups.String():
+	case kubernetes.WorkloadGroups:
 		istioConfigDetail.WorkloadGroup, err = in.userClients[cluster].Istio().NetworkingV1().WorkloadGroups(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.WorkloadGroup.Kind = kubernetes.WorkloadGroups.Kind
 			istioConfigDetail.WorkloadGroup.APIVersion = kubernetes.WorkloadGroups.GroupVersion().String()
 		}
-	case kubernetes.WasmPlugins.String():
+	case kubernetes.WasmPlugins:
 		istioConfigDetail.WasmPlugin, err = in.userClients[cluster].Istio().ExtensionsV1alpha1().WasmPlugins(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.WasmPlugin.Kind = kubernetes.WasmPlugins.Kind
 			istioConfigDetail.WasmPlugin.APIVersion = kubernetes.WasmPlugins.GroupVersion().String()
 		}
-	case kubernetes.Telemetries.String():
+	case kubernetes.Telemetries:
 		istioConfigDetail.Telemetry, err = in.userClients[cluster].Istio().TelemetryV1().Telemetries(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.Telemetry.Kind = kubernetes.Telemetries.Kind
 			istioConfigDetail.Telemetry.APIVersion = kubernetes.Telemetries.GroupVersion().String()
 		}
-	case kubernetes.AuthorizationPolicies.String():
+	case kubernetes.AuthorizationPolicies:
 		istioConfigDetail.AuthorizationPolicy, err = in.userClients[cluster].Istio().SecurityV1().AuthorizationPolicies(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.AuthorizationPolicy.Kind = kubernetes.AuthorizationPolicies.Kind
 			istioConfigDetail.AuthorizationPolicy.APIVersion = kubernetes.AuthorizationPolicies.GroupVersion().String()
 		}
-	case kubernetes.PeerAuthentications.String():
+	case kubernetes.PeerAuthentications:
 		istioConfigDetail.PeerAuthentication, err = in.userClients[cluster].Istio().SecurityV1().PeerAuthentications(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.PeerAuthentication.Kind = kubernetes.PeerAuthentications.Kind
 			istioConfigDetail.PeerAuthentication.APIVersion = kubernetes.PeerAuthentications.GroupVersion().String()
 		}
-	case kubernetes.RequestAuthentications.String():
+	case kubernetes.RequestAuthentications:
 		istioConfigDetail.RequestAuthentication, err = in.userClients[cluster].Istio().SecurityV1().RequestAuthentications(namespace).Get(ctx, object, getOpts)
 		if err == nil {
 			istioConfigDetail.RequestAuthentication.Kind = kubernetes.RequestAuthentications.Kind
@@ -630,44 +630,44 @@ func (in *IstioConfigService) DeleteIstioConfigDetail(ctx context.Context, clust
 		return err
 	}
 
-	switch resourceType.String() {
-	case kubernetes.DestinationRules.String():
+	switch resourceType {
+	case kubernetes.DestinationRules:
 		err = userClient.Istio().NetworkingV1().DestinationRules(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.EnvoyFilters.String():
+	case kubernetes.EnvoyFilters:
 		err = userClient.Istio().NetworkingV1alpha3().EnvoyFilters(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.Gateways.String():
+	case kubernetes.Gateways:
 		err = userClient.Istio().NetworkingV1().Gateways(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.K8sGateways.String():
+	case kubernetes.K8sGateways:
 		err = userClient.GatewayAPI().GatewayV1().Gateways(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.K8sGRPCRoutes.String():
+	case kubernetes.K8sGRPCRoutes:
 		err = userClient.GatewayAPI().GatewayV1().GRPCRoutes(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.K8sHTTPRoutes.String():
+	case kubernetes.K8sHTTPRoutes:
 		err = userClient.GatewayAPI().GatewayV1().HTTPRoutes(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.K8sReferenceGrants.String():
+	case kubernetes.K8sReferenceGrants:
 		err = userClient.GatewayAPI().GatewayV1beta1().ReferenceGrants(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.K8sTCPRoutes.String():
+	case kubernetes.K8sTCPRoutes:
 		err = userClient.GatewayAPI().GatewayV1alpha2().TCPRoutes(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.K8sTLSRoutes.String():
+	case kubernetes.K8sTLSRoutes:
 		err = userClient.GatewayAPI().GatewayV1alpha2().TLSRoutes(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.ServiceEntries.String():
+	case kubernetes.ServiceEntries:
 		err = userClient.Istio().NetworkingV1().ServiceEntries(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.Sidecars.String():
+	case kubernetes.Sidecars:
 		err = userClient.Istio().NetworkingV1().Sidecars(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.VirtualServices.String():
+	case kubernetes.VirtualServices:
 		err = userClient.Istio().NetworkingV1().VirtualServices(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.WorkloadEntries.String():
+	case kubernetes.WorkloadEntries:
 		err = userClient.Istio().NetworkingV1().WorkloadEntries(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.WorkloadGroups.String():
+	case kubernetes.WorkloadGroups:
 		err = userClient.Istio().NetworkingV1().WorkloadGroups(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.AuthorizationPolicies.String():
+	case kubernetes.AuthorizationPolicies:
 		err = userClient.Istio().SecurityV1().AuthorizationPolicies(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.PeerAuthentications.String():
+	case kubernetes.PeerAuthentications:
 		err = userClient.Istio().SecurityV1().PeerAuthentications(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.RequestAuthentications.String():
+	case kubernetes.RequestAuthentications:
 		err = userClient.Istio().SecurityV1().RequestAuthentications(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.WasmPlugins.String():
+	case kubernetes.WasmPlugins:
 		err = userClient.Istio().ExtensionsV1alpha1().WasmPlugins(namespace).Delete(ctx, name, delOpts)
-	case kubernetes.Telemetries.String():
+	case kubernetes.Telemetries:
 		err = userClient.Istio().TelemetryV1().Telemetries(namespace).Delete(ctx, name, delOpts)
 	default:
 		err = fmt.Errorf("object type not found: %v", resourceType)
diff --git a/business/istio_validations.go b/business/istio_validations.go
index b887fcd1d..59a6a78c6 100644
--- a/business/istio_validations.go
+++ b/business/istio_validations.go
@@ -280,32 +280,32 @@ func (in *IstioValidationsService) GetIstioObjectValidations(ctx context.Context
 
 	noServiceChecker := checkers.NoServiceChecker{Cluster: cluster, Namespaces: namespaces, IstioConfigList: &istioConfigList, WorkloadsPerNamespace: workloadsPerNamespace, AuthorizationDetails: &rbacDetails, RegistryServices: registryServices, PolicyAllowAny: in.isPolicyAllowAny()}
 
-	switch objectGVK.String() {
-	case kubernetes.Gateways.String():
+	switch objectGVK {
+	case kubernetes.Gateways:
 		objectCheckers = []checkers.ObjectChecker{
 			checkers.GatewayChecker{Cluster: cluster, Gateways: istioConfigList.Gateways, WorkloadsPerNamespace: workloadsPerNamespace, IsGatewayToNamespace: in.isGatewayToNamespace()},
 		}
 		referenceChecker = references.GatewayReferences{Gateways: istioConfigList.Gateways, VirtualServices: istioConfigList.VirtualServices, WorkloadsPerNamespace: workloadsPerNamespace}
-	case kubernetes.VirtualServices.String():
+	case kubernetes.VirtualServices:
 		virtualServiceChecker := checkers.VirtualServiceChecker{Cluster: cluster, Namespaces: namespaces, VirtualServices: istioConfigList.VirtualServices, DestinationRules: istioConfigList.DestinationRules}
 		objectCheckers = []checkers.ObjectChecker{noServiceChecker, virtualServiceChecker}
 		referenceChecker = references.VirtualServiceReferences{Namespace: namespace, Namespaces: namespaces, VirtualServices: istioConfigList.VirtualServices, DestinationRules: istioConfigList.DestinationRules, AuthorizationPolicies: rbacDetails.AuthorizationPolicies}
-	case kubernetes.DestinationRules.String():
+	case kubernetes.DestinationRules:
 		destinationRulesChecker := checkers.DestinationRulesChecker{Cluster: cluster, Namespaces: namespaces, DestinationRules: istioConfigList.DestinationRules, MTLSDetails: mtlsDetails, ServiceEntries: istioConfigList.ServiceEntries}
 		objectCheckers = []checkers.ObjectChecker{noServiceChecker, destinationRulesChecker}
 		referenceChecker = references.DestinationRuleReferences{Namespace: namespace, Namespaces: namespaces, DestinationRules: istioConfigList.DestinationRules, VirtualServices: istioConfigList.VirtualServices, WorkloadsPerNamespace: workloadsPerNamespace, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices}
-	case kubernetes.ServiceEntries.String():
+	case kubernetes.ServiceEntries:
 		serviceEntryChecker := checkers.ServiceEntryChecker{Cluster: cluster, ServiceEntries: istioConfigList.ServiceEntries, Namespaces: namespaces, WorkloadEntries: istioConfigList.WorkloadEntries}
 		objectCheckers = []checkers.ObjectChecker{serviceEntryChecker}
 		referenceChecker = references.ServiceEntryReferences{AuthorizationPolicies: rbacDetails.AuthorizationPolicies, Namespace: namespace, Namespaces: namespaces, DestinationRules: istioConfigList.DestinationRules, ServiceEntries: istioConfigList.ServiceEntries, Sidecars: istioConfigList.Sidecars, RegistryServices: registryServices}
-	case kubernetes.Sidecars.String():
+	case kubernetes.Sidecars:
 		sidecarsChecker := checkers.SidecarChecker{
 			Cluster: cluster, Sidecars: istioConfigList.Sidecars, Namespaces: namespaces,
 			WorkloadsPerNamespace: workloadsPerNamespace, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices,
 		}
 		objectCheckers = []checkers.ObjectChecker{sidecarsChecker}
 		referenceChecker = references.SidecarReferences{Sidecars: istioConfigList.Sidecars, Namespace: namespace, Namespaces: namespaces, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices, WorkloadsPerNamespace: workloadsPerNamespace}
-	case kubernetes.AuthorizationPolicies.String():
+	case kubernetes.AuthorizationPolicies:
 		authPoliciesChecker := checkers.AuthorizationPolicyChecker{
 			AuthorizationPolicies: rbacDetails.AuthorizationPolicies,
 			Cluster:               cluster, Namespaces: namespaces, ServiceEntries: istioConfigList.ServiceEntries, ServiceAccounts: serviceAccounts,
@@ -313,46 +313,46 @@ func (in *IstioValidationsService) GetIstioObjectValidations(ctx context.Context
 		}
 		objectCheckers = []checkers.ObjectChecker{authPoliciesChecker}
 		referenceChecker = references.AuthorizationPolicyReferences{AuthorizationPolicies: rbacDetails.AuthorizationPolicies, Namespace: namespace, Namespaces: namespaces, VirtualServices: istioConfigList.VirtualServices, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices, WorkloadsPerNamespace: workloadsPerNamespace}
-	case kubernetes.PeerAuthentications.String():
+	case kubernetes.PeerAuthentications:
 		// Validations on PeerAuthentications
 		peerAuthnChecker := checkers.PeerAuthenticationChecker{Cluster: cluster, PeerAuthentications: mtlsDetails.PeerAuthentications, MTLSDetails: mtlsDetails, WorkloadsPerNamespace: workloadsPerNamespace}
 		objectCheckers = []checkers.ObjectChecker{peerAuthnChecker}
 		referenceChecker = references.PeerAuthReferences{MTLSDetails: mtlsDetails, WorkloadsPerNamespace: workloadsPerNamespace}
-	case kubernetes.WorkloadEntries.String():
+	case kubernetes.WorkloadEntries:
 		// Validation on WorkloadEntries are not yet in place
-	case kubernetes.WorkloadGroups.Group:
+	case kubernetes.WorkloadGroups:
 		// Validation on WorkloadGroups are not yet in place
-	case kubernetes.RequestAuthentications.String():
+	case kubernetes.RequestAuthentications:
 		// Validation on RequestAuthentications are not yet in place
 		requestAuthnChecker := checkers.RequestAuthenticationChecker{Cluster: cluster, RequestAuthentications: istioConfigList.RequestAuthentications, WorkloadsPerNamespace: workloadsPerNamespace}
 		objectCheckers = []checkers.ObjectChecker{requestAuthnChecker}
-	case kubernetes.EnvoyFilters.String():
+	case kubernetes.EnvoyFilters:
 		// Validation on EnvoyFilters are not yet in place
-	case kubernetes.WasmPlugins.String():
+	case kubernetes.WasmPlugins:
 		// Validation on WasmPlugins is not expected
-	case kubernetes.Telemetries.String():
+	case kubernetes.Telemetries:
 		// Validation on Telemetries is not expected
-	case kubernetes.K8sGateways.String():
+	case kubernetes.K8sGateways:
 		// Validations on K8sGateways
 		objectCheckers = []checkers.ObjectChecker{
 			checkers.K8sGatewayChecker{Cluster: cluster, K8sGateways: istioConfigList.K8sGateways, GatewayClasses: in.istioConfig.GatewayAPIClasses(cluster)},
 		}
 		referenceChecker = references.K8sGatewayReferences{K8sGateways: istioConfigList.K8sGateways, K8sHTTPRoutes: istioConfigList.K8sHTTPRoutes, K8sGRPCRoutes: istioConfigList.K8sGRPCRoutes}
-	case kubernetes.K8sGRPCRoutes.String():
+	case kubernetes.K8sGRPCRoutes:
 		grpcRouteChecker := checkers.K8sGRPCRouteChecker{Cluster: cluster, K8sGRPCRoutes: istioConfigList.K8sGRPCRoutes, K8sGateways: istioConfigList.K8sGateways, K8sReferenceGrants: istioConfigList.K8sReferenceGrants, Namespaces: namespaces, RegistryServices: registryServices}
 		objectCheckers = []checkers.ObjectChecker{noServiceChecker, grpcRouteChecker}
 		referenceChecker = references.K8sGRPCRouteReferences{K8sGRPCRoutes: istioConfigList.K8sGRPCRoutes, Namespaces: namespaces, K8sReferenceGrants: istioConfigList.K8sReferenceGrants}
-	case kubernetes.K8sHTTPRoutes.String():
+	case kubernetes.K8sHTTPRoutes:
 		httpRouteChecker := checkers.K8sHTTPRouteChecker{Cluster: cluster, K8sHTTPRoutes: istioConfigList.K8sHTTPRoutes, K8sGateways: istioConfigList.K8sGateways, K8sReferenceGrants: istioConfigList.K8sReferenceGrants, Namespaces: namespaces, RegistryServices: registryServices}
 		objectCheckers = []checkers.ObjectChecker{noServiceChecker, httpRouteChecker}
 		referenceChecker = references.K8sHTTPRouteReferences{K8sHTTPRoutes: istioConfigList.K8sHTTPRoutes, Namespaces: namespaces, K8sReferenceGrants: istioConfigList.K8sReferenceGrants}
-	case kubernetes.K8sReferenceGrants.String():
+	case kubernetes.K8sReferenceGrants:
 		objectCheckers = []checkers.ObjectChecker{
 			checkers.K8sReferenceGrantChecker{Cluster: cluster, K8sReferenceGrants: istioConfigList.K8sReferenceGrants, Namespaces: namespaces},
 		}
-	case kubernetes.K8sTCPRoutes.String():
+	case kubernetes.K8sTCPRoutes:
 		// Validation on K8sTCPRoutes is not expected
-	case kubernetes.K8sTLSRoutes.String():
+	case kubernetes.K8sTLSRoutes:
 		// Validation on K8sTLSRoutes is not expected
 	default:
 		err = fmt.Errorf("object type not found: %v", objectGVK.String())
diff --git a/business/openshift_oauth.go b/business/openshift_oauth.go
index 5baa90b9b..2c2898994 100644
--- a/business/openshift_oauth.go
+++ b/business/openshift_oauth.go
@@ -70,15 +70,7 @@ type OAuthAuthorizationServer struct {
 }
 
 // Generates an http.Client with RootCAs specified in kubeconfig along with the system certs.
-func httpClientWithPool(restConfig rest.Config, systemPool *x509.CertPool) (*http.Client, error) {
-	// If it's insecure or there are no system certs then don't bother appending them.
-	if restConfig.Insecure || systemPool == nil {
-		log.Trace("Insecure or empty system pool")
-		return rest.HTTPClientFor(&restConfig)
-	}
-
-	pool := systemPool.Clone()
-
+func httpClientWithPool(conf *config.Config, restConfig rest.Config, systemPool *x509.CertPool) (*http.Client, error) {
 	// Need to populate CAData from CAFile.
 	if err := rest.LoadTLSFiles(&restConfig); err != nil {
 		return nil, fmt.Errorf("unable to load CA info from restConfig")
@@ -95,14 +87,24 @@ func httpClientWithPool(restConfig rest.Config, systemPool *x509.CertPool) (*htt
 		tlsConfig = &tls.Config{}
 	}
 
-	if restConfig.TLSClientConfig.CAData != nil {
-		log.Trace("Appending CA data from tls client config to pool")
-		if !pool.AppendCertsFromPEM(restConfig.TLSClientConfig.CAData) {
-			return nil, fmt.Errorf("unable to append CA from restConfig to system pool: %s", restConfig.TLSClientConfig.CAData)
-		}
+	// If this setting is set in the Kiali config then override whatever is set on the kubeconfig.
+	if conf.Auth.OpenShift.InsecureSkipVerifyTLS {
+		tlsConfig.InsecureSkipVerify = true
 	}
 
-	tlsConfig.RootCAs = pool
+	if !tlsConfig.InsecureSkipVerify {
+		// Append system certs
+		pool := systemPool.Clone()
+		if restConfig.TLSClientConfig.CAData != nil {
+			log.Trace("Appending CA data from tls client config to pool")
+			if !pool.AppendCertsFromPEM(restConfig.TLSClientConfig.CAData) {
+				return nil, fmt.Errorf("unable to append CA from restConfig to system pool: %s", restConfig.TLSClientConfig.CAData)
+			}
+		}
+		tlsConfig.RootCAs = pool
+	} else {
+		log.Debug("Insecure connection to oAuth server.")
+	}
 
 	return &http.Client{
 		Timeout:   restConfig.Timeout,
@@ -146,7 +148,7 @@ func NewOpenshiftOAuthService(ctx context.Context, conf *config.Config, kialiSAC
 		}
 		request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.GetToken()))
 
-		httpClient, err := httpClientWithPool(*client.ClusterInfo().ClientConfig, systemCertPool)
+		httpClient, err := httpClientWithPool(conf, *client.ClusterInfo().ClientConfig, systemCertPool)
 		if err != nil {
 			return nil, fmt.Errorf("failed to create http client for fetching oauth server metadata from kube api server [%s], error: %s", url, err)
 		}
@@ -223,7 +225,7 @@ func (in *OpenshiftOAuthService) Exchange(ctx context.Context, code string, veri
 		return nil, fmt.Errorf("could not get ServiceAccount client for cluster [%s]", cluster)
 	}
 
-	httpClient, err := httpClientWithPool(*client.ClusterInfo().ClientConfig, in.systemCertPool)
+	httpClient, err := httpClientWithPool(in.conf, *client.ClusterInfo().ClientConfig, in.systemCertPool)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create http client for oauth consumption, error: %s", err)
 	}
diff --git a/business/workloads.go b/business/workloads.go
index fa1f423bd..08845a99d 100644
--- a/business/workloads.go
+++ b/business/workloads.go
@@ -23,6 +23,7 @@ import (
 	"k8s.io/apimachinery/pkg/api/errors"
 	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 
 	"github.com/kiali/kiali/business/checkers"
 	"github.com/kiali/kiali/config"
@@ -77,7 +78,7 @@ type WorkloadCriteria struct {
 	Cluster               string
 	Namespace             string
 	WorkloadName          string
-	WorkloadType          string
+	WorkloadGVK           schema.GroupVersionKind
 	IncludeIstioResources bool
 	IncludeServices       bool
 	IncludeHealth         bool
@@ -399,7 +400,7 @@ func (in *WorkloadService) GetWorkload(ctx context.Context, criteria WorkloadCri
 		observability.Attribute("cluster", criteria.Cluster),
 		observability.Attribute("namespace", criteria.Namespace),
 		observability.Attribute("workloadName", criteria.WorkloadName),
-		observability.Attribute("workloadType", criteria.WorkloadType),
+		observability.Attribute("workloadType", criteria.WorkloadGVK.String()),
 		observability.Attribute("includeServices", criteria.IncludeServices),
 		observability.Attribute("rateInterval", criteria.RateInterval),
 		observability.Attribute("queryTime", criteria.QueryTime),
@@ -452,14 +453,15 @@ func (in *WorkloadService) GetWorkload(ctx context.Context, criteria WorkloadCri
 	return workload, nil
 }
 
-func (in *WorkloadService) UpdateWorkload(ctx context.Context, cluster string, namespace string, workloadName string, workloadType string, includeServices bool, jsonPatch string, patchType string) (*models.Workload, error) {
+func (in *WorkloadService) UpdateWorkload(ctx context.Context, cluster string, namespace string, workloadName string, workloadGVK schema.GroupVersionKind, includeServices bool, jsonPatch string, patchType string) (*models.Workload, error) {
 	var end observability.EndFunc
 	ctx, end = observability.StartSpan(ctx, "UpdateWorkload",
 		observability.Attribute("package", "business"),
 		observability.Attribute("cluster", cluster),
 		observability.Attribute("namespace", namespace),
 		observability.Attribute("workloadName", workloadName),
-		observability.Attribute("workloadType", workloadType),
+		observability.Attribute("workloadKind", workloadGVK.Kind),
+		observability.Attribute("workloadGroupVersion", workloadGVK.GroupVersion().String()),
 		observability.Attribute("includeServices", includeServices),
 		observability.Attribute("jsonPatch", jsonPatch),
 		observability.Attribute("patchType", patchType),
@@ -467,7 +469,7 @@ func (in *WorkloadService) UpdateWorkload(ctx context.Context, cluster string, n
 	defer end()
 
 	// Identify controller and apply patch to workload
-	err := in.updateWorkload(ctx, cluster, namespace, workloadName, workloadType, jsonPatch, patchType)
+	err := in.updateWorkload(ctx, cluster, namespace, workloadName, workloadGVK, jsonPatch, patchType)
 	if err != nil {
 		return nil, err
 	}
@@ -482,7 +484,7 @@ func (in *WorkloadService) UpdateWorkload(ctx context.Context, cluster string, n
 	cache.Refresh(namespace)
 
 	// After the update we fetch the whole workload
-	return in.GetWorkload(ctx, WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: workloadName, WorkloadType: workloadType, IncludeServices: includeServices})
+	return in.GetWorkload(ctx, WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: workloadName, WorkloadGVK: workloadGVK, IncludeServices: includeServices})
 }
 
 func (in *WorkloadService) GetPod(cluster, namespace, name string) (*models.Pod, error) {
@@ -918,19 +920,24 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 		return ws, err
 	}
 
-	// Key: name of controller; Value: type of controller
-	controllers := map[string]string{}
+	// Key: name of controller; Value: GroupVersionKind of controller
+	controllers := map[string]schema.GroupVersionKind{}
 
 	// Find controllers from pods
 	for _, pod := range pods {
 		if len(pod.OwnerReferences) != 0 {
 			for _, ref := range pod.OwnerReferences {
+				refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+				if err != nil {
+					log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+					continue
+				}
 				if ref.Controller != nil && *ref.Controller && in.isWorkloadIncluded(ref.Kind) {
 					if _, exist := controllers[ref.Name]; !exist {
-						controllers[ref.Name] = ref.Kind
+						controllers[ref.Name] = refGV.WithKind(ref.Kind)
 					} else {
-						if controllers[ref.Name] != ref.Kind {
-							controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+						if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+							controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].Kind, ref.Kind))
 						}
 					}
 				}
@@ -938,7 +945,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 		} else {
 			if _, exist := controllers[pod.Name]; !exist {
 				// Pod without controller
-				controllers[pod.Name] = "Pod"
+				controllers[pod.Name] = kubernetes.Pods
 			}
 		}
 	}
@@ -946,8 +953,8 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 	// Resolve ReplicaSets from Deployments
 	// Resolve ReplicationControllers from DeploymentConfigs
 	// Resolve Jobs from CronJobs
-	for controllerName, controllerType := range controllers {
-		if controllerType == kubernetes.ReplicaSetType {
+	for controllerName, controllerGVK := range controllers {
+		if controllerGVK == kubernetes.ReplicaSets {
 			found := false
 			iFound := -1
 			for i, rs := range repset {
@@ -960,18 +967,23 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			if found && len(repset[iFound].OwnerReferences) > 0 {
 				for _, ref := range repset[iFound].OwnerReferences {
 					if ref.Controller != nil && *ref.Controller {
+						refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+						if err != nil {
+							log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+							continue
+						}
 						if _, exist := controllers[ref.Name]; !exist {
 							// For valid owner controllers, delete the child ReplicaSet and add the parent controller,
 							// otherwise (for custom controllers), defer to the replica set.
 							if in.isWorkloadValid(ref.Kind) {
-								controllers[ref.Name] = ref.Kind
+								controllers[ref.Name] = refGV.WithKind(ref.Kind)
 								delete(controllers, controllerName)
 							} else {
 								log.Debugf("Add ReplicaSet to workload list for custom controller [%s][%s]", ref.Name, ref.Kind)
 							}
 						} else {
-							if controllers[ref.Name] != ref.Kind {
-								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+							if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+								controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].Kind, ref.Kind))
 							}
 							delete(controllers, controllerName)
 						}
@@ -979,7 +991,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				}
 			}
 		}
-		if controllerType == kubernetes.ReplicationControllerType {
+		if controllerGVK == kubernetes.ReplicationControllers {
 			found := false
 			iFound := -1
 			for i, rc := range repcon {
@@ -991,13 +1003,18 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			}
 			if found && len(repcon[iFound].OwnerReferences) > 0 {
 				for _, ref := range repcon[iFound].OwnerReferences {
+					refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+					if err != nil {
+						log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+						continue
+					}
 					if ref.Controller != nil && *ref.Controller {
 						// Delete the child ReplicationController and add the parent controller
 						if _, exist := controllers[ref.Name]; !exist {
-							controllers[ref.Name] = ref.Kind
+							controllers[ref.Name] = refGV.WithKind(ref.Kind)
 						} else {
-							if controllers[ref.Name] != ref.Kind {
-								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+							if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+								controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].Kind, ref.Kind))
 							}
 						}
 						delete(controllers, controllerName)
@@ -1005,7 +1022,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				}
 			}
 		}
-		if controllerType == kubernetes.JobType {
+		if controllerGVK == kubernetes.Jobs {
 			found := false
 			iFound := -1
 			for i, jb := range jbs {
@@ -1017,13 +1034,18 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			}
 			if found && len(jbs[iFound].OwnerReferences) > 0 {
 				for _, ref := range jbs[iFound].OwnerReferences {
+					refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+					if err != nil {
+						log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+						continue
+					}
 					if ref.Controller != nil && *ref.Controller {
 						// Delete the child Job and add the parent controller
 						if _, exist := controllers[ref.Name]; !exist {
-							controllers[ref.Name] = ref.Kind
+							controllers[ref.Name] = refGV.WithKind(ref.Kind)
 						} else {
-							if controllers[ref.Name] != ref.Kind {
-								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+							if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+								controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].Kind, ref.Kind))
 							}
 						}
 						// Jobs are special as deleting CronJob parent doesn't delete children
@@ -1059,7 +1081,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			selectorCheck = selector.Matches(labels.Set(d.Spec.Template.Labels))
 		}
 		if _, exist := controllers[d.Name]; !exist && selectorCheck {
-			controllers[d.Name] = "Deployment"
+			controllers[d.Name] = kubernetes.Deployments
 		}
 	}
 	for _, rs := range repset {
@@ -1068,7 +1090,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			selectorCheck = selector.Matches(labels.Set(rs.Spec.Template.Labels))
 		}
 		if _, exist := controllers[rs.Name]; !exist && len(rs.OwnerReferences) == 0 && selectorCheck {
-			controllers[rs.Name] = "ReplicaSet"
+			controllers[rs.Name] = kubernetes.ReplicaSets
 		}
 	}
 	for _, dc := range depcon {
@@ -1077,7 +1099,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			selectorCheck = selector.Matches(labels.Set(dc.Spec.Template.Labels))
 		}
 		if _, exist := controllers[dc.Name]; !exist && selectorCheck {
-			controllers[dc.Name] = "DeploymentConfig"
+			controllers[dc.Name] = kubernetes.DeploymentConfigs
 		}
 	}
 	for _, rc := range repcon {
@@ -1086,7 +1108,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			selectorCheck = selector.Matches(labels.Set(rc.Spec.Template.Labels))
 		}
 		if _, exist := controllers[rc.Name]; !exist && len(rc.OwnerReferences) == 0 && selectorCheck {
-			controllers[rc.Name] = "ReplicationController"
+			controllers[rc.Name] = kubernetes.ReplicationControllers
 		}
 	}
 	for _, fs := range fulset {
@@ -1095,7 +1117,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			selectorCheck = selector.Matches(labels.Set(fs.Spec.Template.Labels))
 		}
 		if _, exist := controllers[fs.Name]; !exist && selectorCheck {
-			controllers[fs.Name] = "StatefulSet"
+			controllers[fs.Name] = kubernetes.StatefulSets
 		}
 	}
 	for _, ds := range daeset {
@@ -1104,7 +1126,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			selectorCheck = selector.Matches(labels.Set(ds.Spec.Template.Labels))
 		}
 		if _, exist := controllers[ds.Name]; !exist && selectorCheck {
-			controllers[ds.Name] = "DaemonSet"
+			controllers[ds.Name] = kubernetes.DaemonSets
 		}
 	}
 
@@ -1121,11 +1143,11 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 		}
 		w.Cluster = cluster
 		w.Namespace = namespace
-		controllerType := controllers[controllerName]
+		controllerGVK := controllers[controllerName]
 		// Flag to add a controller if it is found
 		cnFound := true
-		switch controllerType {
-		case kubernetes.DeploymentType:
+		switch controllerGVK {
+		case kubernetes.Deployments:
 			found := false
 			iFound := -1
 			for i, dp := range dep {
@@ -1143,7 +1165,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Errorf("Workload %s is not found as Deployment", controllerName)
 				cnFound = false
 			}
-		case kubernetes.ReplicaSetType:
+		case kubernetes.ReplicaSets:
 			found := false
 			iFound := -1
 			for i, rs := range repset {
@@ -1161,7 +1183,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Errorf("Workload %s is not found as ReplicaSet", controllerName)
 				cnFound = false
 			}
-		case kubernetes.ReplicationControllerType:
+		case kubernetes.ReplicationControllers:
 			found := false
 			iFound := -1
 			for i, rc := range repcon {
@@ -1179,7 +1201,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Errorf("Workload %s is not found as ReplicationController", controllerName)
 				cnFound = false
 			}
-		case kubernetes.DeploymentConfigType:
+		case kubernetes.DeploymentConfigs:
 			found := false
 			iFound := -1
 			for i, dc := range depcon {
@@ -1197,7 +1219,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Errorf("Workload %s is not found as DeploymentConfig", controllerName)
 				cnFound = false
 			}
-		case kubernetes.StatefulSetType:
+		case kubernetes.StatefulSets:
 			found := false
 			iFound := -1
 			for i, fs := range fulset {
@@ -1215,7 +1237,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Errorf("Workload %s is not found as StatefulSet", controllerName)
 				cnFound = false
 			}
-		case kubernetes.PodType:
+		case kubernetes.Pods:
 			found := false
 			iFound := -1
 			for i, pod := range pods {
@@ -1232,7 +1254,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Errorf("Workload %s is not found as Pod", controllerName)
 				cnFound = false
 			}
-		case kubernetes.JobType:
+		case kubernetes.Jobs:
 			found := false
 			iFound := -1
 			for i, jb := range jbs {
@@ -1250,7 +1272,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Errorf("Workload %s is not found as Job", controllerName)
 				cnFound = false
 			}
-		case kubernetes.CronJobType:
+		case kubernetes.CronJobs:
 			found := false
 			iFound := -1
 			for i, cjb := range conjbs {
@@ -1268,7 +1290,7 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 				log.Warningf("Workload %s is not found as CronJob (CronJob could be deleted but children are still in the namespace)", controllerName)
 				cnFound = false
 			}
-		case kubernetes.DaemonSetType:
+		case kubernetes.DaemonSets:
 			found := false
 			iFound := -1
 			for i, ds := range daeset {
@@ -1290,10 +1312,10 @@ func (in *WorkloadService) fetchWorkloadsFromCluster(ctx context.Context, cluste
 			// This covers the scenario of a custom controller without replicaset, controlling pods directly.
 			// Note that a custom controller with replicaset(s) will return the replicaset(s) as the workloads.
 			var cPods []core_v1.Pod
-			cPods = kubernetes.FilterPodsByController(controllerName, controllerType, pods)
+			cPods = kubernetes.FilterPodsByController(controllerName, controllerGVK, pods)
 			if len(cPods) > 0 {
-				w.ParsePods(controllerName, controllerType, cPods)
-				log.Debugf("Workload %s of type %s has no ReplicaSet as a child controller (this may be ok, but is unusual)", controllerName, controllerType)
+				w.ParsePods(controllerName, controllerGVK, cPods)
+				log.Debugf("Workload %s of type %s has no ReplicaSet as a child controller (this may be ok, but is unusual)", controllerName, controllerGVK)
 			}
 			w.SetPods(cPods)
 		}
@@ -1344,7 +1366,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	// Flag used for custom controllers
 	// i.e. a third party framework creates its own "Deployment" controller with extra features
 	// on this case, Kiali will collect basic info from the ReplicaSet controller
-	_, knownWorkloadType := controllerOrder[criteria.WorkloadType]
+	_, knownWorkloadType := controllerOrder[criteria.WorkloadGVK.Kind]
 
 	wg := sync.WaitGroup{}
 	wg.Add(9)
@@ -1376,7 +1398,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 		defer wg.Done()
 		var err error
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.DeploymentType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.Deployments {
 			return
 		}
 		dep, err = kialiCache.GetDeployment(criteria.Namespace, criteria.WorkloadName)
@@ -1394,7 +1416,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	go func() {
 		defer wg.Done()
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.ReplicaSetType && knownWorkloadType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.ReplicaSets && knownWorkloadType {
 			return
 		}
 		var err error
@@ -1409,7 +1431,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	go func() {
 		defer wg.Done()
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.ReplicationControllerType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.ReplicationControllers {
 			return
 		}
 
@@ -1428,7 +1450,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	go func() {
 		defer wg.Done()
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.DeploymentConfigType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.DeploymentConfigs {
 			return
 		}
 
@@ -1446,7 +1468,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	go func() {
 		defer wg.Done()
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.StatefulSetType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.StatefulSets {
 			return
 		}
 
@@ -1463,7 +1485,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	go func() {
 		defer wg.Done()
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.CronJobType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.CronJobs {
 			return
 		}
 
@@ -1482,7 +1504,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	go func() {
 		defer wg.Done()
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.JobType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.Jobs {
 			return
 		}
 
@@ -1501,7 +1523,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	go func() {
 		defer wg.Done()
 
-		if criteria.WorkloadType != "" && criteria.WorkloadType != kubernetes.DaemonSetType {
+		if criteria.WorkloadGVK.Kind != "" && criteria.WorkloadGVK != kubernetes.DaemonSets {
 			return
 		}
 
@@ -1520,19 +1542,24 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 		return wl, err
 	}
 
-	// Key: name of controller; Value: type of controller
-	controllers := map[string]string{}
+	// Key: name of controller; Value: GVK of controller
+	controllers := map[string]schema.GroupVersionKind{}
 
 	// Find controllers from pods
 	for _, pod := range pods {
 		if len(pod.OwnerReferences) != 0 {
 			for _, ref := range pod.OwnerReferences {
+				refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+				if err != nil {
+					log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+					continue
+				}
 				if ref.Controller != nil && *ref.Controller && in.isWorkloadIncluded(ref.Kind) {
 					if _, exist := controllers[ref.Name]; !exist {
-						controllers[ref.Name] = ref.Kind
+						controllers[ref.Name] = refGV.WithKind(ref.Kind)
 					} else {
-						if controllers[ref.Name] != ref.Kind {
-							controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+						if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+							controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].String(), ref.Kind))
 						}
 					}
 				}
@@ -1540,7 +1567,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 		} else {
 			if _, exist := controllers[pod.Name]; !exist {
 				// Pod without controller
-				controllers[pod.Name] = "Pod"
+				controllers[pod.Name] = kubernetes.Pods
 			}
 		}
 	}
@@ -1548,8 +1575,8 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	// Resolve ReplicaSets from Deployments
 	// Resolve ReplicationControllers from DeploymentConfigs
 	// Resolve Jobs from CronJobs
-	for controllerName, controllerType := range controllers {
-		if controllerType == kubernetes.ReplicaSetType {
+	for controllerName, controllerGVK := range controllers {
+		if controllerGVK == kubernetes.ReplicaSets {
 			found := false
 			iFound := -1
 			for i, rs := range repset {
@@ -1561,19 +1588,24 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 			}
 			if found && len(repset[iFound].OwnerReferences) > 0 {
 				for _, ref := range repset[iFound].OwnerReferences {
+					refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+					if err != nil {
+						log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+						continue
+					}
 					if ref.Controller != nil && *ref.Controller {
 						// For valid owner controllers, delete the child ReplicaSet and add the parent controller,
 						// otherwise (for custom controllers), defer to the replica set.
 						if _, exist := controllers[ref.Name]; !exist {
 							if in.isWorkloadValid(ref.Kind) {
-								controllers[ref.Name] = ref.Kind
+								controllers[ref.Name] = refGV.WithKind(ref.Kind)
 								delete(controllers, controllerName)
 							} else {
 								log.Debugf("Use ReplicaSet as workload for custom controller [%s][%s]", ref.Name, ref.Kind)
 							}
 						} else {
-							if controllers[ref.Name] != ref.Kind {
-								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+							if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+								controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].Kind, ref.Kind))
 							}
 							delete(controllers, controllerName)
 						}
@@ -1581,7 +1613,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				}
 			}
 		}
-		if controllerType == kubernetes.ReplicationControllerType {
+		if controllerGVK == kubernetes.ReplicationControllers {
 			found := false
 			iFound := -1
 			for i, rc := range repcon {
@@ -1593,13 +1625,18 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 			}
 			if found && len(repcon[iFound].OwnerReferences) > 0 {
 				for _, ref := range repcon[iFound].OwnerReferences {
+					refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+					if err != nil {
+						log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+						continue
+					}
 					if ref.Controller != nil && *ref.Controller {
 						// Delete the child ReplicationController and add the parent controller
 						if _, exist := controllers[ref.Name]; !exist {
-							controllers[ref.Name] = ref.Kind
+							controllers[ref.Name] = refGV.WithKind(ref.Kind)
 						} else {
-							if controllers[ref.Name] != ref.Kind {
-								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+							if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+								controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].String(), ref.Kind))
 							}
 						}
 						delete(controllers, controllerName)
@@ -1607,7 +1644,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				}
 			}
 		}
-		if controllerType == kubernetes.JobType {
+		if controllerGVK == kubernetes.Jobs {
 			found := false
 			iFound := -1
 			for i, jb := range jbs {
@@ -1619,13 +1656,18 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 			}
 			if found && len(jbs[iFound].OwnerReferences) > 0 {
 				for _, ref := range jbs[iFound].OwnerReferences {
+					refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+					if err != nil {
+						log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+						continue
+					}
 					if ref.Controller != nil && *ref.Controller {
 						// Delete the child Job and add the parent controller
 						if _, exist := controllers[ref.Name]; !exist {
-							controllers[ref.Name] = ref.Kind
+							controllers[ref.Name] = refGV.WithKind(ref.Kind)
 						} else {
-							if controllers[ref.Name] != ref.Kind {
-								controllers[ref.Name] = controllerPriority(controllers[ref.Name], ref.Kind)
+							if controllers[ref.Name] != refGV.WithKind(ref.Kind) {
+								controllers[ref.Name] = refGV.WithKind(controllerPriority(controllers[ref.Name].String(), ref.Kind))
 							}
 						}
 						// Jobs are special as deleting CronJob parent doesn't delete children
@@ -1649,32 +1691,32 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 	// Cornercase, check for controllers without pods, to show them as a workload
 	if dep != nil {
 		if _, exist := controllers[dep.Name]; !exist {
-			controllers[dep.Name] = kubernetes.DeploymentType
+			controllers[dep.Name] = kubernetes.Deployments
 		}
 	}
 	for _, rs := range repset {
 		if _, exist := controllers[rs.Name]; !exist && len(rs.OwnerReferences) == 0 {
-			controllers[rs.Name] = kubernetes.ReplicaSetType
+			controllers[rs.Name] = kubernetes.ReplicaSets
 		}
 	}
 	if depcon != nil {
 		if _, exist := controllers[depcon.Name]; !exist {
-			controllers[depcon.Name] = kubernetes.DeploymentConfigType
+			controllers[depcon.Name] = kubernetes.DeploymentConfigs
 		}
 	}
 	for _, rc := range repcon {
 		if _, exist := controllers[rc.Name]; !exist && len(rc.OwnerReferences) == 0 {
-			controllers[rc.Name] = kubernetes.ReplicationControllerType
+			controllers[rc.Name] = kubernetes.ReplicationControllers
 		}
 	}
 	if fulset != nil {
 		if _, exist := controllers[fulset.Name]; !exist {
-			controllers[fulset.Name] = kubernetes.StatefulSetType
+			controllers[fulset.Name] = kubernetes.StatefulSets
 		}
 	}
 	if ds != nil {
 		if _, exist := controllers[ds.Name]; !exist {
-			controllers[ds.Name] = kubernetes.DaemonSetType
+			controllers[ds.Name] = kubernetes.DaemonSets
 		}
 	}
 
@@ -1699,16 +1741,16 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 		// For custom types: fall through to the default handler and try to get the workload definition working
 		// up from the pods or replicas sets.
 		// see https://github.com/kiali/kiali/issues/3830
-		discoveredControllerType := controllers[criteria.WorkloadName]
-		controllerType := discoveredControllerType
-		if criteria.WorkloadType != "" && discoveredControllerType != criteria.WorkloadType {
-			controllerType = criteria.WorkloadType
+		discoveredControllerGVK := controllers[criteria.WorkloadName]
+		controllerGVK := discoveredControllerGVK
+		if criteria.WorkloadGVK.Kind != "" && discoveredControllerGVK != criteria.WorkloadGVK {
+			controllerGVK = criteria.WorkloadGVK
 		}
 
 		// Handle the known types...
 		cnFound := true
-		switch controllerType {
-		case kubernetes.DeploymentType:
+		switch controllerGVK {
+		case kubernetes.Deployments:
 			if dep != nil && dep.Name == criteria.WorkloadName {
 				selector := labels.Set(dep.Spec.Template.Labels).AsSelector()
 				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
@@ -1717,7 +1759,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Errorf("Workload %s is not found as Deployment", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.ReplicaSetType:
+		case kubernetes.ReplicaSets:
 			found := false
 			iFound := -1
 			for i, rs := range repset {
@@ -1735,7 +1777,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Errorf("Workload %s is not found as ReplicaSet", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.ReplicationControllerType:
+		case kubernetes.ReplicationControllers:
 			found := false
 			iFound := -1
 			for i, rc := range repcon {
@@ -1753,7 +1795,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Errorf("Workload %s is not found as ReplicationController", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.DeploymentConfigType:
+		case kubernetes.DeploymentConfigs:
 			if depcon != nil && depcon.Name == criteria.WorkloadName {
 				selector := labels.Set(depcon.Spec.Template.Labels).AsSelector()
 				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
@@ -1762,7 +1804,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Errorf("Workload %s is not found as DeploymentConfig", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.StatefulSetType:
+		case kubernetes.StatefulSets:
 			if fulset != nil && fulset.Name == criteria.WorkloadName {
 				selector := labels.Set(fulset.Spec.Template.Labels).AsSelector()
 				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
@@ -1771,7 +1813,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Errorf("Workload %s is not found as StatefulSet", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.PodType:
+		case kubernetes.Pods:
 			found := false
 			iFound := -1
 			for i, pod := range pods {
@@ -1788,7 +1830,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Errorf("Workload %s is not found as Pod", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.JobType:
+		case kubernetes.Jobs:
 			found := false
 			iFound := -1
 			for i, jb := range jbs {
@@ -1806,7 +1848,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Errorf("Workload %s is not found as Job", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.CronJobType:
+		case kubernetes.CronJobs:
 			found := false
 			iFound := -1
 			for i, cjb := range conjbs {
@@ -1824,7 +1866,7 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 				log.Warningf("Workload %s is not found as CronJob (CronJob could be deleted but children are still in the namespace)", criteria.WorkloadName)
 				cnFound = false
 			}
-		case kubernetes.DaemonSetType:
+		case kubernetes.DaemonSets:
 			if ds != nil && ds.Name == criteria.WorkloadName {
 				selector := labels.Set(ds.Spec.Template.Labels).AsSelector()
 				w.SetPods(kubernetes.FilterPodsBySelector(selector, pods))
@@ -1841,12 +1883,12 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 			// 1. use the controller type found in the Pod resolution and ignore the unknown criteria type
 			var cPods []core_v1.Pod
 			for _, rs := range repset {
-				if discoveredControllerType == kubernetes.ReplicaSetType && criteria.WorkloadName == rs.Name {
+				if discoveredControllerGVK.Kind == kubernetes.ReplicaSetType && criteria.WorkloadName == rs.Name {
 					w.ParseReplicaSet(&rs)
 				} else {
 					rsOwnerRef := meta_v1.GetControllerOf(&rs.ObjectMeta)
-					if rsOwnerRef != nil && rsOwnerRef.Name == criteria.WorkloadName && rsOwnerRef.Kind == discoveredControllerType {
-						w.ParseReplicaSetParent(&rs, criteria.WorkloadName, discoveredControllerType)
+					if rsOwnerRef != nil && rsOwnerRef.Name == criteria.WorkloadName && rsOwnerRef.Kind == discoveredControllerGVK.Kind {
+						w.ParseReplicaSetParent(&rs, criteria.WorkloadName, discoveredControllerGVK)
 					} else {
 						continue
 					}
@@ -1862,10 +1904,10 @@ func (in *WorkloadService) fetchWorkload(ctx context.Context, criteria WorkloadC
 			// 2. If no pods we're found for a ReplicaSet type, it's possible the controller
 			//    is managing the pods itself i.e. the pod's have an owner ref directly to the controller type.
 			if len(cPods) == 0 {
-				cPods = kubernetes.FilterPodsByController(criteria.WorkloadName, discoveredControllerType, pods)
+				cPods = kubernetes.FilterPodsByController(criteria.WorkloadName, discoveredControllerGVK, pods)
 				if len(cPods) > 0 {
-					w.ParsePods(criteria.WorkloadName, discoveredControllerType, cPods)
-					log.Debugf("Workload %s of type %s has not a ReplicaSet as a child controller, it may need a revisit", criteria.WorkloadName, discoveredControllerType)
+					w.ParsePods(criteria.WorkloadName, discoveredControllerGVK, cPods)
+					log.Debugf("Workload %s of type %s has not a ReplicaSet as a child controller, it may need a revisit", criteria.WorkloadName, discoveredControllerGVK)
 				}
 			}
 
@@ -1998,7 +2040,7 @@ func (in *WorkloadService) getWaypointsForWorkload(ctx context.Context, namespac
 	for _, waypoint := range waypoints {
 		// At the moment, it is not possible to have the waypoint in a different namespace
 		// This is expected to change in future releases
-		wkd, err := in.fetchWorkload(ctx, WorkloadCriteria{Cluster: workload.Cluster, Namespace: namespace, WorkloadName: waypoint.Name, WorkloadType: ""})
+		wkd, err := in.fetchWorkload(ctx, WorkloadCriteria{Cluster: workload.Cluster, Namespace: namespace, WorkloadName: waypoint.Name, WorkloadGVK: schema.GroupVersionKind{}})
 		if err != nil {
 			log.Debugf("getWaypointsForWorkload: Error fetching workloads %s", err.Error())
 			return nil
@@ -2048,7 +2090,7 @@ func (in *WorkloadService) listWaypointWorkloads(ctx context.Context, name, clus
 	return workloadslist
 }
 
-func (in *WorkloadService) updateWorkload(ctx context.Context, cluster string, namespace string, workloadName string, workloadType string, jsonPatch string, patchType string) error {
+func (in *WorkloadService) updateWorkload(ctx context.Context, cluster string, namespace string, workloadName string, workloadGVK schema.GroupVersionKind, jsonPatch string, patchType string) error {
 	// Check if user has access to the namespace (RBAC) in cache scenarios and/or
 	// if namespace is accessible from Kiali (Deployment.AccessibleNamespaces)
 	if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil {
@@ -2060,51 +2102,51 @@ func (in *WorkloadService) updateWorkload(ctx context.Context, cluster string, n
 		return fmt.Errorf("user client for cluster [%s] not found", cluster)
 	}
 
-	workloadTypes := []string{
-		kubernetes.DeploymentType,
-		kubernetes.ReplicaSetType,
-		kubernetes.ReplicationControllerType,
-		kubernetes.DeploymentConfigType,
-		kubernetes.StatefulSetType,
-		kubernetes.JobType,
-		kubernetes.CronJobType,
-		kubernetes.PodType,
-		kubernetes.DaemonSetType,
+	workloadGVKs := []schema.GroupVersionKind{
+		kubernetes.Deployments,
+		kubernetes.ReplicaSets,
+		kubernetes.ReplicationControllers,
+		kubernetes.DeploymentConfigs,
+		kubernetes.StatefulSets,
+		kubernetes.Jobs,
+		kubernetes.CronJobs,
+		kubernetes.Pods,
+		kubernetes.DaemonSets,
 	}
 
-	// workloadType is an optional parameter used to optimize the workload type fetch
+	// workloadGVK is an optional parameter used to optimize the workload type fetch
 	// By default workloads use only the "name" but not the pair "name,type".
-	if workloadType != "" {
+	if workloadGVK.Kind != "" {
 		found := false
-		for _, wt := range workloadTypes {
-			if workloadType == wt {
+		for _, wt := range workloadGVKs {
+			if workloadGVK.Kind == wt.Kind {
 				found = true
 				break
 			}
 		}
 		if found {
-			workloadTypes = []string{workloadType}
+			workloadGVKs = []schema.GroupVersionKind{workloadGVK}
 		}
 	}
 
 	wg := sync.WaitGroup{}
-	wg.Add(len(workloadTypes))
-	errChan := make(chan error, len(workloadTypes))
+	wg.Add(len(workloadGVKs))
+	errChan := make(chan error, len(workloadGVKs))
 
-	for _, workloadType := range workloadTypes {
-		go func(wkType string) {
+	for _, workloadGVK := range workloadGVKs {
+		go func(wkGVK schema.GroupVersionKind) {
 			defer wg.Done()
 			var err error
-			if in.isWorkloadIncluded(wkType) {
-				err = userClient.UpdateWorkload(namespace, workloadName, wkType, jsonPatch, patchType)
+			if in.isWorkloadIncluded(wkGVK.Kind) {
+				err = userClient.UpdateWorkload(namespace, workloadName, wkGVK, jsonPatch, patchType)
 			}
 			if err != nil {
 				if !errors.IsNotFound(err) {
-					log.Errorf("Error fetching %s per namespace %s and name %s: %s", wkType, namespace, workloadName, err)
+					log.Errorf("Error fetching %s per namespace %s and name %s: %s", wkGVK, namespace, workloadName, err)
 					errChan <- err
 				}
 			}
-		}(workloadType)
+		}(workloadGVK)
 	}
 
 	wg.Wait()
@@ -2160,7 +2202,7 @@ func (in *WorkloadService) GetWorkloadAppName(ctx context.Context, cluster, name
 	)
 	defer end()
 
-	wkd, err := in.fetchWorkload(ctx, WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: workload, WorkloadType: ""})
+	wkd, err := in.fetchWorkload(ctx, WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: workload, WorkloadGVK: schema.GroupVersionKind{Group: "", Version: "", Kind: ""}})
 	if err != nil {
 		return "", err
 	}
diff --git a/config/config.go b/config/config.go
index daf4d40ba..bd2b58876 100644
--- a/config/config.go
+++ b/config/config.go
@@ -254,6 +254,8 @@ type GrafanaVariablesConfig struct {
 }
 
 type TempoConfig struct {
+	CacheCapacity int    `yaml:"cache_capacity" json:"cache_capacity,omitempty"`
+	CacheEnabled  bool   `yaml:"cache_enabled" json:"cache_enabled,omitempty"`
 	DatasourceUID string `yaml:"datasource_uid" json:"datasource_uid,omitempty"`
 	OrgID         string `yaml:"org_id" json:"org_id,omitempty"`
 	URLFormat     string `yaml:"url_format" json:"url_format,omitempty"`
@@ -395,7 +397,8 @@ type AuthConfig struct {
 
 // OpenShiftConfig contains specific configuration for authentication when on OpenShift
 type OpenShiftConfig struct {
-	CAFile string `yaml:"ca_file,omitempty"`
+	CAFile                string `yaml:"ca_file,omitempty"`
+	InsecureSkipVerifyTLS bool   `yaml:"insecure_skip_verify_tls,omitempty"`
 }
 
 // OpenIdConfig contains specific configuration for authentication using an OpenID provider
@@ -678,6 +681,9 @@ func NewConfig() (c *Config) {
 				Scopes:                  []string{"openid", "profile", "email"},
 				UsernameClaim:           "sub",
 			},
+			OpenShift: OpenShiftConfig{
+				InsecureSkipVerifyTLS: false,
+			},
 		},
 		CustomDashboards: dashboards.GetBuiltInMonitoringDashboards(),
 		Deployment: DeploymentConfig{
@@ -753,17 +759,20 @@ func NewConfig() (c *Config) {
 				Auth: Auth{
 					Type: AuthTypeNone,
 				},
-				CustomHeaders:        map[string]string{},
-				Enabled:              false,
-				ExternalURL:          "",
-				GrpcPort:             9095,
-				InternalURL:          "http://tracing.istio-system:16685/jaeger",
-				IsCore:               false,
-				Provider:             JaegerProvider,
-				NamespaceSelector:    true,
-				QueryScope:           map[string]string{},
-				QueryTimeout:         5,
-				TempoConfig:          TempoConfig{},
+				CustomHeaders:     map[string]string{},
+				Enabled:           false,
+				ExternalURL:       "",
+				GrpcPort:          9095,
+				InternalURL:       "http://tracing.istio-system:16685/jaeger",
+				IsCore:            false,
+				Provider:          JaegerProvider,
+				NamespaceSelector: true,
+				QueryScope:        map[string]string{},
+				QueryTimeout:      5,
+				TempoConfig: TempoConfig{
+					CacheCapacity: 200,
+					CacheEnabled:  true,
+				},
 				UseGRPC:              true,
 				WhiteListIstioSystem: []string{"jaeger-query", "istio-ingressgateway"},
 			},
diff --git a/frontend/package.json b/frontend/package.json
index 2db6cee5d..02c3bd436 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@kiali/kiali-ui",
-  "version": "2.2.0",
+  "version": "2.3.0",
   "description": "React UI for [Kiali](https://github.com/kiali/kiali).",
   "keywords": [
     "istio service mesh",
@@ -101,7 +101,7 @@
     "react-flexview": "4.0.3",
     "react-i18next": "^11.7.3",
     "react-redux": "7.2.2",
-    "react-resize-detector": "3.4.0",
+    "react-resize-detector": "9.1.1",
     "react-router": "5.3.x",
     "react-router-dom": "5.3.x",
     "react-router-dom-v5-compat": "^6.11.2",
diff --git a/frontend/public/locales/zh/translation.json b/frontend/public/locales/zh/translation.json
index 46be2683a..2a121861d 100644
--- a/frontend/public/locales/zh/translation.json
+++ b/frontend/public/locales/zh/translation.json
@@ -316,6 +316,7 @@
   "This chart shows memory consumption for the istiod {{memoryMetricSource}}": "This chart shows memory consumption for the istiod {{memoryMetricSource}}",
   "This rule is not accessible.": "该规则将无法生效。",
   "This shows the meshConfig.outboundTrafficPolicy.mode setting. It controls the sidecar handling of                 requests to external services (services not defined in Istio’s internal service registry). When                 set to ALLOW_ANY the Istio proxy performs a passthrough to the unknown services. When set to REGISTRY_ONLY                 the Istio proxy blocks requets to hosts without a defined HTTP service or service entry.": "This shows the meshConfig.outboundTrafficPolicy.mode setting. It controls the sidecar handling of                 requests to external services (services not defined in Istio’s internal service registry). When                 set to ALLOW_ANY the Istio proxy performs a passthrough to the unknown services. When set to REGISTRY_ONLY                 the Istio proxy blocks requets to hosts without a defined HTTP service or service entry.",
+  "This type of workload is read-only": "This type of workload is read-only",
   "Time duration": "Time duration",
   "Time range": "Time range",
   "Time Range": "Time Range",
diff --git a/frontend/src/components/Envoy/tables/ClusterTable.tsx b/frontend/src/components/Envoy/tables/ClusterTable.tsx
index f58a90a73..4891915df 100644
--- a/frontend/src/components/Envoy/tables/ClusterTable.tsx
+++ b/frontend/src/components/Envoy/tables/ClusterTable.tsx
@@ -13,7 +13,7 @@ import { kialiStyle } from 'styles/StyleUtils';
 import { isParentKiosk } from '../../Kiosk/KioskActions';
 import { SortableTh } from 'components/Table/SimpleTable';
 import { t } from 'utils/I18nUtils';
-import { dicIstioTypeToGVK } from '../../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../../types/IstioConfigList';
 
 export class ClusterTable implements SummaryTable {
   kiosk: string;
@@ -271,7 +271,7 @@ export class ClusterTable implements SummaryTable {
               value.subset,
               value.direction,
               value.type,
-              istioConfigLink(value.destination_rule, dicIstioTypeToGVK['DestinationRule'])
+              istioConfigLink(value.destination_rule, dicTypeToGVK[gvkType.DestinationRule])
             ]
           };
         }
diff --git a/frontend/src/components/Envoy/tables/RouteTable.tsx b/frontend/src/components/Envoy/tables/RouteTable.tsx
index 627f0d33a..d5ed1a331 100644
--- a/frontend/src/components/Envoy/tables/RouteTable.tsx
+++ b/frontend/src/components/Envoy/tables/RouteTable.tsx
@@ -12,7 +12,7 @@ import { KialiIcon } from 'config/KialiIcon';
 import { kialiStyle } from 'styles/StyleUtils';
 import { isParentKiosk } from '../../Kiosk/KioskActions';
 import { SortableTh } from 'components/Table/SimpleTable';
-import { dicIstioTypeToGVK } from '../../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../../types/IstioConfigList';
 
 export class RouteTable implements SummaryTable {
   kiosk: string;
@@ -187,7 +187,7 @@ export class RouteTable implements SummaryTable {
               summary.name,
               serviceLink(summary.domains, this.namespaces, this.namespace, true, parentKiosk),
               summary.match,
-              istioConfigLink(summary.virtual_service, dicIstioTypeToGVK['VirtualService'])
+              istioConfigLink(summary.virtual_service, dicTypeToGVK[gvkType.VirtualService])
             ]
           };
         }
diff --git a/frontend/src/components/IstioWizards/ServiceWizard.tsx b/frontend/src/components/IstioWizards/ServiceWizard.tsx
index d23dd0d3c..5204a03cf 100644
--- a/frontend/src/components/IstioWizards/ServiceWizard.tsx
+++ b/frontend/src/components/IstioWizards/ServiceWizard.tsx
@@ -73,7 +73,7 @@ import { ConfigPreviewItem, IstioConfigPreview } from 'components/IstioConfigPre
 import { KialiIcon } from '../../config/KialiIcon';
 import { ApiResponse } from 'types/Api';
 import { t } from 'utils/I18nUtils';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 import { getGVKTypeString } from '../../utils/IstioConfigUtils';
 
 const emptyServiceWizardState = (fqdnServiceName: string): ServiceWizardState => {
@@ -371,7 +371,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
           promises.push(
             API.createIstioConfigDetail(
               this.props.namespace,
-              dicIstioTypeToGVK['Gateway'],
+              dicTypeToGVK[gvkType.Gateway],
               JSON.stringify(gw),
               this.props.cluster
             )
@@ -382,7 +382,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
           promises.push(
             API.createIstioConfigDetail(
               this.props.namespace,
-              dicIstioTypeToGVK['K8sGateway'],
+              dicTypeToGVK[gvkType.K8sGateway],
               JSON.stringify(k8sgateway),
               this.props.cluster
             )
@@ -394,7 +394,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.updateIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['DestinationRule'],
+                dicTypeToGVK[gvkType.DestinationRule],
                 dr.metadata.name,
                 JSON.stringify(dr),
                 this.props.cluster
@@ -406,7 +406,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.updateIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['VirtualService'],
+                dicTypeToGVK[gvkType.VirtualService],
                 vs.metadata.name,
                 JSON.stringify(vs),
                 this.props.cluster
@@ -418,7 +418,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.updateIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['K8sHTTPRoute'],
+                dicTypeToGVK[gvkType.K8sHTTPRoute],
                 k8shttproute.metadata.name,
                 JSON.stringify(k8shttproute),
                 this.props.cluster
@@ -430,7 +430,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.updateIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['K8sGRPCRoute'],
+                dicTypeToGVK[gvkType.K8sGRPCRoute],
                 k8sgrpcroute.metadata.name,
                 JSON.stringify(k8sgrpcroute),
                 this.props.cluster
@@ -447,7 +447,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.createIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['DestinationRule'],
+                dicTypeToGVK[gvkType.DestinationRule],
                 JSON.stringify(dr),
                 this.props.cluster
               )
@@ -458,7 +458,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.createIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['VirtualService'],
+                dicTypeToGVK[gvkType.VirtualService],
                 JSON.stringify(vs),
                 this.props.cluster
               )
@@ -469,7 +469,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.createIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['K8sHTTPRoute'],
+                dicTypeToGVK[gvkType.K8sHTTPRoute],
                 JSON.stringify(k8shttproute),
                 this.props.cluster
               )
@@ -480,7 +480,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.createIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['K8sGRPCRoute'],
+                dicTypeToGVK[gvkType.K8sGRPCRoute],
                 JSON.stringify(k8sgrpcroute),
                 this.props.cluster
               )
@@ -491,7 +491,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
             promises.push(
               API.createIstioConfigDetail(
                 this.props.namespace,
-                dicIstioTypeToGVK['PeerAuthentication'],
+                dicTypeToGVK[gvkType.PeerAuthentication],
                 JSON.stringify(pa),
                 this.props.cluster
               )
@@ -539,7 +539,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
         promises.push(
           API.createIstioConfigDetail(
             this.props.namespace,
-            dicIstioTypeToGVK['PeerAuthentication'],
+            dicTypeToGVK[gvkType.PeerAuthentication],
             JSON.stringify(pa),
             this.props.cluster
           )
@@ -548,7 +548,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
         promises.push(
           API.updateIstioConfigDetail(
             this.props.namespace,
-            dicIstioTypeToGVK['PeerAuthentication'],
+            dicTypeToGVK[gvkType.PeerAuthentication],
             dr.metadata.name,
             JSON.stringify(pa),
             this.props.cluster
@@ -559,7 +559,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
       promises.push(
         API.deleteIstioConfigDetail(
           this.props.namespace,
-          dicIstioTypeToGVK['PeerAuthentication'],
+          dicTypeToGVK[gvkType.PeerAuthentication],
           dr.metadata.name,
           this.props.cluster
         )
@@ -756,13 +756,17 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
   };
 
   onConfirmPreview = (items: ConfigPreviewItem[]): void => {
-    const dr = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString('DestinationRule'))[0];
-    const gw = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString('Gateway'))[0];
-    const k8sgateway = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString('K8sGateway'))[0];
-    const pa = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString('PeerAuthentication'))[0];
-    const vs = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString('VirtualService'))[0];
-    const k8shttproute = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString('K8sHTTPRoute'))[0];
-    const k8sgrpcroute = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString('K8sGRPCRoute'))[0];
+    const dr = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString(gvkType.DestinationRule))[0];
+    const gw = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString(gvkType.Gateway))[0];
+    const k8sgateway = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString(gvkType.K8sGateway))[0];
+    const pa = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString(gvkType.PeerAuthentication))[0];
+    const vs = items.filter(it => getGVKTypeString(it.objectGVK) === getGVKTypeString(gvkType.VirtualService))[0];
+    const k8shttproute = items.filter(
+      it => getGVKTypeString(it.objectGVK) === getGVKTypeString(gvkType.K8sHTTPRoute)
+    )[0];
+    const k8sgrpcroute = items.filter(
+      it => getGVKTypeString(it.objectGVK) === getGVKTypeString(gvkType.K8sGRPCRoute)
+    )[0];
 
     const previews: WizardPreviews = {
       dr: dr ? (dr.items[0] as DestinationRule) : undefined,
@@ -783,19 +787,19 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
     if (this.state.previews) {
       if (this.state.previews.dr) {
         items.push({
-          objectGVK: dicIstioTypeToGVK['DestinationRule'],
+          objectGVK: dicTypeToGVK[gvkType.DestinationRule],
           items: [this.state.previews.dr],
           title: 'Destination Rule'
         });
       }
 
       if (this.state.previews.gw) {
-        items.push({ objectGVK: dicIstioTypeToGVK['Gateway'], items: [this.state.previews.gw], title: 'Gateway' });
+        items.push({ objectGVK: dicTypeToGVK[gvkType.Gateway], items: [this.state.previews.gw], title: 'Gateway' });
       }
 
       if (this.state.previews.k8sgateway) {
         items.push({
-          objectGVK: dicIstioTypeToGVK['K8sGateway'],
+          objectGVK: dicTypeToGVK[gvkType.K8sGateway],
           items: [this.state.previews.k8sgateway],
           title: 'K8s Gateway'
         });
@@ -803,7 +807,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
 
       if (this.state.previews.k8shttproute) {
         items.push({
-          objectGVK: dicIstioTypeToGVK['K8sHTTPRoute'],
+          objectGVK: dicTypeToGVK[gvkType.K8sHTTPRoute],
           items: [this.state.previews.k8shttproute],
           title: 'K8s HTTPRoute'
         });
@@ -811,7 +815,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
 
       if (this.state.previews.k8sgrpcroute) {
         items.push({
-          objectGVK: dicIstioTypeToGVK['K8sGRPCRoute'],
+          objectGVK: dicTypeToGVK[gvkType.K8sGRPCRoute],
           items: [this.state.previews.k8sgrpcroute],
           title: 'K8s GRPCRoute'
         });
@@ -819,7 +823,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
 
       if (this.state.previews.pa) {
         items.push({
-          objectGVK: dicIstioTypeToGVK['PeerAuthentication'],
+          objectGVK: dicTypeToGVK[gvkType.PeerAuthentication],
           items: [this.state.previews.pa],
           title: 'Peer Authentication'
         });
@@ -827,7 +831,7 @@ export class ServiceWizard extends React.Component<ServiceWizardProps, ServiceWi
 
       if (this.state.previews.vs) {
         items.push({
-          objectGVK: dicIstioTypeToGVK['VirtualService'],
+          objectGVK: dicTypeToGVK[gvkType.VirtualService],
           items: [this.state.previews.vs],
           title: 'VirtualService'
         });
diff --git a/frontend/src/components/IstioWizards/WizardActions.ts b/frontend/src/components/IstioWizards/WizardActions.ts
index e99235fa5..bbd623147 100644
--- a/frontend/src/components/IstioWizards/WizardActions.ts
+++ b/frontend/src/components/IstioWizards/WizardActions.ts
@@ -11,6 +11,7 @@ import {
   ConnectionPoolSettings,
   DestinationRule,
   Gateway,
+  GroupVersionKind,
   HTTPMatchRequest,
   HTTPRoute,
   HTTPRouteDestination,
@@ -68,6 +69,7 @@ import { ADD, SET, REQ_MOD, RESP_MOD, REQ_RED, REQ_MIR } from './K8sRequestRouti
 import { ANYTHING, PRESENCE } from './RequestRouting/MatchBuilder';
 import { t } from 'utils/I18nUtils';
 import { defaultGatewayLabel, defaultGatewayLabelValue } from 'config/Constants';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 
 export const WIZARD_TRAFFIC_SHIFTING = 'traffic_shifting';
 export const WIZARD_TCP_TRAFFIC_SHIFTING = 'tcp_traffic_shifting';
@@ -2366,7 +2368,7 @@ export const buildNamespaceInjectionPatch = (enable: boolean, remove: boolean, r
   return JSON.stringify(patch);
 };
 
-export const buildWorkloadInjectionPatch = (workloadType: string, enable: boolean, remove: boolean): string => {
+export const buildWorkloadInjectionPatch = (gvk: GroupVersionKind, enable: boolean, remove: boolean): string => {
   const patch = {};
 
   // environments prefer to use the pod label over the annotation
@@ -2375,7 +2377,7 @@ export const buildWorkloadInjectionPatch = (workloadType: string, enable: boolea
   labels[serverConfig.istioAnnotations.istioInjectionAnnotation] = remove ? null : enable ? 'true' : 'false';
   const annotations = {};
   annotations[serverConfig.istioAnnotations.istioInjectionAnnotation] = null;
-  if (workloadType === 'Pod') {
+  if (gvk.Kind === dicTypeToGVK[gvkType.Pod].Kind) {
     patch['labels'] = labels;
     patch['annotations'] = annotations;
   } else {
diff --git a/frontend/src/components/IstioWizards/WorkloadWizardActionsDropdownGroup.tsx b/frontend/src/components/IstioWizards/WorkloadWizardActionsDropdownGroup.tsx
index ae80ea7d5..c2119c61c 100644
--- a/frontend/src/components/IstioWizards/WorkloadWizardActionsDropdownGroup.tsx
+++ b/frontend/src/components/IstioWizards/WorkloadWizardActionsDropdownGroup.tsx
@@ -15,6 +15,8 @@ import { renderDisabledDropdownOption } from 'utils/DropdownUtils';
 import { MessageType } from 'types/MessageCenter';
 import { groupMenuStyle } from 'styles/DropdownStyles';
 import { t } from 'utils/I18nUtils';
+import { getGVKTypeString } from '../../utils/IstioConfigUtils';
+import { gvkType } from '../../types/IstioConfigList';
 
 type Props = {
   actionsLabel: boolean;
@@ -34,11 +36,11 @@ export const WorkloadWizardActionsDropdownGroup: React.FunctionComponent<Props>
       case WIZARD_REMOVE_AUTO_INJECTION:
         const remove = key === WIZARD_REMOVE_AUTO_INJECTION;
         const enable = key === WIZARD_ENABLE_AUTO_INJECTION;
-        const jsonInjectionPatch = buildWorkloadInjectionPatch(props.workload.type, enable, remove);
+        const jsonInjectionPatch = buildWorkloadInjectionPatch(props.workload.gvk, enable, remove);
         API.updateWorkload(
           props.namespace,
           props.workload.name,
-          props.workload.type,
+          props.workload.gvk,
           jsonInjectionPatch,
           undefined,
           props.workload.cluster
@@ -139,7 +141,7 @@ export const WorkloadWizardActionsDropdownGroup: React.FunctionComponent<Props>
   }
 
   // Annotations
-  if (props.annotations && props.workload.type === 'Deployment') {
+  if (props.annotations && getGVKTypeString(props.workload.gvk) === getGVKTypeString(gvkType.Deployment)) {
     const annotationsAction = (
       <DropdownItem
         data-test={WIZARD_EDIT_ANNOTATIONS}
diff --git a/frontend/src/components/IstioWizards/WorkloadWizardDropdown.tsx b/frontend/src/components/IstioWizards/WorkloadWizardDropdown.tsx
index fc8c69b4d..76f29d59c 100644
--- a/frontend/src/components/IstioWizards/WorkloadWizardDropdown.tsx
+++ b/frontend/src/components/IstioWizards/WorkloadWizardDropdown.tsx
@@ -17,6 +17,8 @@ import { WizardLabels } from './WizardLabels';
 import { renderDisabledDropdownOption } from 'utils/DropdownUtils';
 import { WorkloadWizardActionsDropdownGroup } from './WorkloadWizardActionsDropdownGroup';
 import { t } from 'utils/I18nUtils';
+import { getGVKTypeString, isGVKSupported } from '../../utils/IstioConfigUtils';
+import { gvkType } from '../../types/IstioConfigList';
 
 interface Props {
   namespace: string;
@@ -46,7 +48,7 @@ export const WorkloadWizardDropdown: React.FC<Props> = (props: Props) => {
     API.updateWorkload(
       props.namespace,
       props.workload.name,
-      props.workload.type,
+      props.workload.gvk,
       jsonInjectionPatch,
       'json',
       props.workload.cluster
@@ -92,7 +94,9 @@ export const WorkloadWizardDropdown: React.FC<Props> = (props: Props) => {
     //  istio actions
     (serverConfig.kialiFeatureFlags.istioInjectionAction && !props.workload.isAmbient) ||
     // annotations
-    props.workload.type === 'Deployment';
+    getGVKTypeString(props.workload.gvk) === getGVKTypeString(gvkType.Deployment);
+
+  const supportedWorkload = isGVKSupported(props.workload.gvk);
 
   const dropdown = (
     <Dropdown
@@ -105,7 +109,7 @@ export const WorkloadWizardDropdown: React.FC<Props> = (props: Props) => {
           onClick={() => onActionsToggle(!isActionsOpen)}
           data-test="workload-actions-toggle"
           isExpanded={isActionsOpen}
-          isDisabled={!validActions}
+          isDisabled={!validActions || !supportedWorkload}
         >
           {t('Actions')}
         </MenuToggle>
@@ -144,6 +148,13 @@ export const WorkloadWizardDropdown: React.FC<Props> = (props: Props) => {
             t('User does not have permission on this Workload'),
             dropdown
           )
+        : !supportedWorkload
+        ? renderDisabledDropdownOption(
+            'tooltip_wizard_actions',
+            TooltipPosition.top,
+            t('This type of workload is read-only'),
+            dropdown
+          )
         : dropdown}
     </>
   );
diff --git a/frontend/src/components/VirtualList/Config.ts b/frontend/src/components/VirtualList/Config.ts
index da23244d6..cc20e678b 100644
--- a/frontend/src/components/VirtualList/Config.ts
+++ b/frontend/src/components/VirtualList/Config.ts
@@ -4,14 +4,14 @@ import { AppWorkload } from 'types/App';
 import { isWaypoint } from 'helpers/LabelFilterHelper';
 import { WorkloadListItem, Workload } from '../../types/Workload';
 import { ServiceListItem } from '../../types/ServiceList';
-import { dicIstioTypeToGVK, IstioConfigItem } from '../../types/IstioConfigList';
+import { dicTypeToGVK, IstioConfigItem } from '../../types/IstioConfigList';
 import * as Renderers from './Renderers';
 import { Health } from '../../types/Health';
 import { isIstioNamespace } from 'config/ServerConfig';
 import { NamespaceInfo } from '../../types/NamespaceInfo';
 import { StatefulFiltersRef } from '../Filters/StatefulFilters';
 import { PFBadges, PFBadgeType } from '../../components/Pf/PfBadges';
-import { getGVKTypeString } from '../../utils/IstioConfigUtils';
+import { getGVKTypeString, kindToStringIncludeK8s } from '../../utils/IstioConfigUtils';
 
 export type SortResource = AppListItem | WorkloadListItem | ServiceListItem;
 export type TResource = SortResource | IstioConfigItem;
@@ -201,8 +201,8 @@ const istioType: ResourceType<IstioConfigItem> = {
 
 export const GVKToBadge: { [gvk: string]: PFBadgeType } = {};
 
-Object.keys(dicIstioTypeToGVK).forEach(key => {
-  GVKToBadge[getGVKTypeString(key)] = PFBadges[key];
+Object.values(dicTypeToGVK).forEach(value => {
+  GVKToBadge[getGVKTypeString(value)] = PFBadges[kindToStringIncludeK8s(value.Group, value.Kind)];
 });
 
 export type Resource = {
diff --git a/frontend/src/components/VirtualList/Renderers.tsx b/frontend/src/components/VirtualList/Renderers.tsx
index e7c01a545..b76819be3 100644
--- a/frontend/src/components/VirtualList/Renderers.tsx
+++ b/frontend/src/components/VirtualList/Renderers.tsx
@@ -443,7 +443,7 @@ export const workloadType: Renderer<WorkloadListItem> = (item: WorkloadListItem)
       key={`VirtuaItem_WorkloadType_${item.namespace}_${item.name}`}
       style={{ verticalAlign: 'middle' }}
     >
-      {item.type}
+      {item.gvk.Kind}
     </Td>
   );
 };
diff --git a/frontend/src/components/VirtualList/VirtualItem.tsx b/frontend/src/components/VirtualList/VirtualItem.tsx
index e4d233ecc..72d307c42 100644
--- a/frontend/src/components/VirtualList/VirtualItem.tsx
+++ b/frontend/src/components/VirtualList/VirtualItem.tsx
@@ -68,6 +68,8 @@ export class VirtualItem extends React.Component<VirtualItemProps, VirtualItemSt
         ? `_${item.type}`
         : 'kind' in item && 'apiVersion' in item
         ? `_${kindToStringIncludeK8s(item.apiVersion, item.kind)}`
+        : 'gvk' in item
+        ? `_${item.gvk.Kind}`
         : '';
     // End result looks like: VirtualItem_Clusterwest_Nsbookinfo_networking.istio.io.v1.Gateway_bookinfo-gateway
 
diff --git a/frontend/src/hooks/services.ts b/frontend/src/hooks/services.ts
index 4c2a17b5b..7ca9acaa4 100644
--- a/frontend/src/hooks/services.ts
+++ b/frontend/src/hooks/services.ts
@@ -8,6 +8,7 @@ import { DecoratedGraphNodeData, NodeType } from '../types/Graph';
 import * as AlertUtils from '../utils/AlertUtils';
 import { ApiError } from 'types/Api';
 import { getGVKTypeString } from '../utils/IstioConfigUtils';
+import { gvkType } from '../types/IstioConfigList';
 
 type ServiceDetailHookInfo = readonly [
   ServiceDetailsInfo | null,
@@ -38,10 +39,10 @@ const useServiceDetail = (
 
     setIsLoading(true); // Mark as loading
     let getDetailPromise = API.getServiceDetail(namespace, serviceName, false, cluster, duration);
-    let getGwPromise = API.getAllIstioConfigs([getGVKTypeString('Gateway')], false, '', '', cluster);
+    let getGwPromise = API.getAllIstioConfigs([getGVKTypeString(gvkType.Gateway)], false, '', '', cluster);
     let getPeerAuthsPromise = API.getIstioConfig(
       namespace,
-      [getGVKTypeString('PeerAuthentication')],
+      [getGVKTypeString(gvkType.PeerAuthentication)],
       false,
       '',
       '',
@@ -53,10 +54,12 @@ const useServiceDetail = (
       .then(results => {
         setServiceDetails(results[0]);
         setGateways(
-          getGatewaysAsList(filterAutogeneratedGateways(results[1].data.resources[getGVKTypeString('Gateway')])).sort()
+          getGatewaysAsList(
+            filterAutogeneratedGateways(results[1].data.resources[getGVKTypeString(gvkType.Gateway)])
+          ).sort()
         );
 
-        setPeerAuthentications(results[2].data.resources[getGVKTypeString('PeerAuthentication')]);
+        setPeerAuthentications(results[2].data.resources[getGVKTypeString(gvkType.PeerAuthentication)]);
         setFetchError(null);
         setIsLoading(false);
       })
diff --git a/frontend/src/pages/Graph/SummaryLink.tsx b/frontend/src/pages/Graph/SummaryLink.tsx
index d14a6cfe2..eefbf3e1b 100644
--- a/frontend/src/pages/Graph/SummaryLink.tsx
+++ b/frontend/src/pages/Graph/SummaryLink.tsx
@@ -17,7 +17,7 @@ import { KialiPageLink } from 'components/Link/KialiPageLink';
 import { kialiStyle } from 'styles/StyleUtils';
 import { t } from 'utils/I18nUtils';
 import { ExternalLinkAltIcon } from '@patternfly/react-icons';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 
 interface LinkInfo {
   displayName: string;
@@ -186,7 +186,7 @@ export const getLink = (
         break;
       case NodeType.SERVICE:
         if (nodeData.isServiceEntry) {
-          const seGVK = dicIstioTypeToGVK['ServiceEntry'];
+          const seGVK = dicTypeToGVK[gvkType.ServiceEntry];
           link = `/namespaces/${encodeURIComponent(nodeData.isServiceEntry.namespace)}/istio/${seGVK.Group}/${
             seGVK.Version
           }/${seGVK.Kind}/${encodeURIComponent(service!)}`;
diff --git a/frontend/src/pages/Graph/SummaryPanelNode.tsx b/frontend/src/pages/Graph/SummaryPanelNode.tsx
index 530b9bf30..3f9d936ad 100644
--- a/frontend/src/pages/Graph/SummaryPanelNode.tsx
+++ b/frontend/src/pages/Graph/SummaryPanelNode.tsx
@@ -40,7 +40,7 @@ import { groupMenuStyle, kebabToggleStyle } from 'styles/DropdownStyles';
 import { isMultiCluster, serverConfig } from '../../config';
 import { panelBodyStyle, panelHeadingStyle, panelStyle } from './SummaryPanelStyle';
 import { renderWaypoint } from '../../components/DetailDescription/DetailDescription';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 
 type SummaryPanelNodeState = {
   isActionOpen: boolean;
@@ -256,7 +256,7 @@ export class SummaryPanelNodeComponent extends React.Component<SummaryPanelNodeC
     if (!nodeData.hasWorkloadEntry) {
       return <div>{renderBadgedLink(nodeData, NodeType.WORKLOAD)}</div>;
     }
-    const weGVK = dicIstioTypeToGVK['WorkloadEntry'];
+    const weGVK = dicTypeToGVK[gvkType.WorkloadEntry];
 
     const workloadEntryLinks = nodeData.hasWorkloadEntry.map(we => (
       <div>
diff --git a/frontend/src/pages/GraphPF/GraphPF.tsx b/frontend/src/pages/GraphPF/GraphPF.tsx
index 1b6c2e74e..2ef9ce283 100644
--- a/frontend/src/pages/GraphPF/GraphPF.tsx
+++ b/frontend/src/pages/GraphPF/GraphPF.tsx
@@ -82,7 +82,7 @@ const DEFAULT_NODE_SIZE = 40;
 const ZOOM_IN = 4 / 3;
 const ZOOM_OUT = 3 / 4;
 
-export const FIT_PADDING = 80;
+export const FIT_PADDING = 90;
 
 export enum LayoutName {
   BreadthFirst = 'BreadthFirst',
@@ -322,13 +322,14 @@ const TopologyContent: React.FC<{
     }
 
     if (layoutInProgress !== LayoutType.LayoutNoFit) {
-      // On a resize, delay fit to ensure that the canvas size updates before the fit
+      controller.getGraph().fit(FIT_PADDING);
+
+      // On a resize, perform a delayed second fit, this one is performed [hopefully] after
+      // the canvas size is updated (which needs to happen in the underlying PFT code)
       if (layoutInProgress === LayoutType.Resize) {
         setTimeout(() => {
           controller.getGraph().fit(FIT_PADDING);
-        }, 250);
-      } else {
-        controller.getGraph().fit(FIT_PADDING);
+        }, 500);
       }
     }
 
@@ -886,6 +887,10 @@ const TopologyContent: React.FC<{
                   zoomOutCallback: () => {
                     controller && controller.getGraph().scaleBy(ZOOM_OUT);
                   },
+                  // currently unused
+                  fitToScreenCallback: () => {
+                    controller.getGraph().fit(FIT_PADDING);
+                  },
                   resetViewCallback: () => {
                     graphLayout(controller, LayoutType.Layout);
                   },
diff --git a/frontend/src/pages/GraphPF/components/stylesComponentFactory.tsx b/frontend/src/pages/GraphPF/components/stylesComponentFactory.tsx
index 744ddf84b..92631a2b6 100644
--- a/frontend/src/pages/GraphPF/components/stylesComponentFactory.tsx
+++ b/frontend/src/pages/GraphPF/components/stylesComponentFactory.tsx
@@ -35,6 +35,7 @@ import { ServiceDetailsInfo } from 'types/ServiceInfo';
 import { kialiStyle } from 'styles/StyleUtils';
 import { DropdownGroup, DropdownItem } from '@patternfly/react-core';
 import { getGVKTypeString } from '../../../utils/IstioConfigUtils';
+import { gvkType } from '../../../types/IstioConfigList';
 
 type ContextMenuOptionPF = ContextMenuOption & {
   altClickHandler?: (node: GraphElement, kiosk: string) => void;
@@ -121,11 +122,18 @@ const nodeContextMenu = (node: GraphElement, kiosk: string): Promise<React.React
   );
   const getGwPromise = promises.register(
     'getGwPromise',
-    API.getAllIstioConfigs([getGVKTypeString('Gateway')], false, '', '', nodeData.cluster)
+    API.getAllIstioConfigs([getGVKTypeString(gvkType.Gateway)], false, '', '', nodeData.cluster)
   );
   const getPeerAuthsPromise = promises.register(
     'getPeerAuthsPromise',
-    API.getIstioConfig(nodeData.namespace, [getGVKTypeString('PeerAuthentication')], false, '', '', nodeData.cluster)
+    API.getIstioConfig(
+      nodeData.namespace,
+      [getGVKTypeString(gvkType.PeerAuthentication)],
+      false,
+      '',
+      '',
+      nodeData.cluster
+    )
   );
 
   return new Promise<React.ReactElement[]>((resolve, reject) => {
@@ -133,9 +141,9 @@ const nodeContextMenu = (node: GraphElement, kiosk: string): Promise<React.React
       .then(results => {
         const serviceDetails = results[0];
         const gateways = getGatewaysAsList(
-          filterAutogeneratedGateways(results[1].data.resources[getGVKTypeString('Gateway')])
+          filterAutogeneratedGateways(results[1].data.resources[getGVKTypeString(gvkType.Gateway)])
         ).sort();
-        const peerAuthentications = results[2].data.resources[getGVKTypeString('PeerAuthentication')];
+        const peerAuthentications = results[2].data.resources[getGVKTypeString(gvkType.PeerAuthentication)];
 
         items.push(
           <ServiceWizardActionsDropdownGroup
diff --git a/frontend/src/pages/IstioConfigList/IstioConfigListPage.tsx b/frontend/src/pages/IstioConfigList/IstioConfigListPage.tsx
index a1e1f3036..2d0cb20b6 100644
--- a/frontend/src/pages/IstioConfigList/IstioConfigListPage.tsx
+++ b/frontend/src/pages/IstioConfigList/IstioConfigListPage.tsx
@@ -7,6 +7,7 @@ import {
   filterByConfigValidation,
   filterByName,
   filterByNamespaces,
+  gvkType,
   IstioConfigItem,
   toIstioItems
 } from '../../types/IstioConfigList';
@@ -106,7 +107,7 @@ class IstioConfigListPageComponent extends FilterComponent.Component<
     const namespacesSelected = this.props.activeNamespaces!.map(item => item.name);
 
     const istioTypeFilters = getFilterSelectedValues(IstioConfigListFilters.istioTypeFilter, activeFilters).map(value =>
-      getGVKTypeString(value)
+      getGVKTypeString(gvkType[value])
     );
 
     const istioNameFilters = getFilterSelectedValues(IstioConfigListFilters.istioNameFilter, activeFilters);
diff --git a/frontend/src/pages/IstioConfigNew/IstioConfigNewPage.tsx b/frontend/src/pages/IstioConfigNew/IstioConfigNewPage.tsx
index 7c925b8c9..2665a6a88 100644
--- a/frontend/src/pages/IstioConfigNew/IstioConfigNewPage.tsx
+++ b/frontend/src/pages/IstioConfigNew/IstioConfigNewPage.tsx
@@ -72,7 +72,7 @@ import { NamespaceDropdown } from '../../components/Dropdown/NamespaceDropdown';
 import { Labels } from '../../components/Label/Labels';
 import { WizardLabels } from '../../components/IstioWizards/WizardLabels';
 import { isParentKiosk, kioskContextMenuAction } from 'components/Kiosk/KioskActions';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 import { getGVKTypeString } from '../../utils/IstioConfigUtils';
 import { GroupVersionKind } from '../../types/IstioObjects';
 
@@ -126,14 +126,14 @@ const editStyle = kialiStyle({
 
 // Used in the Istio Config list Actions
 export const NEW_ISTIO_RESOURCE = [
-  { value: dicIstioTypeToGVK['AuthorizationPolicy'], disabled: false },
-  { value: dicIstioTypeToGVK['Gateway'], disabled: false },
-  { value: dicIstioTypeToGVK['K8sGateway'], disabled: false },
-  { value: dicIstioTypeToGVK['K8sReferenceGrant'], disabled: false },
-  { value: dicIstioTypeToGVK['PeerAuthentication'], disabled: false },
-  { value: dicIstioTypeToGVK['RequestAuthentication'], disabled: false },
-  { value: dicIstioTypeToGVK['ServiceEntry'], disabled: false },
-  { value: dicIstioTypeToGVK['Sidecar'], disabled: false }
+  { value: dicTypeToGVK[gvkType.AuthorizationPolicy], disabled: false },
+  { value: dicTypeToGVK[gvkType.Gateway], disabled: false },
+  { value: dicTypeToGVK[gvkType.K8sGateway], disabled: false },
+  { value: dicTypeToGVK[gvkType.K8sReferenceGrant], disabled: false },
+  { value: dicTypeToGVK[gvkType.PeerAuthentication], disabled: false },
+  { value: dicTypeToGVK[gvkType.RequestAuthentication], disabled: false },
+  { value: dicTypeToGVK[gvkType.ServiceEntry], disabled: false },
+  { value: dicTypeToGVK[gvkType.Sidecar], disabled: false }
 ];
 
 const initState = (): State => ({
@@ -336,7 +336,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
     const items: ConfigPreviewItem[] = [];
     this.props.activeNamespaces.forEach(ns => {
       switch (getGVKTypeString(this.props.objectGVK)) {
-        case getGVKTypeString('AuthorizationPolicy'):
+        case getGVKTypeString(gvkType.AuthorizationPolicy):
           items.push({
             title: 'Authorization Policy',
             objectGVK: this.props.objectGVK,
@@ -351,7 +351,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
             ]
           });
           break;
-        case getGVKTypeString('Gateway'):
+        case getGVKTypeString(gvkType.Gateway):
           items.push({
             title: 'Gateway',
             objectGVK: this.props.objectGVK,
@@ -360,7 +360,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
             ]
           });
           break;
-        case getGVKTypeString('K8sGateway'):
+        case getGVKTypeString(gvkType.K8sGateway):
           items.push({
             title: 'K8s Gateway',
             objectGVK: this.props.objectGVK,
@@ -375,7 +375,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
             ]
           });
           break;
-        case getGVKTypeString('K8sReferenceGrant'):
+        case getGVKTypeString(gvkType.K8sReferenceGrant):
           items.push({
             title: 'K8s Reference Grant',
             objectGVK: this.props.objectGVK,
@@ -390,7 +390,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
             ]
           });
           break;
-        case getGVKTypeString('PeerAuthentication'):
+        case getGVKTypeString(gvkType.PeerAuthentication):
           items.push({
             title: 'Peer Authentication',
             objectGVK: this.props.objectGVK,
@@ -405,7 +405,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
             ]
           });
           break;
-        case getGVKTypeString('RequestAuthentication'):
+        case getGVKTypeString(gvkType.RequestAuthentication):
           items.push({
             title: 'Request Authentication',
             objectGVK: this.props.objectGVK,
@@ -420,7 +420,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
             ]
           });
           break;
-        case getGVKTypeString('ServiceEntry'):
+        case getGVKTypeString(gvkType.ServiceEntry):
           items.push({
             title: 'Service Entry',
             objectGVK: this.props.objectGVK,
@@ -435,7 +435,7 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
             ]
           });
           break;
-        case getGVKTypeString('Sidecar'):
+        case getGVKTypeString(gvkType.Sidecar):
           items.push({
             title: 'Sidecar',
             objectGVK: this.props.objectGVK,
@@ -465,21 +465,21 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
 
   isIstioFormValid = (): boolean => {
     switch (getGVKTypeString(this.props.objectGVK)) {
-      case getGVKTypeString('AuthorizationPolicy'):
+      case getGVKTypeString(gvkType.AuthorizationPolicy):
         return isAuthorizationPolicyStateValid(this.state.authorizationPolicy);
-      case getGVKTypeString('Gateway'):
+      case getGVKTypeString(gvkType.Gateway):
         return isGatewayStateValid(this.state.gateway);
-      case getGVKTypeString('K8sGateway'):
+      case getGVKTypeString(gvkType.K8sGateway):
         return isK8sGatewayStateValid(this.state.k8sGateway);
-      case getGVKTypeString('K8sReferenceGrant'):
+      case getGVKTypeString(gvkType.K8sReferenceGrant):
         return isK8sReferenceGrantStateValid(this.state.k8sReferenceGrant);
-      case getGVKTypeString('PeerAuthentication'):
+      case getGVKTypeString(gvkType.PeerAuthentication):
         return isPeerAuthenticationStateValid(this.state.peerAuthentication);
-      case getGVKTypeString('RequestAuthentication'):
+      case getGVKTypeString(gvkType.RequestAuthentication):
         return isRequestAuthenticationStateValid(this.state.requestAuthentication);
-      case getGVKTypeString('ServiceEntry'):
+      case getGVKTypeString(gvkType.ServiceEntry):
         return isServiceEntryValid(this.state.serviceEntry);
-      case getGVKTypeString('Sidecar'):
+      case getGVKTypeString(gvkType.Sidecar):
         return isSidecarStateValid(this.state.sidecar);
       default:
         return false;
@@ -625,47 +625,47 @@ class IstioConfigNewPageComponent extends React.Component<Props, State> {
               )}
             </FormGroup>
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('AuthorizationPolicy') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.AuthorizationPolicy) && (
               <AuthorizationPolicyForm
                 authorizationPolicy={this.state.authorizationPolicy}
                 onChange={this.onChangeAuthorizationPolicy}
               />
             )}
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('Gateway') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.Gateway) && (
               <GatewayForm gateway={this.state.gateway} onChange={this.onChangeGateway} />
             )}
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('K8sGateway') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.K8sGateway) && (
               <K8sGatewayForm k8sGateway={this.state.k8sGateway} onChange={this.onChangeK8sGateway} />
             )}
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('K8sReferenceGrant') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.K8sReferenceGrant) && (
               <K8sReferenceGrantForm
                 k8sReferenceGrant={this.state.k8sReferenceGrant}
                 onChange={this.onChangeK8sReferenceGrant}
               />
             )}
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('PeerAuthentication') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.PeerAuthentication) && (
               <PeerAuthenticationForm
                 peerAuthentication={this.state.peerAuthentication}
                 onChange={this.onChangePeerAuthentication}
               />
             )}
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('RequestAuthentication') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.RequestAuthentication) && (
               <RequestAuthenticationForm
                 requestAuthentication={this.state.requestAuthentication}
                 onChange={this.onChangeRequestAuthentication}
               />
             )}
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('ServiceEntry') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.ServiceEntry) && (
               <ServiceEntryForm serviceEntry={this.state.serviceEntry} onChange={this.onChangeServiceEntry} />
             )}
 
-            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString('Sidecar') && (
+            {getGVKTypeString(this.props.objectGVK) === getGVKTypeString(gvkType.Sidecar) && (
               <SidecarForm sidecar={this.state.sidecar} onChange={this.onChangeSidecar} />
             )}
 
diff --git a/frontend/src/pages/IstioConfigNew/K8sReferenceGrantForm.tsx b/frontend/src/pages/IstioConfigNew/K8sReferenceGrantForm.tsx
index 312c7bae5..39ce521b2 100644
--- a/frontend/src/pages/IstioConfigNew/K8sReferenceGrantForm.tsx
+++ b/frontend/src/pages/IstioConfigNew/K8sReferenceGrantForm.tsx
@@ -7,14 +7,14 @@ import { namespaceItemsSelector } from '../../store/Selectors';
 import { KialiDispatch } from '../../types/Redux';
 import { NamespaceThunkActions } from '../../actions/NamespaceThunkActions';
 import { connect } from 'react-redux';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 
 export const FROM_KINDS = [
-  dicIstioTypeToGVK['K8sHTTPRoute'],
-  dicIstioTypeToGVK['K8sGateway'],
-  dicIstioTypeToGVK['K8sGRPCRoute'],
-  dicIstioTypeToGVK['K8sTCPRoute'],
-  dicIstioTypeToGVK['K8sTLSRoute']
+  dicTypeToGVK[gvkType.K8sHTTPRoute],
+  dicTypeToGVK[gvkType.K8sGateway],
+  dicTypeToGVK[gvkType.K8sGRPCRoute],
+  dicTypeToGVK[gvkType.K8sTCPRoute],
+  dicTypeToGVK[gvkType.K8sTLSRoute]
 ];
 
 export const TO_KINDS = {
@@ -70,7 +70,7 @@ export class K8sReferenceGrantFormComponent extends React.Component<Props, K8sRe
   onChangeReferenceGrantFromKind = (_event: React.FormEvent, value: string): void => {
     this.setState(
       {
-        from: [{ group: dicIstioTypeToGVK[`K8s${value}`].Group, kind: value, namespace: this.state.from[0].namespace }]
+        from: [{ group: dicTypeToGVK[`K8s${value}`].Group, kind: value, namespace: this.state.from[0].namespace }]
       },
       () => this.props.onChange(this.state)
     );
diff --git a/frontend/src/pages/Mesh/Mesh.tsx b/frontend/src/pages/Mesh/Mesh.tsx
index b1848e77b..35c6c2347 100644
--- a/frontend/src/pages/Mesh/Mesh.tsx
+++ b/frontend/src/pages/Mesh/Mesh.tsx
@@ -211,13 +211,14 @@ const TopologyContent: React.FC<{
     }
 
     if (layoutInProgress !== LayoutType.LayoutNoFit) {
-      // On a resize, delay fit to ensure that the canvas size updates before the fit
+      controller.getGraph().fit(FIT_PADDING);
+
+      // On a resize, perform a delayed second fit, this one is performed [hopefully] after
+      // the canvas size is updated (which needs to happen in the underlying PFT code)
       if (layoutInProgress === LayoutType.Resize) {
         setTimeout(() => {
           controller.getGraph().fit(FIT_PADDING);
-        }, 250);
-      } else {
-        controller.getGraph().fit(FIT_PADDING);
+        }, 500);
       }
     }
 
@@ -553,6 +554,10 @@ const TopologyContent: React.FC<{
                   zoomOutCallback: () => {
                     controller && controller.getGraph().scaleBy(ZOOM_OUT);
                   },
+                  // currently unused
+                  fitToScreenCallback: () => {
+                    controller.getGraph().fit(FIT_PADDING);
+                  },
                   resetViewCallback: () => {
                     meshLayout(controller, LayoutType.Layout);
                   },
diff --git a/frontend/src/pages/Overview/OverviewPage.tsx b/frontend/src/pages/Overview/OverviewPage.tsx
index cda0b1cb6..782222c99 100644
--- a/frontend/src/pages/Overview/OverviewPage.tsx
+++ b/frontend/src/pages/Overview/OverviewPage.tsx
@@ -73,7 +73,7 @@ import { ControlPlaneVersionBadge } from './ControlPlaneVersionBadge';
 import { AmbientBadge } from '../../components/Ambient/AmbientBadge';
 import { PFBadge, PFBadges } from 'components/Pf/PfBadges';
 import { ApiError } from 'types/Api';
-import { IstioConfigList } from 'types/IstioConfigList';
+import { gvkType, IstioConfigList } from 'types/IstioConfigList';
 import { t } from 'utils/I18nUtils';
 import { getGVKTypeString } from '../../utils/IstioConfigUtils';
 
@@ -880,7 +880,7 @@ export class OverviewPageComponent extends React.Component<OverviewProps, State>
         });
       }
 
-      const aps = nsInfo.istioConfig?.resources[getGVKTypeString('AuthorizationPolicy')] ?? [];
+      const aps = nsInfo.istioConfig?.resources[getGVKTypeString(gvkType.AuthorizationPolicy)] ?? [];
 
       const addAuthorizationAction = {
         isGroup: false,
diff --git a/frontend/src/pages/Overview/OverviewTrafficPolicies.tsx b/frontend/src/pages/Overview/OverviewTrafficPolicies.tsx
index 4dfea491a..9728f000e 100644
--- a/frontend/src/pages/Overview/OverviewTrafficPolicies.tsx
+++ b/frontend/src/pages/Overview/OverviewTrafficPolicies.tsx
@@ -15,7 +15,7 @@ import {
   buildNamespaceInjectionPatch,
   buildGraphSidecars
 } from 'components/IstioWizards/WizardActions';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 import { getGVKTypeString } from '../../utils/IstioConfigUtils';
 
 type OverviewTrafficPoliciesProps = {
@@ -73,8 +73,8 @@ export class OverviewTrafficPolicies extends React.Component<OverviewTrafficPoli
             this.fetchPermission();
           } else if (this.props.opTarget === 'update') {
             const authorizationPolicies =
-              this.props.nsInfo?.istioConfig?.resources[getGVKTypeString('AuthorizationPolicy')] ?? [];
-            const sidecars = this.props.nsInfo?.istioConfig?.resources[getGVKTypeString('Sidecar')] ?? [];
+              this.props.nsInfo?.istioConfig?.resources[getGVKTypeString(gvkType.AuthorizationPolicy)] ?? [];
+            const sidecars = this.props.nsInfo?.istioConfig?.resources[getGVKTypeString(gvkType.Sidecar)] ?? [];
             const remove = ['uid', 'resourceVersion', 'generation', 'creationTimestamp', 'managedFields'];
             sidecars.map(sdc => remove.map(key => delete sdc.metadata[key]));
             authorizationPolicies.map(ap => remove.map(key => delete ap.metadata[key]));
@@ -83,8 +83,8 @@ export class OverviewTrafficPolicies extends React.Component<OverviewTrafficPoli
             const nsInfo = this.props.nsInfo.istioConfig;
             this.setState(
               {
-                authorizationPolicies: nsInfo?.resources[getGVKTypeString('AuthorizationPolicy')] ?? [],
-                sidecars: nsInfo?.resources[getGVKTypeString('Sidecar')] ?? []
+                authorizationPolicies: nsInfo?.resources[getGVKTypeString(gvkType.AuthorizationPolicy)] ?? [],
+                sidecars: nsInfo?.resources[getGVKTypeString(gvkType.Sidecar)] ?? []
               },
               () => this.fetchPermission(true)
             );
@@ -101,7 +101,7 @@ export class OverviewTrafficPolicies extends React.Component<OverviewTrafficPoli
     this.promises
       .register('namespacepermissions', API.getIstioPermissions([this.props.nsTarget], this.props.nsInfo.cluster))
       .then(result => {
-        const permission = result.data[this.props.nsTarget][getGVKTypeString('AuthorizationPolicy')];
+        const permission = result.data[this.props.nsTarget][getGVKTypeString(gvkType.AuthorizationPolicy)];
         const disableOp = !(permission.create && permission.update && permission.delete);
         this.setState({
           confirmationModal,
@@ -183,10 +183,10 @@ export class OverviewTrafficPolicies extends React.Component<OverviewTrafficPoli
           'trafficPoliciesDelete',
           apsP
             .map(ap =>
-              API.deleteIstioConfigDetail(ns, dicIstioTypeToGVK['AuthorizationPolicy'], ap.metadata.name, cluster)
+              API.deleteIstioConfigDetail(ns, dicTypeToGVK[gvkType.AuthorizationPolicy], ap.metadata.name, cluster)
             )
             .concat(
-              sdsP.map(sc => API.deleteIstioConfigDetail(ns, dicIstioTypeToGVK['Sidecar'], sc.metadata.name, cluster))
+              sdsP.map(sc => API.deleteIstioConfigDetail(ns, dicTypeToGVK[gvkType.Sidecar], sc.metadata.name, cluster))
             )
         )
         .then(_ => {
@@ -224,10 +224,10 @@ export class OverviewTrafficPolicies extends React.Component<OverviewTrafficPoli
           'trafficPoliciesCreate',
           aps
             .map(ap =>
-              API.createIstioConfigDetail(ns, dicIstioTypeToGVK['AuthorizationPolicy'], JSON.stringify(ap), cluster)
+              API.createIstioConfigDetail(ns, dicTypeToGVK[gvkType.AuthorizationPolicy], JSON.stringify(ap), cluster)
             )
             .concat(
-              sds.map(sc => API.createIstioConfigDetail(ns, dicIstioTypeToGVK['Sidecar'], JSON.stringify(sc), cluster))
+              sds.map(sc => API.createIstioConfigDetail(ns, dicTypeToGVK[gvkType.Sidecar], JSON.stringify(sc), cluster))
             )
         )
         .then(results => {
@@ -262,20 +262,20 @@ export class OverviewTrafficPolicies extends React.Component<OverviewTrafficPoli
 
     this.state.authorizationPolicies.length > 0 &&
       items.push({
-        objectGVK: dicIstioTypeToGVK['AuthorizationPolicy'],
+        objectGVK: dicTypeToGVK[gvkType.AuthorizationPolicy],
         items: this.state.authorizationPolicies,
         title: 'Authorization Policies'
       });
 
     this.state.sidecars.length > 0 &&
-      items.push({ objectGVK: dicIstioTypeToGVK['Sidecar'], items: this.state.sidecars, title: 'Sidecars' });
+      items.push({ objectGVK: dicTypeToGVK[gvkType.Sidecar], items: this.state.sidecars, title: 'Sidecars' });
 
     return items;
   };
 
   onConfirmPreviewPoliciesModal = (items: ConfigPreviewItem[]): void => {
-    const aps = items.filter(i => getGVKTypeString(i.objectGVK) === getGVKTypeString('AuthorizationPolicy'))[0];
-    const sds = items.filter(i => getGVKTypeString(i.objectGVK) === getGVKTypeString('Sidecar'))[0];
+    const aps = items.filter(i => getGVKTypeString(i.objectGVK) === getGVKTypeString(gvkType.AuthorizationPolicy))[0];
+    const sds = items.filter(i => getGVKTypeString(i.objectGVK) === getGVKTypeString(gvkType.Sidecar))[0];
 
     this.setState(
       {
diff --git a/frontend/src/pages/ServiceDetails/ServiceDetailsPage.tsx b/frontend/src/pages/ServiceDetails/ServiceDetailsPage.tsx
index 84157508e..cdd385391 100644
--- a/frontend/src/pages/ServiceDetails/ServiceDetailsPage.tsx
+++ b/frontend/src/pages/ServiceDetails/ServiceDetailsPage.tsx
@@ -35,6 +35,7 @@ import { durationSelector } from 'store/Selectors';
 import { basicTabStyle } from 'styles/TabStyles';
 import { serverConfig } from 'config';
 import { getGVKTypeString } from '../../utils/IstioConfigUtils';
+import { gvkType } from '../../types/IstioConfigList';
 
 type ServiceDetailsState = {
   cluster?: string;
@@ -119,7 +120,7 @@ class ServiceDetailsPageComponent extends React.Component<ServiceDetailsProps, S
         'gateways',
         API.getIstioConfig(
           this.props.serviceId.namespace,
-          [getGVKTypeString('Gateway'), getGVKTypeString('K8sGateway')],
+          [getGVKTypeString(gvkType.Gateway), getGVKTypeString(gvkType.K8sGateway)],
           false,
           '',
           '',
@@ -127,8 +128,8 @@ class ServiceDetailsPageComponent extends React.Component<ServiceDetailsProps, S
         )
       )
       .then(response => {
-        this.setState({ gateways: response.data.resources[getGVKTypeString('Gateway')] });
-        this.setState({ k8sGateways: response.data.resources[getGVKTypeString('K8sGateway')] });
+        this.setState({ gateways: response.data.resources[getGVKTypeString(gvkType.Gateway)] });
+        this.setState({ k8sGateways: response.data.resources[getGVKTypeString(gvkType.K8sGateway)] });
       })
       .catch(gwError => {
         AlertUtils.addError('Could not fetch Gateways list.', gwError);
@@ -157,10 +158,17 @@ class ServiceDetailsPageComponent extends React.Component<ServiceDetailsProps, S
         this.setState({ error: msg });
       });
 
-    API.getIstioConfig(this.props.serviceId.namespace, [getGVKTypeString('PeerAuthentication')], false, '', '', cluster)
+    API.getIstioConfig(
+      this.props.serviceId.namespace,
+      [getGVKTypeString(gvkType.PeerAuthentication)],
+      false,
+      '',
+      '',
+      cluster
+    )
       .then(results => {
         this.setState({
-          peerAuthentications: results.data.resources[getGVKTypeString('PeerAuthentication')]
+          peerAuthentications: results.data.resources[getGVKTypeString(gvkType.PeerAuthentication)]
         });
       })
       .catch(error => {
diff --git a/frontend/src/pages/WorkloadDetails/WorkloadDescription.tsx b/frontend/src/pages/WorkloadDetails/WorkloadDescription.tsx
index 61da158b4..903ad4ecf 100644
--- a/frontend/src/pages/WorkloadDetails/WorkloadDescription.tsx
+++ b/frontend/src/pages/WorkloadDetails/WorkloadDescription.tsx
@@ -15,12 +15,13 @@ import { MissingSidecar } from '../../components/MissingSidecar/MissingSidecar';
 import { PFBadge, PFBadges } from '../../components/Pf/PfBadges';
 import { MissingLabel } from '../../components/MissingLabel/MissingLabel';
 import { MissingAuthPolicy } from 'components/MissingAuthPolicy/MissingAuthPolicy';
-import { hasMissingAuthPolicy } from 'utils/IstioConfigUtils';
+import { getGVKTypeString, hasMissingAuthPolicy, isGVKSupported } from 'utils/IstioConfigUtils';
 import { DetailDescription } from '../../components/DetailDescription/DetailDescription';
 import { isWaypoint } from '../../helpers/LabelFilterHelper';
 import { AmbientLabel, tooltipMsgType } from '../../components/Ambient/AmbientLabel';
-import { validationKey } from '../../types/IstioConfigList';
+import { gvkType, validationKey } from '../../types/IstioConfigList';
 import { infoStyle } from 'styles/IconStyle';
+import { addInfo } from 'utils/AlertUtils';
 import { classes } from 'typestyle';
 
 type WorkloadDescriptionProps = {
@@ -30,12 +31,16 @@ type WorkloadDescriptionProps = {
 };
 
 const resourceListStyle = kialiStyle({
-  marginBottom: '0.75rem',
+  display: 'flex',
   $nest: {
-    '& > ul > li span': {
-      float: 'left',
-      width: '125px',
-      fontWeight: 700
+    '& > ul > li': {
+      display: 'flex',
+      $nest: {
+        '& span': {
+          minWidth: '125px',
+          fontWeight: 700
+        }
+      }
     }
   }
 });
@@ -80,11 +85,20 @@ export const WorkloadDescription: React.FC<WorkloadDescriptionProps> = (props: W
   workload.services?.forEach(s => services.push(s.name));
 
   const isTemplateLabels =
-    ['Deployment', 'ReplicaSet', 'ReplicationController', 'DeploymentConfig', 'StatefulSet'].indexOf(workload.type) >=
-    0;
+    [
+      getGVKTypeString(gvkType.Deployment),
+      getGVKTypeString(gvkType.ReplicaSet),
+      getGVKTypeString(gvkType.ReplicationController),
+      getGVKTypeString(gvkType.DeploymentConfig),
+      getGVKTypeString(gvkType.StatefulSet)
+    ].indexOf(getGVKTypeString(workload.gvk)) >= 0;
 
   const runtimes = (workload.runtimes ?? []).map(r => r.name).filter(name => name !== '');
 
+  if (!isGVKSupported(workload.gvk)) {
+    addInfo('This type of workload is not fully supported by Kiali, only limited information is available for display');
+  }
+
   const workloadProperties = (
     <>
       <div key="properties-list" className={resourceListStyle}>
@@ -96,9 +110,16 @@ export const WorkloadDescription: React.FC<WorkloadDescriptionProps> = (props: W
             </li>
           )}
 
+          {!isGVKSupported(workload.gvk) && (
+            <li>
+              <span>API Version</span>
+              {`${workload.gvk.Group}.${workload.gvk.Version}`}
+            </li>
+          )}
+
           <li>
             <span>Type</span>
-            {workload.type ? workload.type : 'N/A'}
+            {workload.gvk.Kind || 'N/A'}
           </li>
 
           <li>
diff --git a/frontend/src/pages/WorkloadDetails/WorkloadDetailsPage.tsx b/frontend/src/pages/WorkloadDetails/WorkloadDetailsPage.tsx
index 1f804fb2c..aba1d4e90 100644
--- a/frontend/src/pages/WorkloadDetails/WorkloadDetailsPage.tsx
+++ b/frontend/src/pages/WorkloadDetails/WorkloadDetailsPage.tsx
@@ -270,6 +270,8 @@ class WorkloadDetailsPageComponent extends React.Component<WorkloadDetailsPagePr
       workload.pods.forEach(pod => {
         if (pod.istioContainers && pod.istioContainers.length > 0) {
           hasIstioSidecars = true;
+        } else if (pod.istioInitContainers && pod.istioInitContainers.some(cont => cont.name === 'istio-proxy')) {
+          hasIstioSidecars = true;
         } else {
           hasIstioSidecars =
             hasIstioSidecars || (!!pod.containers && pod.containers.some(cont => cont.name === 'istio-proxy'));
@@ -304,7 +306,7 @@ class WorkloadDetailsPageComponent extends React.Component<WorkloadDetailsPagePr
                     app={app}
                     version={version}
                     workload={this.state.workload!.name}
-                    workloadType={this.state.workload!.type}
+                    workloadType={this.state.workload!.gvk.Kind}
                     template={dashboard.template}
                   />
                 </Tab>
diff --git a/frontend/src/pages/WorkloadDetails/WorkloadInfo.tsx b/frontend/src/pages/WorkloadDetails/WorkloadInfo.tsx
index f0fc2ab48..c35a98772 100644
--- a/frontend/src/pages/WorkloadDetails/WorkloadInfo.tsx
+++ b/frontend/src/pages/WorkloadDetails/WorkloadInfo.tsx
@@ -12,7 +12,7 @@ import { RenderComponentScroll } from '../../components/Nav/Page';
 import { GraphDataSource } from '../../services/GraphDataSource';
 import { DurationInSeconds } from 'types/Common';
 import { isIstioNamespace, serverConfig } from '../../config/ServerConfig';
-import { IstioConfigList, toIstioItems } from '../../types/IstioConfigList';
+import { gvkType, IstioConfigList, toIstioItems } from '../../types/IstioConfigList';
 import { WorkloadPods } from './WorkloadPods';
 import { GraphEdgeTapEvent } from '../../components/CytoscapeGraph/CytoscapeGraph';
 import { location, router, URLParam } from '../../app/History';
@@ -44,12 +44,12 @@ const tabName = 'list';
 const defaultTab = 'pods';
 
 const workloadIstioResources = [
-  getGVKTypeString('Gateway'),
-  getGVKTypeString('AuthorizationPolicy'),
-  getGVKTypeString('PeerAuthentication'),
-  getGVKTypeString('Sidecar'),
-  getGVKTypeString('RequestAuthentication'),
-  getGVKTypeString('EnvoyFilter')
+  getGVKTypeString(gvkType.Gateway),
+  getGVKTypeString(gvkType.AuthorizationPolicy),
+  getGVKTypeString(gvkType.PeerAuthentication),
+  getGVKTypeString(gvkType.Sidecar),
+  getGVKTypeString(gvkType.RequestAuthentication),
+  getGVKTypeString(gvkType.EnvoyFilter)
 ];
 
 export class WorkloadInfo extends React.Component<WorkloadInfoProps, WorkloadInfoState> {
diff --git a/frontend/src/pages/WorkloadList/FiltersAndSorts.ts b/frontend/src/pages/WorkloadList/FiltersAndSorts.ts
index beafa15d6..89d7920d3 100644
--- a/frontend/src/pages/WorkloadList/FiltersAndSorts.ts
+++ b/frontend/src/pages/WorkloadList/FiltersAndSorts.ts
@@ -6,7 +6,7 @@ import {
   AllFilterTypes,
   ToggleType
 } from '../../types/Filters';
-import { WorkloadListItem, WorkloadType } from '../../types/Workload';
+import { WorkloadListItem } from '../../types/Workload';
 import { hasMissingSidecar } from 'components/VirtualList/Config';
 import { SortField } from '../../types/SortFilters';
 import { hasHealth } from '../../types/Health';
@@ -26,6 +26,7 @@ import { istioConfigTypeFilter } from '../IstioConfigList/FiltersAndSorts';
 import { compareObjectReferences } from '../AppList/FiltersAndSorts';
 import { serverConfig } from 'config';
 import { getGVKTypeString, istioTypesToGVKString } from '../../utils/IstioConfigUtils';
+import { dicTypeToGVK } from '../../types/IstioConfigList';
 
 const missingLabels = (r: WorkloadListItem): number => {
   return r.appLabel && r.versionLabel ? 0 : r.appLabel || r.versionLabel ? 1 : 2;
@@ -57,7 +58,7 @@ export const sortFields: SortField<WorkloadListItem>[] = [
     title: 'Workload Type',
     isNumeric: false,
     param: 'wt',
-    compare: (a: WorkloadListItem, b: WorkloadListItem) => a.type.localeCompare(b.type)
+    compare: (a: WorkloadListItem, b: WorkloadListItem) => a.gvk.Kind.localeCompare(b.gvk.Kind)
   },
   {
     id: 'details',
@@ -236,40 +237,40 @@ const workloadTypeFilter: FilterType = {
   action: FILTER_ACTION_APPEND,
   filterValues: [
     {
-      id: WorkloadType.CronJob,
-      title: WorkloadType.CronJob
+      id: dicTypeToGVK.CronJob.Kind,
+      title: dicTypeToGVK.CronJob.Kind
     },
     {
-      id: WorkloadType.DaemonSet,
-      title: WorkloadType.DaemonSet
+      id: dicTypeToGVK.DaemonSet.Kind,
+      title: dicTypeToGVK.DaemonSet.Kind
     },
     {
-      id: WorkloadType.Deployment,
-      title: WorkloadType.Deployment
+      id: dicTypeToGVK.Deployment.Kind,
+      title: dicTypeToGVK.Deployment.Kind
     },
     {
-      id: WorkloadType.DeploymentConfig,
-      title: WorkloadType.DeploymentConfig
+      id: dicTypeToGVK.DeploymentConfig.Kind,
+      title: dicTypeToGVK.DeploymentConfig.Kind
     },
     {
-      id: WorkloadType.Job,
-      title: WorkloadType.Job
+      id: dicTypeToGVK.Job.Kind,
+      title: dicTypeToGVK.Job.Kind
     },
     {
-      id: WorkloadType.Pod,
-      title: WorkloadType.Pod
+      id: dicTypeToGVK.Pod.Kind,
+      title: dicTypeToGVK.Pod.Kind
     },
     {
-      id: WorkloadType.ReplicaSet,
-      title: WorkloadType.ReplicaSet
+      id: dicTypeToGVK.ReplicaSet.Kind,
+      title: dicTypeToGVK.ReplicaSet.Kind
     },
     {
-      id: WorkloadType.ReplicationController,
-      title: WorkloadType.ReplicationController
+      id: dicTypeToGVK.ReplicationController.Kind,
+      title: dicTypeToGVK.ReplicationController.Kind
     },
     {
-      id: WorkloadType.StatefulSet,
-      title: WorkloadType.StatefulSet
+      id: dicTypeToGVK.StatefulSet.Kind,
+      title: dicTypeToGVK.StatefulSet.Kind
     }
   ]
 };
@@ -299,7 +300,7 @@ const filterByType = (items: WorkloadListItem[], filter: string[]): WorkloadList
   if (filter && filter.length === 0) {
     return items;
   }
-  return items.filter(item => includeName(item.type, filter));
+  return items.filter(item => includeName(item.gvk.Kind, filter));
 };
 
 const filterByLabelPresence = (
diff --git a/frontend/src/pages/WorkloadList/WorkloadListPage.tsx b/frontend/src/pages/WorkloadList/WorkloadListPage.tsx
index 623cc2be7..5f37dc83d 100644
--- a/frontend/src/pages/WorkloadList/WorkloadListPage.tsx
+++ b/frontend/src/pages/WorkloadList/WorkloadListPage.tsx
@@ -120,7 +120,7 @@ class WorkloadListPageComponent extends FilterComponent.Component<
         namespace: deployment.namespace,
         name: deployment.name,
         instanceType: InstanceType.Workload,
-        type: deployment.type,
+        gvk: deployment.gvk,
         appLabel: deployment.appLabel,
         versionLabel: deployment.versionLabel,
         istioSidecar: deployment.istioSidecar,
diff --git a/frontend/src/services/Api.ts b/frontend/src/services/Api.ts
index 0bba276eb..5e733a607 100644
--- a/frontend/src/services/Api.ts
+++ b/frontend/src/services/Api.ts
@@ -24,7 +24,8 @@ import {
   IstioPermissionsQuery
 } from '../types/IstioConfigDetails';
 import {
-  dicIstioTypeToGVK,
+  dicTypeToGVK,
+  gvkType,
   IstioConfigList,
   IstioConfigListQuery,
   IstioConfigsMapQuery
@@ -70,6 +71,7 @@ import {
 } from '../types/Workload';
 import { CertsInfo } from 'types/CertsInfo';
 import { ApiError, ApiResponse } from 'types/Api';
+import { getGVKTypeString } from '../utils/IstioConfigUtils';
 
 export const ANONYMOUS_USER = 'anonymous';
 
@@ -1020,12 +1022,12 @@ export const getWorkload = (
 export const updateWorkload = (
   namespace: string,
   name: string,
-  type: string,
+  type: GroupVersionKind | gvkType,
   jsonPatch: string,
   patchType?: string,
   cluster?: string
 ): Promise<ApiResponse<string>> => {
-  const params: QueryParams<WorkloadUpdateQuery> = { type: type };
+  const params: QueryParams<WorkloadUpdateQuery> = { gvk: getGVKTypeString(type) };
 
   if (patchType) {
     params.patchType = patchType;
@@ -1284,7 +1286,7 @@ export function deleteServiceTrafficRouting(
     deletePromises.push(
       deleteIstioConfigDetail(
         vs.metadata.namespace ?? '',
-        dicIstioTypeToGVK['VirtualService'],
+        dicTypeToGVK[gvkType.VirtualService],
         vs.metadata.name,
         cluster
       )
@@ -1295,7 +1297,7 @@ export function deleteServiceTrafficRouting(
     deletePromises.push(
       deleteIstioConfigDetail(
         k8sr.metadata.namespace ?? '',
-        dicIstioTypeToGVK['K8sHTTPRoute'],
+        dicTypeToGVK[gvkType.K8sHTTPRoute],
         k8sr.metadata.name,
         cluster
       )
@@ -1306,7 +1308,7 @@ export function deleteServiceTrafficRouting(
     deletePromises.push(
       deleteIstioConfigDetail(
         k8sr.metadata.namespace ?? '',
-        dicIstioTypeToGVK['K8sGRPCRoute'],
+        dicTypeToGVK[gvkType.K8sGRPCRoute],
         k8sr.metadata.name,
         cluster
       )
@@ -1316,7 +1318,7 @@ export function deleteServiceTrafficRouting(
     deletePromises.push(
       deleteIstioConfigDetail(
         dr.metadata.namespace ?? '',
-        dicIstioTypeToGVK['DestinationRule'],
+        dicTypeToGVK[gvkType.DestinationRule],
         dr.metadata.name,
         cluster
       )
@@ -1325,7 +1327,7 @@ export function deleteServiceTrafficRouting(
     const paName = dr.hasPeerAuthentication();
     if (!!paName) {
       deletePromises.push(
-        deleteIstioConfigDetail(dr.metadata.namespace ?? '', dicIstioTypeToGVK['PeerAuthentication'], paName, cluster)
+        deleteIstioConfigDetail(dr.metadata.namespace ?? '', dicTypeToGVK[gvkType.PeerAuthentication], paName, cluster)
       );
     }
   });
diff --git a/frontend/src/services/__mockData__/getServiceDetail.ts b/frontend/src/services/__mockData__/getServiceDetail.ts
index 3c640318b..2d5d5c907 100644
--- a/frontend/src/services/__mockData__/getServiceDetail.ts
+++ b/frontend/src/services/__mockData__/getServiceDetail.ts
@@ -1,6 +1,6 @@
 import { ServiceDetailsInfo } from '../../types/ServiceInfo';
 import { ValidationTypes } from '../../types/IstioObjects';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 
 export const SERVICE_DETAILS: ServiceDetailsInfo = {
   service: {
@@ -136,7 +136,7 @@ export const SERVICE_DETAILS: ServiceDetailsInfo = {
     'networking.istio.io/v1, Kind=DestinationRule': {
       reviews: {
         name: 'details',
-        objectGVK: dicIstioTypeToGVK['DestinationRule'],
+        objectGVK: dicTypeToGVK[gvkType.DestinationRule],
         valid: false,
         checks: [
           {
diff --git a/frontend/src/types/IstioConfigList.ts b/frontend/src/types/IstioConfigList.ts
index 60086372e..9a28a4017 100644
--- a/frontend/src/types/IstioConfigList.ts
+++ b/frontend/src/types/IstioConfigList.ts
@@ -46,30 +46,76 @@ export interface IstioConfigsMapQuery extends IstioConfigListQuery {
   namespaces?: string;
 }
 
-export const dicIstioTypeToGVK: { [key: string]: GroupVersionKind } = {
-  AuthorizationPolicy: { Group: 'security.istio.io', Version: 'v1', Kind: 'AuthorizationPolicy' },
-  PeerAuthentication: { Group: 'security.istio.io', Version: 'v1', Kind: 'PeerAuthentication' },
-  RequestAuthentication: { Group: 'security.istio.io', Version: 'v1', Kind: 'RequestAuthentication' },
-
-  DestinationRule: { Group: 'networking.istio.io', Version: 'v1', Kind: 'DestinationRule' },
-  Gateway: { Group: 'networking.istio.io', Version: 'v1', Kind: 'Gateway' },
-  EnvoyFilter: { Group: 'networking.istio.io', Version: 'v1alpha3', Kind: 'EnvoyFilter' },
-  Sidecar: { Group: 'networking.istio.io', Version: 'v1', Kind: 'Sidecar' },
-  ServiceEntry: { Group: 'networking.istio.io', Version: 'v1', Kind: 'ServiceEntry' },
-  VirtualService: { Group: 'networking.istio.io', Version: 'v1', Kind: 'VirtualService' },
-  WorkloadEntry: { Group: 'networking.istio.io', Version: 'v1', Kind: 'WorkloadEntry' },
-  WorkloadGroup: { Group: 'networking.istio.io', Version: 'v1', Kind: 'WorkloadGroup' },
-
-  WasmPlugin: { Group: 'extensions.istio.io', Version: 'v1alpha1', Kind: 'WasmPlugin' },
-  Telemetry: { Group: 'telemetry.istio.io', Version: 'v1', Kind: 'Telemetry' },
-
-  K8sGateway: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'Gateway' },
-  K8sGatewayClass: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'GatewayClass' },
-  K8sGRPCRoute: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'GRPCRoute' },
-  K8sHTTPRoute: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'HTTPRoute' },
-  K8sReferenceGrant: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'ReferenceGrant' },
-  K8sTCPRoute: { Group: 'gateway.networking.k8s.io', Version: 'v1alpha2', Kind: 'TCPRoute' },
-  K8sTLSRoute: { Group: 'gateway.networking.k8s.io', Version: 'v1alpha2', Kind: 'TLSRoute' }
+export enum gvkType {
+  AuthorizationPolicy = 'AuthorizationPolicy',
+  PeerAuthentication = 'PeerAuthentication',
+  RequestAuthentication = 'RequestAuthentication',
+
+  DestinationRule = 'DestinationRule',
+  Gateway = 'Gateway',
+  EnvoyFilter = 'EnvoyFilter',
+  Sidecar = 'Sidecar',
+  ServiceEntry = 'ServiceEntry',
+  VirtualService = 'VirtualService',
+  WorkloadEntry = 'WorkloadEntry',
+  WorkloadGroup = 'WorkloadGroup',
+
+  WasmPlugin = 'WasmPlugin',
+  Telemetry = 'Telemetry',
+
+  K8sGateway = 'K8sGateway',
+  K8sGatewayClass = 'K8sGatewayClass',
+  K8sGRPCRoute = 'K8sGRPCRoute',
+  K8sHTTPRoute = 'K8sHTTPRoute',
+  K8sReferenceGrant = 'K8sReferenceGrant',
+  K8sTCPRoute = 'K8sTCPRoute',
+  K8sTLSRoute = 'K8sTLSRoute',
+
+  CronJob = 'CronJob',
+  DaemonSet = 'DaemonSet',
+  Deployment = 'Deployment',
+  DeploymentConfig = 'DeploymentConfig',
+  Job = 'Job',
+  Pod = 'Pod',
+  ReplicaSet = 'ReplicaSet',
+  ReplicationController = 'ReplicationController',
+  StatefulSet = 'StatefulSet'
+}
+
+export const dicTypeToGVK: { [key in gvkType]: GroupVersionKind } = {
+  [gvkType.AuthorizationPolicy]: { Group: 'security.istio.io', Version: 'v1', Kind: gvkType.AuthorizationPolicy },
+  [gvkType.PeerAuthentication]: { Group: 'security.istio.io', Version: 'v1', Kind: gvkType.PeerAuthentication },
+  [gvkType.RequestAuthentication]: { Group: 'security.istio.io', Version: 'v1', Kind: gvkType.RequestAuthentication },
+
+  [gvkType.DestinationRule]: { Group: 'networking.istio.io', Version: 'v1', Kind: gvkType.DestinationRule },
+  [gvkType.Gateway]: { Group: 'networking.istio.io', Version: 'v1', Kind: gvkType.Gateway },
+  [gvkType.EnvoyFilter]: { Group: 'networking.istio.io', Version: 'v1alpha3', Kind: gvkType.EnvoyFilter },
+  [gvkType.Sidecar]: { Group: 'networking.istio.io', Version: 'v1', Kind: gvkType.Sidecar },
+  [gvkType.ServiceEntry]: { Group: 'networking.istio.io', Version: 'v1', Kind: gvkType.ServiceEntry },
+  [gvkType.VirtualService]: { Group: 'networking.istio.io', Version: 'v1', Kind: gvkType.VirtualService },
+  [gvkType.WorkloadEntry]: { Group: 'networking.istio.io', Version: 'v1', Kind: gvkType.WorkloadEntry },
+  [gvkType.WorkloadGroup]: { Group: 'networking.istio.io', Version: 'v1', Kind: gvkType.WorkloadGroup },
+
+  [gvkType.WasmPlugin]: { Group: 'extensions.istio.io', Version: 'v1alpha1', Kind: gvkType.WasmPlugin },
+  [gvkType.Telemetry]: { Group: 'telemetry.istio.io', Version: 'v1', Kind: gvkType.Telemetry },
+
+  [gvkType.K8sGateway]: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'Gateway' },
+  [gvkType.K8sGatewayClass]: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'GatewayClass' },
+  [gvkType.K8sGRPCRoute]: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'GRPCRoute' },
+  [gvkType.K8sHTTPRoute]: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'HTTPRoute' },
+  [gvkType.K8sReferenceGrant]: { Group: 'gateway.networking.k8s.io', Version: 'v1', Kind: 'ReferenceGrant' },
+  [gvkType.K8sTCPRoute]: { Group: 'gateway.networking.k8s.io', Version: 'v1alpha2', Kind: 'TCPRoute' },
+  [gvkType.K8sTLSRoute]: { Group: 'gateway.networking.k8s.io', Version: 'v1alpha2', Kind: 'TLSRoute' },
+
+  [gvkType.CronJob]: { Group: 'batch', Version: 'v1', Kind: 'CronJob' },
+  [gvkType.DaemonSet]: { Group: 'apps', Version: 'v1', Kind: 'DaemonSet' },
+  [gvkType.Deployment]: { Group: 'apps', Version: 'v1', Kind: 'Deployment' },
+  [gvkType.DeploymentConfig]: { Group: 'apps.openshift.io', Version: 'v1', Kind: 'DeploymentConfig' },
+  [gvkType.Job]: { Group: 'batch', Version: 'v1', Kind: 'Job' },
+  [gvkType.Pod]: { Group: '', Version: 'v1', Kind: 'Pod' },
+  [gvkType.ReplicaSet]: { Group: 'apps', Version: 'v1', Kind: 'ReplicaSet' },
+  [gvkType.ReplicationController]: { Group: '', Version: 'v1', Kind: 'ReplicationController' },
+  [gvkType.StatefulSet]: { Group: 'apps', Version: 'v1', Kind: 'StatefulSet' }
 };
 
 export function validationKey(name: string, namespace?: string): string {
@@ -101,9 +147,9 @@ export const filterByNamespaces = (unfiltered: IstioConfigList, namespaces: stri
   const namespaceSet = new Set(namespaces);
   const filteredResources: { [key: string]: any[] } = {};
 
-  // Iterate over dicIstioTypeToGVK to dynamically filter each resource by namespace
-  Object.keys(dicIstioTypeToGVK).forEach(key => {
-    const resourceKey = getGVKTypeString(key);
+  // Iterate over dicTypeToGVK to dynamically filter each resource by namespace
+  Object.values(dicTypeToGVK).forEach(value => {
+    const resourceKey = getGVKTypeString(value);
 
     // Check if the resource exists in the unfiltered list, then filter by namespace
     if (unfiltered.resources[resourceKey]) {
@@ -127,9 +173,9 @@ export const filterByName = (unfiltered: IstioConfigList, names: string[]): Isti
 
   const filteredResources: { [key: string]: any[] } = {};
 
-  // Iterate over the dicIstioTypeToGVK to access each resource type dynamically
-  Object.keys(dicIstioTypeToGVK).forEach(key => {
-    const resourceKey = getGVKTypeString(key);
+  // Iterate over the dicTypeToGVK to access each resource type dynamically
+  Object.values(dicTypeToGVK).forEach(value => {
+    const resourceKey = getGVKTypeString(value);
 
     // Check if the resource exists in the unfiltered list, then filter by names
     if (unfiltered.resources[resourceKey]) {
diff --git a/frontend/src/types/Workload.ts b/frontend/src/types/Workload.ts
index 0858be83f..31d563138 100644
--- a/frontend/src/types/Workload.ts
+++ b/frontend/src/types/Workload.ts
@@ -1,5 +1,5 @@
 import { WorkloadHealth, WorkloadHealthResponse } from './Health';
-import { ObjectReference, Pod, Service, Validations } from './IstioObjects';
+import { GroupVersionKind, ObjectReference, Pod, Service, Validations } from './IstioObjects';
 import { InstanceType } from 'types/Common';
 
 export type WorkloadId = {
@@ -14,6 +14,7 @@ export interface Workload {
   availableReplicas: Number;
   cluster?: string;
   createdAt: string;
+  gvk: GroupVersionKind;
   health?: WorkloadHealthResponse;
   instanceType: InstanceType.Workload;
   isAmbient: boolean;
@@ -28,7 +29,6 @@ export interface Workload {
   resourceVersion: string;
   runtimes: Runtime[];
   services: Service[];
-  type: string;
   validations?: Validations;
   versionLabel: boolean;
   waypointWorkloads: Workload[];
@@ -40,6 +40,7 @@ export const emptyWorkload: Workload = {
   appLabel: false,
   availableReplicas: 0,
   createdAt: '',
+  gvk: { Group: '', Version: '', Kind: '' },
   isAmbient: false,
   isGateway: false,
   istioSidecar: true, // true until proven otherwise
@@ -52,27 +53,15 @@ export const emptyWorkload: Workload = {
   resourceVersion: '',
   runtimes: [],
   services: [],
-  type: '',
   versionLabel: false,
   waypointWorkloads: []
 };
 
-export const WorkloadType = {
-  CronJob: 'CronJob',
-  DaemonSet: 'DaemonSet',
-  Deployment: 'Deployment',
-  DeploymentConfig: 'DeploymentConfig',
-  Job: 'Job',
-  Pod: 'Pod',
-  ReplicaSet: 'ReplicaSet',
-  ReplicationController: 'ReplicationController',
-  StatefulSet: 'StatefulSet'
-};
-
 export interface WorkloadListItem {
   additionalDetailSample?: AdditionalItem;
   appLabel: boolean;
   cluster?: string;
+  gvk: GroupVersionKind;
   health: WorkloadHealth;
   instanceType: InstanceType.Workload;
   isAmbient: boolean;
@@ -83,7 +72,6 @@ export interface WorkloadListItem {
   name: string;
   namespace: string;
   notCoveredAuthPolicy: boolean;
-  type: string;
   versionLabel: boolean;
 }
 
@@ -94,8 +82,8 @@ export interface WorkloadQuery {
 }
 
 export interface WorkloadUpdateQuery {
+  gvk: string;
   patchType?: string;
-  type: string;
 }
 
 export interface WorkloadListQuery {
diff --git a/frontend/src/utils/IstioConfigUtils.ts b/frontend/src/utils/IstioConfigUtils.ts
index 4008fbe69..21cdf75e6 100644
--- a/frontend/src/utils/IstioConfigUtils.ts
+++ b/frontend/src/utils/IstioConfigUtils.ts
@@ -9,7 +9,7 @@ import {
   Validations
 } from '../types/IstioObjects';
 import _ from 'lodash';
-import { dicIstioTypeToGVK, IstioConfigItem } from 'types/IstioConfigList';
+import { dicTypeToGVK, gvkType, IstioConfigItem } from 'types/IstioConfigList';
 
 export const mergeJsonPatch = (objectModified: object, object?: object): object => {
   if (!object) {
@@ -194,14 +194,18 @@ export function getIstioObjectGVK(apiVersion?: string, kind?: string): GroupVers
   const parts = apiVersion.split('/');
   if (parts.length !== 2) {
     // should not happen, but not the best way, only an alternative
-    return dicIstioTypeToGVK[kind];
+    return dicTypeToGVK[kind];
   }
   return { Group: parts[0], Version: parts[1], Kind: kind! };
 }
 
-export function getGVKTypeString(gvk: GroupVersionKind | string): string {
+export function getGVKTypeString(gvk: GroupVersionKind | gvkType): string {
   if (typeof gvk === 'string') {
-    return gvkToString(dicIstioTypeToGVK[gvk]);
+    const gvkEntry = dicTypeToGVK[gvk];
+    if (!gvkEntry) {
+      throw new Error(`GVK type '${gvk}' not found in dicTypeToGVK.`);
+    }
+    return gvkToString(gvkEntry);
   } else {
     return gvkToString(gvk);
   }
@@ -243,6 +247,10 @@ export function kindToStringIncludeK8s(apiVersion?: string, kind?: string): stri
 
 export function istioTypesToGVKString(istioTypes: string[]): string[] {
   return istioTypes.map(type => {
-    return gvkToString(dicIstioTypeToGVK[type]);
+    return gvkToString(dicTypeToGVK[type]);
   });
 }
+
+export function isGVKSupported(gvk: GroupVersionKind): boolean {
+  return getGVKTypeString(gvk) === getGVKTypeString(gvkType[gvk.Kind]);
+}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 7432a0988..c0de20de1 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -8917,7 +8917,7 @@ locate-path@^6.0.0:
   dependencies:
     p-locate "^5.0.0"
 
-lodash-es@^4.17.11, lodash-es@^4.17.21:
+lodash-es@^4.17.21:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
   integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
@@ -8982,7 +8982,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
 
-lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
+lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -10847,15 +10847,12 @@ react-refresh@^0.11.0:
   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
   integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
 
-react-resize-detector@3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-3.4.0.tgz#2ccd399958a0efe9b7c52c5db5a13d87e47cd585"
-  integrity sha512-T96I8Iqa1hGWyooeFA2Sl6FdPoMhXWINfEKg2/EJLxhP37+/94VNuyuyz9CRqpmApD83IWRR+lbB3r0ADMoKJg==
+react-resize-detector@9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-9.1.1.tgz#ce13cf55b9b09d9978fc51e0c87bb3639704921e"
+  integrity sha512-siLzop7i4xIvZIACE/PHTvRegA8QRCEt0TfmvJ/qCIFQJ4U+3NuYcF8tNDmDWxfIn+X1eNCyY2rauH4KRxge8w==
   dependencies:
-    lodash "^4.17.11"
-    lodash-es "^4.17.11"
-    prop-types "^15.6.2"
-    resize-observer-polyfill "^1.5.1"
+    lodash "^4.17.21"
 
 react-router-dom-v5-compat@^6.11.2:
   version "6.24.1"
@@ -11235,7 +11232,7 @@ reselect@4.0.0:
   resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
   integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
 
-resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1:
+resize-observer-polyfill@^1.5.0:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
   integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
diff --git a/graph/telemetry/istio/appender/istio_details.go b/graph/telemetry/istio/appender/istio_details.go
index f5f3538d7..479fb4e79 100644
--- a/graph/telemetry/istio/appender/istio_details.go
+++ b/graph/telemetry/istio/appender/istio_details.go
@@ -368,7 +368,7 @@ func (a IstioAppender) getIstioComponentWorkloads(component string, globalInfo *
 
 		// Find Istio component deployments
 		for _, workload := range wList.Workloads {
-			if workload.Type == "Deployment" {
+			if workload.WorkloadGVK == kubernetes.Deployments {
 				if labelValue, ok := workload.Labels["operator.istio.io/component"]; ok && labelValue == component {
 					componentWorkloads[key] = append(componentWorkloads[key], workload)
 				}
@@ -388,7 +388,7 @@ func (a IstioAppender) getGatewayAPIWorkloads(globalInfo *graph.GlobalInfo) map[
 
 		// Find Istio managed Gateway API deployments
 		for _, workload := range wList.Workloads {
-			if workload.Type == "Deployment" {
+			if workload.WorkloadGVK == kubernetes.Deployments {
 				if _, ok := workload.Labels["istio.io/gateway-name"]; ok {
 					managedWorkloads[key] = append(managedWorkloads[key], workload)
 				}
diff --git a/hack/istio/install-bookinfo-demo.sh b/hack/istio/install-bookinfo-demo.sh
index 246d513f9..49b7308f7 100755
--- a/hack/istio/install-bookinfo-demo.sh
+++ b/hack/istio/install-bookinfo-demo.sh
@@ -366,7 +366,8 @@ sleep 4
 # Expose the OpenShift routes
 if [ "${IS_OPENSHIFT}" == "true" ]; then
   $CLIENT_EXE expose svc/productpage -n ${NAMESPACE}
-  $CLIENT_EXE expose svc/istio-ingressgateway --port http2 -n ${ISTIO_NAMESPACE}
+  $CLIENT_EXE expose svc/istio-ingressgateway --port http2 -n ${INGRESS_NAMESPACE} --name=istio-ingressgateway
+  $CLIENT_EXE expose svc/bookinfo-gateway-istio --port=http -n ${NAMESPACE} --name=bookinfo-gateway-istio
 fi
 
 echo "Bookinfo Demo should be installed and starting up - here are the pods and services"
@@ -399,12 +400,25 @@ fi
 if [ "${TRAFFIC_GENERATOR_ENABLED}" == "true" ]; then
   echo "Installing Traffic Generator"
   if [ "${IS_OPENSHIFT}" == "true" ]; then
-    echo "Determining the route to send traffic to"
+    echo "Determining the route to send traffic to, trying istio-ingressgateway route in ${INGRESS_NAMESPACE} namespace"
+    # first, try istio-ingressgateway in istio-system, wait for a while the host is populated
+    # make sure you have latest kubectl/oc which support JSONPath condition without value
+    ${CLIENT_EXE} wait --for=jsonpath='{.status.ingress[].host}' --timeout=10s route istio-ingressgateway -n ${INGRESS_NAMESPACE}
     INGRESS_ROUTE=$(${CLIENT_EXE} get route istio-ingressgateway -o jsonpath='{.spec.host}{"\n"}' -n ${INGRESS_NAMESPACE})
-    while [ -z "${INGRESS_ROUTE}" ]; do
+    if [ -z "${INGRESS_ROUTE}" ]; then
       sleep 1
-      INGRESS_ROUTE=$(${CLIENT_EXE} get route productpage -o jsonpath='{.spec.host}{"\n"}' -n ${NAMESPACE})
-    done
+      echo "No istio-ingressgateway route in ${INGRESS_NAMESPACE} namespace, next, trying bookinfo-gateway-istio route in ${NAMESPACE} namespace"
+      # next, try route for gateway in bookinfo namespace created by gateway-api
+      ${CLIENT_EXE} wait --for=jsonpath='{.status.ingress[].host}' --timeout=10s route bookinfo-gateway-istio -n ${NAMESPACE}
+      INGRESS_ROUTE=$(${CLIENT_EXE} get route bookinfo-gateway-istio -o jsonpath='{.spec.host}{"\n"}' -n ${NAMESPACE})
+      if [ -z "${INGRESS_ROUTE}" ]; then
+        sleep 1
+        echo "No bookinfo-gateway-istio route in ${NAMESPACE} namespace, the route for productpage app will be used dirrectly"
+        # nevermind, use productpage route directly
+        ${CLIENT_EXE} wait --for=jsonpath='{.status.ingress[].host}' --timeout=10s route productpage -n ${NAMESPACE}
+        INGRESS_ROUTE=$(${CLIENT_EXE} get route productpage -o jsonpath='{.spec.host}{"\n"}' -n ${NAMESPACE})
+      fi
+    fi
     echo
     echo "Traffic Generator will use the OpenShift ingress route of: ${INGRESS_ROUTE}"
   else
diff --git a/handlers/dashboards.go b/handlers/dashboards.go
index db4ac3d74..af5aaf780 100644
--- a/handlers/dashboards.go
+++ b/handlers/dashboards.go
@@ -45,7 +45,7 @@ func CustomDashboard(conf *config.Config, grafana *grafana.Service) http.Handler
 
 		var wkd *models.Workload
 		if params.Workload != "" {
-			wkd, err = layer.Workload.GetWorkload(r.Context(), business.WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: params.Workload, WorkloadType: params.WorkloadType, IncludeServices: false})
+			wkd, err = layer.Workload.GetWorkload(r.Context(), business.WorkloadCriteria{Cluster: cluster, Namespace: namespace, WorkloadName: params.Workload, WorkloadGVK: params.WorkloadGVK, IncludeServices: false})
 			if err != nil {
 				if errors.IsNotFound(err) {
 					RespondWithError(w, http.StatusNotFound, err.Error())
diff --git a/handlers/workloads.go b/handlers/workloads.go
index 15bbfa8a7..98b42412c 100644
--- a/handlers/workloads.go
+++ b/handlers/workloads.go
@@ -9,12 +9,15 @@ import (
 	"strings"
 	"sync"
 
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
 	"github.com/gorilla/mux"
 
 	"github.com/kiali/kiali/business"
 	"github.com/kiali/kiali/config"
 	"github.com/kiali/kiali/log"
 	"github.com/kiali/kiali/models"
+	"github.com/kiali/kiali/util"
 )
 
 // workloadParams holds the path and query parameters for WorkloadList and WorkloadDetails
@@ -28,20 +31,19 @@ type workloadParams struct {
 	Namespace    string `json:"namespace"`
 	WorkloadName string `json:"workload"`
 	// in: query
-	WorkloadType string `json:"type"`
+	WorkloadGVK schema.GroupVersionKind `json:"workloadGVK"`
 	// Optional
 	ClusterName           string `json:"clusterName,omitempty"`
 	IncludeHealth         bool   `json:"health"`
 	IncludeIstioResources bool   `json:"istioResources"`
 }
 
-func (p *workloadParams) extract(r *http.Request) {
+func (p *workloadParams) extract(r *http.Request) error {
 	vars := mux.Vars(r)
 	query := r.URL.Query()
 	p.baseExtract(r, vars)
 	p.Namespace = vars["namespace"]
 	p.WorkloadName = vars["workload"]
-	p.WorkloadType = query.Get("type")
 	p.ClusterName = clusterNameFromQuery(query)
 
 	var err error
@@ -53,6 +55,12 @@ func (p *workloadParams) extract(r *http.Request) {
 	if err != nil {
 		p.IncludeIstioResources = true
 	}
+
+	p.WorkloadGVK, err = util.StringToGVK(query.Get("workloadGVK"))
+	if err != nil {
+		return err
+	}
+	return nil
 }
 
 // ClustersWorkloads is the API handler to fetch all the workloads to be displayed, related to a single namespace
@@ -60,7 +68,11 @@ func ClustersWorkloads(w http.ResponseWriter, r *http.Request) {
 	query := r.URL.Query()
 	namespacesQueryParam := query.Get("namespaces") // csl of namespaces
 	p := workloadParams{}
-	p.extract(r)
+	errParse := p.extract(r)
+	if errParse != nil {
+		RespondWithError(w, http.StatusInternalServerError, "Request parsing error: "+errParse.Error())
+		return
+	}
 
 	// Get business layer
 	businessLayer, err := getBusiness(r)
@@ -121,11 +133,15 @@ func ClustersWorkloads(w http.ResponseWriter, r *http.Request) {
 // WorkloadDetails is the API handler to fetch all details to be displayed, related to a single workload
 func WorkloadDetails(w http.ResponseWriter, r *http.Request) {
 	p := workloadParams{}
-	p.extract(r)
+	errParse := p.extract(r)
+	if errParse != nil {
+		RespondWithError(w, http.StatusInternalServerError, "Request parsing error: "+errParse.Error())
+		return
+	}
 
 	criteria := business.WorkloadCriteria{
 		Namespace: p.Namespace, WorkloadName: p.WorkloadName,
-		WorkloadType: p.WorkloadType, IncludeIstioResources: true, IncludeServices: true, IncludeHealth: p.IncludeHealth, RateInterval: p.RateInterval,
+		WorkloadGVK: p.WorkloadGVK, IncludeIstioResources: true, IncludeServices: true, IncludeHealth: p.IncludeHealth, RateInterval: p.RateInterval,
 		QueryTime: p.QueryTime, Cluster: p.ClusterName,
 	}
 
@@ -195,7 +211,11 @@ func WorkloadUpdate(w http.ResponseWriter, r *http.Request) {
 
 	namespace := params["namespace"]
 	workload := params["workload"]
-	workloadType := query.Get("type")
+	workloadGVK, errGVK := util.StringToGVK(query.Get("workloadGVK"))
+	if errGVK != nil {
+		RespondWithError(w, http.StatusBadRequest, "Update request with bad workloadGVK param: "+errGVK.Error())
+	}
+
 	cluster := clusterNameFromQuery(query)
 	log.Debugf("Cluster: %s", cluster)
 
@@ -222,7 +242,7 @@ func WorkloadUpdate(w http.ResponseWriter, r *http.Request) {
 		}()
 	}
 
-	workloadDetails, err := business.Workload.UpdateWorkload(r.Context(), cluster, namespace, workload, workloadType, true, jsonPatch, patchType)
+	workloadDetails, err := business.Workload.UpdateWorkload(r.Context(), cluster, namespace, workload, workloadGVK, true, jsonPatch, patchType)
 	if includeValidations && err == nil {
 		wg.Wait()
 		workloadDetails.Validations = istioConfigValidations
@@ -232,7 +252,7 @@ func WorkloadUpdate(w http.ResponseWriter, r *http.Request) {
 		handleErrorResponse(w, err)
 		return
 	}
-	auditMsg := fmt.Sprintf("UPDATE on Cluster: [%s] Namespace: [%s] Workload name: [%s] Type: [%s] Patch: [%s]", cluster, namespace, workload, workloadType, jsonPatch)
+	auditMsg := fmt.Sprintf("UPDATE on Cluster: [%s] Namespace: [%s] Workload name: [%s] Type: [%s] Patch: [%s]", cluster, namespace, workload, workloadGVK, jsonPatch)
 	audit(r, auditMsg)
 	RespondWithJSON(w, http.StatusOK, workloadDetails)
 }
diff --git a/kubernetes/filters.go b/kubernetes/filters.go
index 851e57c63..972c80a42 100644
--- a/kubernetes/filters.go
+++ b/kubernetes/filters.go
@@ -11,10 +11,12 @@ import (
 	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	k8s_networking_v1 "sigs.k8s.io/gateway-api/apis/v1"
 	k8s_networking_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 
 	"github.com/kiali/kiali/config"
+	"github.com/kiali/kiali/log"
 )
 
 func FilterAuthorizationPoliciesBySelector(workloadSelector string, authorizationpolicies []*security_v1.AuthorizationPolicy) []*security_v1.AuthorizationPolicy {
@@ -217,12 +219,16 @@ func FilterK8sGatewaysByRoutes(allGws []*k8s_networking_v1.Gateway, httpRoutes [
 	return gateways
 }
 
-func FilterPodsByController(controllerName string, controllerType string, allPods []core_v1.Pod) []core_v1.Pod {
+func FilterPodsByController(controllerName string, controllerGVK schema.GroupVersionKind, allPods []core_v1.Pod) []core_v1.Pod {
 	var pods []core_v1.Pod
 	for _, pod := range allPods {
 		for _, ref := range pod.OwnerReferences {
-			// TODO: Kind is not a definitive reference. Need to include check for API version as well.
-			if ref.Controller != nil && *ref.Controller && ref.Name == controllerName && ref.Kind == controllerType {
+			refGV, err := schema.ParseGroupVersion(ref.APIVersion)
+			if err != nil {
+				log.Errorf("could not parse OwnerReference api version %q: %v", ref.APIVersion, err)
+				continue
+			}
+			if ref.Controller != nil && *ref.Controller && ref.Name == controllerName && refGV.WithKind(ref.Kind) == controllerGVK {
 				pods = append(pods, pod)
 				break
 			}
diff --git a/kubernetes/kubernetes.go b/kubernetes/kubernetes.go
index a94950da0..dbb6c7b5e 100644
--- a/kubernetes/kubernetes.go
+++ b/kubernetes/kubernetes.go
@@ -57,7 +57,7 @@ type K8SClientInterface interface {
 	StreamPodLogs(namespace, name string, opts *core_v1.PodLogOptions) (io.ReadCloser, error)
 	UpdateNamespace(namespace string, jsonPatch string) (*core_v1.Namespace, error)
 	UpdateService(namespace string, name string, jsonPatch string, patchType string) error
-	UpdateWorkload(namespace string, name string, workloadType string, jsonPatch string, patchType string) error
+	UpdateWorkload(namespace string, name string, workloadGVK schema.GroupVersionKind, jsonPatch string, patchType string) error
 }
 
 type OSClientInterface interface {
@@ -456,34 +456,34 @@ func (in *K8SClient) GetSelfSubjectAccessReview(ctx context.Context, namespace,
 	return result, err
 }
 
-func (in *K8SClient) UpdateWorkload(namespace string, workloadName string, workloadType string, jsonPatch string, patchType string) error {
+func (in *K8SClient) UpdateWorkload(namespace string, workloadName string, workloadGVK schema.GroupVersionKind, jsonPatch string, patchType string) error {
 	emptyPatchOptions := meta_v1.PatchOptions{}
 	bytePatch := []byte(jsonPatch)
 	var err error
-	switch workloadType {
-	case DeploymentType:
+	switch workloadGVK {
+	case Deployments:
 		_, err = in.k8s.AppsV1().Deployments(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
-	case ReplicaSetType:
+	case ReplicaSets:
 		_, err = in.k8s.AppsV1().ReplicaSets(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
-	case ReplicationControllerType:
+	case ReplicationControllers:
 		_, err = in.k8s.CoreV1().ReplicationControllers(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
-	case DeploymentConfigType:
+	case DeploymentConfigs:
 		if in.IsOpenShift() {
 			result := &osapps_v1.DeploymentConfigList{}
 			err = in.k8s.Discovery().RESTClient().Patch(GetPatchType(patchType)).Prefix("apis", "apps.openshift.io", "v1").Namespace(namespace).Resource("deploymentconfigs").SubResource(workloadName).Body(bytePatch).Do(in.ctx).Into(result)
 		}
-	case StatefulSetType:
+	case StatefulSets:
 		_, err = in.k8s.AppsV1().StatefulSets(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
-	case JobType:
+	case Jobs:
 		_, err = in.k8s.BatchV1().Jobs(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
-	case CronJobType:
+	case CronJobs:
 		_, err = in.k8s.BatchV1().CronJobs(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
-	case PodType:
+	case Pods:
 		_, err = in.k8s.CoreV1().Pods(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
-	case DaemonSetType:
+	case DaemonSets:
 		_, err = in.k8s.AppsV1().DaemonSets(namespace).Patch(in.ctx, workloadName, GetPatchType(patchType), bytePatch, emptyPatchOptions)
 	default:
-		err = fmt.Errorf("Workload type %s not found", workloadType)
+		err = fmt.Errorf("Workload type %s not found", workloadGVK.String())
 	}
 	return err
 }
diff --git a/make/Makefile.build.mk b/make/Makefile.build.mk
index 2081feb78..678775bb2 100644
--- a/make/Makefile.build.mk
+++ b/make/Makefile.build.mk
@@ -84,8 +84,6 @@ test-integration: test-integration-setup .ensure-envtest-bin-dir-exists
 	@echo Running Integration tests
 	cd tests/integration/tests && ${GO} test ${GO_TEST_FLAGS} -v -timeout 30m 2>&1 | tee >(go-junit-report > ../junit-rest-report.xml) ../int-test.log
 	@echo Test results can be found here: $$(ls -1 ${ROOTDIR}/tests/integration/junit-rest-report.xml)
-	@echo Running controller integration tests
-	@cd tests/integration/controller && ${GO} test -v
 
 test-integration-controller: .ensure-envtest-bin-dir-exists
 	@echo Running controller integration tests
diff --git a/models/dashboards.go b/models/dashboards.go
index bcf7beb1d..5a7deea0f 100644
--- a/models/dashboards.go
+++ b/models/dashboards.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"sort"
 
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
 	"github.com/kiali/kiali/config"
 	"github.com/kiali/kiali/config/dashboards"
 	"github.com/kiali/kiali/prometheus"
@@ -17,7 +19,7 @@ type DashboardQuery struct {
 	AdditionalLabels  []Aggregation
 	RawDataAggregator string
 	Workload          string
-	WorkloadType      string
+	WorkloadGVK       schema.GroupVersionKind
 }
 
 // FillDefaults fills the struct with default parameters
diff --git a/models/istio_config.go b/models/istio_config.go
index e0c30d448..12c76e167 100644
--- a/models/istio_config.go
+++ b/models/istio_config.go
@@ -363,134 +363,134 @@ func (icd *IstioConfigDetails) UnmarshalJSON(data []byte) error {
 	icd.IstioConfigHelpFields = temp.IstioConfigHelpFields
 
 	// Based on the GVK, determine which resource type to unmarshal the resource into
-	switch temp.ObjectGVK.String() {
-	case kubernetes.AuthorizationPolicies.String():
+	switch temp.ObjectGVK {
+	case kubernetes.AuthorizationPolicies:
 		var ap security_v1.AuthorizationPolicy
 		if err := json.Unmarshal(temp.Resource, &ap); err != nil {
 			return err
 		}
 		icd.AuthorizationPolicy = &ap
 
-	case kubernetes.DestinationRules.String():
+	case kubernetes.DestinationRules:
 		var dr networking_v1.DestinationRule
 		if err := json.Unmarshal(temp.Resource, &dr); err != nil {
 			return err
 		}
 		icd.DestinationRule = &dr
 
-	case kubernetes.EnvoyFilters.String():
+	case kubernetes.EnvoyFilters:
 		var ef networking_v1alpha3.EnvoyFilter
 		if err := json.Unmarshal(temp.Resource, &ef); err != nil {
 			return err
 		}
 		icd.EnvoyFilter = &ef
 
-	case kubernetes.Gateways.String():
+	case kubernetes.Gateways:
 		var gw networking_v1.Gateway
 		if err := json.Unmarshal(temp.Resource, &gw); err != nil {
 			return err
 		}
 		icd.Gateway = &gw
 
-	case kubernetes.PeerAuthentications.String():
+	case kubernetes.PeerAuthentications:
 		var pa security_v1.PeerAuthentication
 		if err := json.Unmarshal(temp.Resource, &pa); err != nil {
 			return err
 		}
 		icd.PeerAuthentication = &pa
 
-	case kubernetes.RequestAuthentications.String():
+	case kubernetes.RequestAuthentications:
 		var ra security_v1.RequestAuthentication
 		if err := json.Unmarshal(temp.Resource, &ra); err != nil {
 			return err
 		}
 		icd.RequestAuthentication = &ra
 
-	case kubernetes.ServiceEntries.String():
+	case kubernetes.ServiceEntries:
 		var se networking_v1.ServiceEntry
 		if err := json.Unmarshal(temp.Resource, &se); err != nil {
 			return err
 		}
 		icd.ServiceEntry = &se
 
-	case kubernetes.Sidecars.String():
+	case kubernetes.Sidecars:
 		var sc networking_v1.Sidecar
 		if err := json.Unmarshal(temp.Resource, &sc); err != nil {
 			return err
 		}
 		icd.Sidecar = &sc
 
-	case kubernetes.VirtualServices.String():
+	case kubernetes.VirtualServices:
 		var vs networking_v1.VirtualService
 		if err := json.Unmarshal(temp.Resource, &vs); err != nil {
 			return err
 		}
 		icd.VirtualService = &vs
 
-	case kubernetes.WorkloadEntries.String():
+	case kubernetes.WorkloadEntries:
 		var we networking_v1.WorkloadEntry
 		if err := json.Unmarshal(temp.Resource, &we); err != nil {
 			return err
 		}
 		icd.WorkloadEntry = &we
 
-	case kubernetes.WorkloadGroups.String():
+	case kubernetes.WorkloadGroups:
 		var wg networking_v1.WorkloadGroup
 		if err := json.Unmarshal(temp.Resource, &wg); err != nil {
 			return err
 		}
 		icd.WorkloadGroup = &wg
 
-	case kubernetes.WasmPlugins.String():
+	case kubernetes.WasmPlugins:
 		var wp extentions_v1alpha1.WasmPlugin
 		if err := json.Unmarshal(temp.Resource, &wp); err != nil {
 			return err
 		}
 		icd.WasmPlugin = &wp
 
-	case kubernetes.Telemetries.String():
+	case kubernetes.Telemetries:
 		var tm telemetry_v1.Telemetry
 		if err := json.Unmarshal(temp.Resource, &tm); err != nil {
 			return err
 		}
 		icd.Telemetry = &tm
 
-	case kubernetes.K8sGateways.String():
+	case kubernetes.K8sGateways:
 		var kg k8s_networking_v1.Gateway
 		if err := json.Unmarshal(temp.Resource, &kg); err != nil {
 			return err
 		}
 		icd.K8sGateway = &kg
 
-	case kubernetes.K8sGRPCRoutes.String():
+	case kubernetes.K8sGRPCRoutes:
 		var grpcRoute k8s_networking_v1.GRPCRoute
 		if err := json.Unmarshal(temp.Resource, &grpcRoute); err != nil {
 			return err
 		}
 		icd.K8sGRPCRoute = &grpcRoute
 
-	case kubernetes.K8sHTTPRoutes.String():
+	case kubernetes.K8sHTTPRoutes:
 		var httpRoute k8s_networking_v1.HTTPRoute
 		if err := json.Unmarshal(temp.Resource, &httpRoute); err != nil {
 			return err
 		}
 		icd.K8sHTTPRoute = &httpRoute
 
-	case kubernetes.K8sReferenceGrants.String():
+	case kubernetes.K8sReferenceGrants:
 		var refGrant k8s_networking_v1beta1.ReferenceGrant
 		if err := json.Unmarshal(temp.Resource, &refGrant); err != nil {
 			return err
 		}
 		icd.K8sReferenceGrant = &refGrant
 
-	case kubernetes.K8sTCPRoutes.String():
+	case kubernetes.K8sTCPRoutes:
 		var tcpRoute k8s_networking_v1alpha2.TCPRoute
 		if err := json.Unmarshal(temp.Resource, &tcpRoute); err != nil {
 			return err
 		}
 		icd.K8sTCPRoute = &tcpRoute
 
-	case kubernetes.K8sTLSRoutes.String():
+	case kubernetes.K8sTLSRoutes:
 		var tlsRoute k8s_networking_v1alpha2.TLSRoute
 		if err := json.Unmarshal(temp.Resource, &tlsRoute); err != nil {
 			return err
diff --git a/models/istio_validation.go b/models/istio_validation.go
index 381f35fe2..34abf41e5 100644
--- a/models/istio_validation.go
+++ b/models/istio_validation.go
@@ -381,7 +381,7 @@ func (iv IstioValidations) FilterBySingleType(objectGVK schema.GroupVersionKind,
 	fiv := IstioValidations{}
 	for k, v := range iv {
 		// We don't want to filter other types
-		if k.ObjectGVK.String() != objectGVK.String() {
+		if k.ObjectGVK != objectGVK {
 			fiv[k] = v
 		} else {
 			// But for this exact type we're strict
@@ -397,7 +397,7 @@ func (iv IstioValidations) FilterBySingleType(objectGVK schema.GroupVersionKind,
 func (iv IstioValidations) FilterByKey(objectGVK schema.GroupVersionKind, name string) IstioValidations {
 	fiv := IstioValidations{}
 	for k, v := range iv {
-		if k.Name == name && k.ObjectGVK.String() == objectGVK.String() {
+		if k.Name == name && k.ObjectGVK == objectGVK {
 			fiv[k] = v
 		}
 	}
diff --git a/models/workload.go b/models/workload.go
index 91544ad03..a399037a6 100644
--- a/models/workload.go
+++ b/models/workload.go
@@ -10,8 +10,10 @@ import (
 	core_v1 "k8s.io/api/core/v1"
 	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 
 	"github.com/kiali/kiali/config"
+	"github.com/kiali/kiali/kubernetes"
 )
 
 type ClusterWorkloads struct {
@@ -67,10 +69,10 @@ type WorkloadListItem struct {
 	// The kube cluster where this workload is located.
 	Cluster string `json:"cluster"`
 
-	// Type of the workload
+	// Group Version Kind of the workload
 	// required: true
-	// example: deployment
-	Type string `json:"type"`
+	// example: 'apps/v1, Kind=Deployment'
+	WorkloadGVK schema.GroupVersionKind `json:"gvk"`
 
 	// Creation timestamp (in RFC3339 format)
 	// required: true
@@ -207,7 +209,7 @@ func (workload *WorkloadListItem) ParseWorkload(w *Workload) {
 	conf := config.Get()
 	workload.Name = w.Name
 	workload.Namespace = w.Namespace
-	workload.Type = w.Type
+	workload.WorkloadGVK = w.WorkloadGVK
 	workload.CreatedAt = w.CreatedAt
 	workload.ResourceVersion = w.ResourceVersion
 	workload.IstioSidecar = w.HasIstioSidecar()
@@ -284,7 +286,7 @@ func (workload *Workload) parseObjectMeta(meta *meta_v1.ObjectMeta, tplMeta *met
 }
 
 func (workload *Workload) ParseDeployment(d *apps_v1.Deployment) {
-	workload.Type = "Deployment"
+	workload.WorkloadGVK = kubernetes.Deployments
 	workload.parseObjectMeta(&d.ObjectMeta, &d.Spec.Template.ObjectMeta)
 	if d.Spec.Replicas != nil {
 		workload.DesiredReplicas = *d.Spec.Replicas
@@ -299,7 +301,7 @@ func (workload *Workload) ParseDeployment(d *apps_v1.Deployment) {
 }
 
 func (workload *Workload) ParseReplicaSet(r *apps_v1.ReplicaSet) {
-	workload.Type = "ReplicaSet"
+	workload.WorkloadGVK = kubernetes.ReplicaSets
 	workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta)
 	if r.Spec.Replicas != nil {
 		workload.DesiredReplicas = *r.Spec.Replicas
@@ -308,12 +310,12 @@ func (workload *Workload) ParseReplicaSet(r *apps_v1.ReplicaSet) {
 	workload.AvailableReplicas = r.Status.AvailableReplicas
 }
 
-func (workload *Workload) ParseReplicaSetParent(r *apps_v1.ReplicaSet, workloadName string, workloadType string) {
+func (workload *Workload) ParseReplicaSetParent(r *apps_v1.ReplicaSet, workloadName string, workloadGVK schema.GroupVersionKind) {
 	// Some properties are taken from the ReplicaSet
 	workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta)
 	// But name and type are coming from the parent
 	// Custom properties from parent controller are not processed by Kiali
-	workload.Type = workloadType
+	workload.WorkloadGVK = workloadGVK
 	workload.Name = workloadName
 	if r.Spec.Replicas != nil {
 		workload.DesiredReplicas = *r.Spec.Replicas
@@ -323,7 +325,7 @@ func (workload *Workload) ParseReplicaSetParent(r *apps_v1.ReplicaSet, workloadN
 }
 
 func (workload *Workload) ParseReplicationController(r *core_v1.ReplicationController) {
-	workload.Type = "ReplicationController"
+	workload.WorkloadGVK = kubernetes.ReplicationControllers
 	workload.parseObjectMeta(&r.ObjectMeta, &r.Spec.Template.ObjectMeta)
 	if r.Spec.Replicas != nil {
 		workload.DesiredReplicas = *r.Spec.Replicas
@@ -333,7 +335,7 @@ func (workload *Workload) ParseReplicationController(r *core_v1.ReplicationContr
 }
 
 func (workload *Workload) ParseDeploymentConfig(dc *osapps_v1.DeploymentConfig) {
-	workload.Type = "DeploymentConfig"
+	workload.WorkloadGVK = kubernetes.DeploymentConfigs
 	workload.parseObjectMeta(&dc.ObjectMeta, &dc.Spec.Template.ObjectMeta)
 	workload.DesiredReplicas = dc.Spec.Replicas
 	workload.CurrentReplicas = dc.Status.Replicas
@@ -341,7 +343,7 @@ func (workload *Workload) ParseDeploymentConfig(dc *osapps_v1.DeploymentConfig)
 }
 
 func (workload *Workload) ParseStatefulSet(s *apps_v1.StatefulSet) {
-	workload.Type = "StatefulSet"
+	workload.WorkloadGVK = kubernetes.StatefulSets
 	workload.parseObjectMeta(&s.ObjectMeta, &s.Spec.Template.ObjectMeta)
 	if s.Spec.Replicas != nil {
 		workload.DesiredReplicas = *s.Spec.Replicas
@@ -351,7 +353,7 @@ func (workload *Workload) ParseStatefulSet(s *apps_v1.StatefulSet) {
 }
 
 func (workload *Workload) ParsePod(pod *core_v1.Pod) {
-	workload.Type = "Pod"
+	workload.WorkloadGVK = kubernetes.Pods
 	workload.parseObjectMeta(&pod.ObjectMeta, &pod.ObjectMeta)
 
 	var podReplicas, podAvailableReplicas int32
@@ -375,7 +377,7 @@ func (workload *Workload) ParsePod(pod *core_v1.Pod) {
 }
 
 func (workload *Workload) ParseJob(job *batch_v1.Job) {
-	workload.Type = "Job"
+	workload.WorkloadGVK = kubernetes.Jobs
 	workload.parseObjectMeta(&job.ObjectMeta, &job.ObjectMeta)
 	// Job controller does not use replica parameters as other controllers
 	// this is a workaround to use same values from Workload perspective
@@ -385,7 +387,7 @@ func (workload *Workload) ParseJob(job *batch_v1.Job) {
 }
 
 func (workload *Workload) ParseCronJob(cnjb *batch_v1.CronJob) {
-	workload.Type = "CronJob"
+	workload.WorkloadGVK = kubernetes.CronJobs
 	workload.parseObjectMeta(&cnjb.ObjectMeta, &cnjb.ObjectMeta)
 
 	// We don't have the information of this controller
@@ -410,7 +412,7 @@ func (workload *Workload) ParseCronJob(cnjb *batch_v1.CronJob) {
 }
 
 func (workload *Workload) ParseDaemonSet(ds *apps_v1.DaemonSet) {
-	workload.Type = "DaemonSet"
+	workload.WorkloadGVK = kubernetes.DaemonSets
 	workload.parseObjectMeta(&ds.ObjectMeta, &ds.Spec.Template.ObjectMeta)
 	// This is a cornercase for DaemonSet controllers
 	// Desired is the number of desired nodes in a cluster that are running a DaemonSet Pod
@@ -421,10 +423,10 @@ func (workload *Workload) ParseDaemonSet(ds *apps_v1.DaemonSet) {
 	workload.HealthAnnotations = GetHealthAnnotation(ds.Annotations, GetHealthConfigAnnotation())
 }
 
-func (workload *Workload) ParsePods(controllerName string, controllerType string, pods []core_v1.Pod) {
+func (workload *Workload) ParsePods(controllerName string, controllerGVK schema.GroupVersionKind, pods []core_v1.Pod) {
 	conf := config.Get()
 	workload.Name = controllerName
-	workload.Type = controllerType
+	workload.WorkloadGVK = controllerGVK
 	// We don't have the information of this controller
 	// We will infer the number of replicas as the number of pods without succeed state
 	// We will infer the number of available as the number of pods with running state
@@ -476,7 +478,7 @@ func (workload *Workload) HasIstioSidecar() bool {
 		return true
 	}
 	// All pods in a deployment should be the same
-	if workload.Type == "Deployment" {
+	if workload.WorkloadGVK == kubernetes.Deployments {
 		return workload.Pods[0].HasIstioSidecar()
 	}
 	// Need to check each pod
@@ -545,7 +547,7 @@ func (workload *Workload) HasIstioAmbient() bool {
 		return false
 	}
 	// All pods in a deployment should be the same
-	if workload.Type == "Deployment" {
+	if workload.WorkloadGVK == kubernetes.Deployments {
 		return workload.Pods[0].AmbientEnabled()
 	}
 	// Need to check each pod
diff --git a/prometheus/internalmetrics/internal_metrics.go b/prometheus/internalmetrics/internal_metrics.go
index 9ca21de94..b600d07c5 100644
--- a/prometheus/internalmetrics/internal_metrics.go
+++ b/prometheus/internalmetrics/internal_metrics.go
@@ -40,6 +40,8 @@ type MetricsType struct {
 	MeshGraphMarshalTime           *prometheus.HistogramVec
 	PrometheusProcessingTime       *prometheus.HistogramVec
 	SingleValidationProcessingTime *prometheus.HistogramVec
+	CacheTotalRequests             *prometheus.CounterVec
+	CacheHitsTotal                 *prometheus.CounterVec
 	ValidationProcessingTime       *prometheus.HistogramVec
 }
 
@@ -124,6 +126,20 @@ var Metrics = MetricsType{
 		},
 		[]string{labelNamespace, labelType, labelName},
 	),
+	CacheTotalRequests: prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "kiali_cache_requests_total",
+			Help: "The number of total requests for the cache.",
+		},
+		[]string{labelName},
+	),
+	CacheHitsTotal: prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "kiali_cache_hits_total",
+			Help: "The number of total hits for the cache.",
+		},
+		[]string{labelName},
+	),
 }
 
 // SuccessOrFailureMetricType let's you capture metrics for both successes and failures,
@@ -185,6 +201,8 @@ func RegisterInternalMetrics() {
 		Metrics.CheckerProcessingTime,
 		Metrics.ValidationProcessingTime,
 		Metrics.SingleValidationProcessingTime,
+		Metrics.CacheTotalRequests,
+		Metrics.CacheHitsTotal,
 	)
 }
 
@@ -392,3 +410,15 @@ func GetAPIFailureMetric(route string) prometheus.Counter {
 func SetKubernetesClients(clientCount int) {
 	Metrics.KubernetesClients.With(prometheus.Labels{}).Set(float64(clientCount))
 }
+
+func GetCacheRequestsTotalMetric(cache string) prometheus.Counter {
+	return Metrics.CacheTotalRequests.With(prometheus.Labels{
+		labelName: cache,
+	})
+}
+
+func GetCacheHitsTotalMetric(cache string) prometheus.Counter {
+	return Metrics.CacheHitsTotal.With(prometheus.Labels{
+		labelName: cache,
+	})
+}
diff --git a/store/fifo_store.go b/store/fifo_store.go
new file mode 100644
index 000000000..e3dfed453
--- /dev/null
+++ b/store/fifo_store.go
@@ -0,0 +1,103 @@
+package store
+
+import (
+	"container/list"
+	"sync"
+
+	"github.com/kiali/kiali/log"
+	"github.com/kiali/kiali/prometheus/internalmetrics"
+)
+
+// fifoStore uses a FIFO approach storage and is safe for concurrent use.
+type fifoStore[K comparable, V any] struct {
+	capacity int
+	lock     sync.RWMutex
+	name     string // Used for metrics
+	order    *list.List
+	Store[K, V]
+}
+
+func NewFIFOStore[K comparable, V any](store Store[K, V], capacity int, name string) *fifoStore[K, V] {
+
+	f := &fifoStore[K, V]{
+		capacity: capacity,
+		Store:    store,
+		order:    list.New(),
+		name:     name,
+	}
+	return f
+}
+
+// Get returns the value associated with the given key or an error.
+func (f *fifoStore[K, V]) Get(key K) (V, bool) {
+	internalmetrics.GetCacheRequestsTotalMetric(f.name).Inc()
+	elem, exists := f.Store.Get(key)
+	if !exists {
+		var value V
+		log.Tracef("[FIFO store] Element doesnt exist: %v", key)
+		return value, false
+	}
+
+	log.Tracef("[FIFO store] Returning from cache: %v", key)
+	internalmetrics.GetCacheHitsTotalMetric(f.name).Inc()
+	return elem, true
+}
+
+// Set Adds an item into the store, at the end of the list
+// If the key already exists, modifies the value, without modify the element order
+func (f *fifoStore[K, V]) Set(key K, value V) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+
+	_, exists := f.Store.Get(key)
+
+	// Remove older
+	if f.order != nil && f.order.Len() >= f.capacity && !exists {
+		oldest := f.order.Front()
+		if oldest != nil {
+			f.order.Remove(oldest)
+			f.Store.Remove(oldest.Value.(K))
+		}
+	}
+
+	if !exists {
+		f.order.PushBack(key)
+	}
+	f.Store.Set(key, value)
+}
+
+// Remove removes an element from the store
+func (f *fifoStore[K, V]) Remove(key K) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+
+	f.order.Remove(&list.Element{Value: key})
+	f.Store.Remove(key)
+}
+
+// Replace replaces the contents of the store with the given map and updates the order list
+func (f *fifoStore[K, V]) Replace(items map[K]V) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+
+	if len(items) > f.capacity {
+		truncated := make(map[K]V, f.capacity)
+		count := 0
+
+		for k, v := range items {
+			if count >= f.capacity {
+				break
+			}
+			truncated[k] = v
+			count++
+		}
+		items = truncated
+	}
+
+	f.Store.Replace(items)
+	f.order = list.New()
+
+	for key := range items {
+		f.order.PushBack(key)
+	}
+}
diff --git a/tracing/client.go b/tracing/client.go
index f64e70874..49968d8e1 100644
--- a/tracing/client.go
+++ b/tracing/client.go
@@ -180,7 +180,7 @@ func newClient(ctx context.Context, cfg *config.Config, token string) (*Client,
 		log.Infof("Create Tracing HTTP client %s", u)
 
 		if cfgTracing.Provider == config.TempoProvider {
-			httpTracingClient, err = tempo.NewOtelClient(client, u)
+			httpTracingClient, err = tempo.NewOtelClient(ctx, client, u)
 			if err != nil {
 				log.Errorf("Error creating HTTP client %s", err.Error())
 				return nil, err
diff --git a/tracing/tempo/http_client.go b/tracing/tempo/http_client.go
index d5a045ee3..7b415a73a 100644
--- a/tracing/tempo/http_client.go
+++ b/tracing/tempo/http_client.go
@@ -1,6 +1,7 @@
 package tempo
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -10,10 +11,12 @@ import (
 	"path"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/kiali/kiali/config"
 	"github.com/kiali/kiali/log"
 	"github.com/kiali/kiali/models"
+	"github.com/kiali/kiali/store"
 	"github.com/kiali/kiali/tracing/jaeger/model"
 	jaegerModels "github.com/kiali/kiali/tracing/jaeger/model/json"
 	otel "github.com/kiali/kiali/tracing/otel/model"
@@ -22,13 +25,20 @@ import (
 	"github.com/kiali/kiali/util"
 )
 
+const (
+	expirationCheckInterval = 1 * time.Minute
+	TTL                     = 5 * time.Minute
+)
+
 type OtelHTTPClient struct {
 	ClusterTag bool
+	TempoCache store.Store[string, *model.TracingSingleTrace]
 }
 
 // New client
-func NewOtelClient(client http.Client, baseURL *url.URL) (otelClient *OtelHTTPClient, err error) {
+func NewOtelClient(ctx context.Context, client http.Client, baseURL *url.URL) (otelClient *OtelHTTPClient, err error) {
 	url := *baseURL
+
 	// Istio adds the istio.cluster_id tag
 	// That allows to filter traces by cluster in MC environments
 	// This is a check to validate that this tag exists before use it
@@ -50,13 +60,23 @@ func NewOtelClient(client http.Client, baseURL *url.URL) (otelClient *OtelHTTPCl
 		}
 	}
 
-	return &OtelHTTPClient{ClusterTag: tags}, nil
+	otelHTTPClient := &OtelHTTPClient{ClusterTag: tags}
+	if config.Get().ExternalServices.Tracing.TempoConfig.CacheEnabled {
+		s := store.New[string, *model.TracingSingleTrace]()
+		fifoStore := store.NewFIFOStore(s, config.Get().ExternalServices.Tracing.TempoConfig.CacheCapacity, "tempo")
+		otelHTTPClient.TempoCache = store.NewExpirationStore[string, *model.TracingSingleTrace](ctx, fifoStore, util.AsPtr(TTL), util.AsPtr(expirationCheckInterval))
+	}
+
+	return otelHTTPClient, nil
 }
 
 // GetAppTracesHTTP search traces
-func (oc OtelHTTPClient) GetAppTracesHTTP(client http.Client, baseURL *url.URL, serviceName string, q models.TracingQuery) (response *model.TracingResponse, err error) {
+func (oc *OtelHTTPClient) GetAppTracesHTTP(client http.Client, baseURL *url.URL, serviceName string, q models.TracingQuery) (response *model.TracingResponse, err error) {
 	url := *baseURL
 	url.Path = path.Join(url.Path, "/api/search")
+	if q.End.Before(q.Start) {
+		return nil, fmt.Errorf("end time must be greater than start time")
+	}
 	oc.prepareTraceQL(&url, serviceName, q)
 
 	r, err := oc.queryTracesHTTP(client, &url, q.Tags["error"])
@@ -68,8 +88,13 @@ func (oc OtelHTTPClient) GetAppTracesHTTP(client http.Client, baseURL *url.URL,
 }
 
 // GetTraceDetailHTTP get one trace by trace ID
-func (oc OtelHTTPClient) GetTraceDetailHTTP(client http.Client, endpoint *url.URL, traceID string) (*model.TracingSingleTrace, error) {
+func (oc *OtelHTTPClient) GetTraceDetailHTTP(client http.Client, endpoint *url.URL, traceID string) (*model.TracingSingleTrace, error) {
 	u := *endpoint
+	if config.Get().ExternalServices.Tracing.TempoConfig.CacheEnabled {
+		if result, isCached := oc.TempoCache.Get(traceID); isCached {
+			return result, nil
+		}
+	}
 	u.Path = path.Join(u.Path, "/api/traces/", traceID)
 	resp, code, reqError := makeRequest(client, u.String(), nil)
 	if reqError != nil {
@@ -97,6 +122,12 @@ func (oc OtelHTTPClient) GetTraceDetailHTTP(client http.Client, endpoint *url.UR
 	if len(response.Data) == 0 {
 		return &model.TracingSingleTrace{Errors: response.Errors}, nil
 	}
+	if config.Get().ExternalServices.Tracing.TempoConfig.CacheEnabled {
+		oc.TempoCache.Set(traceID, &model.TracingSingleTrace{
+			Data:   response.Data[0],
+			Errors: response.Errors,
+		})
+	}
 	return &model.TracingSingleTrace{
 		Data:   response.Data[0],
 		Errors: response.Errors,
@@ -104,7 +135,7 @@ func (oc OtelHTTPClient) GetTraceDetailHTTP(client http.Client, endpoint *url.UR
 }
 
 // GetServiceStatusHTTP get service status
-func (oc OtelHTTPClient) GetServiceStatusHTTP(client http.Client, baseURL *url.URL) (bool, error) {
+func (oc *OtelHTTPClient) GetServiceStatusHTTP(client http.Client, baseURL *url.URL) (bool, error) {
 	var u url.URL
 	healthCheckUrl := config.Get().ExternalServices.Tracing.HealthCheckUrl
 	if healthCheckUrl != "" {
@@ -126,7 +157,7 @@ func (oc OtelHTTPClient) GetServiceStatusHTTP(client http.Client, baseURL *url.U
 }
 
 // queryTracesHTTP
-func (oc OtelHTTPClient) queryTracesHTTP(client http.Client, u *url.URL, error string) (*model.TracingResponse, error) {
+func (oc *OtelHTTPClient) queryTracesHTTP(client http.Client, u *url.URL, error string) (*model.TracingResponse, error) {
 	// HTTP and GRPC requests co-exist, but when minDuration is present, for HTTP it requires a unit (ms)
 	// https://github.com/kiali/kiali/issues/3939
 	minDuration := u.Query().Get("minDuration")
@@ -155,7 +186,7 @@ func (oc OtelHTTPClient) queryTracesHTTP(client http.Client, u *url.URL, error s
 }
 
 // transformTrace processes every trace ID and make a request to get all the spans for that trace
-func (oc OtelHTTPClient) transformTrace(traces *otel.Traces, error string, limit int) (*model.TracingResponse, error) {
+func (oc *OtelHTTPClient) transformTrace(traces *otel.Traces, error string, limit int) (*model.TracingResponse, error) {
 	var response model.TracingResponse
 	serviceName := ""
 
@@ -245,7 +276,7 @@ func convertSingleTrace(traces *otelModels.Data, id string) (*model.TracingRespo
 }
 
 // prepareTraceQL set the query in TraceQL format
-func (oc OtelHTTPClient) prepareTraceQL(u *url.URL, tracingServiceName string, query models.TracingQuery) {
+func (oc *OtelHTTPClient) prepareTraceQL(u *url.URL, tracingServiceName string, query models.TracingQuery) {
 	q := url.Values{}
 	q.Set("start", fmt.Sprintf("%d", query.Start.Unix()))
 	q.Set("end", fmt.Sprintf("%d", query.End.Unix()))
@@ -280,7 +311,7 @@ func (oc OtelHTTPClient) prepareTraceQL(u *url.URL, tracingServiceName string, q
 }
 
 // GetTraceQLQuery returns the raw query in TraceQL format
-func (oc OtelHTTPClient) GetTraceQLQuery(u *url.URL, tracingServiceName string, query models.TracingQuery) string {
+func (oc *OtelHTTPClient) GetTraceQLQuery(u *url.URL, tracingServiceName string, query models.TracingQuery) string {
 	oc.prepareTraceQL(u, tracingServiceName, query)
 	return u.RawQuery
 }


Test patch:
diff --git a/business/openshift_oauth_internal_test.go b/business/openshift_oauth_internal_test.go
index 9e4010072..712fe10ab 100644
--- a/business/openshift_oauth_internal_test.go
+++ b/business/openshift_oauth_internal_test.go
@@ -37,9 +37,11 @@ func TestSystemPoolAddedToClient(t *testing.T) {
 	require.NoError(t, err)
 
 	cases := map[string]struct {
-		restConfig rest.Config
-		pool       []*x509.Certificate
-		expected   []*x509.Certificate
+		conf             *config.Config
+		restConfig       rest.Config
+		pool             []*x509.Certificate
+		expected         []*x509.Certificate
+		expectedInsecure bool
 	}{
 		"cert in pool but not in rest config has just one cert": {
 			pool:     []*x509.Certificate{caCert},
@@ -54,11 +56,22 @@ func TestSystemPoolAddedToClient(t *testing.T) {
 			pool:       []*x509.Certificate{caCert2},
 			expected:   []*x509.Certificate{caCert, caCert2},
 		},
+		"insecure setting has insecure set": {
+			conf:             &config.Config{Auth: config.AuthConfig{OpenShift: config.OpenShiftConfig{InsecureSkipVerifyTLS: true}}},
+			restConfig:       rest.Config{TLSClientConfig: rest.TLSClientConfig{CAData: ca}},
+			pool:             []*x509.Certificate{caCert2},
+			expected:         []*x509.Certificate{caCert}, // this will still get loaded from restconfig.
+			expectedInsecure: true,
+		},
 	}
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
 			require := require.New(t)
 
+			if tc.conf == nil {
+				tc.conf = config.NewConfig()
+			}
+
 			expected := x509.NewCertPool()
 			for _, cert := range tc.expected {
 				expected.AddCert(cert)
@@ -69,7 +82,7 @@ func TestSystemPoolAddedToClient(t *testing.T) {
 				pool.AddCert(cert)
 			}
 
-			httpClient, err := httpClientWithPool(tc.restConfig, pool)
+			httpClient, err := httpClientWithPool(tc.conf, tc.restConfig, pool)
 			require.NoError(err)
 
 			tr, ok := httpClient.Transport.(*http.Transport)
@@ -77,6 +90,7 @@ func TestSystemPoolAddedToClient(t *testing.T) {
 			require.NotNil(tr.TLSClientConfig)
 			require.NotNil(tr.TLSClientConfig.RootCAs)
 			require.True(tr.TLSClientConfig.RootCAs.Equal(expected), "cert pools not equal")
+			require.Equal(tc.expectedInsecure, tr.TLSClientConfig.InsecureSkipVerify)
 		})
 	}
 }
diff --git a/business/test_util.go b/business/test_util.go
index 87278c398..cba06a570 100644
--- a/business/test_util.go
+++ b/business/test_util.go
@@ -26,7 +26,8 @@ func FakeDeployments(conf config.Config) []apps_v1.Deployment {
 	return []apps_v1.Deployment{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "Deployment",
+				APIVersion: kubernetes.Deployments.GroupVersion().String(),
+				Kind:       kubernetes.Deployments.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v1",
@@ -48,7 +49,8 @@ func FakeDeployments(conf config.Config) []apps_v1.Deployment {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "Deployment",
+				APIVersion: kubernetes.Deployments.GroupVersion().String(),
+				Kind:       kubernetes.Deployments.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v2",
@@ -70,7 +72,8 @@ func FakeDeployments(conf config.Config) []apps_v1.Deployment {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "Deployment",
+				APIVersion: kubernetes.Deployments.GroupVersion().String(),
+				Kind:       kubernetes.Deployments.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v3",
@@ -102,7 +105,8 @@ func FakeDuplicatedDeployments() []apps_v1.Deployment {
 	return []apps_v1.Deployment{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "Deployment",
+				APIVersion: kubernetes.Deployments.GroupVersion().String(),
+				Kind:       kubernetes.Deployments.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "duplicated-v1",
@@ -131,7 +135,8 @@ func FakeReplicaSets(conf config.Config) []apps_v1.ReplicaSet {
 	return []apps_v1.ReplicaSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicaSet",
+				APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+				Kind:       kubernetes.ReplicaSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v1",
@@ -153,7 +158,8 @@ func FakeReplicaSets(conf config.Config) []apps_v1.ReplicaSet {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicaSet",
+				APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+				Kind:       kubernetes.ReplicaSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v2",
@@ -175,7 +181,8 @@ func FakeReplicaSets(conf config.Config) []apps_v1.ReplicaSet {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicaSet",
+				APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+				Kind:       kubernetes.ReplicaSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v3",
@@ -208,7 +215,8 @@ func FakeDuplicatedReplicaSets() []apps_v1.ReplicaSet {
 	return []apps_v1.ReplicaSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicaSet",
+				APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+				Kind:       kubernetes.ReplicaSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name: "duplicated-v1-12345",
@@ -216,7 +224,8 @@ func FakeDuplicatedReplicaSets() []apps_v1.ReplicaSet {
 				CreationTimestamp: meta_v1.NewTime(t1),
 				OwnerReferences: []meta_v1.OwnerReference{{
 					Controller: &controller,
-					Kind:       "Deployment",
+					APIVersion: kubernetes.Deployments.GroupVersion().String(),
+					Kind:       kubernetes.Deployments.Kind,
 					Name:       "duplicated-v1",
 				}},
 			},
@@ -247,7 +256,8 @@ func FakeReplicationControllers() []core_v1.ReplicationController {
 	return []core_v1.ReplicationController{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicationController",
+				APIVersion: kubernetes.ReplicationControllers.GroupVersion().String(),
+				Kind:       kubernetes.ReplicationControllers.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v1",
@@ -269,7 +279,8 @@ func FakeReplicationControllers() []core_v1.ReplicationController {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicationController",
+				APIVersion: kubernetes.ReplicationControllers.GroupVersion().String(),
+				Kind:       kubernetes.ReplicationControllers.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v2",
@@ -291,7 +302,8 @@ func FakeReplicationControllers() []core_v1.ReplicationController {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicationController",
+				APIVersion: kubernetes.ReplicationControllers.GroupVersion().String(),
+				Kind:       kubernetes.ReplicationControllers.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v3",
@@ -324,7 +336,8 @@ func FakeDeploymentConfigs() []osapps_v1.DeploymentConfig {
 	return []osapps_v1.DeploymentConfig{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "DeploymentConfig",
+				APIVersion: kubernetes.DeploymentConfigs.GroupVersion().String(),
+				Kind:       kubernetes.DeploymentConfigs.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v1",
@@ -346,7 +359,8 @@ func FakeDeploymentConfigs() []osapps_v1.DeploymentConfig {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "DeploymentConfig",
+				APIVersion: kubernetes.DeploymentConfigs.GroupVersion().String(),
+				Kind:       kubernetes.DeploymentConfigs.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v2",
@@ -368,7 +382,8 @@ func FakeDeploymentConfigs() []osapps_v1.DeploymentConfig {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "DeploymentConfig",
+				APIVersion: kubernetes.DeploymentConfigs.GroupVersion().String(),
+				Kind:       kubernetes.DeploymentConfigs.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v3",
@@ -401,7 +416,8 @@ func FakeStatefulSets() []apps_v1.StatefulSet {
 	return []apps_v1.StatefulSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "StatefulSet",
+				APIVersion: kubernetes.StatefulSets.GroupVersion().String(),
+				Kind:       kubernetes.StatefulSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v1",
@@ -422,7 +438,8 @@ func FakeStatefulSets() []apps_v1.StatefulSet {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "StatefulSet",
+				APIVersion: kubernetes.StatefulSets.GroupVersion().String(),
+				Kind:       kubernetes.StatefulSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v2",
@@ -443,7 +460,8 @@ func FakeStatefulSets() []apps_v1.StatefulSet {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "StatefulSet",
+				APIVersion: kubernetes.StatefulSets.GroupVersion().String(),
+				Kind:       kubernetes.StatefulSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v3",
@@ -475,7 +493,8 @@ func FakeDaemonSets() []apps_v1.DaemonSet {
 	return []apps_v1.DaemonSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "DaemonSet",
+				APIVersion: kubernetes.DaemonSets.GroupVersion().String(),
+				Kind:       kubernetes.DaemonSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v1",
@@ -497,7 +516,8 @@ func FakeDaemonSets() []apps_v1.DaemonSet {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "DaemonSet",
+				APIVersion: kubernetes.DaemonSets.GroupVersion().String(),
+				Kind:       kubernetes.DaemonSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v2",
@@ -519,7 +539,8 @@ func FakeDaemonSets() []apps_v1.DaemonSet {
 		},
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "DaemonSet",
+				APIVersion: kubernetes.DaemonSets.GroupVersion().String(),
+				Kind:       kubernetes.DaemonSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "httpbin-v3",
@@ -551,7 +572,8 @@ func FakeDuplicatedStatefulSets() []apps_v1.StatefulSet {
 	return []apps_v1.StatefulSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "StatefulSet",
+				APIVersion: kubernetes.StatefulSets.GroupVersion().String(),
+				Kind:       kubernetes.StatefulSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "duplicated-v1",
@@ -582,7 +604,8 @@ func FakeDepSyncedWithRS() []apps_v1.Deployment {
 	return []apps_v1.Deployment{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "Deployment",
+				APIVersion: kubernetes.Deployments.GroupVersion().String(),
+				Kind:       kubernetes.Deployments.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "details-v1",
@@ -615,7 +638,8 @@ func FakeRSSyncedWithPods() []apps_v1.ReplicaSet {
 	return []apps_v1.ReplicaSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicaSet",
+				APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+				Kind:       kubernetes.ReplicaSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "details-v1-3618568057",
@@ -623,7 +647,8 @@ func FakeRSSyncedWithPods() []apps_v1.ReplicaSet {
 				CreationTimestamp: meta_v1.NewTime(t1),
 				OwnerReferences: []meta_v1.OwnerReference{{
 					Controller: &controller,
-					Kind:       "Deployment",
+					APIVersion: kubernetes.Deployments.GroupVersion().String(),
+					Kind:       kubernetes.Deployments.Kind,
 					Name:       "details-v1",
 				}},
 			},
@@ -659,7 +684,8 @@ func FakePodsSyncedWithDeployments() []core_v1.Pod {
 				Labels:            map[string]string{appLabel: "httpbin", versionLabel: "v1"},
 				OwnerReferences: []meta_v1.OwnerReference{{
 					Controller: &controller,
-					Kind:       "ReplicaSet",
+					APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+					Kind:       kubernetes.ReplicaSets.Kind,
 					Name:       "details-v1-3618568057",
 				}},
 				Annotations: kubetest.FakeIstioAnnotations(),
@@ -693,7 +719,8 @@ func FakePodSyncedWithDeployments() *core_v1.Pod {
 			Labels:            map[string]string{appLabel: "httpbin", versionLabel: "v1"},
 			OwnerReferences: []meta_v1.OwnerReference{{
 				Controller: &controller,
-				Kind:       "ReplicaSet",
+				APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+				Kind:       kubernetes.ReplicaSets.Kind,
 				Name:       "details-v1-3618568057",
 			}},
 			Annotations: kubetest.FakeIstioAnnotations(),
@@ -752,7 +779,8 @@ func FakePodsSyncedWithDuplicated() []core_v1.Pod {
 				Labels:            map[string]string{appLabel: "duplicated", versionLabel: "v1"},
 				OwnerReferences: []meta_v1.OwnerReference{{
 					Controller: &controller,
-					Kind:       "StatefulSet",
+					APIVersion: kubernetes.StatefulSets.GroupVersion().String(),
+					Kind:       kubernetes.StatefulSets.Kind,
 					Name:       "duplicated-v1",
 				}},
 				Annotations: kubetest.FakeIstioAnnotations(),
@@ -776,7 +804,8 @@ func FakePodsSyncedWithDuplicated() []core_v1.Pod {
 				Labels:            map[string]string{appLabel: "duplicated", versionLabel: "v1"},
 				OwnerReferences: []meta_v1.OwnerReference{{
 					Controller: &controller,
-					Kind:       "StatefulSet",
+					APIVersion: kubernetes.StatefulSets.GroupVersion().String(),
+					Kind:       kubernetes.StatefulSets.Kind,
 					Name:       "duplicated-v1",
 				}},
 				Annotations: kubetest.FakeIstioAnnotations(),
@@ -805,7 +834,8 @@ func FakePodsNoController() []core_v1.Pod {
 	return []core_v1.Pod{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "Pod",
+				APIVersion: kubernetes.Pods.GroupVersion().String(),
+				Kind:       kubernetes.Pods.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "orphan-pod",
@@ -844,7 +874,8 @@ func FakePodsFromCustomController() []core_v1.Pod {
 				Labels:            map[string]string{appLabel: "httpbin", versionLabel: "v1"},
 				OwnerReferences: []meta_v1.OwnerReference{{
 					Controller: &controller,
-					Kind:       "ReplicaSet",
+					APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+					Kind:       kubernetes.ReplicaSets.Kind,
 					Name:       "custom-controller-RS-123",
 				}},
 				Annotations: kubetest.FakeIstioAnnotations(),
@@ -879,7 +910,8 @@ func FakeZtunnelPods() []core_v1.Pod {
 				Labels:            map[string]string{appLabel: "ztunnel", versionLabel: "v1"},
 				OwnerReferences: []meta_v1.OwnerReference{{
 					Controller: &controller,
-					Kind:       "ReplicaSet",
+					APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+					Kind:       kubernetes.ReplicaSets.Kind,
 					Name:       "ztunnel",
 				}},
 				Annotations: kubetest.FakeIstioAnnotations(),
@@ -996,7 +1028,8 @@ func FakeZtunnelDaemonSet() []apps_v1.DaemonSet {
 	return []apps_v1.DaemonSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "DaemonSet",
+				APIVersion: kubernetes.DaemonSets.GroupVersion().String(),
+				Kind:       kubernetes.DaemonSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "ztunnel",
@@ -1032,7 +1065,8 @@ func FakeCustomControllerRSSyncedWithPods() []apps_v1.ReplicaSet {
 	return []apps_v1.ReplicaSet{
 		{
 			TypeMeta: meta_v1.TypeMeta{
-				Kind: "ReplicaSet",
+				APIVersion: kubernetes.ReplicaSets.GroupVersion().String(),
+				Kind:       kubernetes.ReplicaSets.Kind,
 			},
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:              "custom-controller-RS-123",
diff --git a/business/workloads_test.go b/business/workloads_test.go
index d2103111d..48135260e 100644
--- a/business/workloads_test.go
+++ b/business/workloads_test.go
@@ -81,15 +81,15 @@ func TestGetWorkloadListFromDeployments(t *testing.T) {
 	assert.Equal("httpbin-v1", workloads[0].Name)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(false, workloads[0].VersionLabel)
-	assert.Equal("Deployment", workloads[0].Type)
+	assert.Equal("Deployment", workloads[0].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v2", workloads[1].Name)
 	assert.Equal(true, workloads[1].AppLabel)
 	assert.Equal(true, workloads[1].VersionLabel)
-	assert.Equal("Deployment", workloads[1].Type)
+	assert.Equal("Deployment", workloads[1].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v3", workloads[2].Name)
 	assert.Equal(false, workloads[2].AppLabel)
 	assert.Equal(false, workloads[2].VersionLabel)
-	assert.Equal("Deployment", workloads[2].Type)
+	assert.Equal("Deployment", workloads[2].WorkloadGVK.Kind)
 }
 
 func TestGetWorkloadListFromReplicaSets(t *testing.T) {
@@ -122,15 +122,15 @@ func TestGetWorkloadListFromReplicaSets(t *testing.T) {
 	assert.Equal("httpbin-v1", workloads[0].Name)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(false, workloads[0].VersionLabel)
-	assert.Equal("ReplicaSet", workloads[0].Type)
+	assert.Equal("ReplicaSet", workloads[0].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v2", workloads[1].Name)
 	assert.Equal(true, workloads[1].AppLabel)
 	assert.Equal(true, workloads[1].VersionLabel)
-	assert.Equal("ReplicaSet", workloads[1].Type)
+	assert.Equal("ReplicaSet", workloads[1].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v3", workloads[2].Name)
 	assert.Equal(false, workloads[2].AppLabel)
 	assert.Equal(false, workloads[2].VersionLabel)
-	assert.Equal("ReplicaSet", workloads[2].Type)
+	assert.Equal("ReplicaSet", workloads[2].WorkloadGVK.Kind)
 }
 
 func TestGetWorkloadListFromReplicationControllers(t *testing.T) {
@@ -161,15 +161,15 @@ func TestGetWorkloadListFromReplicationControllers(t *testing.T) {
 	assert.Equal("httpbin-v1", workloads[0].Name)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(false, workloads[0].VersionLabel)
-	assert.Equal("ReplicationController", workloads[0].Type)
+	assert.Equal("ReplicationController", workloads[0].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v2", workloads[1].Name)
 	assert.Equal(true, workloads[1].AppLabel)
 	assert.Equal(true, workloads[1].VersionLabel)
-	assert.Equal("ReplicationController", workloads[1].Type)
+	assert.Equal("ReplicationController", workloads[1].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v3", workloads[2].Name)
 	assert.Equal(false, workloads[2].AppLabel)
 	assert.Equal(false, workloads[2].VersionLabel)
-	assert.Equal("ReplicationController", workloads[2].Type)
+	assert.Equal("ReplicationController", workloads[2].WorkloadGVK.Kind)
 }
 
 func TestGetWorkloadListFromDeploymentConfigs(t *testing.T) {
@@ -200,15 +200,15 @@ func TestGetWorkloadListFromDeploymentConfigs(t *testing.T) {
 	assert.Equal("httpbin-v1", workloads[0].Name)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(false, workloads[0].VersionLabel)
-	assert.Equal("DeploymentConfig", workloads[0].Type)
+	assert.Equal("DeploymentConfig", workloads[0].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v2", workloads[1].Name)
 	assert.Equal(true, workloads[1].AppLabel)
 	assert.Equal(true, workloads[1].VersionLabel)
-	assert.Equal("DeploymentConfig", workloads[1].Type)
+	assert.Equal("DeploymentConfig", workloads[1].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v3", workloads[2].Name)
 	assert.Equal(false, workloads[2].AppLabel)
 	assert.Equal(false, workloads[2].VersionLabel)
-	assert.Equal("DeploymentConfig", workloads[2].Type)
+	assert.Equal("DeploymentConfig", workloads[2].WorkloadGVK.Kind)
 }
 
 func TestGetWorkloadListFromStatefulSets(t *testing.T) {
@@ -239,15 +239,15 @@ func TestGetWorkloadListFromStatefulSets(t *testing.T) {
 	assert.Equal("httpbin-v1", workloads[0].Name)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(false, workloads[0].VersionLabel)
-	assert.Equal("StatefulSet", workloads[0].Type)
+	assert.Equal("StatefulSet", workloads[0].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v2", workloads[1].Name)
 	assert.Equal(true, workloads[1].AppLabel)
 	assert.Equal(true, workloads[1].VersionLabel)
-	assert.Equal("StatefulSet", workloads[1].Type)
+	assert.Equal("StatefulSet", workloads[1].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v3", workloads[2].Name)
 	assert.Equal(false, workloads[2].AppLabel)
 	assert.Equal(false, workloads[2].VersionLabel)
-	assert.Equal("StatefulSet", workloads[2].Type)
+	assert.Equal("StatefulSet", workloads[2].WorkloadGVK.Kind)
 }
 
 func TestGetWorkloadListFromDaemonSets(t *testing.T) {
@@ -278,15 +278,15 @@ func TestGetWorkloadListFromDaemonSets(t *testing.T) {
 	assert.Equal("httpbin-v1", workloads[0].Name)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(false, workloads[0].VersionLabel)
-	assert.Equal("DaemonSet", workloads[0].Type)
+	assert.Equal("DaemonSet", workloads[0].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v2", workloads[1].Name)
 	assert.Equal(true, workloads[1].AppLabel)
 	assert.Equal(true, workloads[1].VersionLabel)
-	assert.Equal("DaemonSet", workloads[1].Type)
+	assert.Equal("DaemonSet", workloads[1].WorkloadGVK.Kind)
 	assert.Equal("httpbin-v3", workloads[2].Name)
 	assert.Equal(false, workloads[2].AppLabel)
 	assert.Equal(false, workloads[2].VersionLabel)
-	assert.Equal("DaemonSet", workloads[2].Type)
+	assert.Equal("DaemonSet", workloads[2].WorkloadGVK.Kind)
 }
 
 func TestGetWorkloadListFromDepRCPod(t *testing.T) {
@@ -322,7 +322,7 @@ func TestGetWorkloadListFromDepRCPod(t *testing.T) {
 
 	require.Equal(1, len(workloads))
 	assert.Equal("details-v1", workloads[0].Name)
-	assert.Equal("Deployment", workloads[0].Type)
+	assert.Equal("Deployment", workloads[0].WorkloadGVK.Kind)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(true, workloads[0].VersionLabel)
 }
@@ -352,7 +352,7 @@ func TestGetWorkloadListFromPod(t *testing.T) {
 
 	require.Equal(1, len(workloads))
 	assert.Equal("orphan-pod", workloads[0].Name)
-	assert.Equal("Pod", workloads[0].Type)
+	assert.Equal("Pod", workloads[0].WorkloadGVK.Kind)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(true, workloads[0].VersionLabel)
 }
@@ -386,7 +386,7 @@ func TestGetWorkloadListFromPods(t *testing.T) {
 
 	require.Equal(1, len(workloads))
 	assert.Equal("custom-controller-RS-123", workloads[0].Name)
-	assert.Equal("ReplicaSet", workloads[0].Type)
+	assert.Equal("ReplicaSet", workloads[0].WorkloadGVK.Kind)
 	assert.Equal(true, workloads[0].AppLabel)
 	assert.Equal(true, workloads[0].VersionLabel)
 }
@@ -416,12 +416,12 @@ func TestGetWorkloadFromDeployment(t *testing.T) {
 	SetupBusinessLayer(t, k8s, *conf)
 	svc := setupWorkloadService(k8s, conf)
 
-	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details-v1", WorkloadType: "", IncludeServices: false}
+	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details-v1", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 	require.NoError(err)
 
 	assert.Equal("details-v1", workload.Name)
-	assert.Equal("Deployment", workload.Type)
+	assert.Equal("Deployment", workload.WorkloadGVK.Kind)
 	assert.Equal(true, workload.AppLabel)
 	assert.Equal(true, workload.VersionLabel)
 }
@@ -454,12 +454,12 @@ func TestGetWorkloadWithInvalidWorkloadType(t *testing.T) {
 	SetupBusinessLayer(t, k8s, *conf)
 	svc := setupWorkloadService(k8s, conf)
 
-	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details-v1", WorkloadType: "invalid", IncludeServices: false}
+	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details-v1", WorkloadGVK: schema.GroupVersionKind{Kind: "invalid"}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 	require.NoError(err)
 
 	assert.Equal("details-v1", workload.Name)
-	assert.Equal("Deployment", workload.Type)
+	assert.Equal("Deployment", workload.WorkloadGVK.Kind)
 	assert.Equal(true, workload.AppLabel)
 	assert.Equal(true, workload.VersionLabel)
 }
@@ -491,19 +491,19 @@ func TestGetWorkloadFromPods(t *testing.T) {
 	SetupBusinessLayer(t, k8s, *conf)
 	svc := setupWorkloadService(k8s, conf)
 
-	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "custom-controller", WorkloadType: "", IncludeServices: false}
+	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "custom-controller", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 	require.Error(err)
 
 	// custom controller is not a workload type, only its replica set(s)
 	assert.Equal((*models.Workload)(nil), workload)
 
-	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "custom-controller-RS-123", WorkloadType: "", IncludeServices: false}
+	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "custom-controller-RS-123", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err = svc.GetWorkload(context.TODO(), criteria)
 	require.NoError(err)
 
 	assert.Equal("custom-controller-RS-123", workload.Name)
-	assert.Equal("ReplicaSet", workload.Type)
+	assert.Equal("ReplicaSet", workload.WorkloadGVK.Kind)
 	assert.Equal(true, workload.AppLabel)
 	assert.Equal(true, workload.VersionLabel)
 	assert.Equal(0, len(workload.Runtimes))
@@ -803,11 +803,11 @@ func TestDuplicatedControllers(t *testing.T) {
 
 	workloads := workloadList.Workloads
 
-	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "duplicated-v1", WorkloadType: "", IncludeServices: false}
+	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "duplicated-v1", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 
 	require.NoError(err)
-	assert.Equal(workloads[0].Type, workload.Type)
+	assert.Equal(workloads[0].WorkloadGVK.String(), workload.WorkloadGVK.String())
 }
 
 func TestGetWorkloadListFromGenericPodController(t *testing.T) {
@@ -851,7 +851,7 @@ func TestGetWorkloadListFromGenericPodController(t *testing.T) {
 	workloadList, _ := svc.GetWorkloadList(context.TODO(), criteria)
 	workloads := workloadList.Workloads
 
-	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: owner.Name, WorkloadType: "", IncludeServices: false}
+	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: owner.Name, WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 
 	require.NoError(err)
@@ -870,8 +870,8 @@ func TestGetWorkloadListKindsWithSameName(t *testing.T) {
 
 	rs := FakeRSSyncedWithPods()
 	pods := FakePodsSyncedWithDeployments()
-	pods[0].OwnerReferences[0].APIVersion = "shiny.new.apps/v1"
-	pods[0].OwnerReferences[0].Kind = "ReplicaSet"
+	pods[0].OwnerReferences[0].APIVersion = kubernetes.ReplicaSets.GroupVersion().String()
+	pods[0].OwnerReferences[0].Kind = kubernetes.ReplicaSets.Kind
 
 	// Setup mocks
 	kubeObjs := []runtime.Object{
@@ -995,7 +995,7 @@ func TestGetWorkloadListRSOwnedByCustom(t *testing.T) {
 	require.NoError(err)
 	workloads := workloadList.Workloads
 
-	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: owner.Name, WorkloadType: "", IncludeServices: false}
+	criteria = WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: owner.Name, WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, _ := svc.GetWorkload(context.TODO(), criteria)
 
 	require.Equal(len(workloads), 1)
@@ -1126,7 +1126,7 @@ func TestValidateWaypoint(t *testing.T) {
 	svc := setupWorkloadService(k8s, conf)
 
 	// Get waypoint proxy
-	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "waypoint", WorkloadType: "", IncludeServices: false}
+	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "waypoint", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 
 	require.NoError(err)
@@ -1136,7 +1136,7 @@ func TestValidateWaypoint(t *testing.T) {
 	assert.True(workload.Pods[0].IsWaypoint())
 
 	// Get waypoint proxy
-	criteriaDetails := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details", WorkloadType: "", IncludeServices: false}
+	criteriaDetails := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workloadDetails, errDetails := svc.GetWorkload(context.TODO(), criteriaDetails)
 
 	require.NoError(errDetails)
@@ -1147,7 +1147,7 @@ func TestValidateWaypoint(t *testing.T) {
 	assert.Equal("waypoint", workloadDetails.WaypointWorkloads[0].Name)
 
 	// productPage should not have a waypoint
-	criteriaProductPage := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "productpage", WorkloadType: "", IncludeServices: false}
+	criteriaProductPage := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "productpage", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workloadProductPage, errProductPage := svc.GetWorkload(context.TODO(), criteriaProductPage)
 
 	require.NoError(errProductPage)
@@ -1198,7 +1198,7 @@ func TestValidateWaypointNS(t *testing.T) {
 	svc := setupWorkloadService(k8s, conf)
 
 	// Get waypoint proxy
-	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "waypoint", WorkloadType: "", IncludeServices: false}
+	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "waypoint", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 
 	require.NoError(err)
@@ -1208,7 +1208,7 @@ func TestValidateWaypointNS(t *testing.T) {
 	assert.True(workload.Pods[0].IsWaypoint())
 
 	// Get waypoint proxy
-	criteriaDetails := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details", WorkloadType: "", IncludeServices: false}
+	criteriaDetails := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workloadDetails, errDetails := svc.GetWorkload(context.TODO(), criteriaDetails)
 
 	require.NoError(errDetails)
@@ -1219,7 +1219,7 @@ func TestValidateWaypointNS(t *testing.T) {
 	assert.Equal("waypoint", workloadDetails.WaypointWorkloads[0].Name)
 
 	// productPage should have also a waypoint
-	criteriaProductPage := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "productpage", WorkloadType: "", IncludeServices: false}
+	criteriaProductPage := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "productpage", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workloadProductPage, errProductPage := svc.GetWorkload(context.TODO(), criteriaProductPage)
 
 	require.NoError(errProductPage)
@@ -1263,7 +1263,7 @@ func TestValidateWaypointService(t *testing.T) {
 	svc := setupWorkloadService(k8s, conf)
 
 	// Get waypoint proxy
-	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "waypoint", WorkloadType: "", IncludeServices: false}
+	criteria := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "waypoint", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workload, err := svc.GetWorkload(context.TODO(), criteria)
 
 	require.NoError(err)
@@ -1273,7 +1273,7 @@ func TestValidateWaypointService(t *testing.T) {
 	assert.True(workload.Pods[0].IsWaypoint())
 
 	// Get waypoint proxy
-	criteriaDetails := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details", WorkloadType: "", IncludeServices: false}
+	criteriaDetails := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "details", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workloadDetails, errDetails := svc.GetWorkload(context.TODO(), criteriaDetails)
 
 	require.NoError(errDetails)
@@ -1284,7 +1284,7 @@ func TestValidateWaypointService(t *testing.T) {
 	assert.Equal("waypoint", workloadDetails.WaypointWorkloads[0].Name)
 
 	// productPage should not have a waypoint
-	criteriaProductPage := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "productpage", WorkloadType: "", IncludeServices: false}
+	criteriaProductPage := WorkloadCriteria{Cluster: conf.KubernetesConfig.ClusterName, Namespace: "Namespace", WorkloadName: "productpage", WorkloadGVK: schema.GroupVersionKind{}, IncludeServices: false}
 	workloadProductPage, errProductPage := svc.GetWorkload(context.TODO(), criteriaProductPage)
 
 	require.NoError(errProductPage)
@@ -1365,3 +1365,36 @@ func TestGetAllGateways(t *testing.T) {
 	require.Len(workloads, 1)
 	require.True(workloads[0].IsGateway(), "Expected IsGateway to be True but it was false")
 }
+
+func TestGetWorkloadListWithCustomKindThatMatchesCoreKind(t *testing.T) {
+	assert := assert.New(t)
+	require := require.New(t)
+
+	conf := config.NewConfig()
+
+	kubeObjs := []runtime.Object{
+		&osproject_v1.Project{ObjectMeta: v1.ObjectMeta{Name: "Namespace"}},
+	}
+	for _, pod := range FakePodsFromCustomController() {
+		p := pod
+		// Setting here a custom type whose Kind matches a core Kube type.
+		p.OwnerReferences[0].APIVersion = "customAPI/v1"
+		p.OwnerReferences[0].Kind = "DaemonSet"
+		kubeObjs = append(kubeObjs, &p)
+	}
+
+	k8s := kubetest.NewFakeK8sClient(kubeObjs...)
+	k8s.OpenShift = true
+	SetupBusinessLayer(t, k8s, *conf)
+	svc := setupWorkloadService(k8s, conf)
+
+	criteria := WorkloadCriteria{Namespace: "Namespace", IncludeIstioResources: false, IncludeHealth: false}
+	workloadList, _ := svc.GetWorkloadList(context.TODO(), criteria)
+	workloads := workloadList.Workloads
+
+	assert.Equal("Namespace", workloadList.Namespace)
+
+	require.Equal(1, len(workloads))
+	assert.Equal("custom-controller-RS-123", workloads[0].Name)
+	assert.Equal("DaemonSet", workloads[0].WorkloadGVK.Kind)
+}
diff --git a/frontend/src/helpers/__tests__/LabelFilterHelper.test.ts b/frontend/src/helpers/__tests__/LabelFilterHelper.test.ts
index 06e5d5393..896b64772 100644
--- a/frontend/src/helpers/__tests__/LabelFilterHelper.test.ts
+++ b/frontend/src/helpers/__tests__/LabelFilterHelper.test.ts
@@ -6,6 +6,7 @@ import { InstanceType } from 'types/Common';
 import { ServiceListItem } from '../../types/ServiceList';
 import { setServerConfig } from '../../config/ServerConfig';
 import { serverRateConfig } from '../../types/ErrorRate/__testData__/ErrorRateConfig';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 
 setServerConfig(serverRateConfig);
 const emptyAppHealth = new AppHealth(
@@ -80,7 +81,7 @@ const workloadList: WorkloadListItem[] = [
     namespace: 'bookinfo',
     health: emptyWorkHealth,
     name: 'details-v1',
-    type: 'Deployment',
+    gvk: dicTypeToGVK[gvkType.Deployment],
     istioSidecar: false,
     isAmbient: false,
     isGateway: false,
@@ -95,7 +96,7 @@ const workloadList: WorkloadListItem[] = [
     namespace: 'bookinfo',
     health: emptyWorkHealth,
     name: 'productpage-v1',
-    type: 'Deployment',
+    gvk: dicTypeToGVK[gvkType.Deployment],
     istioSidecar: false,
     isAmbient: false,
     isGateway: false,
@@ -110,7 +111,7 @@ const workloadList: WorkloadListItem[] = [
     namespace: 'bookinfo',
     health: emptyWorkHealth,
     name: 'ratings-v1',
-    type: 'Deployment',
+    gvk: dicTypeToGVK[gvkType.Deployment],
     istioSidecar: false,
     isAmbient: false,
     isGateway: false,
@@ -125,7 +126,7 @@ const workloadList: WorkloadListItem[] = [
     namespace: 'bookinfo',
     health: emptyWorkHealth,
     name: 'reviews-v1',
-    type: 'Deployment',
+    gvk: dicTypeToGVK[gvkType.Deployment],
     istioSidecar: false,
     isAmbient: false,
     isGateway: false,
@@ -140,7 +141,7 @@ const workloadList: WorkloadListItem[] = [
     namespace: 'bookinfo',
     health: emptyWorkHealth,
     name: 'reviews-v2',
-    type: 'Deployment',
+    gvk: dicTypeToGVK[gvkType.Deployment],
     istioSidecar: false,
     isAmbient: false,
     isGateway: false,
@@ -155,7 +156,7 @@ const workloadList: WorkloadListItem[] = [
     namespace: 'bookinfo',
     health: emptyWorkHealth,
     name: 'reviews-v3',
-    type: 'Deployment',
+    gvk: dicTypeToGVK[gvkType.Deployment],
     istioSidecar: false,
     isAmbient: false,
     isGateway: false,
@@ -285,7 +286,7 @@ describe('LabelFilter', () => {
         instanceType: InstanceType.Workload,
         health: emptyWorkHealth,
         name: 'reviews-v1',
-        type: 'Deployment',
+        gvk: dicTypeToGVK[gvkType.Deployment],
         istioSidecar: false,
         isAmbient: false,
         isGateway: false,
@@ -300,7 +301,7 @@ describe('LabelFilter', () => {
         instanceType: InstanceType.Workload,
         health: emptyWorkHealth,
         name: 'reviews-v2',
-        type: 'Deployment',
+        gvk: dicTypeToGVK[gvkType.Deployment],
         istioSidecar: false,
         isAmbient: false,
         isGateway: false,
@@ -315,7 +316,7 @@ describe('LabelFilter', () => {
         instanceType: InstanceType.Workload,
         health: emptyWorkHealth,
         name: 'reviews-v3',
-        type: 'Deployment',
+        gvk: dicTypeToGVK[gvkType.Deployment],
         istioSidecar: false,
         isAmbient: false,
         isGateway: false,
diff --git a/frontend/src/pages/IstioConfigList/__tests__/IstioConfigListComponent.test.ts b/frontend/src/pages/IstioConfigList/__tests__/IstioConfigListComponent.test.ts
index cd1ee30bc..c3a6ced80 100644
--- a/frontend/src/pages/IstioConfigList/__tests__/IstioConfigListComponent.test.ts
+++ b/frontend/src/pages/IstioConfigList/__tests__/IstioConfigListComponent.test.ts
@@ -1,6 +1,7 @@
 import {
-  dicIstioTypeToGVK,
+  dicTypeToGVK,
   filterByName,
+  gvkType,
   IstioConfigItem,
   IstioConfigList,
   toIstioItems
@@ -15,40 +16,42 @@ const mockIstioConfigList = (names: string[]): IstioConfigList => {
     permissions: {},
     resources: {}
   };
-  Object.keys(dicIstioTypeToGVK).forEach(index => {
+  Object.values(dicTypeToGVK).forEach(index => {
     const key = getGVKTypeString(index);
     testData.resources[key] = [];
   });
   names.forEach(name => {
-    testData.resources[getGVKTypeString('AuthorizationPolicy')].push({
+    testData.resources[getGVKTypeString(gvkType.AuthorizationPolicy)].push({
       metadata: { name: `${name}0` },
       spec: {},
-      kind: dicIstioTypeToGVK['AuthorizationPolicy'].Kind,
-      apiVersion: `${dicIstioTypeToGVK['AuthorizationPolicy'].Group}/${dicIstioTypeToGVK['AuthorizationPolicy'].Version}`
+      kind: dicTypeToGVK[gvkType.AuthorizationPolicy].Kind,
+      apiVersion: `${dicTypeToGVK[gvkType.AuthorizationPolicy].Group}/${
+        dicTypeToGVK[gvkType.AuthorizationPolicy].Version
+      }`
     });
-    testData.resources[getGVKTypeString('DestinationRule')].push({
+    testData.resources[getGVKTypeString(gvkType.DestinationRule)].push({
       metadata: { name: `${name}1` },
       spec: {},
-      kind: dicIstioTypeToGVK['DestinationRule'].Kind,
-      apiVersion: `${dicIstioTypeToGVK['DestinationRule'].Group}/${dicIstioTypeToGVK['DestinationRule'].Version}`
+      kind: dicTypeToGVK[gvkType.DestinationRule].Kind,
+      apiVersion: `${dicTypeToGVK[gvkType.DestinationRule].Group}/${dicTypeToGVK[gvkType.DestinationRule].Version}`
     });
-    testData.resources[getGVKTypeString('Gateway')].push({
+    testData.resources[getGVKTypeString(gvkType.Gateway)].push({
       metadata: { name: `${name}2` },
       spec: {},
-      kind: dicIstioTypeToGVK['Gateway'].Kind,
-      apiVersion: `${dicIstioTypeToGVK['Gateway'].Group}/${dicIstioTypeToGVK['Gateway'].Version}`
+      kind: dicTypeToGVK[gvkType.Gateway].Kind,
+      apiVersion: `${dicTypeToGVK[gvkType.Gateway].Group}/${dicTypeToGVK[gvkType.Gateway].Version}`
     });
-    testData.resources[getGVKTypeString('ServiceEntry')].push({
+    testData.resources[getGVKTypeString(gvkType.ServiceEntry)].push({
       metadata: { name: `${name}3` },
       spec: {},
-      kind: dicIstioTypeToGVK['ServiceEntry'].Kind,
-      apiVersion: `${dicIstioTypeToGVK['ServiceEntry'].Group}/${dicIstioTypeToGVK['ServiceEntry'].Version}`
+      kind: dicTypeToGVK[gvkType.ServiceEntry].Kind,
+      apiVersion: `${dicTypeToGVK[gvkType.ServiceEntry].Group}/${dicTypeToGVK[gvkType.ServiceEntry].Version}`
     });
-    testData.resources[getGVKTypeString('VirtualService')].push({
+    testData.resources[getGVKTypeString(gvkType.VirtualService)].push({
       metadata: { name: `${name}4` },
       spec: {},
-      kind: dicIstioTypeToGVK['VirtualService'].Kind,
-      apiVersion: `${dicIstioTypeToGVK['VirtualService'].Group}/${dicIstioTypeToGVK['VirtualService'].Version}`
+      kind: dicTypeToGVK[gvkType.VirtualService].Kind,
+      apiVersion: `${dicTypeToGVK[gvkType.VirtualService].Group}/${dicTypeToGVK[gvkType.VirtualService].Version}`
     });
   });
   return testData;
@@ -60,30 +63,30 @@ describe('IstioConfigList#filterByName', () => {
   it('should filter IstioConfigList by name', () => {
     let filtered = filterByName(unfiltered, ['white', 'red']);
     expect(filtered).toBeDefined();
-    expect(filtered.resources[getGVKTypeString('Gateway')].length).toBe(2);
-    expect(filtered.resources[getGVKTypeString('VirtualService')].length).toBe(2);
-    expect(filtered.resources[getGVKTypeString('DestinationRule')].length).toBe(2);
-    expect(filtered.resources[getGVKTypeString('ServiceEntry')].length).toBe(2);
+    expect(filtered.resources[getGVKTypeString(gvkType.Gateway)].length).toBe(2);
+    expect(filtered.resources[getGVKTypeString(gvkType.VirtualService)].length).toBe(2);
+    expect(filtered.resources[getGVKTypeString(gvkType.DestinationRule)].length).toBe(2);
+    expect(filtered.resources[getGVKTypeString(gvkType.ServiceEntry)].length).toBe(2);
 
-    expect(filtered.resources[getGVKTypeString('AuthorizationPolicy')][0].metadata.name).toBe('white0');
-    expect(filtered.resources[getGVKTypeString('DestinationRule')][0].metadata.name).toBe('white1');
-    expect(filtered.resources[getGVKTypeString('ServiceEntry')][0].metadata.name).toBe('white3');
-    expect(filtered.resources[getGVKTypeString('VirtualService')][0].metadata.name).toBe('white4');
+    expect(filtered.resources[getGVKTypeString(gvkType.AuthorizationPolicy)][0].metadata.name).toBe('white0');
+    expect(filtered.resources[getGVKTypeString(gvkType.DestinationRule)][0].metadata.name).toBe('white1');
+    expect(filtered.resources[getGVKTypeString(gvkType.ServiceEntry)][0].metadata.name).toBe('white3');
+    expect(filtered.resources[getGVKTypeString(gvkType.VirtualService)][0].metadata.name).toBe('white4');
 
     filtered = filterByName(unfiltered, ['bad']);
     expect(filtered).toBeDefined();
-    expect(filtered.resources[getGVKTypeString('Gateway')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('VirtualService')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('DestinationRule')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('ServiceEntry')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('WasmPlugin')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('Telemetry')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('K8sGateway')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('K8sGRPCRoute')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('K8sHTTPRoute')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('K8sReferenceGrant')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('K8sTCPRoute')].length).toBe(0);
-    expect(filtered.resources[getGVKTypeString('K8sTLSRoute')].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.Gateway)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.VirtualService)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.DestinationRule)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.ServiceEntry)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.WasmPlugin)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.Telemetry)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.K8sGateway)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.K8sGRPCRoute)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.K8sHTTPRoute)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.K8sReferenceGrant)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.K8sTCPRoute)].length).toBe(0);
+    expect(filtered.resources[getGVKTypeString(gvkType.K8sTLSRoute)].length).toBe(0);
   });
 });
 
@@ -94,11 +97,11 @@ describe('IstioConfigListContainer#toIstioItems', () => {
     expect(istioItems).toBeDefined();
     expect(istioItems.length).toBe(15);
     expect(istioItems[0].resource).toBeDefined();
-    expect(istioItems[0].resource.kind).toBe(dicIstioTypeToGVK['AuthorizationPolicy'].Kind);
+    expect(istioItems[0].resource.kind).toBe(dicTypeToGVK[gvkType.AuthorizationPolicy].Kind);
     expect(istioItems[3].resource).toBeDefined();
-    expect(istioItems[3].resource.kind).toBe(dicIstioTypeToGVK['DestinationRule'].Kind);
+    expect(istioItems[3].resource.kind).toBe(dicTypeToGVK[gvkType.DestinationRule].Kind);
     expect(istioItems[6].resource).toBeDefined();
-    expect(istioItems[6].resource.kind).toBe(dicIstioTypeToGVK['Gateway'].Kind);
+    expect(istioItems[6].resource.kind).toBe(dicTypeToGVK[gvkType.Gateway].Kind);
   });
 });
 
@@ -114,17 +117,17 @@ describe('IstioConfigComponent#sortIstioItems', () => {
 
     const first = sorted[0];
     expect(first.resource).toBeDefined();
-    expect(first.resource.kind).toBe(dicIstioTypeToGVK['AuthorizationPolicy'].Kind);
+    expect(first.resource.kind).toBe(dicTypeToGVK[gvkType.AuthorizationPolicy].Kind);
     expect(first.resource.metadata.name).toBe('blue0');
 
     const second = sorted[1];
     expect(second.resource).toBeDefined();
-    expect(second.resource.kind).toBe(dicIstioTypeToGVK['DestinationRule'].Kind);
+    expect(second.resource.kind).toBe(dicTypeToGVK[gvkType.DestinationRule].Kind);
     expect(second.resource.metadata.name).toBe('blue1');
 
     const last = sorted[14];
     expect(last.resource).toBeDefined();
-    expect(last.resource.kind).toBe(dicIstioTypeToGVK['VirtualService'].Kind);
+    expect(last.resource.kind).toBe(dicTypeToGVK[gvkType.VirtualService].Kind);
     expect(last.resource.metadata.name).toBe('white4');
   });
 
@@ -140,12 +143,12 @@ describe('IstioConfigComponent#sortIstioItems', () => {
 
     const first = sorted[0];
     expect(first.resource).toBeDefined();
-    expect(first.resource.kind).toBe(dicIstioTypeToGVK['VirtualService'].Kind);
+    expect(first.resource.kind).toBe(dicTypeToGVK[gvkType.VirtualService].Kind);
     expect(first.resource.metadata.name).toBe('white4');
 
     const last = sorted[14];
     expect(last.resource).toBeDefined();
-    expect(last.resource.kind).toBe(dicIstioTypeToGVK['AuthorizationPolicy'].Kind);
+    expect(last.resource.kind).toBe(dicTypeToGVK[gvkType.AuthorizationPolicy].Kind);
     expect(last.resource.metadata.name).toBe('blue0');
   });
 
@@ -160,17 +163,17 @@ describe('IstioConfigComponent#sortIstioItems', () => {
 
     const first = sorted[0];
     expect(first.resource).toBeDefined();
-    expect(first.resource.kind).toBe(dicIstioTypeToGVK['AuthorizationPolicy'].Kind);
+    expect(first.resource.kind).toBe(dicTypeToGVK[gvkType.AuthorizationPolicy].Kind);
     expect(first.resource.metadata.name).toBe('blue0');
 
     const second = sorted[3];
     expect(second.resource).toBeDefined();
-    expect(second.resource.kind).toBe(dicIstioTypeToGVK['DestinationRule'].Kind);
+    expect(second.resource.kind).toBe(dicTypeToGVK[gvkType.DestinationRule].Kind);
     expect(second.resource.metadata.name).toBe('blue1');
 
     const last = sorted[14];
     expect(last.resource).toBeDefined();
-    expect(last.resource.kind).toBe(dicIstioTypeToGVK['VirtualService'].Kind);
+    expect(last.resource.kind).toBe(dicTypeToGVK[gvkType.VirtualService].Kind);
     expect(last.resource.metadata.name).toBe('white4');
   });
 
@@ -185,7 +188,7 @@ describe('IstioConfigComponent#sortIstioItems', () => {
 
     const first = sorted[0];
     expect(first.resource).toBeDefined();
-    expect(first.resource.kind).toBe(dicIstioTypeToGVK['VirtualService'].Kind);
+    expect(first.resource.kind).toBe(dicTypeToGVK[gvkType.VirtualService].Kind);
     expect(first.resource.metadata.name).toBe('white4');
   });
 });
diff --git a/frontend/src/types/__tests__/AceValidations.test.ts b/frontend/src/types/__tests__/AceValidations.test.ts
index d4a70cef2..cb2bc515e 100644
--- a/frontend/src/types/__tests__/AceValidations.test.ts
+++ b/frontend/src/types/__tests__/AceValidations.test.ts
@@ -1,12 +1,12 @@
 import { ObjectValidation, ValidationTypes } from '../IstioObjects';
 import { parseKialiValidations } from '../AceValidations';
-import { dicIstioTypeToGVK } from '../IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../IstioConfigList';
 
 const fs = require('fs');
 
 const destinationRuleValidations: ObjectValidation = {
   name: 'details',
-  objectGVK: dicIstioTypeToGVK['DestinationRule'],
+  objectGVK: dicTypeToGVK[gvkType.DestinationRule],
   valid: false,
   checks: [
     {
@@ -19,7 +19,7 @@ const destinationRuleValidations: ObjectValidation = {
 
 const vsInvalidHosts: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -32,7 +32,7 @@ const vsInvalidHosts: ObjectValidation = {
 
 const vsInvalidHttpFirstRoute: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -45,7 +45,7 @@ const vsInvalidHttpFirstRoute: ObjectValidation = {
 
 const vsInvalidHttpSecondRoute: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -58,7 +58,7 @@ const vsInvalidHttpSecondRoute: ObjectValidation = {
 
 const vsInvalidHttpThirdRoute: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -71,7 +71,7 @@ const vsInvalidHttpThirdRoute: ObjectValidation = {
 
 const vsInvalidHttpSecondSecondDestinationField: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -84,7 +84,7 @@ const vsInvalidHttpSecondSecondDestinationField: ObjectValidation = {
 
 const vsInvalidHttpThirdFirstDestinationField: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -97,7 +97,7 @@ const vsInvalidHttpThirdFirstDestinationField: ObjectValidation = {
 
 const vsInvalidHttpThirdFirstSubsetNotFound: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -110,7 +110,7 @@ const vsInvalidHttpThirdFirstSubsetNotFound: ObjectValidation = {
 
 const vsInvalidHttpFirstSecondSubsetNotFound: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -123,7 +123,7 @@ const vsInvalidHttpFirstSecondSubsetNotFound: ObjectValidation = {
 
 const vsInvalidHttpFourthFirstWeigth: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
@@ -136,7 +136,7 @@ const vsInvalidHttpFourthFirstWeigth: ObjectValidation = {
 
 const vsInvalidHttpFifthSecondWeigth: ObjectValidation = {
   name: 'productpage',
-  objectGVK: dicIstioTypeToGVK['VirtualService'],
+  objectGVK: dicTypeToGVK[gvkType.VirtualService],
   valid: false,
   checks: [
     {
diff --git a/frontend/src/utils/__tests__/IstioConfigUtils.test.ts b/frontend/src/utils/__tests__/IstioConfigUtils.test.ts
index b68bc5b77..45625145a 100644
--- a/frontend/src/utils/__tests__/IstioConfigUtils.test.ts
+++ b/frontend/src/utils/__tests__/IstioConfigUtils.test.ts
@@ -8,7 +8,7 @@ import {
   mergeJsonPatch,
   stringToGVK
 } from '../IstioConfigUtils';
-import { dicIstioTypeToGVK } from '../../types/IstioConfigList';
+import { dicTypeToGVK, gvkType } from '../../types/IstioConfigList';
 
 describe('Validate JSON Patchs', () => {
   const gateway: object = {
@@ -119,7 +119,7 @@ describe('Validate returned GoupVersionKind for IstioObject', () => {
 
   it('Invalid apiVersion, valid Kind', () => {
     const result = getIstioObjectGVK('invalidApiVersion', 'AuthorizationPolicy');
-    expect(result).toEqual(dicIstioTypeToGVK['AuthorizationPolicy']);
+    expect(result).toEqual(dicTypeToGVK[gvkType.AuthorizationPolicy]);
   });
 
   it('Empty apiVersion, valid kind', () => {
@@ -139,6 +139,21 @@ describe('Validate converting GroupVersionKind To String', () => {
     expect(result).toBe('networking.istio.io/v1, Kind=VirtualService');
   });
 
+  it('Correct Workload GroupVersionKind properties', () => {
+    const result = getGVKTypeString({ Group: 'apps', Version: 'v1', Kind: 'Deployment' });
+    expect(result).toBe('apps/v1, Kind=Deployment');
+  });
+
+  it('Correct Workload Kind properties', () => {
+    const result = getGVKTypeString(gvkType.Deployment);
+    expect(result).toBe('apps/v1, Kind=Deployment');
+  });
+
+  it('Correct Gateway Kind properties', () => {
+    const result = getGVKTypeString(gvkType.Gateway);
+    expect(result).toBe('networking.istio.io/v1, Kind=Gateway');
+  });
+
   it('Validate empty string when Group, Version, and Kind are all empty', () => {
     const result = getGVKTypeString({ Group: '', Version: '', Kind: '' });
     expect(result).toBe('');
diff --git a/hack/istio/install-testing-demos.sh b/hack/istio/install-testing-demos.sh
index e5d1f207d..38c1e2c1a 100755
--- a/hack/istio/install-testing-demos.sh
+++ b/hack/istio/install-testing-demos.sh
@@ -121,32 +121,12 @@ if [ "${DELETE_DEMOS}" != "true" ]; then
     AMBIENT_ARGS_ERROR_RATES="-ei false"
   fi
 
-  # Installed demos should be the exact same for both environments.
-  # Only the args passed to the scripts differ from each other.
-  if [ "${IS_OPENSHIFT}" == "true" ]; then
-    echo "Deploying bookinfo demo ..."
-    "${SCRIPT_DIR}/install-bookinfo-demo.sh" -tg -in ${ISTIO_NAMESPACE} -a ${ARCH} ${AMBIENT_ARGS_BOOKINFO}
-    # Install just bookinfo for now for Ambient
-    if [ "${AMBIENT_ENABLED}" != "true" ]; then
-      echo "Deploying error rates demo ..."
-      "${SCRIPT_DIR}/install-error-rates-demo.sh" -in ${ISTIO_NAMESPACE} -a ${ARCH} ${AMBIENT_ARGS_ERROR_RATES}
-    fi
-    echo "Deploying sleep demo ..."
-    "${SCRIPT_DIR}/install-sleep-demo.sh" -in ${ISTIO_NAMESPACE} -a ${ARCH} ${AMBIENT_ARGS_BOOKINFO}
-  elif [ "${AMBIENT_ENABLED}" == "true" ]; then
-    echo "Deploying bookinfo demo..."
-    "${SCRIPT_DIR}/install-bookinfo-demo.sh" -c kubectl -mp ${MINIKUBE_PROFILE} -tg ${AMBIENT_ARGS_BOOKINFO}
-    "${ISTIO_DIR}/bin/istioctl" waypoint apply -n bookinfo
-
-    echo "Deploying sleep demo ..."
-    "${SCRIPT_DIR}/install-sleep-demo.sh" -c kubectl -in ${ISTIO_NAMESPACE} -a ${ARCH} ${AMBIENT_ARGS_BOOKINFO}
-  else
-    gateway_yaml=""
-    if [ "${USE_GATEWAY_API}" == "true" ]; then
-      gateway_yaml="${ISTIO_DIR}/samples/bookinfo/gateway-api/bookinfo-gateway.yaml"
-    elif [ -v GATEWAY_HOST ]; then
-      gateway_yaml=$(mktemp)
-      cat << EOF > "${gateway_yaml}"
+  gateway_yaml=""
+  if [ "${USE_GATEWAY_API}" == "true" ]; then
+    gateway_yaml="${ISTIO_DIR}/samples/bookinfo/gateway-api/bookinfo-gateway.yaml"
+  elif [ -v GATEWAY_HOST ]; then
+    gateway_yaml=$(mktemp)
+    cat << EOF > "${gateway_yaml}"
 apiVersion: networking.istio.io/v1
 kind: Gateway
 metadata:
@@ -189,7 +169,27 @@ spec:
         port:
           number: 9080
 EOF
+  fi
+  # Installed demos should be the exact same for both environments.
+  # Only the args passed to the scripts differ from each other.
+  if [ "${IS_OPENSHIFT}" == "true" ]; then
+    echo "Deploying bookinfo demo ..."
+    "${SCRIPT_DIR}/install-bookinfo-demo.sh" -tg -in ${ISTIO_NAMESPACE} -a ${ARCH} ${gateway_yaml:+-g ${gateway_yaml}} ${AMBIENT_ARGS_BOOKINFO}
+    # Install just bookinfo for now for Ambient
+    if [ "${AMBIENT_ENABLED}" != "true" ]; then
+      echo "Deploying error rates demo ..."
+      "${SCRIPT_DIR}/install-error-rates-demo.sh" -in ${ISTIO_NAMESPACE} -a ${ARCH} ${AMBIENT_ARGS_ERROR_RATES}
     fi
+    echo "Deploying sleep demo ..."
+    "${SCRIPT_DIR}/install-sleep-demo.sh" -in ${ISTIO_NAMESPACE} -a ${ARCH} ${AMBIENT_ARGS_BOOKINFO}
+  elif [ "${AMBIENT_ENABLED}" == "true" ]; then
+    echo "Deploying bookinfo demo..."
+    "${SCRIPT_DIR}/install-bookinfo-demo.sh" -c kubectl -mp ${MINIKUBE_PROFILE} -tg ${AMBIENT_ARGS_BOOKINFO}
+    "${ISTIO_DIR}/bin/istioctl" waypoint apply -n bookinfo
+
+    echo "Deploying sleep demo ..."
+    "${SCRIPT_DIR}/install-sleep-demo.sh" -c kubectl -in ${ISTIO_NAMESPACE} -a ${ARCH} ${AMBIENT_ARGS_BOOKINFO}
+  else
     echo "Deploying bookinfo demo..."
     "${SCRIPT_DIR}/install-bookinfo-demo.sh" -c kubectl -mp ${MINIKUBE_PROFILE} -tg -in ${ISTIO_NAMESPACE} -a ${ARCH} ${gateway_yaml:+-g ${gateway_yaml}} ${AMBIENT_ARGS_BOOKINFO}
 
diff --git a/kubernetes/filters_test.go b/kubernetes/filters_test.go
index 5dc3c843a..cb51260c8 100644
--- a/kubernetes/filters_test.go
+++ b/kubernetes/filters_test.go
@@ -11,6 +11,7 @@ import (
 	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	k8s_networking_v1 "sigs.k8s.io/gateway-api/apis/v1"
 	k8s_networking_v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
@@ -146,19 +147,19 @@ func TestFilterPodsByController(t *testing.T) {
 			UID:  types.UID("e07b722f-c922-4046-8d98-7aa8487d41c1"),
 		},
 		TypeMeta: meta_v1.TypeMeta{
-			Kind:       "ReplicaSet",
-			APIVersion: apps_v1.SchemeGroupVersion.String(),
+			Kind:       ReplicaSets.Kind,
+			APIVersion: ReplicaSets.GroupVersion().String(),
 		},
 	}
 	cases := map[string]struct {
 		controllerName string
-		controllerType string
+		controllerGVK  schema.GroupVersionKind
 		pods           []core_v1.Pod
 		expectedLen    int
 	}{
 		"Filters by kind and full name": {
 			controllerName: rs.Name,
-			controllerType: "ReplicaSet",
+			controllerGVK:  ReplicaSets,
 			pods: []core_v1.Pod{
 				{
 					ObjectMeta: meta_v1.ObjectMeta{
@@ -176,7 +177,7 @@ func TestFilterPodsByController(t *testing.T) {
 		},
 		"Includes APIVersion": {
 			controllerName: rs.Name,
-			controllerType: "ReplicaSet",
+			controllerGVK:  ReplicaSets,
 			pods: []core_v1.Pod{
 				{
 					ObjectMeta: meta_v1.ObjectMeta{
@@ -189,8 +190,7 @@ func TestFilterPodsByController(t *testing.T) {
 					},
 				},
 			},
-			// TODO: This should be 0
-			expectedLen: 1,
+			expectedLen: 0,
 		},
 	}
 
@@ -198,7 +198,7 @@ func TestFilterPodsByController(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			assert := assert.New(t)
 
-			pods := FilterPodsByController(tc.controllerName, tc.controllerType, tc.pods)
+			pods := FilterPodsByController(tc.controllerName, tc.controllerGVK, tc.pods)
 			assert.Equal(tc.expectedLen, len(pods))
 		})
 	}
diff --git a/kubernetes/kubetest/mock_kubernetes.go b/kubernetes/kubetest/mock_kubernetes.go
index dcb6204e3..c82444ef5 100644
--- a/kubernetes/kubetest/mock_kubernetes.go
+++ b/kubernetes/kubetest/mock_kubernetes.go
@@ -9,6 +9,7 @@ import (
 	batch_v1 "k8s.io/api/batch/v1"
 	core_v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/kubernetes"
 )
 
@@ -156,8 +157,8 @@ func (o *K8SClientMock) UpdateNamespace(namespace string, jsonPatch string) (*co
 	return args.Get(0).(*core_v1.Namespace), args.Error(1)
 }
 
-func (o *K8SClientMock) UpdateWorkload(namespace string, name string, workloadType string, jsonPatch string, patchType string) error {
-	args := o.Called(namespace, name, workloadType, jsonPatch, patchType)
+func (o *K8SClientMock) UpdateWorkload(namespace string, name string, workloadGVK schema.GroupVersionKind, jsonPatch string, patchType string) error {
+	args := o.Called(namespace, name, workloadGVK, jsonPatch, patchType)
 	return args.Error(1)
 }
 
diff --git a/mesh/api/testdata/test_mesh_graph.expected b/mesh/api/testdata/test_mesh_graph.expected
index 05a8257ba..301278d9a 100644
--- a/mesh/api/testdata/test_mesh_graph.expected
+++ b/mesh/api/testdata/test_mesh_graph.expected
@@ -143,7 +143,10 @@
             "InternalURL": "http://tracing.istio-system:16685/jaeger",
             "IsCore": false,
             "Provider": "jaeger",
-            "TempoConfig": {},
+            "TempoConfig": {
+              "cache_capacity": 200,
+              "cache_enabled": true
+            },
             "NamespaceSelector": true,
             "QueryScope": {},
             "QueryTimeout": 5,
diff --git a/models/workload_test.go b/models/workload_test.go
index 882419c20..0b070b6ee 100644
--- a/models/workload_test.go
+++ b/models/workload_test.go
@@ -10,6 +10,7 @@ import (
 	apps_v1 "k8s.io/api/apps/v1"
 	core_v1 "k8s.io/api/core/v1"
 	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 
 	"github.com/kiali/kiali/config"
 )
@@ -41,7 +42,7 @@ func TestParseDeploymentToWorkload(t *testing.T) {
 	assert.Equal("bar", w.Labels["foo"])
 	assert.Equal("v1", w.Labels["version"])
 	assert.Equal("2709198702082918", w.ResourceVersion)
-	assert.Equal("Deployment", w.Type)
+	assert.Equal("Deployment", w.WorkloadGVK.Kind)
 	assert.Equal(int32(1), w.DesiredReplicas)
 	assert.Equal(int32(1), w.CurrentReplicas)
 	assert.Equal(int32(1), w.AvailableReplicas)
@@ -75,7 +76,7 @@ func TestParseReplicaSetToWorkload(t *testing.T) {
 	assert.Equal("bar", w.Labels["foo"])
 	assert.Equal("v1", w.Labels["version"])
 	assert.Equal("2709198702082918", w.ResourceVersion)
-	assert.Equal("ReplicaSet", w.Type)
+	assert.Equal("ReplicaSet", w.WorkloadGVK.Kind)
 	assert.Equal(int32(1), w.DesiredReplicas)
 	assert.Equal(int32(1), w.CurrentReplicas)
 	assert.Equal(int32(1), w.AvailableReplicas)
@@ -102,7 +103,7 @@ func TestParseReplicationControllerToWorkload(t *testing.T) {
 	assert.Equal("bar", w.Labels["foo"])
 	assert.Equal("v1", w.Labels["version"])
 	assert.Equal("2709198702082918", w.ResourceVersion)
-	assert.Equal("ReplicationController", w.Type)
+	assert.Equal("ReplicationController", w.WorkloadGVK.Kind)
 	assert.Equal(int32(1), w.DesiredReplicas)
 	assert.Equal(int32(1), w.CurrentReplicas)
 	assert.Equal(int32(1), w.AvailableReplicas)
@@ -155,7 +156,7 @@ func TestParsePodToWorkload(t *testing.T) {
 	assert.Equal("bar", w.Labels["foo"])
 	assert.Equal("v1", w.Labels["version"])
 	assert.Equal("2709198702082918", w.ResourceVersion)
-	assert.Equal("Pod", w.Type)
+	assert.Equal("Pod", w.WorkloadGVK.Kind)
 	assert.Equal(int32(1), w.DesiredReplicas)
 	assert.Equal(int32(1), w.CurrentReplicas)
 	assert.Equal(int32(1), w.AvailableReplicas)
@@ -169,13 +170,13 @@ func TestParsePodsToWorkload(t *testing.T) {
 	config.Set(config.NewConfig())
 
 	w := Workload{}
-	w.ParsePods("workload-from-controller", "Controller", []core_v1.Pod{*fakePod()})
+	w.ParsePods("workload-from-controller", schema.GroupVersionKind{Kind: "Controller"}, []core_v1.Pod{*fakePod()})
 
 	assert.Equal("workload-from-controller", w.Name)
 	assert.Equal("bar", w.Labels["foo"])
 	assert.Equal("v1", w.Labels["version"])
 	assert.Equal("2709198702082918", w.ResourceVersion)
-	assert.Equal("Controller", w.Type)
+	assert.Equal("Controller", w.WorkloadGVK.Kind)
 	assert.Equal(int32(1), w.DesiredReplicas)
 	assert.Equal(int32(1), w.CurrentReplicas)
 	assert.Equal(int32(1), w.AvailableReplicas)
diff --git a/store/fifo_store_test.go b/store/fifo_store_test.go
new file mode 100644
index 000000000..0a88f5a0f
--- /dev/null
+++ b/store/fifo_store_test.go
@@ -0,0 +1,160 @@
+package store
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/kiali/kiali/util"
+)
+
+func TestSetAndGet(t *testing.T) {
+	require := require.New(t)
+
+	s := New[string, string]()
+	fifoStore := NewFIFOStore[string, string](s, 3, "test")
+	fifoStore.Set("foo", "bar")
+	fifoStore.Set("foo2", "bar2")
+	fifoStore.Set("foo3", "bar3")
+
+	require.Equal(3, fifoStore.order.Len())
+
+	elem, found := fifoStore.Get("foo")
+	require.True(found)
+	require.Equal(elem, "bar")
+
+	elem, found = fifoStore.Get("foo2")
+	require.True(found)
+	require.Equal(elem, "bar2")
+
+	fifoStore.Set("foo4", "bar4")
+	_, found = fifoStore.Get("foo")
+	require.Equal(fifoStore.order.Len(), 3)
+	require.False(found)
+}
+
+func TestUpdate(t *testing.T) {
+	require := require.New(t)
+
+	s := New[string, string]()
+	fifoStore := NewFIFOStore[string, string](s, 3, "test")
+	fifoStore.Set("foo", "bar")
+	fifoStore.Set("foo2", "bar2")
+	fifoStore.Set("foo3", "bar3")
+
+	require.Equal(fifoStore.order.Len(), 3)
+
+	elem, found := fifoStore.Get("foo")
+	require.True(found)
+	require.Equal(elem, "bar")
+
+	elem, found = fifoStore.Get("foo2")
+	require.True(found)
+	require.Equal(elem, "bar2")
+
+	fifoStore.Set("foo2", "newValue")
+	elem, found = fifoStore.Get("foo2")
+	require.Equal(fifoStore.order.Len(), 3)
+	require.True(found)
+	require.Equal(elem, "newValue")
+
+	fifoStore.Set("foo4", "bar4")
+	elem, found = fifoStore.Get("foo2")
+	require.Equal(fifoStore.order.Len(), 3)
+	require.True(found)
+	require.Equal(elem, "newValue")
+	_, found = fifoStore.Get("foo")
+	require.False(found)
+}
+
+func TestExpired(t *testing.T) {
+	require := require.New(t)
+
+	store := New[string, string]()
+	fifoStore := NewFIFOStore[string, string](store, 3, "test")
+	expirationStore := NewExpirationStore[string, string](context.Background(), fifoStore, util.AsPtr(10*time.Second), util.AsPtr(10*time.Second))
+	expirationStore.Set("foo", "bar")
+	expirationStore.Set("foo2", "bar2")
+	expirationStore.Set("foo3", "bar3")
+
+	require.Equal(len(expirationStore.Store.Items()), 3)
+
+	time.Sleep(5 * time.Second)
+	elem, found := expirationStore.Get("foo")
+	require.True(found)
+	require.Equal(elem, "bar")
+
+	time.Sleep(15 * time.Second)
+
+	_, found1 := expirationStore.Get("foo")
+	require.False(found1)
+	_, found2 := expirationStore.Get("foo2")
+	require.False(found2)
+	_, found3 := expirationStore.Get("foo3")
+	require.False(found3)
+}
+
+func TestExpiredNoCleanup(t *testing.T) {
+	require := require.New(t)
+
+	store := New[string, string]()
+	fifoStore := NewFIFOStore[string, string](store, 3, "test")
+	expirationStore := NewExpirationStore[string, string](context.Background(), fifoStore, util.AsPtr(10*time.Second), util.AsPtr(10*time.Second))
+	expirationStore.Set("foo", "bar")
+	expirationStore.Set("foo2", "bar2")
+	expirationStore.Set("foo3", "bar3")
+
+	require.Equal(len(expirationStore.Store.Items()), 3)
+
+	time.Sleep(5 * time.Second)
+	elem, found := expirationStore.Get("foo")
+	require.True(found)
+	require.Equal(elem, "bar")
+
+	time.Sleep(15 * time.Second)
+
+	_, found1 := expirationStore.Get("foo")
+	require.False(found1)
+	_, found2 := expirationStore.Get("foo2")
+	require.False(found2)
+	_, found3 := expirationStore.Get("foo3")
+	require.False(found3)
+
+}
+
+func TestReplace(t *testing.T) {
+	require := require.New(t)
+
+	s := New[string, string]()
+	fifoStore := NewFIFOStore[string, string](s, 3, "test")
+	fifoStore.Set("foo", "bar")
+	fifoStore.Set("foo2", "bar2")
+	fifoStore.Set("foo3", "bar3")
+
+	require.Equal(fifoStore.order.Len(), 3)
+	elem, found := fifoStore.Get("foo")
+
+	require.True(found)
+	require.Equal(elem, "bar")
+
+	replaced := map[string]string{"newKey": "newValue", "newKey2": "newValue2", "newKey3": "newValue3"}
+	fifoStore.Replace(replaced)
+	require.Equal(fifoStore.order.Len(), 3)
+	_, found = fifoStore.Get("foo")
+
+	require.False(found)
+	elem, found = fifoStore.Get("newKey")
+
+	require.True(found)
+	require.Equal(elem, "newValue")
+
+	replaced2 := map[string]string{}
+	replaced2["replacedKey"] = "replacedValue"
+	replaced2["replacedKey2"] = "replacedValue2"
+	replaced2["replacedKey3"] = "replacedValue3"
+	replaced2["newKey"] = "newValue"
+	fifoStore.Replace(replaced2)
+	require.Equal(fifoStore.order.Len(), 3)
+}
diff --git a/tests/integration/tests/no_istiod_test.go b/tests/integration/tests/no_istiod_test.go
index 3b41554f8..980c8bd8b 100644
--- a/tests/integration/tests/no_istiod_test.go
+++ b/tests/integration/tests/no_istiod_test.go
@@ -78,7 +78,7 @@ func noProxyStatus(t *testing.T) {
 	require.NoError(err)
 	require.NotNil(wl)
 	require.Equal(name, wl.Name)
-	require.Equal("Deployment", wl.Type)
+	require.Equal("Deployment", wl.WorkloadGVK.Kind)
 	require.NotNil(wl.Pods)
 	for _, pod := range wl.Pods {
 		require.NotEmpty(pod.Status)
diff --git a/tests/integration/tests/workloads_test.go b/tests/integration/tests/workloads_test.go
index aae823d92..f90cb3091 100644
--- a/tests/integration/tests/workloads_test.go
+++ b/tests/integration/tests/workloads_test.go
@@ -42,7 +42,7 @@ func TestWorkloadDetails(t *testing.T) {
 	require.NoError(err)
 	require.NotNil(wl)
 	require.Equal(name, wl.Name)
-	require.Equal("Deployment", wl.Type)
+	require.Equal("Deployment", wl.WorkloadGVK.Kind)
 	require.NotNil(wl.Pods)
 	for _, pod := range wl.Pods {
 		require.NotEmpty(pod.Status)
@@ -73,7 +73,7 @@ func TestWorkloadIstioIngressEmptyProxyStatus(t *testing.T) {
 	require.NoError(err)
 	require.NotNil(wl)
 	require.Equal(name, wl.Name)
-	require.Equal("Deployment", wl.Type)
+	require.Equal("Deployment", wl.WorkloadGVK.Kind)
 	require.NotNil(wl.Pods)
 	for _, pod := range wl.Pods {
 		require.NotEmpty(pod.Status)
@@ -109,7 +109,7 @@ func TestDiscoverWorkload(t *testing.T) {
 		foundWorkloads := 0
 		for _, wl := range wlList.Workloads {
 			for k, v := range extraWorkloads {
-				if k == wl.Name && v == wl.Type {
+				if k == wl.Name && v == wl.WorkloadGVK.Kind {
 					foundWorkloads++
 				}
 			}
diff --git a/tracing/tempo/http_client_test.go b/tracing/tempo/http_client_test.go
index 3fe22215a..66a6ae97f 100644
--- a/tracing/tempo/http_client_test.go
+++ b/tracing/tempo/http_client_test.go
@@ -1,6 +1,7 @@
 package tempo
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"net/http"
@@ -50,7 +51,7 @@ func TestGetTraces(t *testing.T) {
 		}
 	})}
 
-	tempoClient, err := NewOtelClient(httpClient, baseUrl)
+	tempoClient, err := NewOtelClient(context.TODO(), httpClient, baseUrl)
 	assert.Nil(t, err)
 	assert.NotNil(t, tempoClient)
 
@@ -89,7 +90,7 @@ func TestGetTrace(t *testing.T) {
 		}
 	})}
 
-	tempoClient, err := NewOtelClient(httpClient, baseUrl)
+	tempoClient, err := NewOtelClient(context.TODO(), httpClient, baseUrl)
 	assert.Nil(t, err)
 	assert.NotNil(t, tempoClient)
 
@@ -113,7 +114,7 @@ func TestErrorResponse(t *testing.T) {
 		}
 	})}
 
-	tempoClient, err := NewOtelClient(httpClient, baseUrl)
+	tempoClient, err := NewOtelClient(context.TODO(), httpClient, baseUrl)
 	assert.Nil(t, err)
 	assert.NotNil(t, tempoClient)
 
@@ -143,7 +144,7 @@ func TestQuery(t *testing.T) {
 		}
 	})}
 
-	tempoClient, err := NewOtelClient(httpClient, baseUrl)
+	tempoClient, err := NewOtelClient(context.TODO(), httpClient, baseUrl)
 	assert.Nil(t, err)
 	assert.NotNil(t, tempoClient)