Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,5 +143,16 @@
},
"main": {
"failedMessage": "Failed to load frontend configuration"
},
"Providers": {
"headerProviders": "Providers",
"tableHeaderVersion": "Version",
"tableHeaderName": "Name",
"tableHeaderCreated": "Created",
"tableHeaderInstalled": "Installed",
"tableHeaderHealthy": "Healthy"
},
"ProvidersConfig": {
"headerProvidersConfig": "Providers Config"
}
}
11 changes: 4 additions & 7 deletions src/components/ControlPlane/ManagedResources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { timeAgo } from '../../utils/i18n/timeAgo';
import IllustratedError from '../Shared/IllustratedError';
import '@ui5/webcomponents-icons/dist/sys-enter-2';
import '@ui5/webcomponents-icons/dist/sys-cancel-2';
import { resourcesInterval } from '../../lib/shared/constants';
import { StatusCellProps } from '../../lib/shared/interfaces';

interface CellData<T> {
cell: {
Expand Down Expand Up @@ -40,7 +42,7 @@ export function ManagedResources() {
error,
isLoading,
} = useResource(ManagedResourcesRequest, {
refreshInterval: 30000, // Resources are quite expensive to fetch, so we refresh every 30 seconds
refreshInterval: resourcesInterval, // Resources are quite expensive to fetch, so we refresh every 30 seconds
});

const columns: AnalyticalTableColumnDefinition[] = [
Expand Down Expand Up @@ -136,15 +138,10 @@ export function ManagedResources() {
);
}

interface ResourceStatusCellProps {
value: boolean;
transitionTime: string;
}

function ResourceStatusCell({
value,
transitionTime,
}: ResourceStatusCellProps) {
}: StatusCellProps) {
return (
<Icon
design={value ? 'Positive' : 'Negative'}
Expand Down
121 changes: 121 additions & 0 deletions src/components/ControlPlane/Providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@

import { useTranslation } from 'react-i18next';
import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, Icon, Title } from '@ui5/webcomponents-react';
import useResource from '../../lib/api/useApiResource';
import IllustratedError from '../Shared/IllustratedError';
import '@ui5/webcomponents-icons/dist/sys-enter-2';
import '@ui5/webcomponents-icons/dist/sys-cancel-2';
import { ProvidersListRequest } from '../../lib/api/types/crossplane/listProviders';
import { resourcesInterval } from '../../lib/shared/constants';
import { timeAgo } from '../../utils/i18n/timeAgo';
import { StatusCellProps } from '../../lib/shared/interfaces';

interface CellData<T> {
cell: {
value: T | null; // null for grouping rows
row: {
original?: ProvidersRow; // missing for grouping rows
}
};
}

type ProvidersRow = {
name: string
version: string;
healthy: boolean;
healthyTransitionTime: string;
installed: boolean;
installedTransitionTime: string;
created: string;
}

export function Providers() {
const { t } = useTranslation();

let {data: providers, error, isLoading} = useResource(ProvidersListRequest, {
refreshInterval: resourcesInterval
});

const columns: AnalyticalTableColumnDefinition[] = [
{
Header: t('Providers.tableHeaderName'),
accessor: 'name',
},
{
Header: t('Providers.tableHeaderVersion'),
accessor: 'version',
},
{
Header: t('Providers.tableHeaderInstalled'),
accessor: 'installed',
Cell: (cellData: CellData<ProvidersRow['installed']>) => cellData.cell.row.original?.installed != null ? <ResourceStatusCell value={cellData.cell.row.original?.installed} transitionTime={cellData.cell.row.original?.installedTransitionTime} /> : null
},
{
Header: t('Providers.tableHeaderHealthy'),
accessor: 'healthy',
Cell: (cellData: CellData<ProvidersRow['healthy']>) => cellData.cell.row.original?.installed != null ? <ResourceStatusCell value={cellData.cell.row.original?.healthy} transitionTime={cellData.cell.row.original?.healthyTransitionTime} /> : null
},
{
Header: t('Providers.tableHeaderCreated'),
accessor: 'created',
},
];

const rows: ProvidersRow[] =
providers?.items?.map((item) => {
const installed = item.status.conditions?.find((condition) => condition.type === 'Installed');
const healthy = item.status.conditions?.find((condition) => condition.type === 'Healthy');

return {
name: item.metadata.name,
created: timeAgo.format(new Date(item.metadata.creationTimestamp)),
installed: installed?.status === "True",
installedTransitionTime: installed?.lastTransitionTime ?? "",
healthy: healthy?.status === "True",
healthyTransitionTime: healthy?.lastTransitionTime ?? "",
version: item.spec.package.match(/\d+(\.\d+)+/g)?.toString() ?? "",
}
})
?? [];

return (
<>
<Title level='H4'>{t('Providers.headerProviders')}</Title>

{error && <IllustratedError error={error}/>}

{!error &&
<AnalyticalTable
columns={columns}
data={rows}
minRows={1}
scaleWidthMode={AnalyticalTableScaleWidthMode.Smart}
loading={isLoading}
filterable
// Prevent the table from resetting when the data changes
retainColumnWidth
reactTableOptions={{
autoResetHiddenColumns: false,
autoResetPage: false,
autoResetExpanded: false,
autoResetGroupBy: false,
autoResetSelectedRows: false,
autoResetSortBy: false,
autoResetFilters: false,
autoResetRowState: false,
autoResetResize: false
}}
/>
}
</>
)
}

function ResourceStatusCell({ value, transitionTime }: StatusCellProps) {
return <Icon
design={value ? 'Positive' : 'Negative'}
name={value ? 'sys-enter-2' : 'sys-cancel-2'}
showTooltip={true}
accessibleName={timeAgo.format(new Date(transitionTime))}
/>
}
40 changes: 40 additions & 0 deletions src/components/ControlPlane/ProvidersConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

import { useTranslation } from 'react-i18next';
import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, Title } from '@ui5/webcomponents-react';
import '@ui5/webcomponents-icons/dist/sys-enter-2';
import '@ui5/webcomponents-icons/dist/sys-cancel-2';

//empty table TBD
export function ProvidersConfig() {
const { t } = useTranslation();

const columns: AnalyticalTableColumnDefinition[] = [];

return (
<>
<Title level='H4'>{t('ProvidersConfig.headerProvidersConfig')}</Title>
<AnalyticalTable
columns={columns}
data={[]}
minRows={1}
groupBy={['name']}
scaleWidthMode={AnalyticalTableScaleWidthMode.Smart}
loading={false}
filterable
// Prevent the table from resetting when the data changes
retainColumnWidth
reactTableOptions={{
autoResetHiddenColumns: false,
autoResetPage: false,
autoResetExpanded: false,
autoResetGroupBy: false,
autoResetSelectedRows: false,
autoResetSortBy: false,
autoResetFilters: false,
autoResetRowState: false,
autoResetResize: false
}}
/>
</>
)
}
47 changes: 4 additions & 43 deletions src/components/ControlPlane/ProvidersList.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,12 @@
import ConfiguredAnalyticstable from '../Shared/ConfiguredAnalyticsTable.tsx';
import {
AnalyticalTableColumnDefinition,
Title,
} from '@ui5/webcomponents-react';
import ReactTimeAgo from 'react-time-ago';
import IllustratedError from '../Shared/IllustratedError.tsx';
import useResource from '../../lib/api/useApiResource';
import { ListProviders } from '../../lib/api/types/crossplane/listProviders';
import { useTranslation } from 'react-i18next';
import { Providers } from "./Providers.tsx";
import { ProvidersConfig } from "./ProvidersConfig.tsx";
import { ManagedResources } from './ManagedResources';

export default function ProvidersList() {
const { data, error, isLoading } = useResource(ListProviders);
const { t } = useTranslation();

if (error) {
return <IllustratedError error={error} />;
}

const columns: AnalyticalTableColumnDefinition[] = [
{
Header: t('ProvidersList.tableNameHeader'),
accessor: 'metadata.name',
},
{
Header: t('ProvidersList.tableStatusHeader'),
accessor: 'status.phase',
},
{
Header: t('ProvidersList.tableCreatedHeader'),
accessor: 'metadata.creationTimestamp',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (props: any) => <ReactTimeAgo date={new Date(props.cell.value)} />,
},
];

return (
<>
<Title level="H4">Providers</Title>
<ConfiguredAnalyticstable
columns={columns}
isLoading={isLoading}
data={data ?? []}
/>
<Title level="H2">ProviderConfigs</Title>
<ConfiguredAnalyticstable columns={columns} data={[]} />

<Providers />
<ProvidersConfig />
<ManagedResources />
</>
);
Expand Down
28 changes: 23 additions & 5 deletions src/lib/api/types/crossplane/listProviders.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import { Resource } from '../resource';

//TODO: type any to correct type and adapt the jq query to the required paramters
export const ListProviders: Resource<any> = {
path: '/apis/pkg.crossplane.io/v1/providers',
jq: '[.items[]]',
};
export type ProvidersListResponse = {
items: [{
spec: {
package: string;
};
kind: string;
metadata: {
name: string;
creationTimestamp: string;
};
status: {
conditions: [{
type: "Healthy" | "Installed" | unknown;
status: "True" | "False";
lastTransitionTime: string;
}]
};
}];
};

export const ProvidersListRequest: Resource<ProvidersListResponse> = {
path: "/apis/pkg.crossplane.io/v1/providers",
};
1 change: 1 addition & 0 deletions src/lib/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const resourcesInterval = 30000;
4 changes: 4 additions & 0 deletions src/lib/shared/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface StatusCellProps {
value: boolean;
transitionTime: string;
}