Skip to content

feat(kiali): add card for resources #1565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 7, 2024
31 changes: 30 additions & 1 deletion plugins/kiali/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { TestApiProvider } from '@backstage/test-utils';

import { Grid } from '@material-ui/core';

import { kialiPlugin } from '../src';
import { EntityKialiResourcesCard, kialiPlugin } from '../src';
import { getEntityRoutes, getRoutes } from '../src/components/Router';
import { KialiHeader } from '../src/pages/Kiali/Header/KialiHeader';
import { KialiHelper } from '../src/pages/Kiali/KialiHelper';
Expand Down Expand Up @@ -530,6 +530,30 @@ const MockProvider = (props: Props) => {
);
};

const MockEntityCard = () => {
const content = (
<EntityProvider entity={mockEntity}>
<BrowserRouter>
<div style={{ padding: '20px' }}>
<TestApiProvider apis={[[kialiApiRef, new MockKialiClient()]]}>
<Grid container spacing={3} alignItems="stretch">
<Grid item md={8} xs={12}>
<EntityKialiResourcesCard />
</Grid>
</Grid>
</TestApiProvider>
</div>
</BrowserRouter>
</EntityProvider>
);

return (
<TestApiProvider apis={[[kialiApiRef, new MockKialiClient()]]}>
{content}
</TestApiProvider>
);
};

