Skip to content

Commit 9301269

Browse files
feat: add PEM visualization for certificates (#11)
Signed-off-by: Carlos Feria <[email protected]>
1 parent cdaa5e6 commit 9301269

File tree

11 files changed

+415
-20
lines changed

11 files changed

+415
-20
lines changed

client/openapi/console.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,13 +777,17 @@ components:
777777
expiration:
778778
type: string
779779
description: Expiration date and time of the certificate (notAfter).
780+
pem:
781+
type: string
782+
description: Certificate in PEM-encoded format.
780783
required:
781784
- subject
782785
- issuer
783786
- type
784787
- status
785788
- target
786789
- expiration
790+
- pem
787791
CertificateInfoList:
788792
type: object
789793
properties:

client/src/app/Constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ import ENV from "./env";
33
export const isAuthRequired = ENV.AUTH_REQUIRED !== "false";
44

55
export const RENDER_DATE_FORMAT = "MMM DD, YYYY";
6+
7+
/**
8+
* The name of the client generated id field inserted in a object marked with mixin type
9+
* `WithUiId`.
10+
*/
11+
export const UI_UNIQUE_ID = "_ui_unique_id";
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import type { KeyWithValueType } from "@app/utils/type-utils";
2+
import type { IExpansionState } from "./useExpansionState";
3+
4+
/**
5+
* Args for getExpansionDerivedState
6+
*/
7+
export interface IExpansionDerivedStateArgs<TItem, TColumnKey extends string> {
8+
/**
9+
* The string key/name of a property on the API data item objects that can be used as a unique identifier (string or number)
10+
*/
11+
idProperty: KeyWithValueType<TItem, string | number>;
12+
/**
13+
* The "source of truth" state for the expansion feature (returned by useExpansionState)
14+
*/
15+
expansionState: IExpansionState<TColumnKey>;
16+
}
17+
18+
/**
19+
* Derived state for the expansion feature
20+
* - "Derived state" here refers to values and convenience functions derived at render time based on the "source of truth" state.
21+
* - "source of truth" (persisted) state and "derived state" are kept separate to prevent out-of-sync duplicated state.
22+
*/
23+
export interface IExpansionDerivedState<TItem, TColumnKey extends string> {
24+
/**
25+
* Returns whether a cell or a row is expanded
26+
* - If called with a columnKey, returns whether that column's cell in this row is expanded (for compound-expand)
27+
* - If called without a columnKey, returns whether the entire row or any cell in it is expanded (for both single-expand and compound-expand)
28+
*/
29+
isCellExpanded: (item: TItem, columnKey?: TColumnKey) => boolean;
30+
/**
31+
* Set a cell or a row as expanded or collapsed
32+
* - If called with a columnKey, sets that column's cell in this row expanded or collapsed (for compound-expand)
33+
* - If called without a columnKey, sets the entire row as expanded or collapsed (for single-expand)
34+
*/
35+
setCellExpanded: (args: { item: TItem; isExpanding?: boolean; columnKey?: TColumnKey }) => void;
36+
}
37+
38+
/**
39+
* Given the "source of truth" state for the expansion feature and additional arguments, returns "derived state" values and convenience functions.
40+
* - "source of truth" (persisted) state and "derived state" are kept separate to prevent out-of-sync duplicated state.
41+
*/
42+
export const getExpansionDerivedState = <TItem, TColumnKey extends string>({
43+
idProperty,
44+
expansionState: { expandedCells, setExpandedCells },
45+
}: IExpansionDerivedStateArgs<TItem, TColumnKey>): IExpansionDerivedState<TItem, TColumnKey> => {
46+
const isCellExpanded = (item: TItem, columnKey?: TColumnKey) => {
47+
return columnKey
48+
? expandedCells[String(item[idProperty])] === columnKey
49+
: !!expandedCells[String(item[idProperty])];
50+
};
51+
52+
const setCellExpanded = ({
53+
item,
54+
isExpanding = true,
55+
columnKey,
56+
}: {
57+
item: TItem;
58+
isExpanding?: boolean;
59+
columnKey?: TColumnKey;
60+
}) => {
61+
const newExpandedCells = { ...expandedCells };
62+
if (isExpanding) {
63+
newExpandedCells[String(item[idProperty])] = columnKey ?? true;
64+
} else {
65+
delete newExpandedCells[String(item[idProperty])];
66+
}
67+
setExpandedCells(newExpandedCells);
68+
};
69+
70+
return { isCellExpanded, setCellExpanded };
71+
};
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import type { TdProps } from "@patternfly/react-table";
2+
3+
import type { KeyWithValueType } from "@app/utils/type-utils";
4+
import { getExpansionDerivedState } from "./getExpansionDerivedState";
5+
import type { IExpansionState } from "./useExpansionState";
6+
7+
/**
8+
* Args for useExpansionPropHelpers
9+
*/
10+
export interface IExpansionPropHelpersExternalArgs<TItem, TColumnKey extends string> {
11+
/**
12+
* The string key/name of a property on the API data item objects that can be used as a unique identifier (string or number)
13+
*/
14+
idProperty: KeyWithValueType<TItem, string | number>;
15+
/**
16+
* The "source of truth" state for the expansion feature (returned by useExpansionState)
17+
*/
18+
expansionState: IExpansionState<TColumnKey>;
19+
}
20+
21+
/**
22+
* Additional args for useExpansionPropHelpers that come from logic inside useTableControlProps
23+
* @see useTableControlProps
24+
*/
25+
export interface IExpansionPropHelpersInternalArgs<TColumnKey extends string> {
26+
/**
27+
* The keys of the `columnNames` object (unique keys identifying each column).
28+
*/
29+
columnKeys: TColumnKey[];
30+
}
31+
32+
/**
33+
* Returns derived state and prop helpers for the expansion feature based on given "source of truth" state.
34+
* - Used internally by useTableControlProps
35+
* - "Derived state" here refers to values and convenience functions derived at render time.
36+
* - "source of truth" (persisted) state and "derived state" are kept separate to prevent out-of-sync duplicated state.
37+
*/
38+
export const useExpansionPropHelpers = <TItem, TColumnKey extends string>(
39+
args: IExpansionPropHelpersExternalArgs<TItem, TColumnKey> & IExpansionPropHelpersInternalArgs<TColumnKey>
40+
) => {
41+
const { idProperty, columnKeys } = args;
42+
43+
const expansionDerivedState = getExpansionDerivedState(args);
44+
const { isCellExpanded, setCellExpanded } = expansionDerivedState;
45+
46+
/**
47+
* Returns props for the Td to the left of the data cells which contains each row's expansion toggle button (only for single-expand).
48+
*/
49+
const getSingleExpandButtonTdProps = ({
50+
item,
51+
rowIndex,
52+
}: {
53+
item: TItem;
54+
rowIndex: number;
55+
}): Omit<TdProps, "ref"> => ({
56+
expand: {
57+
rowIndex,
58+
isExpanded: isCellExpanded(item),
59+
onToggle: () =>
60+
setCellExpanded({
61+
item,
62+
isExpanding: !isCellExpanded(item),
63+
}),
64+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
65+
expandId: `expandable-row-${item[idProperty]}`,
66+
},
67+
});
68+
69+
/**
70+
* Returns props for the Td which is a data cell in an expandable column and functions as an expand toggle (only for compound-expand)
71+
*/
72+
const getCompoundExpandTdProps = ({
73+
columnKey,
74+
item,
75+
rowIndex,
76+
}: {
77+
columnKey: TColumnKey;
78+
item: TItem;
79+
rowIndex: number;
80+
}): Omit<TdProps, "ref"> => ({
81+
compoundExpand: {
82+
isExpanded: isCellExpanded(item, columnKey),
83+
onToggle: () =>
84+
setCellExpanded({
85+
item,
86+
isExpanding: !isCellExpanded(item, columnKey),
87+
columnKey,
88+
}),
89+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
90+
expandId: `compound-expand-${item[idProperty]}-${columnKey}`,
91+
rowIndex,
92+
columnIndex: columnKeys.indexOf(columnKey),
93+
},
94+
});
95+
96+
return {
97+
expansionDerivedState,
98+
getSingleExpandButtonTdProps,
99+
getCompoundExpandTdProps,
100+
};
101+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from "react";
2+
3+
/**
4+
* A map of item ids (strings resolved from `item[idProperty]`) to either:
5+
* - a `columnKey` if that item's row has a compound-expanded cell
6+
* - or a boolean:
7+
* - true if the row is expanded (for single-expand)
8+
* - false if the row and all its cells are collapsed (for both single-expand and compound-expand).
9+
*/
10+
export type TExpandedCells<TColumnKey extends string> = Record<string, TColumnKey | boolean>;
11+
12+
/**
13+
* The "source of truth" state for the expansion feature.
14+
*/
15+
export interface IExpansionState<TColumnKey extends string> {
16+
/**
17+
* A map of item ids to a `columnKey` or boolean for the current expansion state of that cell/row
18+
* @see TExpandedCells
19+
*/
20+
expandedCells: TExpandedCells<TColumnKey>;
21+
/**
22+
* Updates the `expandedCells` map (replacing the entire map).
23+
* - See `expansionDerivedState` for helper functions to expand/collapse individual cells/rows.
24+
* @see IExpansionDerivedState
25+
*/
26+
setExpandedCells: (newExpandedCells: TExpandedCells<TColumnKey>) => void;
27+
}
28+
29+
/**
30+
* Args for useExpansionState
31+
*/
32+
export interface IExpansionStateArgs {
33+
/**
34+
* Whether to use single-expand or compound-expand behavior
35+
* - "single" for the entire row to be expandable with one toggle.
36+
* - "compound" for multiple cells in a row to be expandable with individual toggles.
37+
*/
38+
expandableVariant: "single" | "compound";
39+
}
40+
41+
/**
42+
* Provides the "source of truth" state for the expansion feature.
43+
* - Used internally by useTableControlState
44+
* - Takes args defined above as well as optional args for persisting state to a configurable storage target.
45+
*/
46+
export const useExpansionState = <TColumnKey extends string>(): IExpansionState<TColumnKey> => {
47+
const [expandedCells, setExpandedCells] = React.useState<TExpandedCells<TColumnKey>>({});
48+
return { expandedCells, setExpandedCells };
49+
};

client/src/app/hooks/TableControls/usePFTable.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1+
import type { KeyWithValueType } from "@app/utils/type-utils";
2+
3+
import { useExpansionPropHelpers } from "./expansion/useExpansionPropHelpers";
4+
import { useExpansionState } from "./expansion/useExpansionState";
15
import { usePaginationPropHelpers } from "./pagination/usePaginationPropHelpers";
26
import { useSortPropHelpers } from "./sorting/useSortPropHelpers";
37
import { useTable, type ITableArgs } from "./useTable";
48

5-
export const usePFTable = <TItem, TSortableColumnKey extends string, TFilterCategoryKey extends string>(
6-
args: ITableArgs<TItem, TSortableColumnKey, TFilterCategoryKey>
9+
export interface IPFTableArgs<TItem, TColumnKey extends string> {
10+
columns: TColumnKey[];
11+
idProperty: KeyWithValueType<TItem, string | number>;
12+
}
13+
14+
export const usePFTable = <
15+
TItem,
16+
TColumnKey extends string,
17+
TSortableColumnKey extends TColumnKey,
18+
TFilterCategoryKey extends string,
19+
>(
20+
args: ITableArgs<TItem, TSortableColumnKey, TFilterCategoryKey> & IPFTableArgs<TItem, TColumnKey>
721
) => {
822
const tableState = useTable(args);
923

24+
const expansionState = useExpansionState<TColumnKey>();
25+
1026
const { getSortThProps } = useSortPropHelpers({
1127
sortableColumns: args.sorting?.sortableColumns,
1228
sortState: tableState.sortingState,
@@ -19,12 +35,24 @@ export const usePFTable = <TItem, TSortableColumnKey extends string, TFilterCate
1935
isPaginationEnabled: true,
2036
});
2137

38+
const { expansionDerivedState, getCompoundExpandTdProps, getSingleExpandButtonTdProps } = useExpansionPropHelpers<
39+
TItem,
40+
TColumnKey
41+
>({
42+
idProperty: args.idProperty,
43+
expansionState,
44+
columnKeys: args.columns,
45+
});
46+
2247
return {
2348
...tableState,
2449
propHelpers: {
2550
getSortThProps,
2651
paginationProps,
2752
paginationToolbarItemProps,
53+
getSingleExpandButtonTdProps,
54+
getCompoundExpandTdProps,
2855
},
56+
expansionDerivedState,
2957
};
3058
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useMemo } from "react";
2+
3+
import { UI_UNIQUE_ID } from "@app/Constants";
4+
5+
export type WithUiId<T> = T & { _ui_unique_id: string };
6+
7+
/**
8+
* Make a shallow copy of `data` and insert a new `UI_UNIQUE_ID` field in each element
9+
* with the output of the `generator` function. This hook allows generating the needed
10+
* UI id field for any object that does not already have a unique id field so the object
11+
* can be used with our table selection handlers.
12+
*
13+
* @returns A shallow copy of `T` with an added `UI_UNIQUE_ID` field.
14+
*/
15+
export const useWithUiId = <T>(
16+
/** Source data to modify. */
17+
data: T[] | undefined,
18+
/** Generate the unique id for a specific `T`. */
19+
generator: (item: T) => string
20+
): WithUiId<T>[] => {
21+
const result = useMemo(() => {
22+
if (!data || data.length === 0) {
23+
return [];
24+
}
25+
26+
const dataWithUiId: WithUiId<T>[] = data.map((item) => ({
27+
...item,
28+
[UI_UNIQUE_ID]: generator(item),
29+
}));
30+
31+
return dataWithUiId;
32+
}, [data, generator]);
33+
34+
return result;
35+
};

client/src/app/hooks/usePFToolbarTable.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import React from "react";
22

33
import { useFilterPropHelpers } from "./TableControls/filtering/useFilterPropHelpers";
4-
import { usePFTable } from "./TableControls/usePFTable";
4+
import { usePFTable, type IPFTableArgs } from "./TableControls/usePFTable";
55
import { type ITableArgs } from "./TableControls/useTable";
66
import { useFilterControlPropHelpers } from "./ToolbarControls/useFilterControlPropHelpers";
77

8-
export const usePFToolbarTable = <TItem, TSortableColumnKey extends string, TFilterCategoryKey extends string>(
9-
args: ITableArgs<TItem, TSortableColumnKey, TFilterCategoryKey> & {
10-
toolbar: {
11-
categoryTitles: Record<TFilterCategoryKey, string>;
12-
};
13-
}
8+
export interface IPFToolbarTableArgs<TFilterCategoryKey extends string> {
9+
toolbar: {
10+
categoryTitles: Record<TFilterCategoryKey, string>;
11+
};
12+
}
13+
14+
export const usePFToolbarTable = <
15+
TItem,
16+
TColumnKey extends string,
17+
TSortableColumnKey extends TColumnKey,
18+
TFilterCategoryKey extends string,
19+
>(
20+
args: ITableArgs<TItem, TSortableColumnKey, TFilterCategoryKey> &
21+
IPFTableArgs<TItem, TColumnKey> &
22+
IPFToolbarTableArgs<TFilterCategoryKey>
1423
) => {
1524
const [currentFilterCategoryKey, setCurrentFilterCategoryKey] = React.useState(
1625
args.filtering?.filterCategories?.[0].categoryKey

0 commit comments

Comments
 (0)