Skip to content

Commit f8047b8

Browse files
authored
Provide VM links to Obs metrics & CNV dashboard (stolostron#4171)
* Provide VM links to Obs metrics & CNV dashboard Signed-off-by: zlayne <zlayne@redhat.com> * add translations Signed-off-by: zlayne <zlayne@redhat.com> * hide Observability link in VM page header if not installed Signed-off-by: zlayne <zlayne@redhat.com> * fix typo Signed-off-by: zlayne <zlayne@redhat.com> --------- Signed-off-by: zlayne <zlayne@redhat.com>
1 parent 819971d commit f8047b8

File tree

8 files changed

+358
-17
lines changed

8 files changed

+358
-17
lines changed

backend/src/routes/events.ts

+11
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ const definitions: IWatchOptions[] = [
195195
{ kind: 'AgentMachine', apiVersion: 'capi-provider.agent-install.openshift.io/v1alpha1' },
196196
{ kind: 'ConfigMap', apiVersion: 'v1', labelSelector: { 'hypershift.openshift.io/supported-versions': 'true' } },
197197
{ kind: 'Search', apiVersion: 'search.open-cluster-management.io/v1alpha1' },
198+
// Configmaps that contain Grafana dashboard IDs
199+
{
200+
kind: 'ConfigMap',
201+
apiVersion: 'v1',
202+
fieldSelector: { 'metadata.name': 'grafana-dashboard-acm-openshift-virtualization-clusters-overview' },
203+
},
204+
{
205+
kind: 'ConfigMap',
206+
apiVersion: 'v1',
207+
fieldSelector: { 'metadata.name': 'grafana-dashboard-acm-openshift-virtualization-single-vm-view' },
208+
},
198209
]
199210

200211
export function startWatching(): void {

frontend/public/locales/en/translation.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,7 @@
18341834
"Memory request": "Memory request",
18351835
"Message": "Message",
18361836
"Messages": "Messages",
1837+
"Metrics": "Metrics",
18371838
"Microsoft Azure": "Microsoft Azure",
18381839
"Min {{min}} Max {{max}}": "Min {{min}} Max {{max}}",
18391840
"Minimum duration": "Minimum duration",
@@ -1984,6 +1985,8 @@
19841985
"Nutanix requires x86_64 release image. No other architecture is supported.": "Nutanix requires x86_64 release image. No other architecture is supported.",
19851986
"NV-series": "NV-series",
19861987
"Object storage": "Object storage",
1988+
"Observability dashboards": "Observability dashboards",
1989+
"Observability metrics": "Observability metrics",
19871990
"of": "of",
19881991
"Offline": "Offline",
19891992
"OIDC Secret for Red Hat OpenShift Provisioning with hosted control plane": "OIDC Secret for Red Hat OpenShift Provisioning with hosted control plane",
@@ -3251,9 +3254,10 @@
32513254
"violations: {{count}} cluster_plural": "violations: {{count}} clusters",
32523255
"violations: {{count}} policy": "violations: {{count}} policy",
32533256
"violations: {{count}} policy_plural": "violations: {{count}} policies",
3257+
"Virtual machine console": "Virtual machine console",
3258+
"Virtual machine details": "Virtual machine details",
32543259
"Virtual machines": "Virtual machines",
32553260
"Virtual Machines": "Virtual Machines",
3256-
"VM details": "VM details",
32573261
"VM size": "VM size",
32583262
"VMware": "VMware",
32593263
"Volume mode": "Volume mode",
@@ -3267,6 +3271,7 @@
32673271
"Want to learn more?": "Want to learn more?",
32683272
"Warning": "Warning",
32693273
"Warning:": "Warning:",
3274+
"Web console": "Web console",
32703275
"Welcome": "Welcome",
32713276
"Welcome!": "Welcome!",
32723277
"What are the different credentials types?": "What are the different credentials types?",

frontend/src/atoms.ts

+7
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ export function usePolicies() {
178178
)
179179
}
180180

181+
export function useIsObservabilityInstalled() {
182+
const clusterManagementAddons = useRecoilValue(clusterManagementAddonsState)
183+
return useMemo(() => {
184+
return clusterManagementAddons.filter((cma) => cma.metadata.name === 'observability-controller').length > 0
185+
}, [clusterManagementAddons])
186+
}
187+
181188
// Search is available if api, collector, indexer & postgres are in ready state
182189
export function useIsSearchAvailable() {
183190
const searchOperator = useRecoilValue(searchOperatorState)

frontend/src/routes/Infrastructure/VirtualMachines/VirtualMachinesPage.tsx

+55-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import { Pages, usePageVisitMetricHandler } from '../../../hooks/console-metrics
1919
import { useTranslation } from '../../../lib/acm-i18next'
2020
import { OCP_DOC } from '../../../lib/doc-util'
2121
import { PluginContext } from '../../../lib/PluginContext'
22+
import { ConfigMap } from '../../../resources'
2223
import { useRecoilValue, useSharedAtoms } from '../../../shared-recoil'
2324
import {
25+
AcmActionGroup,
2426
AcmButton,
2527
AcmEmptyState,
2628
AcmPage,
@@ -221,10 +223,62 @@ function VirtualMachineTable() {
221223

222224
export default function VirtualMachinesPage() {
223225
const { t } = useTranslation()
226+
const { useIsObservabilityInstalled, clusterManagementAddonsState, configMapsState } = useSharedAtoms()
227+
const isObservabilityInstalled = useIsObservabilityInstalled()
228+
const configMaps = useRecoilValue(configMapsState)
229+
const clusterManagementAddons = useRecoilValue(clusterManagementAddonsState)
224230
usePageVisitMetricHandler(Pages.virtualMachines)
225231

232+
const vmMetricLink = useMemo(() => {
233+
const obsCont = clusterManagementAddons.filter((cma) => cma.metadata.name === 'observability-controller')
234+
let grafanaLink = obsCont?.[0]?.metadata?.annotations?.['console.open-cluster-management.io/launch-link']
235+
if (grafanaLink) {
236+
grafanaLink = new URL(grafanaLink).origin
237+
}
238+
if (isObservabilityInstalled) {
239+
const vmDashboard = configMaps.filter(
240+
(cm: ConfigMap) => cm.metadata.name === 'grafana-dashboard-acm-openshift-virtualization-clusters-overview'
241+
)
242+
if (vmDashboard.length > 0) {
243+
const parsedDashboardData = JSON.parse(
244+
vmDashboard[0].data?.['acm-openshift-virtualization-clusters-overview.json']
245+
)
246+
const dashboardId = parsedDashboardData?.uid
247+
return `${grafanaLink}/d/${dashboardId}/executive-dashboards-clusters-overview?orgId=1`
248+
}
249+
}
250+
return ''
251+
}, [clusterManagementAddons, configMaps, isObservabilityInstalled])
252+
226253
return (
227-
<AcmPage hasDrawer header={<AcmPageHeader title={t('Virtual machines')} />}>
254+
<AcmPage
255+
hasDrawer
256+
header={
257+
<AcmPageHeader
258+
title={t('Virtual machines')}
259+
actions={
260+
isObservabilityInstalled ? (
261+
<AcmActionGroup>
262+
{[
263+
<AcmButton
264+
key={'observability-launch-link'}
265+
variant="link"
266+
component="a"
267+
target="_blank"
268+
isInline={true}
269+
href={vmMetricLink}
270+
icon={<ExternalLinkAltIcon />}
271+
iconPosition="right"
272+
>
273+
{t('Observability dashboards')}
274+
</AcmButton>,
275+
]}
276+
</AcmActionGroup>
277+
) : undefined
278+
}
279+
/>
280+
}
281+
>
228282
<AcmPageContent id="virtual-machines">
229283
<PageSection>
230284
<VirtualMachineTable />

frontend/src/routes/Search/Details/DetailsOverviewPage.tsx

+94-3
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@ import {
1515
Text,
1616
Tooltip,
1717
} from '@patternfly/react-core'
18-
import { GlobeAmericasIcon, PencilAltIcon, SearchIcon } from '@patternfly/react-icons'
18+
import { ExternalLinkAltIcon, GlobeAmericasIcon, PencilAltIcon, SearchIcon } from '@patternfly/react-icons'
1919
import _ from 'lodash'
2020
import { Fragment, useEffect, useMemo, useState } from 'react'
2121
import { generatePath, Link, useNavigate } from 'react-router-dom-v5-compat'
2222
import { findResourceFieldLineNumber } from '../../../components/YamlEditor'
2323
import { useTranslation } from '../../../lib/acm-i18next'
2424
import { canUser } from '../../../lib/rbac-util'
2525
import { NavigationPath } from '../../../NavigationPath'
26-
import { OwnerReference } from '../../../resources'
27-
import { AcmAlert, AcmLoadingPage, AcmTable, compareStrings } from '../../../ui-components'
26+
import { ConfigMap, OwnerReference } from '../../../resources'
27+
import { useRecoilValue, useSharedAtoms } from '../../../shared-recoil'
28+
import { AcmAlert, AcmButton, AcmLoadingPage, AcmTable, compareStrings } from '../../../ui-components'
29+
import { useAllClusters } from '../../Infrastructure/Clusters/ManagedClusters/components/useAllClusters'
2830
import { useSearchDetailsContext } from './DetailsPage'
2931

3032
export function ResourceSearchLink(props: {
@@ -216,6 +218,11 @@ export default function DetailsOverviewPage() {
216218
const { cluster, resource, resourceLoading, resourceError, name } = useSearchDetailsContext()
217219
const { t } = useTranslation()
218220
const navigate = useNavigate()
221+
const allClusters = useAllClusters(true)
222+
const { useIsObservabilityInstalled, configMapsState, clusterManagementAddonsState } = useSharedAtoms()
223+
const configMaps = useRecoilValue(configMapsState)
224+
const clusterManagementAddons = useRecoilValue(clusterManagementAddonsState)
225+
const isObservabilityInstalled = useIsObservabilityInstalled()
219226
const [canEditResource, setCanEditResource] = useState<boolean>(false)
220227

221228
const { labelsLineNumber, annotationsLineNumber, tolerationsLineNumber } = useMemo(() => {
@@ -319,6 +326,35 @@ export default function DetailsOverviewPage() {
319326
}
320327
}, [cluster, resource, navigate])
321328

329+
const vmCNVLink = useMemo(() => {
330+
const clusterURL = allClusters.filter((c) => c.name === cluster)?.[0]?.consoleURL
331+
if (resource) {
332+
return `${clusterURL}/k8s/ns/${resource.metadata?.namespace}/kubevirt.io~v1~VirtualMachine/${name}`
333+
}
334+
return ''
335+
}, [allClusters, cluster, resource, name])
336+
337+
const vmMetricLink = useMemo(() => {
338+
const obsCont = clusterManagementAddons.filter((cma) => cma.metadata.name === 'observability-controller')
339+
let grafanaLink = obsCont?.[0]?.metadata?.annotations?.['console.open-cluster-management.io/launch-link']
340+
if (grafanaLink) {
341+
grafanaLink = new URL(grafanaLink).origin
342+
}
343+
if (isObservabilityInstalled && resource) {
344+
const vmDashboard = configMaps.filter(
345+
(cm: ConfigMap) => cm.metadata.name === 'grafana-dashboard-acm-openshift-virtualization-single-vm-view'
346+
)
347+
if (vmDashboard.length > 0) {
348+
const parsedDashboardData = JSON.parse(
349+
vmDashboard[0].data?.['acm-openshift-virtualization-single-vm-view.json']
350+
)
351+
const dashboardId = parsedDashboardData?.uid
352+
return `${grafanaLink}/d/${dashboardId}/executive-dashboards-single-virtual-machine-view?orgId=1&var-name=${name}&var-namespace=${resource?.metadata?.namespace}&var-cluster=${cluster}`
353+
}
354+
}
355+
return ''
356+
}, [cluster, clusterManagementAddons, configMaps, name, resource, isObservabilityInstalled])
357+
322358
if (resourceError) {
323359
return (
324360
<PageSection>
@@ -496,6 +532,61 @@ export default function DetailsOverviewPage() {
496532
/>
497533
</DescriptionListDescription>
498534
</DescriptionListGroup>
535+
536+
{resource.kind.toLowerCase() === 'virtualmachine' && (
537+
<>
538+
<DescriptionListGroup>
539+
<DescriptionListTerm>{t('Details')}</DescriptionListTerm>
540+
<DescriptionListDescription>
541+
<AcmButton
542+
variant="link"
543+
component="a"
544+
target="_blank"
545+
isInline={true}
546+
href={vmCNVLink}
547+
icon={<ExternalLinkAltIcon />}
548+
iconPosition="right"
549+
>
550+
{t('Launch')}
551+
</AcmButton>
552+
</DescriptionListDescription>
553+
</DescriptionListGroup>
554+
<DescriptionListGroup>
555+
<DescriptionListTerm>{t('Web console')}</DescriptionListTerm>
556+
<DescriptionListDescription>
557+
<AcmButton
558+
variant="link"
559+
component="a"
560+
target="_blank"
561+
isInline={true}
562+
href={`${vmCNVLink}/console`}
563+
icon={<ExternalLinkAltIcon />}
564+
iconPosition="right"
565+
>
566+
{t('Launch')}
567+
</AcmButton>
568+
</DescriptionListDescription>
569+
</DescriptionListGroup>
570+
{isObservabilityInstalled && (
571+
<DescriptionListGroup>
572+
<DescriptionListTerm>{t('Metrics')}</DescriptionListTerm>
573+
<DescriptionListDescription>
574+
<AcmButton
575+
variant="link"
576+
component="a"
577+
target="_blank"
578+
isInline={true}
579+
href={vmMetricLink}
580+
icon={<ExternalLinkAltIcon />}
581+
iconPosition="right"
582+
>
583+
{t('Launch')}
584+
</AcmButton>
585+
</DescriptionListDescription>
586+
</DescriptionListGroup>
587+
)}
588+
</>
589+
)}
499590
</DescriptionList>
500591
</Stack>
501592
</PageSection>

frontend/src/routes/Search/__snapshots__/searchDefinitions.test.tsx.snap

+31-5
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,31 @@ exports[`Correctly returns CreateGlobalSearchDetailsLink managed hub default res
497497
</body>
498498
`;
499499

500+
exports[`Correctly returns VMLaunchLinks 1`] = `
501+
<body>
502+
<div>
503+
<div
504+
style="display: contents;"
505+
>
506+
<span
507+
class="pf-v5-c-label"
508+
>
509+
<button
510+
class="pf-v5-c-label__content"
511+
type="button"
512+
>
513+
<span
514+
class="pf-v5-c-label__text"
515+
>
516+
Launch links
517+
</span>
518+
</button>
519+
</span>
520+
</div>
521+
</div>
522+
</body>
523+
`;
524+
500525
exports[`Correctly returns all resource definitions: SearchDefinitions-application 1`] = `
501526
[
502527
{
@@ -2426,7 +2451,7 @@ exports[`Correctly returns all resource definitions: SearchDefinitions-virtualma
24262451
"sort": "ready",
24272452
},
24282453
{
2429-
"cell": <CreateExternalVMLink
2454+
"cell": <VMLaunchLinks
24302455
item={
24312456
{
24322457
"cluster": "testCluster",
@@ -2440,7 +2465,8 @@ exports[`Correctly returns all resource definitions: SearchDefinitions-virtualma
24402465
}
24412466
t={[Function]}
24422467
/>,
2443-
"header": "VM details",
2468+
"header": "",
2469+
"id": "launch-links",
24442470
},
24452471
{
24462472
"cell": "in a month",
@@ -2597,7 +2623,7 @@ exports[`Correctly returns all resource definitions: SearchDefinitions-virtualma
25972623
"sort": "ipaddress",
25982624
},
25992625
{
2600-
"cell": <CreateExternalVMLink
2626+
"cell": <VMLaunchLinks
26012627
item={
26022628
{
26032629
"cluster": "testCluster",
@@ -2611,8 +2637,8 @@ exports[`Correctly returns all resource definitions: SearchDefinitions-virtualma
26112637
}
26122638
t={[Function]}
26132639
/>,
2614-
"header": "VM details",
2615-
"id": "details-link",
2640+
"header": "",
2641+
"id": "launch-links",
26162642
"isDefault": false,
26172643
"isFirstVisitChecked": true,
26182644
"order": 7,

0 commit comments

Comments
 (0)