Merge pull request #318 from ylei-tsubame/manage-ups
Add UPS management tab
This commit is contained in:
commit
3701517b41
@ -134,10 +134,10 @@ const dbWrite = (query: string, options?: SpawnSyncOptions) => {
|
|||||||
return execAnvilAccessModule(['--query', query, '--mode', 'write'], options);
|
return execAnvilAccessModule(['--query', query, '--mode', 'write'], options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAnvilData = (
|
const getAnvilData = <HashType>(
|
||||||
dataStruct: AnvilDataStruct,
|
dataStruct: AnvilDataStruct,
|
||||||
{ predata, ...spawnSyncOptions }: GetAnvilDataOptions = {},
|
{ predata, ...spawnSyncOptions }: GetAnvilDataOptions = {},
|
||||||
) =>
|
): HashType =>
|
||||||
execAnvilAccessModule(
|
execAnvilAccessModule(
|
||||||
[
|
[
|
||||||
'--predata',
|
'--predata',
|
||||||
|
@ -6,7 +6,7 @@ export const getFenceTemplate: RequestHandler = (request, response) => {
|
|||||||
let rawFenceData;
|
let rawFenceData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
({ fence_data: rawFenceData } = getAnvilData(
|
({ fence_data: rawFenceData } = getAnvilData<{ fence_data: unknown }>(
|
||||||
{ fence_data: true },
|
{ fence_data: true },
|
||||||
{ predata: [['Striker->get_fence_data']] },
|
{ predata: [['Striker->get_fence_data']] },
|
||||||
));
|
));
|
||||||
|
@ -154,7 +154,7 @@ export const createHostConnection: RequestHandler<
|
|||||||
database: {
|
database: {
|
||||||
[localHostUUID]: { port: rawLocalDBPort },
|
[localHostUUID]: { port: rawLocalDBPort },
|
||||||
},
|
},
|
||||||
} = getAnvilData({ database: true }) as { database: DatabaseHash };
|
} = getAnvilData<{ database: AnvilDataDatabaseHash }>({ database: true });
|
||||||
|
|
||||||
localDBPort = sanitize(rawLocalDBPort, 'number');
|
localDBPort = sanitize(rawLocalDBPort, 'number');
|
||||||
} catch (subError) {
|
} catch (subError) {
|
||||||
|
@ -7,7 +7,7 @@ import { stdout } from '../../shell';
|
|||||||
|
|
||||||
const buildHostConnections = (
|
const buildHostConnections = (
|
||||||
fromHostUUID: string,
|
fromHostUUID: string,
|
||||||
databaseHash: DatabaseHash,
|
databaseHash: AnvilDataDatabaseHash,
|
||||||
{
|
{
|
||||||
defaultPort = 5432,
|
defaultPort = 5432,
|
||||||
defaultUser = 'admin',
|
defaultUser = 'admin',
|
||||||
@ -42,7 +42,7 @@ export const getHostConnection = buildGetRequestHandler(
|
|||||||
(request, buildQueryOptions) => {
|
(request, buildQueryOptions) => {
|
||||||
const { hostUUIDs: rawHostUUIDs } = request.query;
|
const { hostUUIDs: rawHostUUIDs } = request.query;
|
||||||
|
|
||||||
let rawDatabaseData: DatabaseHash;
|
let rawDatabaseData: AnvilDataDatabaseHash;
|
||||||
|
|
||||||
const hostUUIDField = 'ip_add.ip_address_host_uuid';
|
const hostUUIDField = 'ip_add.ip_address_host_uuid';
|
||||||
const localHostUUID: string = getLocalHostUUID();
|
const localHostUUID: string = getLocalHostUUID();
|
||||||
@ -59,7 +59,9 @@ export const getHostConnection = buildGetRequestHandler(
|
|||||||
stdout(`condHostUUIDs=[${condHostUUIDs}]`);
|
stdout(`condHostUUIDs=[${condHostUUIDs}]`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
({ database: rawDatabaseData } = getAnvilData({ database: true }));
|
({ database: rawDatabaseData } = getAnvilData<{ database: AnvilDataDatabaseHash }>(
|
||||||
|
{ database: true },
|
||||||
|
));
|
||||||
} catch (subError) {
|
} catch (subError) {
|
||||||
throw new Error(`Failed to get anvil data; CAUSE: ${subError}`);
|
throw new Error(`Failed to get anvil data; CAUSE: ${subError}`);
|
||||||
}
|
}
|
||||||
|
37
striker-ui-api/src/lib/request_handlers/ups/getUPS.ts
Normal file
37
striker-ui-api/src/lib/request_handlers/ups/getUPS.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { RequestHandler } from 'express';
|
||||||
|
|
||||||
|
import buildGetRequestHandler from '../buildGetRequestHandler';
|
||||||
|
import { buildQueryResultReducer } from '../../buildQueryResultModifier';
|
||||||
|
|
||||||
|
export const getUPS: RequestHandler = buildGetRequestHandler(
|
||||||
|
(request, buildQueryOptions) => {
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
ups_uuid,
|
||||||
|
ups_name,
|
||||||
|
ups_agent,
|
||||||
|
ups_ip_address
|
||||||
|
FROM upses
|
||||||
|
ORDER BY ups_name ASC;`;
|
||||||
|
const afterQueryReturn: QueryResultModifierFunction | undefined =
|
||||||
|
buildQueryResultReducer<{ [upsUUID: string]: UPSOverview }>(
|
||||||
|
(previous, [upsUUID, upsName, upsAgent, upsIPAddress]) => {
|
||||||
|
previous[upsUUID] = {
|
||||||
|
upsAgent,
|
||||||
|
upsIPAddress,
|
||||||
|
upsName,
|
||||||
|
upsUUID,
|
||||||
|
};
|
||||||
|
|
||||||
|
return previous;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (buildQueryOptions) {
|
||||||
|
buildQueryOptions.afterQueryReturn = afterQueryReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
},
|
||||||
|
);
|
@ -0,0 +1,52 @@
|
|||||||
|
import { RequestHandler } from 'express';
|
||||||
|
|
||||||
|
import { getAnvilData } from '../../accessModule';
|
||||||
|
import { stderr } from '../../shell';
|
||||||
|
|
||||||
|
export const getUPSTemplate: RequestHandler = (request, response) => {
|
||||||
|
let rawUPSData: AnvilDataUPSHash;
|
||||||
|
|
||||||
|
try {
|
||||||
|
({ ups_data: rawUPSData } = getAnvilData<{ ups_data: AnvilDataUPSHash }>(
|
||||||
|
{ ups_data: true },
|
||||||
|
{ predata: [['Striker->get_ups_data']] },
|
||||||
|
));
|
||||||
|
} catch (subError) {
|
||||||
|
stderr(`Failed to get ups template; CAUSE: ${subError}`);
|
||||||
|
|
||||||
|
response.status(500).send();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsData: AnvilDataUPSHash = Object.entries(
|
||||||
|
rawUPSData,
|
||||||
|
).reduce<UPSTemplate>((previous, [upsTypeId, value]) => {
|
||||||
|
const { brand, description: rawDescription, ...rest } = value;
|
||||||
|
|
||||||
|
const matched = rawDescription.match(
|
||||||
|
/^(.+)\s+[-]\s+[<][^>]+href=[\\"]+([^\s]+)[\\"]+.+[>]([^<]+)[<]/,
|
||||||
|
);
|
||||||
|
const result: UPSTemplate[string] = {
|
||||||
|
...rest,
|
||||||
|
brand,
|
||||||
|
description: rawDescription,
|
||||||
|
links: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
const [, description, linkHref, linkLabel] = matched;
|
||||||
|
|
||||||
|
result.description = description;
|
||||||
|
result.links[0] = { linkHref, linkLabel };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/apc/i.test(brand)) {
|
||||||
|
previous[upsTypeId] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return previous;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
response.status(200).send(upsData);
|
||||||
|
};
|
2
striker-ui-api/src/lib/request_handlers/ups/index.ts
Normal file
2
striker-ui-api/src/lib/request_handlers/ups/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './getUPS';
|
||||||
|
export * from './getUPSTemplate';
|
@ -10,6 +10,7 @@ import jobRouter from './job';
|
|||||||
import networkInterfaceRouter from './network-interface';
|
import networkInterfaceRouter from './network-interface';
|
||||||
import serverRouter from './server';
|
import serverRouter from './server';
|
||||||
import sshKeyRouter from './ssh-key';
|
import sshKeyRouter from './ssh-key';
|
||||||
|
import upsRouter from './ups';
|
||||||
import userRouter from './user';
|
import userRouter from './user';
|
||||||
|
|
||||||
const routes: Readonly<Record<string, Router>> = {
|
const routes: Readonly<Record<string, Router>> = {
|
||||||
@ -23,6 +24,7 @@ const routes: Readonly<Record<string, Router>> = {
|
|||||||
'network-interface': networkInterfaceRouter,
|
'network-interface': networkInterfaceRouter,
|
||||||
server: serverRouter,
|
server: serverRouter,
|
||||||
'ssh-key': sshKeyRouter,
|
'ssh-key': sshKeyRouter,
|
||||||
|
ups: upsRouter,
|
||||||
user: userRouter,
|
user: userRouter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
9
striker-ui-api/src/routes/ups.ts
Normal file
9
striker-ui-api/src/routes/ups.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import express from 'express';
|
||||||
|
|
||||||
|
import { getUPS, getUPSTemplate } from '../lib/request_handlers/ups';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get('/', getUPS).get('/template', getUPSTemplate);
|
||||||
|
|
||||||
|
export default router;
|
17
striker-ui-api/src/types/APIUPS.d.ts
vendored
Normal file
17
striker-ui-api/src/types/APIUPS.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
type UPSOverview = {
|
||||||
|
upsAgent: string;
|
||||||
|
upsIPAddress: string;
|
||||||
|
upsName: string;
|
||||||
|
upsUUID: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UPSTemplate = {
|
||||||
|
[upsName: string]: AnvilDataUPSHash[string] & {
|
||||||
|
links: {
|
||||||
|
[linkId: string]: {
|
||||||
|
linkHref: string;
|
||||||
|
linkLabel: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
@ -1,3 +0,0 @@
|
|||||||
interface AnvilDataStruct {
|
|
||||||
[key: string]: AnvilDataStruct | boolean;
|
|
||||||
}
|
|
10
striker-ui-api/src/types/DatabaseHash.d.ts
vendored
10
striker-ui-api/src/types/DatabaseHash.d.ts
vendored
@ -1,10 +0,0 @@
|
|||||||
type DatabaseHash = {
|
|
||||||
[hostUUID: string]: {
|
|
||||||
host: string;
|
|
||||||
name: string;
|
|
||||||
password: string;
|
|
||||||
ping: string;
|
|
||||||
port: string;
|
|
||||||
user: string;
|
|
||||||
};
|
|
||||||
};
|
|
26
striker-ui-api/src/types/GetAnvilDataFunction.d.ts
vendored
Normal file
26
striker-ui-api/src/types/GetAnvilDataFunction.d.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
interface AnvilDataStruct {
|
||||||
|
[key: string]: AnvilDataStruct | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnvilDataDatabaseHash = {
|
||||||
|
[hostUUID: string]: {
|
||||||
|
host: string;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
ping: string;
|
||||||
|
port: string;
|
||||||
|
user: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type AnvilDataUPSHash = {
|
||||||
|
[upsName: string]: {
|
||||||
|
agent: string;
|
||||||
|
brand: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetAnvilDataOptions = import('child_process').SpawnSyncOptions & {
|
||||||
|
predata?: Array<[string, ...unknown[]]>;
|
||||||
|
};
|
@ -1,3 +0,0 @@
|
|||||||
type GetAnvilDataOptions = import('child_process').SpawnSyncOptions & {
|
|
||||||
predata?: Array<[string, ...unknown[]]>;
|
|
||||||
};
|
|
@ -49,6 +49,7 @@ const ConfirmDialog = forwardRef<
|
|||||||
onProceedAppend,
|
onProceedAppend,
|
||||||
onSubmitAppend,
|
onSubmitAppend,
|
||||||
openInitially = false,
|
openInitially = false,
|
||||||
|
preActionArea,
|
||||||
proceedButtonProps = {},
|
proceedButtonProps = {},
|
||||||
proceedColour: proceedColourKey = 'blue',
|
proceedColour: proceedColourKey = 'blue',
|
||||||
scrollContent: isScrollContent = false,
|
scrollContent: isScrollContent = false,
|
||||||
@ -234,6 +235,7 @@ const ConfirmDialog = forwardRef<
|
|||||||
<Box {...restScrollBoxProps} sx={combinedScrollBoxSx}>
|
<Box {...restScrollBoxProps} sx={combinedScrollBoxSx}>
|
||||||
{contentElement}
|
{contentElement}
|
||||||
</Box>
|
</Box>
|
||||||
|
{preActionArea}
|
||||||
{actionAreaElement}
|
{actionAreaElement}
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</MUIDialog>
|
</MUIDialog>
|
||||||
|
34
striker-ui/components/FormDialog.tsx
Normal file
34
striker-ui/components/FormDialog.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { forwardRef, useMemo } from 'react';
|
||||||
|
|
||||||
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
|
|
||||||
|
const FormDialog = forwardRef<
|
||||||
|
ConfirmDialogForwardedRefContent,
|
||||||
|
ConfirmDialogProps
|
||||||
|
>((props, ref) => {
|
||||||
|
const { scrollContent: isScrollContent } = props;
|
||||||
|
|
||||||
|
const scrollBoxPaddingRight = useMemo(
|
||||||
|
() => (isScrollContent ? '.5em' : undefined),
|
||||||
|
[isScrollContent],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmDialog
|
||||||
|
dialogProps={{
|
||||||
|
PaperProps: { sx: { minWidth: { xs: '90%', md: '50em' } } },
|
||||||
|
}}
|
||||||
|
formContent
|
||||||
|
scrollBoxProps={{
|
||||||
|
paddingRight: scrollBoxPaddingRight,
|
||||||
|
paddingTop: '.3em',
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
FormDialog.displayName = 'FormDialog';
|
||||||
|
|
||||||
|
export default FormDialog;
|
@ -5,7 +5,6 @@ import {
|
|||||||
forwardRef,
|
forwardRef,
|
||||||
ReactElement,
|
ReactElement,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
@ -26,7 +25,7 @@ type InputWithRefOptionalPropsWithoutDefault<
|
|||||||
TypeName extends keyof MapToInputType,
|
TypeName extends keyof MapToInputType,
|
||||||
> = {
|
> = {
|
||||||
inputTestBatch?: InputTestBatch;
|
inputTestBatch?: InputTestBatch;
|
||||||
onFirstRender?: (args: { isRequired: boolean }) => void;
|
onFirstRender?: InputFirstRenderFunction;
|
||||||
valueKey?: CreateInputOnChangeHandlerOptions<TypeName>['valueKey'];
|
valueKey?: CreateInputOnChangeHandlerOptions<TypeName>['valueKey'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -167,11 +166,15 @@ const InputWithRef = forwardRef(
|
|||||||
[initOnFocus, inputTestBatch],
|
[initOnFocus, inputTestBatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
if (isFirstRender) {
|
||||||
if (isFirstRender) {
|
const isValid =
|
||||||
onFirstRender?.call(null, { isRequired });
|
testInput?.call(null, {
|
||||||
}
|
inputs: { [INPUT_TEST_ID]: { value: inputValue } },
|
||||||
}, [isFirstRender, isRequired, onFirstRender]);
|
isIgnoreOnCallbacks: true,
|
||||||
|
}) ?? false;
|
||||||
|
|
||||||
|
onFirstRender?.call(null, { isValid });
|
||||||
|
}
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
|
@ -176,7 +176,7 @@ const List = forwardRef(
|
|||||||
const listEmptyElement = useMemo(
|
const listEmptyElement = useMemo(
|
||||||
() =>
|
() =>
|
||||||
typeof listEmpty === 'string' ? (
|
typeof listEmpty === 'string' ? (
|
||||||
<BodyText>{listEmpty}</BodyText>
|
<BodyText align="center">{listEmpty}</BodyText>
|
||||||
) : (
|
) : (
|
||||||
listEmpty
|
listEmpty
|
||||||
),
|
),
|
||||||
|
156
striker-ui/components/ManageUps/AddUpsInputGroup.tsx
Normal file
156
striker-ui/components/ManageUps/AddUpsInputGroup.tsx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import { ReactElement, ReactNode, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { BLACK } from '../../lib/consts/DEFAULT_THEME';
|
||||||
|
|
||||||
|
import CommonUpsInputGroup, {
|
||||||
|
INPUT_ID_UPS_IP,
|
||||||
|
INPUT_ID_UPS_NAME,
|
||||||
|
} from './CommonUpsInputGroup';
|
||||||
|
import FlexBox from '../FlexBox';
|
||||||
|
import Link from '../Link';
|
||||||
|
import SelectWithLabel from '../SelectWithLabel';
|
||||||
|
import Spinner from '../Spinner';
|
||||||
|
import { BodyText } from '../Text';
|
||||||
|
import useIsFirstRender from '../../hooks/useIsFirstRender';
|
||||||
|
|
||||||
|
const INPUT_ID_UPS_TYPE = 'add-ups-select-ups-type-id';
|
||||||
|
|
||||||
|
const INPUT_LABEL_UPS_TYPE = 'UPS type';
|
||||||
|
|
||||||
|
const AddUpsInputGroup = <
|
||||||
|
M extends {
|
||||||
|
[K in
|
||||||
|
| typeof INPUT_ID_UPS_IP
|
||||||
|
| typeof INPUT_ID_UPS_NAME
|
||||||
|
| typeof INPUT_ID_UPS_TYPE]: string;
|
||||||
|
},
|
||||||
|
>({
|
||||||
|
formUtils,
|
||||||
|
loading: isExternalLoading,
|
||||||
|
previous = {},
|
||||||
|
upsTemplate,
|
||||||
|
}: AddUpsInputGroupProps<M>): ReactElement => {
|
||||||
|
const { buildInputFirstRenderFunction, setValidity } = formUtils;
|
||||||
|
|
||||||
|
const { upsTypeId: previousUpsTypeId = '' } = previous;
|
||||||
|
|
||||||
|
const isFirstRender = useIsFirstRender();
|
||||||
|
|
||||||
|
const [inputUpsTypeIdValue, setInputUpsTypeIdValue] =
|
||||||
|
useState<string>(previousUpsTypeId);
|
||||||
|
|
||||||
|
const upsTypeOptions = useMemo<SelectItem[]>(
|
||||||
|
() =>
|
||||||
|
upsTemplate
|
||||||
|
? Object.entries(upsTemplate).map<SelectItem>(
|
||||||
|
([
|
||||||
|
upsTypeId,
|
||||||
|
{
|
||||||
|
brand,
|
||||||
|
description,
|
||||||
|
links: { 0: link },
|
||||||
|
},
|
||||||
|
]) => {
|
||||||
|
let linkElement: ReactNode;
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
const { linkHref, linkLabel } = link;
|
||||||
|
|
||||||
|
linkElement = (
|
||||||
|
<Link
|
||||||
|
href={linkHref}
|
||||||
|
onClick={(event) => {
|
||||||
|
// Don't trigger the (parent) item selection event.
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
sx={{ display: 'inline-flex', color: BLACK }}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{linkLabel}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
displayValue: (
|
||||||
|
<FlexBox spacing={0}>
|
||||||
|
<BodyText inverted>{brand}</BodyText>
|
||||||
|
<BodyText inverted>
|
||||||
|
{description} ({linkElement})
|
||||||
|
</BodyText>
|
||||||
|
</FlexBox>
|
||||||
|
),
|
||||||
|
value: upsTypeId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: [],
|
||||||
|
[upsTemplate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const pickUpsTypeElement = useMemo(
|
||||||
|
() =>
|
||||||
|
upsTemplate && (
|
||||||
|
<SelectWithLabel
|
||||||
|
formControlProps={{ sx: { marginTop: '.3em' } }}
|
||||||
|
id={INPUT_ID_UPS_TYPE}
|
||||||
|
label={INPUT_LABEL_UPS_TYPE}
|
||||||
|
onChange={({ target: { value: rawNewValue } }) => {
|
||||||
|
const newValue = String(rawNewValue);
|
||||||
|
|
||||||
|
setValidity(INPUT_ID_UPS_TYPE, true);
|
||||||
|
setInputUpsTypeIdValue(newValue);
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
selectItems={upsTypeOptions}
|
||||||
|
selectProps={{
|
||||||
|
onClearIndicatorClick: () => {
|
||||||
|
setValidity(INPUT_ID_UPS_TYPE, false);
|
||||||
|
setInputUpsTypeIdValue('');
|
||||||
|
},
|
||||||
|
renderValue: (rawValue) => {
|
||||||
|
const upsTypeId = String(rawValue);
|
||||||
|
const { brand } = upsTemplate[upsTypeId];
|
||||||
|
|
||||||
|
return brand;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={inputUpsTypeIdValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[upsTemplate, upsTypeOptions, inputUpsTypeIdValue, setValidity],
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = useMemo<ReactElement>(
|
||||||
|
() =>
|
||||||
|
isExternalLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<FlexBox>
|
||||||
|
{pickUpsTypeElement}
|
||||||
|
{inputUpsTypeIdValue && (
|
||||||
|
<CommonUpsInputGroup formUtils={formUtils} previous={previous} />
|
||||||
|
)}
|
||||||
|
</FlexBox>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
formUtils,
|
||||||
|
inputUpsTypeIdValue,
|
||||||
|
isExternalLoading,
|
||||||
|
pickUpsTypeElement,
|
||||||
|
previous,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isFirstRender) {
|
||||||
|
buildInputFirstRenderFunction(INPUT_ID_UPS_TYPE)({
|
||||||
|
isValid: Boolean(inputUpsTypeIdValue),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { INPUT_ID_UPS_TYPE, INPUT_LABEL_UPS_TYPE };
|
||||||
|
|
||||||
|
export default AddUpsInputGroup;
|
104
striker-ui/components/ManageUps/CommonUpsInputGroup.tsx
Normal file
104
striker-ui/components/ManageUps/CommonUpsInputGroup.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
|
import Grid from '../Grid';
|
||||||
|
import InputWithRef from '../InputWithRef';
|
||||||
|
import OutlinedInputWithLabel from '../OutlinedInputWithLabel';
|
||||||
|
import {
|
||||||
|
buildIPAddressTestBatch,
|
||||||
|
buildPeacefulStringTestBatch,
|
||||||
|
} from '../../lib/test_input';
|
||||||
|
|
||||||
|
const INPUT_ID_UPS_IP = 'common-ups-input-ip-address';
|
||||||
|
const INPUT_ID_UPS_NAME = 'common-ups-input-host-name';
|
||||||
|
|
||||||
|
const INPUT_LABEL_UPS_IP = 'IP address';
|
||||||
|
const INPUT_LABEL_UPS_NAME = 'Host name';
|
||||||
|
|
||||||
|
const CommonUpsInputGroup = <
|
||||||
|
M extends {
|
||||||
|
[K in typeof INPUT_ID_UPS_IP | typeof INPUT_ID_UPS_NAME]: string;
|
||||||
|
},
|
||||||
|
>({
|
||||||
|
formUtils: {
|
||||||
|
buildFinishInputTestBatchFunction,
|
||||||
|
buildInputFirstRenderFunction,
|
||||||
|
msgSetters,
|
||||||
|
},
|
||||||
|
previous: { upsIPAddress: previousIpAddress, upsName: previousUpsName } = {},
|
||||||
|
}: CommonUpsInputGroupProps<M>): ReactElement => (
|
||||||
|
<Grid
|
||||||
|
columns={{ xs: 1, sm: 2 }}
|
||||||
|
layout={{
|
||||||
|
'common-ups-input-cell-host-name': {
|
||||||
|
children: (
|
||||||
|
<InputWithRef
|
||||||
|
input={
|
||||||
|
<OutlinedInputWithLabel
|
||||||
|
id={INPUT_ID_UPS_NAME}
|
||||||
|
label={INPUT_LABEL_UPS_NAME}
|
||||||
|
value={previousUpsName}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
inputTestBatch={buildPeacefulStringTestBatch(
|
||||||
|
INPUT_LABEL_UPS_NAME,
|
||||||
|
() => {
|
||||||
|
msgSetters[INPUT_ID_UPS_NAME]();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onFinishBatch:
|
||||||
|
buildFinishInputTestBatchFunction(INPUT_ID_UPS_NAME),
|
||||||
|
},
|
||||||
|
(message) => {
|
||||||
|
msgSetters[INPUT_ID_UPS_NAME]({
|
||||||
|
children: message,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onFirstRender={buildInputFirstRenderFunction(INPUT_ID_UPS_NAME)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'common-ups-input-cell-ip-address': {
|
||||||
|
children: (
|
||||||
|
<InputWithRef
|
||||||
|
input={
|
||||||
|
<OutlinedInputWithLabel
|
||||||
|
id={INPUT_ID_UPS_IP}
|
||||||
|
label={INPUT_LABEL_UPS_IP}
|
||||||
|
value={previousIpAddress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
inputTestBatch={buildIPAddressTestBatch(
|
||||||
|
INPUT_LABEL_UPS_IP,
|
||||||
|
() => {
|
||||||
|
msgSetters[INPUT_ID_UPS_IP]();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onFinishBatch:
|
||||||
|
buildFinishInputTestBatchFunction(INPUT_ID_UPS_IP),
|
||||||
|
},
|
||||||
|
(message) => {
|
||||||
|
msgSetters[INPUT_ID_UPS_IP]({
|
||||||
|
children: message,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onFirstRender={buildInputFirstRenderFunction(INPUT_ID_UPS_IP)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
spacing="1em"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export {
|
||||||
|
INPUT_ID_UPS_IP,
|
||||||
|
INPUT_ID_UPS_NAME,
|
||||||
|
INPUT_LABEL_UPS_IP,
|
||||||
|
INPUT_LABEL_UPS_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommonUpsInputGroup;
|
45
striker-ui/components/ManageUps/EditUpsInputGroup.tsx
Normal file
45
striker-ui/components/ManageUps/EditUpsInputGroup.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { ReactElement, useMemo } from 'react';
|
||||||
|
|
||||||
|
import AddUpsInputGroup, { INPUT_ID_UPS_TYPE } from './AddUpsInputGroup';
|
||||||
|
import { INPUT_ID_UPS_IP, INPUT_ID_UPS_NAME } from './CommonUpsInputGroup';
|
||||||
|
import Spinner from '../Spinner';
|
||||||
|
|
||||||
|
const INPUT_ID_UPS_UUID = 'edit-ups-input-ups-uuid';
|
||||||
|
|
||||||
|
const EditUpsInputGroup = <
|
||||||
|
M extends {
|
||||||
|
[K in
|
||||||
|
| typeof INPUT_ID_UPS_IP
|
||||||
|
| typeof INPUT_ID_UPS_NAME
|
||||||
|
| typeof INPUT_ID_UPS_TYPE]: string;
|
||||||
|
},
|
||||||
|
>({
|
||||||
|
formUtils,
|
||||||
|
loading: isExternalLoading,
|
||||||
|
previous,
|
||||||
|
upsTemplate,
|
||||||
|
upsUUID,
|
||||||
|
}: EditUpsInputGroupProps<M>): ReactElement => {
|
||||||
|
const content = useMemo<ReactElement>(
|
||||||
|
() =>
|
||||||
|
isExternalLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<AddUpsInputGroup
|
||||||
|
formUtils={formUtils}
|
||||||
|
previous={previous}
|
||||||
|
upsTemplate={upsTemplate}
|
||||||
|
/>
|
||||||
|
<input hidden id={INPUT_ID_UPS_UUID} readOnly value={upsUUID} />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[formUtils, isExternalLoading, previous, upsTemplate, upsUUID],
|
||||||
|
);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { INPUT_ID_UPS_UUID };
|
||||||
|
|
||||||
|
export default EditUpsInputGroup;
|
301
striker-ui/components/ManageUps/ManageUpsPanel.tsx
Normal file
301
striker-ui/components/ManageUps/ManageUpsPanel.tsx
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
import {
|
||||||
|
FC,
|
||||||
|
FormEventHandler,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import API_BASE_URL from '../../lib/consts/API_BASE_URL';
|
||||||
|
|
||||||
|
import AddUpsInputGroup, { INPUT_ID_UPS_TYPE } from './AddUpsInputGroup';
|
||||||
|
import api from '../../lib/api';
|
||||||
|
import { INPUT_ID_UPS_IP, INPUT_ID_UPS_NAME } from './CommonUpsInputGroup';
|
||||||
|
import ConfirmDialog from '../ConfirmDialog';
|
||||||
|
import EditUpsInputGroup, { INPUT_ID_UPS_UUID } from './EditUpsInputGroup';
|
||||||
|
import FlexBox from '../FlexBox';
|
||||||
|
import FormDialog from '../FormDialog';
|
||||||
|
import handleAPIError from '../../lib/handleAPIError';
|
||||||
|
import List from '../List';
|
||||||
|
import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup';
|
||||||
|
import { Panel, PanelHeader } from '../Panels';
|
||||||
|
import periodicFetch from '../../lib/fetchers/periodicFetch';
|
||||||
|
import Spinner from '../Spinner';
|
||||||
|
import { BodyText, HeaderText, InlineMonoText, MonoText } from '../Text';
|
||||||
|
import useConfirmDialogProps from '../../hooks/useConfirmDialogProps';
|
||||||
|
import useFormUtils from '../../hooks/useFormUtils';
|
||||||
|
import useIsFirstRender from '../../hooks/useIsFirstRender';
|
||||||
|
import useProtectedState from '../../hooks/useProtectedState';
|
||||||
|
|
||||||
|
type UpsFormData = {
|
||||||
|
upsAgent: string;
|
||||||
|
upsBrand: string;
|
||||||
|
upsIPAddress: string;
|
||||||
|
upsName: string;
|
||||||
|
upsTypeId: string;
|
||||||
|
upsUUID: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUpsFormData = (
|
||||||
|
upsTemplate: APIUpsTemplate,
|
||||||
|
...[{ target }]: Parameters<FormEventHandler<HTMLDivElement>>
|
||||||
|
): UpsFormData => {
|
||||||
|
const { elements } = target as HTMLFormElement;
|
||||||
|
|
||||||
|
const { value: upsName } = elements.namedItem(
|
||||||
|
INPUT_ID_UPS_NAME,
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const { value: upsIPAddress } = elements.namedItem(
|
||||||
|
INPUT_ID_UPS_IP,
|
||||||
|
) as HTMLInputElement;
|
||||||
|
|
||||||
|
const inputUpsTypeId = elements.namedItem(INPUT_ID_UPS_TYPE);
|
||||||
|
|
||||||
|
let upsAgent = '';
|
||||||
|
let upsBrand = '';
|
||||||
|
let upsTypeId = '';
|
||||||
|
|
||||||
|
if (inputUpsTypeId) {
|
||||||
|
({ value: upsTypeId } = inputUpsTypeId as HTMLInputElement);
|
||||||
|
({ agent: upsAgent, brand: upsBrand } = upsTemplate[upsTypeId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputUpsUUID = elements.namedItem(INPUT_ID_UPS_UUID);
|
||||||
|
|
||||||
|
let upsUUID = '';
|
||||||
|
|
||||||
|
if (inputUpsUUID) {
|
||||||
|
({ value: upsUUID } = inputUpsUUID as HTMLInputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { upsAgent, upsBrand, upsIPAddress, upsName, upsTypeId, upsUUID };
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildConfirmUpsFormData = ({
|
||||||
|
upsBrand,
|
||||||
|
upsIPAddress,
|
||||||
|
upsName,
|
||||||
|
upsUUID,
|
||||||
|
}: UpsFormData) => {
|
||||||
|
const listItems: Record<string, { label: string; value: string }> = {
|
||||||
|
'ups-brand': { label: 'Brand', value: upsBrand },
|
||||||
|
'ups-name': { label: 'Host name', value: upsName },
|
||||||
|
'ups-ip-address': { label: 'IP address', value: upsIPAddress },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
listItems={listItems}
|
||||||
|
listItemProps={{ sx: { padding: 0 } }}
|
||||||
|
renderListItem={(part, { label, value }) => (
|
||||||
|
<FlexBox fullWidth growFirst key={`confirm-ups-${upsUUID}-${part}`} row>
|
||||||
|
<BodyText>{label}</BodyText>
|
||||||
|
<MonoText>{value}</MonoText>
|
||||||
|
</FlexBox>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManageUpsPanel: FC = () => {
|
||||||
|
const isFirstRender = useIsFirstRender();
|
||||||
|
|
||||||
|
const confirmDialogRef = useRef<ConfirmDialogForwardedRefContent>({});
|
||||||
|
const formDialogRef = useRef<ConfirmDialogForwardedRefContent>({});
|
||||||
|
const messageGroupRef = useRef<MessageGroupForwardedRefContent>({});
|
||||||
|
|
||||||
|
const [confirmDialogProps, setConfirmDialogProps] = useConfirmDialogProps();
|
||||||
|
const [formDialogProps, setFormDialogProps] = useConfirmDialogProps();
|
||||||
|
const [isEditUpses, setIsEditUpses] = useState<boolean>(false);
|
||||||
|
const [isLoadingUpsTemplate, setIsLoadingUpsTemplate] =
|
||||||
|
useProtectedState<boolean>(true);
|
||||||
|
const [upsTemplate, setUpsTemplate] = useProtectedState<
|
||||||
|
APIUpsTemplate | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
const { data: upsOverviews, isLoading: isUpsOverviewLoading } =
|
||||||
|
periodicFetch<APIUpsOverview>(`${API_BASE_URL}/ups`, {
|
||||||
|
refreshInterval: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formUtils = useFormUtils(
|
||||||
|
[INPUT_ID_UPS_IP, INPUT_ID_UPS_NAME, INPUT_ID_UPS_TYPE],
|
||||||
|
messageGroupRef,
|
||||||
|
);
|
||||||
|
const { isFormInvalid } = formUtils;
|
||||||
|
|
||||||
|
const buildEditUpsFormDialogProps = useCallback<
|
||||||
|
(args: APIUpsOverview[string]) => ConfirmDialogProps
|
||||||
|
>(
|
||||||
|
({ upsAgent, upsIPAddress, upsName, upsUUID }) => {
|
||||||
|
// Determine the type of existing UPS based on its scan agent.
|
||||||
|
// TODO: should identity an existing UPS's type in the DB.
|
||||||
|
const upsTypeId: string =
|
||||||
|
Object.entries(upsTemplate ?? {}).find(
|
||||||
|
([, { agent }]) => upsAgent === agent,
|
||||||
|
)?.[0] ?? '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
actionProceedText: 'Update',
|
||||||
|
content: (
|
||||||
|
<EditUpsInputGroup
|
||||||
|
formUtils={formUtils}
|
||||||
|
previous={{
|
||||||
|
upsIPAddress,
|
||||||
|
upsName,
|
||||||
|
upsTypeId,
|
||||||
|
}}
|
||||||
|
upsTemplate={upsTemplate}
|
||||||
|
upsUUID={upsUUID}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
onSubmitAppend: (event) => {
|
||||||
|
if (!upsTemplate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editData = getUpsFormData(upsTemplate, event);
|
||||||
|
const { upsName: newUpsName } = editData;
|
||||||
|
|
||||||
|
setConfirmDialogProps({
|
||||||
|
actionProceedText: 'Update',
|
||||||
|
content: buildConfirmUpsFormData(editData),
|
||||||
|
titleText: (
|
||||||
|
<HeaderText>
|
||||||
|
Update{' '}
|
||||||
|
<InlineMonoText fontSize="inherit">{newUpsName}</InlineMonoText>{' '}
|
||||||
|
with the following data?
|
||||||
|
</HeaderText>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmDialogRef.current.setOpen?.call(null, true);
|
||||||
|
},
|
||||||
|
titleText: (
|
||||||
|
<HeaderText>
|
||||||
|
Update UPS{' '}
|
||||||
|
<InlineMonoText fontSize="inherit">{upsName}</InlineMonoText>
|
||||||
|
</HeaderText>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[formUtils, setConfirmDialogProps, upsTemplate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addUpsFormDialogProps = useMemo<ConfirmDialogProps>(
|
||||||
|
() => ({
|
||||||
|
actionProceedText: 'Add',
|
||||||
|
content: (
|
||||||
|
<AddUpsInputGroup formUtils={formUtils} upsTemplate={upsTemplate} />
|
||||||
|
),
|
||||||
|
onSubmitAppend: (event) => {
|
||||||
|
if (!upsTemplate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addData = getUpsFormData(upsTemplate, event);
|
||||||
|
const { upsBrand } = addData;
|
||||||
|
|
||||||
|
setConfirmDialogProps({
|
||||||
|
actionProceedText: 'Add',
|
||||||
|
content: buildConfirmUpsFormData(addData),
|
||||||
|
titleText: (
|
||||||
|
<HeaderText>
|
||||||
|
Add a{' '}
|
||||||
|
<InlineMonoText fontSize="inherit">{upsBrand}</InlineMonoText> UPS
|
||||||
|
with the following data?
|
||||||
|
</HeaderText>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmDialogRef.current.setOpen?.call(null, true);
|
||||||
|
},
|
||||||
|
titleText: 'Add a UPS',
|
||||||
|
}),
|
||||||
|
[formUtils, setConfirmDialogProps, upsTemplate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const listElement = useMemo(
|
||||||
|
() => (
|
||||||
|
<List
|
||||||
|
allowEdit
|
||||||
|
allowItemButton={isEditUpses}
|
||||||
|
edit={isEditUpses}
|
||||||
|
header
|
||||||
|
listEmpty="No Ups(es) registered."
|
||||||
|
listItems={upsOverviews}
|
||||||
|
onAdd={() => {
|
||||||
|
setFormDialogProps(addUpsFormDialogProps);
|
||||||
|
formDialogRef.current.setOpen?.call(null, true);
|
||||||
|
}}
|
||||||
|
onEdit={() => {
|
||||||
|
setIsEditUpses((previous) => !previous);
|
||||||
|
}}
|
||||||
|
onItemClick={(value) => {
|
||||||
|
setFormDialogProps(buildEditUpsFormDialogProps(value));
|
||||||
|
formDialogRef.current.setOpen?.call(null, true);
|
||||||
|
}}
|
||||||
|
renderListItem={(upsUUID, { upsAgent, upsIPAddress, upsName }) => (
|
||||||
|
<FlexBox fullWidth row>
|
||||||
|
<BodyText>{upsName}</BodyText>
|
||||||
|
<BodyText>agent="{upsAgent}"</BodyText>
|
||||||
|
<BodyText>ip="{upsIPAddress}"</BodyText>
|
||||||
|
</FlexBox>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[
|
||||||
|
addUpsFormDialogProps,
|
||||||
|
buildEditUpsFormDialogProps,
|
||||||
|
isEditUpses,
|
||||||
|
setFormDialogProps,
|
||||||
|
upsOverviews,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const panelContent = useMemo(
|
||||||
|
() =>
|
||||||
|
isLoadingUpsTemplate || isUpsOverviewLoading ? <Spinner /> : listElement,
|
||||||
|
[isLoadingUpsTemplate, isUpsOverviewLoading, listElement],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isFirstRender) {
|
||||||
|
api
|
||||||
|
.get<APIUpsTemplate>('/ups/template')
|
||||||
|
.then(({ data }) => {
|
||||||
|
setUpsTemplate(data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
handleAPIError(error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoadingUpsTemplate(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Panel>
|
||||||
|
<PanelHeader>
|
||||||
|
<HeaderText>Manage UPSes</HeaderText>
|
||||||
|
</PanelHeader>
|
||||||
|
{panelContent}
|
||||||
|
</Panel>
|
||||||
|
<FormDialog
|
||||||
|
{...formDialogProps}
|
||||||
|
ref={formDialogRef}
|
||||||
|
preActionArea={
|
||||||
|
<MessageGroup
|
||||||
|
count={1}
|
||||||
|
defaultMessageType="warning"
|
||||||
|
ref={messageGroupRef}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
proceedButtonProps={{ disabled: isFormInvalid }}
|
||||||
|
/>
|
||||||
|
<ConfirmDialog {...confirmDialogProps} ref={confirmDialogRef} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageUpsPanel;
|
3
striker-ui/components/ManageUps/index.tsx
Normal file
3
striker-ui/components/ManageUps/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import ManageUpsPanel from './ManageUpsPanel';
|
||||||
|
|
||||||
|
export default ManageUpsPanel;
|
@ -23,7 +23,10 @@ const SelectWithLabel: FC<SelectWithLabelProps> = ({
|
|||||||
isReadOnly = false,
|
isReadOnly = false,
|
||||||
messageBoxProps = {},
|
messageBoxProps = {},
|
||||||
name,
|
name,
|
||||||
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
|
onFocus,
|
||||||
|
required: isRequired,
|
||||||
selectProps: {
|
selectProps: {
|
||||||
multiple: selectMultiple,
|
multiple: selectMultiple,
|
||||||
sx: selectSx,
|
sx: selectSx,
|
||||||
@ -71,15 +74,24 @@ const SelectWithLabel: FC<SelectWithLabelProps> = ({
|
|||||||
[createCheckbox, disableItem, hideItem, id],
|
[createCheckbox, disableItem, hideItem, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputElement = useMemo(() => <OutlinedInput label={label} />, [label]);
|
const selectId = useMemo(() => `${id}-select-element`, [id]);
|
||||||
|
|
||||||
|
const inputElement = useMemo(
|
||||||
|
() => <OutlinedInput id={id} label={label} />,
|
||||||
|
[id, label],
|
||||||
|
);
|
||||||
const labelElement = useMemo(
|
const labelElement = useMemo(
|
||||||
() =>
|
() =>
|
||||||
label && (
|
label && (
|
||||||
<OutlinedInputLabel htmlFor={id} {...inputLabelProps}>
|
<OutlinedInputLabel
|
||||||
|
htmlFor={selectId}
|
||||||
|
isNotifyRequired={isRequired}
|
||||||
|
{...inputLabelProps}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</OutlinedInputLabel>
|
</OutlinedInputLabel>
|
||||||
),
|
),
|
||||||
[id, inputLabelProps, label],
|
[inputLabelProps, isRequired, label, selectId],
|
||||||
);
|
);
|
||||||
const menuItemElements = useMemo(
|
const menuItemElements = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -96,11 +108,13 @@ const SelectWithLabel: FC<SelectWithLabelProps> = ({
|
|||||||
<MUIFormControl fullWidth {...formControlProps}>
|
<MUIFormControl fullWidth {...formControlProps}>
|
||||||
{labelElement}
|
{labelElement}
|
||||||
<Select
|
<Select
|
||||||
id={id}
|
id={selectId}
|
||||||
input={inputElement}
|
input={inputElement}
|
||||||
multiple={selectMultiple}
|
multiple={selectMultiple}
|
||||||
name={name}
|
name={name}
|
||||||
|
onBlur={onBlur}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onFocus={onFocus}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
value={selectValue}
|
value={selectValue}
|
||||||
{...restSelectProps}
|
{...restSelectProps}
|
||||||
|
@ -61,8 +61,8 @@ const AddPeerDialog = forwardRef<
|
|||||||
|
|
||||||
const buildInputFirstRenderFunction = useCallback(
|
const buildInputFirstRenderFunction = useCallback(
|
||||||
(key: string) =>
|
(key: string) =>
|
||||||
({ isRequired }: { isRequired: boolean }) => {
|
({ isValid }: InputFirstRenderFunctionArgs) => {
|
||||||
setFormValidity(buildObjectStateSetterCallback(key, !isRequired));
|
setFormValidity(buildObjectStateSetterCallback(key, isValid));
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
19
striker-ui/hooks/useConfirmDialogProps.ts
Normal file
19
striker-ui/hooks/useConfirmDialogProps.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
|
||||||
|
const useConfirmDialogProps = ({
|
||||||
|
actionProceedText = '',
|
||||||
|
content = '',
|
||||||
|
titleText = '',
|
||||||
|
...restProps
|
||||||
|
}: Partial<ConfirmDialogProps> = {}): [
|
||||||
|
ConfirmDialogProps,
|
||||||
|
Dispatch<SetStateAction<ConfirmDialogProps>>,
|
||||||
|
] =>
|
||||||
|
useState<ConfirmDialogProps>({
|
||||||
|
actionProceedText,
|
||||||
|
content,
|
||||||
|
titleText,
|
||||||
|
...restProps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useConfirmDialogProps;
|
59
striker-ui/hooks/useFormUtils.ts
Normal file
59
striker-ui/hooks/useFormUtils.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { MutableRefObject, useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import buildMapToMessageSetter from '../lib/buildMapToMessageSetter';
|
||||||
|
import buildObjectStateSetterCallback from '../lib/buildObjectStateSetterCallback';
|
||||||
|
import { MessageGroupForwardedRefContent } from '../components/MessageGroup';
|
||||||
|
|
||||||
|
const useFormUtils = <
|
||||||
|
U extends string,
|
||||||
|
I extends InputIds<U>,
|
||||||
|
M extends MapToInputId<U, I>,
|
||||||
|
>(
|
||||||
|
ids: I,
|
||||||
|
messageGroupRef: MutableRefObject<MessageGroupForwardedRefContent>,
|
||||||
|
): FormUtils<M> => {
|
||||||
|
const [formValidity, setFormValidity] = useState<FormValidity<M>>({});
|
||||||
|
|
||||||
|
const setValidity = useCallback((key: keyof M, value: boolean) => {
|
||||||
|
setFormValidity(
|
||||||
|
buildObjectStateSetterCallback<FormValidity<M>>(key, value),
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const buildFinishInputTestBatchFunction = useCallback(
|
||||||
|
(key: keyof M) => (result: boolean) => {
|
||||||
|
setValidity(key, result);
|
||||||
|
},
|
||||||
|
[setValidity],
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildInputFirstRenderFunction = useCallback(
|
||||||
|
(key: keyof M) =>
|
||||||
|
({ isValid }: InputFirstRenderFunctionArgs) => {
|
||||||
|
setValidity(key, isValid);
|
||||||
|
},
|
||||||
|
[setValidity],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFormInvalid = useMemo(
|
||||||
|
() => Object.values(formValidity).some((isInputValid) => !isInputValid),
|
||||||
|
[formValidity],
|
||||||
|
);
|
||||||
|
|
||||||
|
const msgSetters = useMemo(
|
||||||
|
() => buildMapToMessageSetter<U, I, M>(ids, messageGroupRef),
|
||||||
|
[ids, messageGroupRef],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildFinishInputTestBatchFunction,
|
||||||
|
buildInputFirstRenderFunction,
|
||||||
|
formValidity,
|
||||||
|
isFormInvalid,
|
||||||
|
msgSetters,
|
||||||
|
setFormValidity,
|
||||||
|
setValidity,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFormUtils;
|
@ -2,25 +2,49 @@ import { MutableRefObject } from 'react';
|
|||||||
|
|
||||||
import { MessageGroupForwardedRefContent } from '../components/MessageGroup';
|
import { MessageGroupForwardedRefContent } from '../components/MessageGroup';
|
||||||
|
|
||||||
type BuildMapToMessageSetterReturnType<T extends MapToInputTestID> = {
|
const buildMessageSetter = <T extends MapToInputTestID>(
|
||||||
[MessageSetterID in keyof T]: MessageSetterFunction;
|
id: string,
|
||||||
|
messageGroupRef: MutableRefObject<MessageGroupForwardedRefContent>,
|
||||||
|
container?: MapToMessageSetter<T>,
|
||||||
|
key: string = id,
|
||||||
|
): MessageSetterFunction => {
|
||||||
|
const setter: MessageSetterFunction = (message?) => {
|
||||||
|
messageGroupRef.current.setMessage?.call(null, id, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
container[key as keyof T] = setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return setter;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildMapToMessageSetter = <T extends MapToInputTestID>(
|
const buildMapToMessageSetter = <
|
||||||
mapToID: T,
|
U extends string,
|
||||||
|
I extends InputIds<U>,
|
||||||
|
M extends MapToInputId<U, I>,
|
||||||
|
>(
|
||||||
|
ids: I,
|
||||||
messageGroupRef: MutableRefObject<MessageGroupForwardedRefContent>,
|
messageGroupRef: MutableRefObject<MessageGroupForwardedRefContent>,
|
||||||
): BuildMapToMessageSetterReturnType<T> =>
|
): MapToMessageSetter<M> => {
|
||||||
Object.entries(mapToID).reduce<BuildMapToMessageSetterReturnType<T>>(
|
let result: MapToMessageSetter<M> = {} as MapToMessageSetter<M>;
|
||||||
(previous, [key, id]) => {
|
|
||||||
const setter: MessageSetterFunction = (message?) => {
|
|
||||||
messageGroupRef.current.setMessage?.call(null, id, message);
|
|
||||||
};
|
|
||||||
|
|
||||||
previous[key as keyof T] = setter;
|
|
||||||
|
|
||||||
|
if (ids instanceof Array) {
|
||||||
|
result = ids.reduce<MapToMessageSetter<M>>((previous, id) => {
|
||||||
|
buildMessageSetter(id, messageGroupRef, previous);
|
||||||
return previous;
|
return previous;
|
||||||
},
|
}, result);
|
||||||
{} as BuildMapToMessageSetterReturnType<T>,
|
} else {
|
||||||
);
|
result = Object.entries(ids).reduce<MapToMessageSetter<M>>(
|
||||||
|
(previous, [key, id]) => {
|
||||||
|
buildMessageSetter(id, messageGroupRef, previous, key);
|
||||||
|
return previous;
|
||||||
|
},
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
export default buildMapToMessageSetter;
|
export default buildMapToMessageSetter;
|
||||||
|
@ -6,10 +6,11 @@ import { InlineMonoText } from '../../components/Text';
|
|||||||
const buildDomainTestBatch: BuildInputTestBatchFunction = (
|
const buildDomainTestBatch: BuildInputTestBatchFunction = (
|
||||||
inputName,
|
inputName,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
{ onFinishBatch, ...defaults } = {},
|
{ isRequired, onFinishBatch, ...defaults } = {},
|
||||||
onDomainTestFailure,
|
onDomainTestFailure,
|
||||||
) => ({
|
) => ({
|
||||||
defaults: { ...defaults, onSuccess },
|
defaults: { ...defaults, onSuccess },
|
||||||
|
isRequired,
|
||||||
onFinishBatch,
|
onFinishBatch,
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
|
@ -5,10 +5,11 @@ import testNotBlank from './testNotBlank';
|
|||||||
const buildIPAddressTestBatch: BuildInputTestBatchFunction = (
|
const buildIPAddressTestBatch: BuildInputTestBatchFunction = (
|
||||||
inputName,
|
inputName,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
{ onFinishBatch, ...defaults } = {},
|
{ isRequired, onFinishBatch, ...defaults } = {},
|
||||||
onIPv4TestFailure,
|
onIPv4TestFailure,
|
||||||
) => ({
|
) => ({
|
||||||
defaults: { ...defaults, onSuccess },
|
defaults: { ...defaults, onSuccess },
|
||||||
|
isRequired,
|
||||||
onFinishBatch,
|
onFinishBatch,
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,7 @@ import toNumber from '../toNumber';
|
|||||||
const buildNumberTestBatch: BuildInputTestBatchFunction = (
|
const buildNumberTestBatch: BuildInputTestBatchFunction = (
|
||||||
inputName,
|
inputName,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
{ onFinishBatch, ...defaults } = {},
|
{ isRequired, onFinishBatch, ...defaults } = {},
|
||||||
onIntTestFailure?,
|
onIntTestFailure?,
|
||||||
onFloatTestFailure?,
|
onFloatTestFailure?,
|
||||||
onRangeTestFailure?,
|
onRangeTestFailure?,
|
||||||
@ -48,6 +48,7 @@ const buildNumberTestBatch: BuildInputTestBatchFunction = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
defaults: { ...defaults, onSuccess },
|
defaults: { ...defaults, onSuccess },
|
||||||
|
isRequired,
|
||||||
onFinishBatch,
|
onFinishBatch,
|
||||||
tests,
|
tests,
|
||||||
};
|
};
|
||||||
|
@ -6,10 +6,11 @@ import { InlineMonoText } from '../../components/Text';
|
|||||||
const buildPeacefulStringTestBatch: BuildInputTestBatchFunction = (
|
const buildPeacefulStringTestBatch: BuildInputTestBatchFunction = (
|
||||||
inputName,
|
inputName,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
{ onFinishBatch, ...defaults } = {},
|
{ isRequired, onFinishBatch, ...defaults } = {},
|
||||||
onTestPeacefulStringFailureAppend,
|
onTestPeacefulStringFailureAppend,
|
||||||
) => ({
|
) => ({
|
||||||
defaults: { ...defaults, onSuccess },
|
defaults: { ...defaults, onSuccess },
|
||||||
|
isRequired,
|
||||||
onFinishBatch,
|
onFinishBatch,
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
|
@ -5,10 +5,11 @@ import testNotBlank from './testNotBlank';
|
|||||||
const buildUUIDTestBatch: BuildInputTestBatchFunction = (
|
const buildUUIDTestBatch: BuildInputTestBatchFunction = (
|
||||||
inputName,
|
inputName,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
{ onFinishBatch, ...defaults } = {},
|
{ isRequired, onFinishBatch, ...defaults } = {},
|
||||||
onUUIDTestFailure,
|
onUUIDTestFailure,
|
||||||
) => ({
|
) => ({
|
||||||
defaults: { ...defaults, onSuccess },
|
defaults: { ...defaults, onSuccess },
|
||||||
|
isRequired,
|
||||||
onFinishBatch,
|
onFinishBatch,
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ import Grid from '../../components/Grid';
|
|||||||
import handleAPIError from '../../lib/handleAPIError';
|
import handleAPIError from '../../lib/handleAPIError';
|
||||||
import Header from '../../components/Header';
|
import Header from '../../components/Header';
|
||||||
import ManageFencePanel from '../../components/ManageFence';
|
import ManageFencePanel from '../../components/ManageFence';
|
||||||
|
import ManageUpsPanel from '../../components/ManageUps';
|
||||||
import { Panel } from '../../components/Panels';
|
import { Panel } from '../../components/Panels';
|
||||||
import PrepareHostForm from '../../components/PrepareHostForm';
|
import PrepareHostForm from '../../components/PrepareHostForm';
|
||||||
import PrepareNetworkForm from '../../components/PrepareNetworkForm';
|
import PrepareNetworkForm from '../../components/PrepareNetworkForm';
|
||||||
@ -130,6 +131,19 @@ const ManageFenceTabContent: FC = () => (
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ManageUpsTabContent: FC = () => (
|
||||||
|
<Grid
|
||||||
|
columns={STEP_CONTENT_GRID_COLUMNS}
|
||||||
|
layout={{
|
||||||
|
'manageups-left-column': {},
|
||||||
|
'manageups-center-column': {
|
||||||
|
children: <ManageUpsPanel />,
|
||||||
|
...STEP_CONTENT_GRID_CENTER_COLUMN,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const ManageElement: FC = () => {
|
const ManageElement: FC = () => {
|
||||||
const {
|
const {
|
||||||
isReady,
|
isReady,
|
||||||
@ -177,6 +191,7 @@ const ManageElement: FC = () => {
|
|||||||
<Tab label="Prepare host" value="prepare-host" />
|
<Tab label="Prepare host" value="prepare-host" />
|
||||||
<Tab label="Prepare network" value="prepare-network" />
|
<Tab label="Prepare network" value="prepare-network" />
|
||||||
<Tab label="Manage fence devices" value="manage-fence" />
|
<Tab label="Manage fence devices" value="manage-fence" />
|
||||||
|
<Tab label="Manage UPSes" value="manage-ups" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Panel>
|
</Panel>
|
||||||
<TabContent changingTabId={pageTabId} tabId="prepare-host">
|
<TabContent changingTabId={pageTabId} tabId="prepare-host">
|
||||||
@ -188,6 +203,9 @@ const ManageElement: FC = () => {
|
|||||||
<TabContent changingTabId={pageTabId} tabId="manage-fence">
|
<TabContent changingTabId={pageTabId} tabId="manage-fence">
|
||||||
<ManageFenceTabContent />
|
<ManageFenceTabContent />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
<TabContent changingTabId={pageTabId} tabId="manage-ups">
|
||||||
|
<ManageUpsTabContent />
|
||||||
|
</TabContent>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
22
striker-ui/types/APIUps.d.ts
vendored
Normal file
22
striker-ui/types/APIUps.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
type APIUpsTemplate = {
|
||||||
|
[upsTypeId: string]: {
|
||||||
|
agent: string;
|
||||||
|
brand: string;
|
||||||
|
description: string;
|
||||||
|
links: {
|
||||||
|
[linkId: string]: {
|
||||||
|
linkHref: string;
|
||||||
|
linkLabel: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type APIUpsOverview = {
|
||||||
|
[upsUUID: string]: {
|
||||||
|
upsAgent: string;
|
||||||
|
upsIPAddress: string;
|
||||||
|
upsName: string;
|
||||||
|
upsUUID: string;
|
||||||
|
};
|
||||||
|
};
|
11
striker-ui/types/AddUpsInputGroup.d.ts
vendored
Normal file
11
striker-ui/types/AddUpsInputGroup.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
type AddUpsInputGroupOptionalProps = {
|
||||||
|
loading?: boolean;
|
||||||
|
previous?: CommonUpsInputGroupOptionalProps['previous'] & {
|
||||||
|
upsTypeId?: string;
|
||||||
|
};
|
||||||
|
upsTemplate?: APIUpsTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddUpsInputGroupProps<M extends MapToInputTestID> =
|
||||||
|
AddUpsInputGroupOptionalProps &
|
||||||
|
Pick<CommonUpsInputGroupProps<M>, 'formUtils'>;
|
11
striker-ui/types/CommonUpsInputGroup.d.ts
vendored
Normal file
11
striker-ui/types/CommonUpsInputGroup.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
type CommonUpsInputGroupOptionalProps = {
|
||||||
|
previous?: {
|
||||||
|
upsIPAddress?: string;
|
||||||
|
upsName?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type CommonUpsInputGroupProps<M extends MapToInputTestID> =
|
||||||
|
CommonUpsInputGroupOptionalProps & {
|
||||||
|
formUtils: FormUtils<M>;
|
||||||
|
};
|
1
striker-ui/types/ConfirmDialog.d.ts
vendored
1
striker-ui/types/ConfirmDialog.d.ts
vendored
@ -10,6 +10,7 @@ type ConfirmDialogOptionalProps = {
|
|||||||
onCancelAppend?: ContainedButtonProps['onClick'];
|
onCancelAppend?: ContainedButtonProps['onClick'];
|
||||||
onSubmitAppend?: import('react').FormEventHandler<HTMLDivElement>;
|
onSubmitAppend?: import('react').FormEventHandler<HTMLDivElement>;
|
||||||
openInitially?: boolean;
|
openInitially?: boolean;
|
||||||
|
preActionArea?: import('react').ReactNode;
|
||||||
proceedButtonProps?: ContainedButtonProps;
|
proceedButtonProps?: ContainedButtonProps;
|
||||||
proceedColour?: 'blue' | 'red';
|
proceedColour?: 'blue' | 'red';
|
||||||
scrollContent?: boolean;
|
scrollContent?: boolean;
|
||||||
|
9
striker-ui/types/EditUpsInputGroup.d.ts
vendored
Normal file
9
striker-ui/types/EditUpsInputGroup.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type EditUpsInputGroupOptionalProps = {
|
||||||
|
loading?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EditUpsInputGroupProps<M extends MapToInputTestID> =
|
||||||
|
EditUpsInputGroupOptionalProps &
|
||||||
|
Pick<AddUpsInputGroupProps<M>, 'formUtils' | 'previous' | 'upsTemplate'> & {
|
||||||
|
upsUUID: string;
|
||||||
|
};
|
27
striker-ui/types/FormUtils.d.ts
vendored
Normal file
27
striker-ui/types/FormUtils.d.ts
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
type FormValidity<T> = {
|
||||||
|
[K in keyof T]?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InputTestBatchFinishCallbackBuilder<M extends MapToInputTestID> = (
|
||||||
|
key: keyof M,
|
||||||
|
) => InputTestBatchFinishCallback;
|
||||||
|
|
||||||
|
type InputFirstRenderFunctionArgs = { isValid: boolean };
|
||||||
|
|
||||||
|
type InputFirstRenderFunction = (args: InputFirstRenderFunctionArgs) => void;
|
||||||
|
|
||||||
|
type InputFirstRenderFunctionBuilder<M extends MapToInputTestID> = (
|
||||||
|
key: keyof M,
|
||||||
|
) => InputFirstRenderFunction;
|
||||||
|
|
||||||
|
type FormUtils<M extends MapToInputTestID> = {
|
||||||
|
buildFinishInputTestBatchFunction: InputTestBatchFinishCallbackBuilder<M>;
|
||||||
|
buildInputFirstRenderFunction: InputFirstRenderFunctionBuilder<M>;
|
||||||
|
formValidity: FormValidity<M>;
|
||||||
|
isFormInvalid: boolean;
|
||||||
|
msgSetters: MapToMessageSetter<M>;
|
||||||
|
setFormValidity: import('react').Dispatch<
|
||||||
|
import('react').SetStateAction<FormValidity<M>>
|
||||||
|
>;
|
||||||
|
setValidity: (key: keyof M, value: boolean) => void;
|
||||||
|
};
|
16
striker-ui/types/MapToMessageSetter.d.ts
vendored
Normal file
16
striker-ui/types/MapToMessageSetter.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
type MapToMessageSetter<T extends MapToInputTestID> = {
|
||||||
|
[MessageSetterID in keyof T]: MessageSetterFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InputIds<T> = ReadonlyArray<T> | MapToInputTestID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given either:
|
||||||
|
* 1. an array of input identifiers, or
|
||||||
|
* 2. a key-value object of input indentifiers,
|
||||||
|
* transform it into a key-value object of identifiers.
|
||||||
|
*/
|
||||||
|
type MapToInputId<
|
||||||
|
U extends string,
|
||||||
|
I extends InputIds<U>,
|
||||||
|
> = I extends ReadonlyArray<U> ? { [K in I[number]]: K } : I;
|
3
striker-ui/types/SelectWithLabel.d.ts
vendored
3
striker-ui/types/SelectWithLabel.d.ts
vendored
@ -20,11 +20,12 @@ type SelectWithLabelOptionalProps = {
|
|||||||
>;
|
>;
|
||||||
label?: string;
|
label?: string;
|
||||||
messageBoxProps?: Partial<import('../components/MessageBox').MessageBoxProps>;
|
messageBoxProps?: Partial<import('../components/MessageBox').MessageBoxProps>;
|
||||||
|
required?: boolean;
|
||||||
selectProps?: Partial<SelectProps>;
|
selectProps?: Partial<SelectProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SelectWithLabelProps = SelectWithLabelOptionalProps &
|
type SelectWithLabelProps = SelectWithLabelOptionalProps &
|
||||||
Pick<SelectProps, 'name' | 'onChange' | 'value'> & {
|
Pick<SelectProps, 'name' | 'onBlur' | 'onChange' | 'onFocus' | 'value'> & {
|
||||||
id: string;
|
id: string;
|
||||||
selectItems: Array<SelectItem | string>;
|
selectItems: Array<SelectItem | string>;
|
||||||
};
|
};
|
||||||
|
3
striker-ui/types/TestInputFunction.d.ts
vendored
3
striker-ui/types/TestInputFunction.d.ts
vendored
@ -65,7 +65,8 @@ type InputTestBatch = {
|
|||||||
type BuildInputTestBatchFunction = (
|
type BuildInputTestBatchFunction = (
|
||||||
inputName: string,
|
inputName: string,
|
||||||
onSuccess: InputTestSuccessCallback,
|
onSuccess: InputTestSuccessCallback,
|
||||||
options?: InputTestBatch['defaults'] & Pick<InputTestBatch, 'onFinishBatch'>,
|
options?: InputTestBatch['defaults'] &
|
||||||
|
Pick<InputTestBatch, 'isRequired' | 'onFinishBatch'>,
|
||||||
...onFailureAppends: InputTestFailureAppendCallback[]
|
...onFailureAppends: InputTestFailureAppendCallback[]
|
||||||
) => InputTestBatch;
|
) => InputTestBatch;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user