From 481c125a025f4337c93b071f9572342d24ba4a3d Mon Sep 17 00:00:00 2001 From: Andreas Kienle Date: Fri, 14 Mar 2025 16:22:47 +0100 Subject: [PATCH 1/2] Add Managed Resources --- public/locales/en.json | 10 ++ .../ControlPlane/ManagedResources.tsx | 118 ++++++++++++++++++ src/components/ControlPlane/ProvidersList.tsx | 8 +- .../types/crossplane/listManagedResources.ts | 20 +++ src/main.tsx | 4 +- src/utils/i18n/timeAgo.ts | 5 + 6 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 src/components/ControlPlane/ManagedResources.tsx create mode 100644 src/lib/api/types/crossplane/listManagedResources.ts create mode 100644 src/utils/i18n/timeAgo.ts diff --git a/public/locales/en.json b/public/locales/en.json index 3bfe811d..f4308ea4 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -19,6 +19,16 @@ "tableStatusHeader": "Status", "tableCreatedHeader": "Created" }, + "ManagedResources": { + "headerManagedResources": "Resources", + "tableHeaderKind": "Kind", + "tableHeaderName": "Name", + "tableHeaderCreated": "Created", + "tableHeaderSynced": "Synced", + "tableHeaderReady": "Ready", + "iconAriaYes": "Yes", + "iconAriaNo": "No" + }, "ControlPlaneListToolbar": { "buttonText": "Workspace" }, diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx new file mode 100644 index 00000000..1c25c954 --- /dev/null +++ b/src/components/ControlPlane/ManagedResources.tsx @@ -0,0 +1,118 @@ +import { useTranslation } from 'react-i18next'; +import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, Icon, Title } from '@ui5/webcomponents-react'; +import useResource from '../../lib/api/useApiResource'; +import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources'; +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'; + +interface CellData { + cell: { + value: T | null; // null for grouping rows + }; +} + +type ResourceRow = { + kind: string + name: string + created: string; + synced: boolean; + ready: boolean; +} + +export function ManagedResources() { + const { t } = useTranslation(); + + let {data: managedResources, error, isLoading} = useResource(ManagedResourcesRequest, { + refreshInterval: 30000 // Resources are quite expensive to fetch, so we refresh every 30 seconds + }); + + const columns: AnalyticalTableColumnDefinition[] = [ + { + Header: t('ManagedResources.tableHeaderKind'), + accessor: 'kind', + }, + { + Header: t('ManagedResources.tableHeaderName'), + accessor: 'name', + }, + { + Header: t('ManagedResources.tableHeaderCreated'), + accessor: 'created', + }, + { + Header: t('ManagedResources.tableHeaderSynced'), + accessor: 'synced', + Cell: (cellData: CellData) => + }, + { + Header: t('ManagedResources.tableHeaderReady'), + accessor: 'ready', + Cell: (cellData: CellData) => + }, + ]; + + const rows: ResourceRow[] = managedResources?.flatMap((managedResource) => + managedResource.items?.map((item) => ({ + kind: item.kind, + name: item.metadata.name, + created: timeAgo.format(new Date(item.metadata.creationTimestamp)), + synced: item.status.conditions?.some((condition) => condition.type === 'Synced') ?? false, + ready: item.status.conditions?.some((condition) => condition.type === 'Ready') ?? false, + })) + ) ?? []; + + + return ( + <> + {t('ManagedResources.headerManagedResources')} + + {error && } + + {!error && + + } + + ) +} + + +interface ResourceStatusCellProps { + cellData: CellData; +} + +function ResourceStatusCell({ cellData }: ResourceStatusCellProps) { + const { t } = useTranslation(); + + if (cellData.cell.value === null) { + return null; + } + + return +} diff --git a/src/components/ControlPlane/ProvidersList.tsx b/src/components/ControlPlane/ProvidersList.tsx index 98067851..aea26f4d 100644 --- a/src/components/ControlPlane/ProvidersList.tsx +++ b/src/components/ControlPlane/ProvidersList.tsx @@ -5,6 +5,7 @@ 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 { ManagedResources } from './ManagedResources'; export default function ProvidersList() { const { data, error, isLoading } = useResource(ListProviders); @@ -45,11 +46,8 @@ export default function ProvidersList() { columns={columns} data={[]} /> - Resources - + + ); } diff --git a/src/lib/api/types/crossplane/listManagedResources.ts b/src/lib/api/types/crossplane/listManagedResources.ts new file mode 100644 index 00000000..4d0b82ca --- /dev/null +++ b/src/lib/api/types/crossplane/listManagedResources.ts @@ -0,0 +1,20 @@ +import { Resource } from "../resource"; + +export type ManagedResourcesResponse = [{ + items: [{ + kind: string; + metadata: { + name: string; + creationTimestamp: string; + }; + status: { + conditions: [{ + type: "Ready" | "Synced" | unknown; + }] + }; + }]; +}]; + +export const ManagedResourcesRequest: Resource = { + path: "/managed", +}; diff --git a/src/main.tsx b/src/main.tsx index f3b58e9d..4e31991d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,8 +5,6 @@ import App from "./App"; import { ThemeProvider } from "@ui5/webcomponents-react"; import { AuthProvider } from "react-oidc-context"; import { LoadCrateKubeConfig } from "./lib/oidc/crate.ts"; -import TimeAgo from "javascript-time-ago"; -import en from "javascript-time-ago/locale/en"; import { SWRConfig } from "swr"; import { ToastProvider } from "./context/ToastContext.tsx"; import { CopyButtonProvider } from './context/CopyButtonContext.tsx'; @@ -14,6 +12,7 @@ import { FrontendConfigProvider, LoadFrontendConfig } from "./context/FrontendCo import '@ui5/webcomponents-react/dist/Assets'; //used for loading themes import { DarkModeSystemSwitcher } from "./components/Core/DarkModeSystemSwitcher.tsx"; import ".././i18n"; +import "./utils/i18n/timeAgo"; import { useTranslation } from "react-i18next"; (async () => { @@ -21,7 +20,6 @@ import { useTranslation } from "react-i18next"; const frontendConfig = await LoadFrontendConfig(); const authconfig = await LoadCrateKubeConfig(frontendConfig.backendUrl); - TimeAgo.addDefaultLocale(en); ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/src/utils/i18n/timeAgo.ts b/src/utils/i18n/timeAgo.ts new file mode 100644 index 00000000..cdcba428 --- /dev/null +++ b/src/utils/i18n/timeAgo.ts @@ -0,0 +1,5 @@ +import en from 'javascript-time-ago/locale/en'; +import TimeAgo from 'javascript-time-ago'; + +TimeAgo.addDefaultLocale(en); +export const timeAgo = new TimeAgo('en-US'); \ No newline at end of file From 74710899b4a6996f7953a99f3bd0cecba97dbdf2 Mon Sep 17 00:00:00 2001 From: Andreas Kienle Date: Tue, 18 Mar 2025 14:35:38 +0100 Subject: [PATCH 2/2] Fix status and add transition time --- public/locales/en.json | 4 +- .../ControlPlane/ManagedResources.tsx | 48 +++++++++++-------- .../types/crossplane/listManagedResources.ts | 2 + 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index f4308ea4..1c24f1b6 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -25,9 +25,7 @@ "tableHeaderName": "Name", "tableHeaderCreated": "Created", "tableHeaderSynced": "Synced", - "tableHeaderReady": "Ready", - "iconAriaYes": "Yes", - "iconAriaNo": "No" + "tableHeaderReady": "Ready" }, "ControlPlaneListToolbar": { "buttonText": "Workspace" diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx index 1c25c954..32fabc2e 100644 --- a/src/components/ControlPlane/ManagedResources.tsx +++ b/src/components/ControlPlane/ManagedResources.tsx @@ -10,6 +10,9 @@ import '@ui5/webcomponents-icons/dist/sys-cancel-2'; interface CellData { cell: { value: T | null; // null for grouping rows + row: { + original?: ResourceRow; // missing for grouping rows + } }; } @@ -18,7 +21,9 @@ type ResourceRow = { name: string created: string; synced: boolean; + syncedTransitionTime: string; ready: boolean; + readyTransitionTime: string; } export function ManagedResources() { @@ -44,23 +49,30 @@ export function ManagedResources() { { Header: t('ManagedResources.tableHeaderSynced'), accessor: 'synced', - Cell: (cellData: CellData) => + Cell: (cellData: CellData) => cellData.cell.row.original?.synced != null ? : null }, { Header: t('ManagedResources.tableHeaderReady'), accessor: 'ready', - Cell: (cellData: CellData) => + Cell: (cellData: CellData) => cellData.cell.row.original?.ready != null ? : null }, ]; const rows: ResourceRow[] = managedResources?.flatMap((managedResource) => - managedResource.items?.map((item) => ({ - kind: item.kind, - name: item.metadata.name, - created: timeAgo.format(new Date(item.metadata.creationTimestamp)), - synced: item.status.conditions?.some((condition) => condition.type === 'Synced') ?? false, - ready: item.status.conditions?.some((condition) => condition.type === 'Ready') ?? false, - })) + managedResource.items?.map((item) => { + const conditionSynced = item.status.conditions?.find((condition) => condition.type === 'Synced'); + const conditionReady = item.status.conditions?.find((condition) => condition.type === 'Ready'); + + return { + kind: item.kind, + name: item.metadata.name, + created: timeAgo.format(new Date(item.metadata.creationTimestamp)), + synced: conditionSynced?.status === "True", + syncedTransitionTime: conditionSynced?.lastTransitionTime ?? "", + ready: conditionReady?.status === "True", + readyTransitionTime: conditionReady?.lastTransitionTime ?? "", + } + }) ) ?? []; @@ -100,19 +112,15 @@ export function ManagedResources() { interface ResourceStatusCellProps { - cellData: CellData; + value: boolean; + transitionTime: string; } -function ResourceStatusCell({ cellData }: ResourceStatusCellProps) { - const { t } = useTranslation(); - - if (cellData.cell.value === null) { - return null; - } - +function ResourceStatusCell({ value, transitionTime }: ResourceStatusCellProps) { return } diff --git a/src/lib/api/types/crossplane/listManagedResources.ts b/src/lib/api/types/crossplane/listManagedResources.ts index 4d0b82ca..c0deb68a 100644 --- a/src/lib/api/types/crossplane/listManagedResources.ts +++ b/src/lib/api/types/crossplane/listManagedResources.ts @@ -10,6 +10,8 @@ export type ManagedResourcesResponse = [{ status: { conditions: [{ type: "Ready" | "Synced" | unknown; + status: "True" | "False"; + lastTransitionTime: string; }] }; }];