diff --git a/.env b/.env index 1844f4b..4bed8ba 100644 --- a/.env +++ b/.env @@ -33,3 +33,9 @@ VITE_REMOVE_BACKLINK_TEXT=true VITE_DOCS_URL=https://in-cloud.io/docs/tech-docs/introduction/ VITE_SEARCH_TABLE_CUSTOMIZATION_PREFIX=stock- + +VITE_BASE_FACTORY_NAMESPACED_API_KEY=base-factory-namespaced-api +VITE_BASE_FACTORY_CLUSTERSCOPED_API_KEY=base-factory-clusterscoped-api +VITE_BASE_FACTORY_NAMESPACED_BUILTIN_KEY=base-factory-namespaced-builtin +VITE_BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY=base-factory-clusterscoped-builtin +VITE_BASE_NAMESPACE_FACTORY_KEY=base-factory-clusterscoped-builtin diff --git a/.env.options.dist b/.env.options.dist index a04925e..4efefbc 100644 --- a/.env.options.dist +++ b/.env.options.dist @@ -35,3 +35,9 @@ REMOVE_BACKLINK_TEXT= DOCS_URL= SEARCH_TABLE_CUSTOMIZATION_PREFIX= + +BASE_FACTORY_NAMESPACED_API_KEY= +BASE_FACTORY_CLUSTERSCOPED_API_KEY= +BASE_FACTORY_NAMESPACED_BUILTIN_KEY= +BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY= +BASE_NAMESPACE_FACTORY_KEY= diff --git a/package-lock.json b/package-lock.json index 93b509e..a69a72d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@ant-design/icons": "5.6.0", "@monaco-editor/react": "4.6.0", "@originjs/vite-plugin-federation": "1.3.6", - "@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.150", + "@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.151", "@readme/openapi-parser": "4.0.0", "@reduxjs/toolkit": "2.2.5", "@tanstack/react-query": "5.62.2", @@ -2804,9 +2804,9 @@ } }, "node_modules/@prorobotech/openapi-k8s-toolkit": { - "version": "0.0.1-alpha.150", - "resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.150.tgz", - "integrity": "sha512-WkrZDN4XNHA5p/Vtcj4vE+yNHu1vyG6ezX2QCrE77TxmCTRfTSJM69Pvh6AllqWwoL7kDeUKG66Zbh+TiDm+vg==", + "version": "0.0.1-alpha.151", + "resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.151.tgz", + "integrity": "sha512-AV+6muJNp75WLUPGuLi3JIH8j3P1sBgodHFNfebJ25CdfzJQeYUVkRs0yqWQoNFnw1lG92W4i7fIToYmk1WzJQ==", "license": "MIT", "dependencies": { "@monaco-editor/react": "4.6.0", diff --git a/package.json b/package.json index c79b935..eb29204 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@ant-design/icons": "5.6.0", "@monaco-editor/react": "4.6.0", "@originjs/vite-plugin-federation": "1.3.6", - "@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.150", + "@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.151", "@readme/openapi-parser": "4.0.0", "@reduxjs/toolkit": "2.2.5", "@tanstack/react-query": "5.62.2", diff --git a/server/index.ts b/server/index.ts index f95b1ec..d1bf62f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -73,6 +73,23 @@ const SEARCH_TABLE_CUSTOMIZATION_PREFIX = ? options?.SEARCH_TABLE_CUSTOMIZATION_PREFIX : process.env.SEARCH_TABLE_CUSTOMIZATION_PREFIX +const BASE_FACTORY_NAMESPACED_API_KEY = + process.env.LOCAL === 'true' ? options?.BASE_FACTORY_NAMESPACED_API_KEY : process.env.BASE_FACTORY_NAMESPACED_API_KEY +const BASE_FACTORY_CLUSTERSCOPED_API_KEY = + process.env.LOCAL === 'true' + ? options?.BASE_FACTORY_CLUSTERSCOPED_API_KEY + : process.env.BASE_FACTORY_CLUSTERSCOPED_API_KEY +const BASE_FACTORY_NAMESPACED_BUILTIN_KEY = + process.env.LOCAL === 'true' + ? options?.BASE_FACTORY_NAMESPACED_BUILTIN_KEY + : process.env.BASE_FACTORY_NAMESPACED_BUILTIN_KEY +const BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY = + process.env.LOCAL === 'true' + ? options?.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY + : process.env.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY +const BASE_NAMESPACE_FACTORY_KEY = + process.env.LOCAL === 'true' ? options?.BASE_NAMESPACE_FACTORY_KEY : process.env.BASE_NAMESPACE_FACTORY_KEY + const healthcheck = require('express-healthcheck') const promBundle = require('express-prom-bundle') @@ -199,7 +216,14 @@ app.get(`${basePrefix ? basePrefix : ''}/env.js`, (_, res) => { DOCS_URL: ${JSON.stringify(DOCS_URL) || '"/docs"'}, SEARCH_TABLE_CUSTOMIZATION_PREFIX: ${JSON.stringify(SEARCH_TABLE_CUSTOMIZATION_PREFIX) || '"search-"'}, REMOVE_BACKLINK: ${!!REMOVE_BACKLINK ? JSON.stringify(REMOVE_BACKLINK).toLowerCase() : '"false"'}, - REMOVE_BACKLINK_TEXT: ${!!REMOVE_BACKLINK_TEXT ? JSON.stringify(REMOVE_BACKLINK_TEXT).toLowerCase() : '"false"'} + REMOVE_BACKLINK_TEXT: ${!!REMOVE_BACKLINK_TEXT ? JSON.stringify(REMOVE_BACKLINK_TEXT).toLowerCase() : '"false"'}, + BASE_FACTORY_NAMESPACED_API_KEY: ${JSON.stringify(BASE_FACTORY_NAMESPACED_API_KEY) || '"check envs"'}, + BASE_FACTORY_CLUSTERSCOPED_API_KEY: ${JSON.stringify(BASE_FACTORY_CLUSTERSCOPED_API_KEY) || '"check envs"'}, + BASE_FACTORY_NAMESPACED_BUILTIN_KEY: ${JSON.stringify(BASE_FACTORY_NAMESPACED_BUILTIN_KEY) || '"check envs"'}, + BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY: ${ + JSON.stringify(BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY) || '"check envs"' + }, + BASE_NAMESPACE_FACTORY_KEY: ${JSON.stringify(BASE_NAMESPACE_FACTORY_KEY) || '"check envs"'} } `, ) diff --git a/src/components/organisms/Events/Events.tsx b/src/components/organisms/Events/Events.tsx index abb722c..497c80d 100644 --- a/src/components/organisms/Events/Events.tsx +++ b/src/components/organisms/Events/Events.tsx @@ -10,8 +10,19 @@ // ------------------------------------------------------------ import React, { FC, useCallback, useEffect, useReducer, useRef, useState } from 'react' -import { theme as antdtheme, Flex, Tooltip } from 'antd' -import { ResumeCircleIcon, PauseCircleIcon, LockedIcon, UnlockedIcon } from '@prorobotech/openapi-k8s-toolkit' +import { theme as antdtheme, Flex, Tooltip, Empty } from 'antd' +import { + // TRequestError, + TKindIndex, + TKindWithVersion, + getKinds, + getSortedKindsAll, + pluralByKind, + ResumeCircleIcon, + PauseCircleIcon, + LockedIcon, + UnlockedIcon, +} from '@prorobotech/openapi-k8s-toolkit' import { TScrollMsg, TServerFrame } from './types' import { eventKey, compareRV, getRV, getMaxRV } from './utils' import { reducer } from './reducer' @@ -19,15 +30,41 @@ import { EventRow } from './molecules' import { Styled } from './styled' type TEventsProps = { + baseprefix?: string + cluster: string wsUrl: string // e.g. ws://localhost:3000/api/events?namespace=default&limit=40 pageSize?: number // SCROLL page size (optional) height?: number // optional override title?: string } -export const Events: FC = ({ wsUrl, pageSize = 50, height }) => { +export const Events: FC = ({ baseprefix, cluster, wsUrl, pageSize = 50, height }) => { const { token } = antdtheme.useToken() + // const [error, setError] = useState() + // const [isLoading, setIsLoading] = useState(false) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [kindIndex, setKindIndex] = useState() + const [kindsWithVersion, setKindWithVersion] = useState() + + useEffect(() => { + // setIsLoading(true) + // setError(undefined) + getKinds({ clusterName: cluster }) + .then(data => { + setKindIndex(data) + setKindWithVersion(getSortedKindsAll(data)) + // setIsLoading(false) + // setError(undefined) + }) + .catch(error => { + // setIsLoading(false) + // setError(error) + // eslint-disable-next-line no-console + console.error(error) + }) + }, [cluster]) + // pause behaviour const [isPaused, setIsPaused] = useState(false) const pausedRef = useRef(isPaused) @@ -315,6 +352,8 @@ export const Events: FC = ({ wsUrl, pageSize = 50, height }) => { const total = state.order.length + const getPlural = kindsWithVersion ? pluralByKind(kindsWithVersion) : undefined + return ( @@ -361,14 +400,18 @@ export const Events: FC = ({ wsUrl, pageSize = 50, height }) => { {/* Scrollable list of event rows */} - {state.order.map(k => ( - - ))} + {state.order.length > 0 ? ( + state.order.map(k => ( + + )) + ) : ( + + )} {/* Infinite scroll sentinel */} - + {state.order.length > 0 && } ) } diff --git a/src/components/organisms/Events/molecules/EventRow/EventRow.tsx b/src/components/organisms/Events/molecules/EventRow/EventRow.tsx index 103aff3..824c191 100644 --- a/src/components/organisms/Events/molecules/EventRow/EventRow.tsx +++ b/src/components/organisms/Events/molecules/EventRow/EventRow.tsx @@ -1,24 +1,49 @@ import React, { FC } from 'react' +import { useNavigate } from 'react-router-dom' import { theme as antdtheme, Flex, Typography } from 'antd' import { EarthIcon, getUppercase, hslFromString, Spacer } from '@prorobotech/openapi-k8s-toolkit' import { useSelector } from 'react-redux' import { RootState } from 'store/store' import { TEventsV1Event } from '../../types' -import { eventText, timeAgo } from './utils' +import { eventText, timeAgo, getResourceLink, getNamespaceLink, formatEventSummary } from './utils' import { Styled } from './styled' type TEventRowProps = { e: TEventsV1Event + baseprefix?: string + cluster: string + getPlural?: (kind: string, apiVersion?: string) => string | undefined } -export const EventRow: FC = ({ e }) => { +export const EventRow: FC = ({ e, baseprefix, cluster, getPlural }) => { const { token } = antdtheme.useToken() + const navigate = useNavigate() const theme = useSelector((state: RootState) => state.openapiTheme.theme) const abbr = e.regarding?.kind ? getUppercase(e.regarding.kind) : undefined const bgColor = e.regarding?.kind && abbr ? hslFromString(e.regarding?.kind, theme) : 'initial' const bgColorNamespace = hslFromString('Namespace', theme) + const regardingKind: string | undefined = e.regarding?.kind + const regardingApiVersion: string = e.regarding?.apiVersion || 'v1' + const pluralName: string | undefined = + regardingKind && regardingApiVersion ? getPlural?.(regardingKind, regardingApiVersion) : undefined + const resourceLink: string | undefined = getResourceLink({ + baseprefix, + cluster, + namespace: e.regarding?.namespace, + apiGroupVersion: regardingApiVersion, + pluralName, + name: e.regarding?.name, + }) + const namespaceLink: string | undefined = getNamespaceLink({ + baseprefix, + cluster, + apiGroupVersion: 'v1', + pluralName: 'namespaces', + namespace: e.regarding?.namespace, + }) + return ( = ({ e }) => { {abbr} - {e.regarding?.name} + {resourceLink ? ( + { + e.preventDefault() + navigate(resourceLink) + }} + > + {e.regarding?.name} + + ) : ( + {e.regarding?.name} + )} - {e.metadata?.namespace && ( + {e.regarding?.namespace && ( NS - {e.metadata?.namespace} + {namespaceLink ? ( + { + e.preventDefault() + navigate(namespaceLink) + }} + > + {e.regarding?.namespace} + + ) : ( + {e.regarding?.namespace} + )} )} @@ -47,21 +94,26 @@ export const EventRow: FC = ({ e }) => { )} - -
- {e.deprecatedSource?.component && ( - - - Generated by - {e.deprecatedSource?.component} + + +
+ {e.deprecatedSource?.component && ( + + + Generated by + {e.deprecatedSource?.component} + +
+ +
-
- -
- - )} -
- {e.reason || e.action || 'Event'} + )} +
+ {e.reason || e.action || 'Event'} +
+ + {formatEventSummary(e)} + {eventText(e) &&
{eventText(e)}
} diff --git a/src/components/organisms/Events/molecules/EventRow/styled.ts b/src/components/organisms/Events/molecules/EventRow/styled.ts index 2d14afe..315b71e 100644 --- a/src/components/organisms/Events/molecules/EventRow/styled.ts +++ b/src/components/organisms/Events/molecules/EventRow/styled.ts @@ -60,9 +60,14 @@ const Title = styled.div` font-weight: 700; ` +const TimesInPeriod = styled.div` + margin-top: -16px; +` + export const Styled = { Card, Abbr, TimeStamp, Title, + TimesInPeriod, } diff --git a/src/components/organisms/Events/molecules/EventRow/utils.ts b/src/components/organisms/Events/molecules/EventRow/utils.ts index 11d1b42..739bff9 100644 --- a/src/components/organisms/Events/molecules/EventRow/utils.ts +++ b/src/components/organisms/Events/molecules/EventRow/utils.ts @@ -1,3 +1,10 @@ +import { + BASE_FACTORY_NAMESPACED_API_KEY, + BASE_FACTORY_CLUSTERSCOPED_API_KEY, + BASE_FACTORY_NAMESPACED_BUILTIN_KEY, + BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY, + BASE_NAMESPACE_FACTORY_KEY, +} from 'constants/customizationApiGroupAndVersion' import { TEventsV1Event } from '../../types' // Prefer modern `note`, fallback to legacy `message` @@ -24,3 +31,65 @@ export const timeAgo = (iso?: string) => { return new Date(iso).toLocaleString() } + +export const getResourceLink = ({ + baseprefix, + cluster, + namespace, + apiGroupVersion, + pluralName, + name, +}: { + baseprefix?: string + cluster: string + namespace?: string + apiGroupVersion: string + pluralName?: string + name?: string +}): string | undefined => { + if (!pluralName || !name) { + return undefined + } + + if (apiGroupVersion === 'v1') { + return `${baseprefix}/${cluster}${namespace ? `/${namespace}` : ''}/factory/${ + namespace ? BASE_FACTORY_NAMESPACED_BUILTIN_KEY : BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY + }/${apiGroupVersion}/${pluralName}/${name}` + } + + return `${baseprefix}/${cluster}${namespace ? `/${namespace}` : ''}/factory/${ + namespace ? BASE_FACTORY_NAMESPACED_API_KEY : BASE_FACTORY_CLUSTERSCOPED_API_KEY + }/${apiGroupVersion}/${pluralName}/${name}` +} + +export const getNamespaceLink = ({ + baseprefix, + cluster, + apiGroupVersion, + pluralName, + namespace, +}: { + baseprefix?: string + cluster: string + pluralName: string + apiGroupVersion: string + namespace?: string +}): string | undefined => { + if (!namespace) { + return undefined + } + + return `${baseprefix}/${cluster}/factory/${BASE_NAMESPACE_FACTORY_KEY}/${apiGroupVersion}/${pluralName}/${namespace}` +} + +export const formatEventSummary = (event: TEventsV1Event): string | undefined => { + if (!event.deprecatedCount || !event.deprecatedFirstTimestamp) { + return undefined + } + + const now = new Date() + const first = new Date(event.deprecatedFirstTimestamp) + const days = Math.floor((now.getTime() - first.getTime()) / (1000 * 60 * 60 * 24)) + + return `${event.deprecatedCount} times ${days === 0 ? 'today' : `in the last ${days} days`}` +} diff --git a/src/components/organisms/Events/types.ts b/src/components/organisms/Events/types.ts index e751516..15008de 100644 --- a/src/components/organisms/Events/types.ts +++ b/src/components/organisms/Events/types.ts @@ -21,9 +21,11 @@ export type TEventsV1Event = { reportingController?: string reportingInstance?: string deprecatedCount?: number + deprecatedFirstTimestamp?: Date action?: string eventTime?: string regarding?: { + apiVersion?: string kind?: string name?: string namespace?: string diff --git a/src/constants/customizationApiGroupAndVersion.ts b/src/constants/customizationApiGroupAndVersion.ts index b261e26..45d6198 100644 --- a/src/constants/customizationApiGroupAndVersion.ts +++ b/src/constants/customizationApiGroupAndVersion.ts @@ -77,3 +77,19 @@ export const BASE_REMOVE_BACKLINK_TEXT = import.meta.env.DEV ? window._env_.REMOVE_BACKLINK_TEXT === 'true' || import.meta.env.VITE_REMOVE_BACKLINK_TEXT?.toString().toLowerCase() === 'true' : window._env_.REMOVE_BACKLINK_TEXT === 'true' + +export const BASE_FACTORY_NAMESPACED_API_KEY = import.meta.env.DEV + ? window._env_.BASE_FACTORY_NAMESPACED_API_KEY || import.meta.env.VITE_BASE_FACTORY_NAMESPACED_API_KEY + : window._env_.BASE_FACTORY_NAMESPACED_API_KEY +export const BASE_FACTORY_CLUSTERSCOPED_API_KEY = import.meta.env.DEV + ? window._env_.BASE_FACTORY_CLUSTERSCOPED_API_KEY || import.meta.env.VITE_BASE_FACTORY_CLUSTERSCOPED_API_KEY + : window._env_.BASE_FACTORY_CLUSTERSCOPED_API_KEY +export const BASE_FACTORY_NAMESPACED_BUILTIN_KEY = import.meta.env.DEV + ? window._env_.BASE_FACTORY_NAMESPACED_BUILTIN_KEY || import.meta.env.VITE_BASE_FACTORY_NAMESPACED_BUILTIN_KEY + : window._env_.BASE_FACTORY_NAMESPACED_BUILTIN_KEY +export const BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY = import.meta.env.DEV + ? window._env_.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY || import.meta.env.VITE_BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY + : window._env_.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY +export const BASE_NAMESPACE_FACTORY_KEY = import.meta.env.DEV + ? window._env_.BASE_NAMESPACE_FACTORY_KEY || import.meta.env.VITE_BASE_NAMESPACE_FACTORY_KEY + : window._env_.BASE_NAMESPACE_FACTORY_KEY diff --git a/src/pages/EventsPage/EventsPage.tsx b/src/pages/EventsPage/EventsPage.tsx index f6584f2..2264b65 100644 --- a/src/pages/EventsPage/EventsPage.tsx +++ b/src/pages/EventsPage/EventsPage.tsx @@ -36,7 +36,13 @@ export const EventsPage: FC = () => { - + {clusterName && ( + + )} ) }