const MockKialiError = () => {
const errorsTypes: KialiChecker[] = [
{
Expand Down Expand Up @@ -619,4 +643,9 @@ createDevApp()
title: 'No Annotation',
path: '/no-annotation',
})
.addPage({
element: <MockEntityCard />,
title: 'Resources card',
path: '/kiali-entity-card',
})
.render();
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { Link } from 'react-router-dom';

import { Tooltip } from '@material-ui/core';

Expand All @@ -10,6 +11,7 @@ import { kialiStyle } from '../../styles/StyleUtils';
import { AppWorkload } from '../../types/App';
import * as H from '../../types/Health';
import { HealthSubItem } from '../../types/Health';
import { DRAWER } from '../../types/types';
import { Workload } from '../../types/Workload';
import { JanusObjectLink } from '../../utils/janusLinks';
import { renderTrafficStatus } from '../Health/HealthDetails';
Expand All @@ -25,6 +27,7 @@ type Props = {
services?: string[];
waypointWorkloads?: Workload[];
workloads?: AppWorkload[];
view?: string;
};

const iconStyle = kialiStyle({
Expand Down Expand Up @@ -74,7 +77,9 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
namespace: string,
appName: string,
): React.ReactNode => {
const link = (
let link: React.ReactNode;

link = (
<JanusObjectLink
entity={props.entity}
namespace={namespace}
Expand All @@ -88,6 +93,18 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
</JanusObjectLink>
);

let href = `/namespaces/${namespace}/applications/${appName}`;

if (props.cluster && isMultiCluster) {
href = `${href}?clusterName=${props.cluster}`;
}

if (props.view === DRAWER) {
href = `#application/${namespace}_${appName}`;

link = <Link to={href}>{appName}</Link>;
}

return (
<li key={`App_${namespace}_${appName}`} className={itemStyle}>
<div className={iconStyle}>
Expand All @@ -103,7 +120,9 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
namespace: string,
serviceName: string,
): React.ReactNode => {
const link = (
let link: React.ReactNode;

link = (
<JanusObjectLink
entity={props.entity}
namespace={namespace}
Expand All @@ -117,6 +136,17 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
</JanusObjectLink>
);

if (props.view === DRAWER) {
let href = `/namespaces/${namespace}/services/${serviceName}`;

if (props.cluster && isMultiCluster) {
href = `${href}?clusterName=${props.cluster}`;
}

href = `#service/${namespace}_${serviceName}`;
link = <Link to={href}>{serviceName}</Link>;
}

return (
<li key={`Service_${serviceName}`} className={itemStyle}>
<div className={iconStyle}>
Expand Down Expand Up @@ -184,7 +214,9 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
};

const renderWorkloadItem = (workload: AppWorkload): React.ReactNode => {
const link = (
let link: React.ReactNode;

link = (
<JanusObjectLink
entity={props.entity}
namespace={props.namespace}
Expand All @@ -197,6 +229,18 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
{workload.workloadName}
</JanusObjectLink>
);

if (props.view === DRAWER) {
let href = `/namespaces/${props.namespace}/workloads/${workload.workloadName}`;

if (props.cluster && isMultiCluster) {
href = `${href}?clusterName=${props.cluster}`;
}

href = `#workload/${props.namespace}_${workload.workloadName}`;
link = <Link to={href}>{workload.workloadName}</Link>;
}

return (
<span key={`WorkloadItem_${workload.workloadName}`}>
<div className={iconStyle}>
Expand Down Expand Up @@ -237,21 +281,34 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
}

if (workload) {
const link = (
<JanusObjectLink
entity={props.entity}
namespace={props.namespace}
type="workloads"
query={
props.cluster && isMultiCluster
? `clusterName=${props.cluster}`
: ''
}
name={workload.workloadName}
>
{workload.workloadName}
</JanusObjectLink>
);
let link: React.ReactNode;

if (props.view === DRAWER) {
let href = `/namespaces/${props.namespace}/workloads/${workload.workloadName}`;

if (props.cluster && isMultiCluster) {
href = `${href}?clusterName=${props.cluster}`;
}

href = `#workload/${props.namespace}_${workload.workloadName}`;
link = <Link to={href}>{workload.workloadName}</Link>;
} else {
link = (
<JanusObjectLink
entity={props.entity}
namespace={props.namespace}
type="workloads"
query={
props.cluster && isMultiCluster
? `clusterName=${props.cluster}`
: ''
}
name={workload.workloadName}
>
{workload.workloadName}
</JanusObjectLink>
);
}

return (
<span key={`WorkloadItem_${workload.workloadName}`}>
Expand Down
75 changes: 75 additions & 0 deletions plugins/kiali/src/components/Drawers/AppDetailsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as React from 'react';
import { useAsyncFn, useDebounce } from 'react-use';

import { useApi } from '@backstage/core-plugin-api';

import { CircularProgress } from '@material-ui/core';
import { AxiosError } from 'axios';

import { HistoryManager } from '../../app/History';
import { AppInfo } from '../../pages/AppDetails/AppInfo';
import { kialiApiRef } from '../../services/Api';
import { App, AppQuery } from '../../types/App';
import { AppHealth } from '../../types/Health';
import { DRAWER } from '../../types/types';

type Props = {
namespace: string;
app: string;
};
export const AppDetailsDrawer = (props: Props) => {
const kialiClient = useApi(kialiApiRef);
const [appItem, setAppItem] = React.useState<App>();
const [health, setHealth] = React.useState<AppHealth>();
const cluster = HistoryManager.getClusterName();

const fetchApp = async () => {
const params: AppQuery = {
rateInterval: `60s`,
health: 'true',
};

kialiClient
.getApp(props.namespace, props.app, params, cluster)
.then((appResponse: App) => {
const healthR = AppHealth.fromJson(
props.namespace,
props.app,
appResponse.health,
{
rateInterval: 60,
hasSidecar: appResponse.workloads.some(w => w.istioSidecar),
hasAmbient: appResponse.workloads.some(w => w.istioAmbient),
},
);
setAppItem(appResponse);
setHealth(healthR);
})
.catch((err: AxiosError<unknown, any>) => {
// eslint-disable-next-line no-console
console.log(err);
});
};

const [{ loading }, refresh] = useAsyncFn(
async () => {
// Check if the config is loaded
await fetchApp();
},
[],
{ loading: true },
);
useDebounce(refresh, 10);

if (loading) {
return <CircularProgress />;
}

return (
<>
{appItem && (
<AppInfo app={appItem} duration={60} health={health} view={DRAWER} />
)}
</>
);
};
71 changes: 71 additions & 0 deletions plugins/kiali/src/components/Drawers/ServiceDetailsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react';
import { useAsyncFn, useDebounce } from 'react-use';

import { useApi } from '@backstage/core-plugin-api';

import { CircularProgress } from '@material-ui/core';

import { HistoryManager } from '../../app/History';
import { ServiceInfo } from '../../pages/ServiceDetails/ServiceInfo';
import { kialiApiRef } from '../../services/Api';
import { Validations } from '../../types/IstioObjects';
import { ServiceDetailsInfo } from '../../types/ServiceInfo';
import { DRAWER } from '../../types/types';

type Props = {
namespace: string;
service: string;
};
export const ServiceDetailsDrawer = (props: Props) => {
const kialiClient = useApi(kialiApiRef);
const [serviceItem, setServiceItem] = React.useState<ServiceDetailsInfo>();
const cluster = HistoryManager.getClusterName();
const [validations, setValidations] = React.useState<Validations>({});

const fetchService = async () => {
kialiClient
.getServiceDetail(props.namespace, props.service, true, cluster, 60)
.then((serviceResponse: ServiceDetailsInfo) => {
setServiceItem(serviceResponse);
setValidations(serviceResponse.validations);
})
.catch(err => {
// eslint-disable-next-line no-console
console.log(err);
});
};

const [{ loading }, refresh] = useAsyncFn(
async () => {
// Check if the config is loaded
await fetchService();
},
[],
{ loading: true },
);
useDebounce(refresh, 10);

if (loading) {
return <CircularProgress />;
}

return (
<>
{serviceItem && (
<ServiceInfo
service={props.service}
duration={60}
namespace={props.namespace}
validations={validations}
cluster={cluster}
serviceDetails={serviceItem}
gateways={[]}
k8sGateways={[]}
peerAuthentications={[]}
istioAPIEnabled
view={DRAWER}
/>
)}
</>
);
};
Loading