Merge pull request #609 from ylei-tsubame/rebuild-webui

Web UI: combined UI patches prior to beta 2
main
Digimer 9 months ago committed by GitHub
commit fd5cfeed33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      striker-ui-api/out/index.js
  2. 22
      striker-ui-api/src/lib/accessModule.ts
  3. 1
      striker-ui-api/src/lib/consts/SERVER_PATHS.ts
  4. 76
      striker-ui-api/src/lib/request_handlers/command/buildPowerHandler.ts
  5. 4
      striker-ui-api/src/lib/request_handlers/command/startServer.ts
  6. 4
      striker-ui-api/src/lib/request_handlers/command/stopServer.ts
  7. 9
      striker-ui-api/src/lib/request_handlers/host/deleteHostConnection.ts
  8. 15
      striker-ui-api/src/lib/request_handlers/host/getHostConnection.ts
  9. 1
      striker-ui-api/src/types/BuildPowerHandlerFunction.d.ts
  10. 4
      striker-ui-api/src/types/GetAnvilDataFunction.d.ts
  11. 3
      striker-ui/components/Display/Preview.tsx
  12. 97
      striker-ui/components/GeneralInitForm.tsx
  13. 1
      striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx
  14. 6
      striker-ui/components/NetworkInitForm.tsx
  15. 14
      striker-ui/components/OutlinedInputWithLabel.tsx
  16. 23
      striker-ui/components/ProvisionServerDialog.tsx
  17. 17
      striker-ui/components/SelectWithLabel.tsx
  18. 3
      striker-ui/components/StrikerConfig/AddPeerDialog.tsx
  19. 56
      striker-ui/components/StrikerInitForm.tsx
  20. 1
      striker-ui/out/_next/static/JM2ldb5hOFExU7LFXSU9o/_buildManifest.js
  21. 1
      striker-ui/out/_next/static/Yce3ppcWMaUxwSt_BBIL2/_buildManifest.js
  22. 0
      striker-ui/out/_next/static/Yce3ppcWMaUxwSt_BBIL2/_ssgManifest.js
  23. 2
      striker-ui/out/_next/static/chunks/270-1756b18f0d4b1547.js
  24. 2
      striker-ui/out/_next/static/chunks/302-56ef8ca7e64551d5.js
  25. 2
      striker-ui/out/_next/static/chunks/633-057bae89c8ebfecf.js
  26. 2
      striker-ui/out/_next/static/chunks/675-852f892156e3dddd.js
  27. 1
      striker-ui/out/_next/static/chunks/750-56f9c4fd6f13ad2b.js
  28. 1
      striker-ui/out/_next/static/chunks/750-9f873f4e10dbcacd.js
  29. 2
      striker-ui/out/_next/static/chunks/pages/config-a837144441f39a8e.js
  30. 1
      striker-ui/out/_next/static/chunks/pages/init-210f96453904f447.js
  31. 1
      striker-ui/out/_next/static/chunks/pages/init-3c6b0fb0f99640a3.js
  32. 2
      striker-ui/out/_next/static/chunks/pages/manage-element-bb1b216f332e08a0.js
  33. 2
      striker-ui/out/anvil.html
  34. 2
      striker-ui/out/config.html
  35. 2
      striker-ui/out/file-manager.html
  36. 2
      striker-ui/out/index.html
  37. 2
      striker-ui/out/init.html
  38. 2
      striker-ui/out/login.html
  39. 2
      striker-ui/out/mail-config.html
  40. 2
      striker-ui/out/manage-element.html
  41. 2
      striker-ui/out/server.html
  42. 12
      striker-ui/package-lock.json
  43. 24
      tools/anvil-access-module

File diff suppressed because one or more lines are too long

@ -331,6 +331,24 @@ const getData = async <T>(...keys: string[]) => {
return data; return data;
}; };
const mutateData = async <T>(args: {
keys: string[];
operator: string;
value: string;
}): Promise<T> => {
const { keys, operator, value } = args;
const chain = `data->${keys.join('->')}`;
const {
sub_results: [data],
} = await access.interact<{ sub_results: [T] }>('x', chain, operator, value);
shvar(data, `${chain} data: `);
return data;
};
const getAnvilData = async () => { const getAnvilData = async () => {
await subroutine('get_anvils'); await subroutine('get_anvils');
@ -338,6 +356,9 @@ const getAnvilData = async () => {
}; };
const getDatabaseConfigData = async () => { const getDatabaseConfigData = async () => {
// Empty the existing data->database hash before re-reading updated values.
await mutateData<string>({ keys: ['database'], operator: '=', value: '{}' });
const [ecode] = await subroutine<[ecode: string]>('read_config', { const [ecode] = await subroutine<[ecode: string]>('read_config', {
pre: ['Storage'], pre: ['Storage'],
}); });
@ -494,6 +515,7 @@ export {
getPeerData, getPeerData,
getUpsSpec, getUpsSpec,
getVncinfo, getVncinfo,
mutateData,
query, query,
subroutine as sub, subroutine as sub,
write, write,

@ -32,7 +32,6 @@ const EMPTY_SERVER_PATHS: ServerPath = {
'anvil-boot-server': {}, 'anvil-boot-server': {},
'anvil-configure-host': {}, 'anvil-configure-host': {},
'anvil-delete-server': {}, 'anvil-delete-server': {},
'anvil-get-server-screenshot': {},
'anvil-join-anvil': {}, 'anvil-join-anvil': {},
'anvil-manage-alerts': {}, 'anvil-manage-alerts': {},
'anvil-manage-keys': {}, 'anvil-manage-keys': {},

@ -1,7 +1,7 @@
import assert from 'assert'; import assert from 'assert';
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { LOCAL, REP_UUID, SERVER_PATHS } from '../../consts'; import { DELETED, LOCAL, REP_UUID, SERVER_PATHS } from '../../consts';
import { job, query } from '../../accessModule'; import { job, query } from '../../accessModule';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
@ -34,29 +34,39 @@ const MAP_TO_POWER_JOB_PARAMS_BUILDER: Record<
job_name: `set_power::on`, job_name: `set_power::on`,
job_title: 'job_0334', job_title: 'job_0334',
}), }),
startserver: ({ uuid } = {}) => ({ startserver: ({ runOn, uuid } = {}) => ({
job_command: `${SERVER_PATHS.usr.sbin['anvil-boot-server'].self} --server-uuid '${uuid}'`, job_command: `${SERVER_PATHS.usr.sbin['anvil-boot-server'].self}`,
job_data: `server-uuid=${uuid}`,
job_description: 'job_0341', job_description: 'job_0341',
job_host_uuid: runOn,
job_name: 'set_power::server::on', job_name: 'set_power::server::on',
job_title: 'job_0340', job_title: 'job_0340',
}), }),
stop: ({ isStopServers, uuid } = {}) => ({ stop: ({ isStopServers, uuid, runOn = uuid } = {}) => ({
job_command: `${SERVER_PATHS.usr.sbin['anvil-safe-stop'].self} --power-off${ job_command: `${SERVER_PATHS.usr.sbin['anvil-safe-stop'].self} --power-off${
isStopServers ? ' --stop-servers' : '' isStopServers ? ' --stop-servers' : ''
}`, }`,
job_description: 'job_0333', job_description: 'job_0333',
job_host_uuid: uuid, job_host_uuid: runOn,
job_name: 'set_power::off', job_name: 'set_power::off',
job_title: 'job_0332', job_title: 'job_0332',
}), }),
stopserver: ({ force, uuid } = {}) => ({ stopserver: ({ force, runOn, uuid } = {}) => {
job_command: `${ let command = SERVER_PATHS.usr.sbin['anvil-shutdown-server'].self;
SERVER_PATHS.usr.sbin['anvil-shutdown-server'].self
} --server-uuid '${uuid}'${force ? ' --immediate' : ''}`, if (force) {
job_description: 'job_0343', command += ' --immediate';
job_name: 'set_power::server::off', }
job_title: 'job_0342',
}), return {
job_command: command,
job_data: `server-uuid=${uuid}`,
job_description: 'job_0343',
job_host_uuid: runOn,
job_name: 'set_power::server::off',
job_title: 'job_0342',
};
},
}; };
const queuePowerJob = async ( const queuePowerJob = async (
@ -74,8 +84,10 @@ const queuePowerJob = async (
export const buildPowerHandler: ( export const buildPowerHandler: (
task: PowerTask, task: PowerTask,
options?: { getJobHostUuid?: (uuid?: string) => Promise<string | undefined> },
) => RequestHandler<{ uuid?: string }> = ) => RequestHandler<{ uuid?: string }> =
(task) => async (request, response) => { (task, { getJobHostUuid } = {}) =>
async (request, response) => {
const { const {
params: { uuid }, params: { uuid },
query: { force: rForce }, query: { force: rForce },
@ -97,7 +109,9 @@ export const buildPowerHandler: (
} }
try { try {
await queuePowerJob(task, { force, uuid }); const runOn = await getJobHostUuid?.call(null, uuid);
await queuePowerJob(task, { force, runOn, uuid });
} catch (error) { } catch (error) {
stderr(`Failed to ${task} ${uuid ?? LOCAL}; CAUSE: ${error}`); stderr(`Failed to ${task} ${uuid ?? LOCAL}; CAUSE: ${error}`);
@ -154,3 +168,35 @@ export const buildAnPowerHandler: (
return response.status(204).send(); return response.status(204).send();
}; };
export const buildServerPowerHandler: (
task: Extract<PowerTask, 'startserver' | 'stopserver'>,
) => RequestHandler<{ uuid: string }> = (task) =>
buildPowerHandler(task, {
getJobHostUuid: async (uuid) => {
if (!uuid) return;
let serverHostUuid: string | undefined;
try {
const rows = await query<[[null | string]]>(
`SELECT server_host_uuid
FROM servers
WHERE server_uuid = '${uuid}'
AND server_state != '${DELETED}';`,
);
assert.ok(rows.length, `No entry found`);
const [[hostUuid]] = rows;
if (hostUuid) {
serverHostUuid = hostUuid;
}
} catch (error) {
throw new Error(`Failed to get server host; CAUSE: ${error}`);
}
return serverHostUuid;
},
});

@ -1,3 +1,3 @@
import { buildPowerHandler } from './buildPowerHandler'; import { buildServerPowerHandler } from './buildPowerHandler';
export const startServer = buildPowerHandler('startserver'); export const startServer = buildServerPowerHandler('startserver');

@ -1,3 +1,3 @@
import { buildPowerHandler } from './buildPowerHandler'; import { buildServerPowerHandler } from './buildPowerHandler';
export const stopServer = buildPowerHandler('stopserver'); export const stopServer = buildServerPowerHandler('stopserver');

@ -18,6 +18,15 @@ export const deleteHostConnection: RequestHandler<
const hostUuid = toHostUUID(key); const hostUuid = toHostUUID(key);
const peerHostUuids = body[key]; const peerHostUuids = body[key];
/**
* Removing one or more peer of a striker doesn't update the globals in
* access module's memory, meaning there will be broken references to the
* removed peer for, i.e., database connections.
*
* TODO: find a solution to update the necessary pieces after removing
* peer(s).
*/
for (const peerHostUuid of peerHostUuids) { for (const peerHostUuid of peerHostUuids) {
try { try {
await job({ await job({

@ -14,8 +14,19 @@ const buildHostConnections = (
}: { defaultPort?: number; defaultUser?: string } = {}, }: { defaultPort?: number; defaultUser?: string } = {},
) => ) =>
Object.entries(databaseHash).reduce<HostConnectionOverview>( Object.entries(databaseHash).reduce<HostConnectionOverview>(
(previous, [hostUUID, { host: ipAddress, ping, port: rawPort, user }]) => { (
const port = Number(rawPort); previous,
[
hostUUID,
{
host: ipAddress,
ping,
port: rPort = defaultPort,
user = defaultUser,
},
],
) => {
const port = Number(rPort);
if (hostUUID === fromHostUUID) { if (hostUUID === fromHostUUID) {
previous.inbound.port = port; previous.inbound.port = port;

@ -11,6 +11,7 @@ type PowerJobParams = Omit<JobParams, 'file' | 'line'>;
type BuildPowerJobParamsOptions = { type BuildPowerJobParamsOptions = {
force?: boolean; force?: boolean;
isStopServers?: boolean; isStopServers?: boolean;
runOn?: string;
uuid?: string; uuid?: string;
}; };

@ -25,11 +25,11 @@ type AnvilDataAnvilListHash = {
type AnvilDataDatabaseHash = { type AnvilDataDatabaseHash = {
[hostUUID: string]: { [hostUUID: string]: {
host: string; host: string;
name: string; name?: string;
password: string; password: string;
ping: string; ping: string;
port: string; port: string;
user: string; user?: string;
}; };
}; };

@ -123,11 +123,12 @@ const Preview: FC<PreviewProps> = ({
serverState === 'running' ? ( serverState === 'running' ? (
<> <>
<Box <Box
alt="" alt={`Preview is temporarily unavailable, but the server is ${serverState}.`}
component="img" component="img"
src={`data:image;base64,${preview}`} src={`data:image;base64,${preview}`}
sx={{ sx={{
height: '100%', height: '100%',
minHeight: '10em',
opacity: isPreviewStale ? '0.4' : '1', opacity: isPreviewStale ? '0.4' : '1',
padding: isUseInnerPanel ? '.2em' : 0, padding: isUseInnerPanel ? '.2em' : 0,
width: '100%', width: '100%',

@ -116,8 +116,6 @@ const GeneralInitForm = forwardRef<
] = useState<boolean>(false); ] = useState<boolean>(false);
const [isShowHostNameSuggest, setIsShowHostNameSuggest] = const [isShowHostNameSuggest, setIsShowHostNameSuggest] =
useState<boolean>(false); useState<boolean>(false);
const [isConfirmAdminPassword, setIsConfirmAdminPassword] =
useState<boolean>(true);
const [isValidateDomain, setIsValidateDomain] = useState<boolean>(true); const [isValidateDomain, setIsValidateDomain] = useState<boolean>(true);
const readHostDetailRef = useRef<boolean>(true); const readHostDetailRef = useRef<boolean>(true);
@ -203,6 +201,7 @@ const GeneralInitForm = forwardRef<
setAdminPasswordInputMessage(undefined); setAdminPasswordInputMessage(undefined);
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -234,6 +233,7 @@ const GeneralInitForm = forwardRef<
setConfirmAdminPasswordInputMessage(undefined); setConfirmAdminPasswordInputMessage(undefined);
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -255,6 +255,7 @@ const GeneralInitForm = forwardRef<
setDomainNameInputMessage(undefined); setDomainNameInputMessage(undefined);
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -282,6 +283,7 @@ const GeneralInitForm = forwardRef<
setHostNameInputMessage(undefined); setHostNameInputMessage(undefined);
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -309,6 +311,7 @@ const GeneralInitForm = forwardRef<
setHostNumberInputMessage(undefined); setHostNumberInputMessage(undefined);
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -326,6 +329,7 @@ const GeneralInitForm = forwardRef<
getValue: () => getValue: () =>
organizationNameInputRef.current.getValue?.call(null), organizationNameInputRef.current.getValue?.call(null),
}, },
isRequired: true,
tests: [{ test: testNotBlank }], tests: [{ test: testNotBlank }],
}, },
[IT_IDS.organizationPrefix]: { [IT_IDS.organizationPrefix]: {
@ -338,6 +342,7 @@ const GeneralInitForm = forwardRef<
setOrganizationPrefixInputMessage(undefined); setOrganizationPrefixInputMessage(undefined);
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: ({ max, min }) => { onFailure: ({ max, min }) => {
@ -371,17 +376,10 @@ const GeneralInitForm = forwardRef<
excludeTestIds = [], excludeTestIds = [],
inputs, inputs,
isContinueOnFailure, isContinueOnFailure,
isExcludeConfirmAdminPassword = !isConfirmAdminPassword,
}: Pick< }: Pick<
TestInputFunctionOptions, TestInputFunctionOptions,
'inputs' | 'excludeTestIds' | 'isContinueOnFailure' 'inputs' | 'excludeTestIds' | 'isContinueOnFailure'
> & { > = {}) => {
isExcludeConfirmAdminPassword?: boolean;
} = {}) => {
if (isExcludeConfirmAdminPassword) {
excludeTestIds.push(IT_IDS.confirmAdminPassword);
}
toggleSubmitDisabled?.call( toggleSubmitDisabled?.call(
null, null,
testInput({ testInput({
@ -393,7 +391,7 @@ const GeneralInitForm = forwardRef<
}), }),
); );
}, },
[isConfirmAdminPassword, testInput, toggleSubmitDisabled], [testInput, toggleSubmitDisabled],
); );
const populateOrganizationPrefixInput = useCallback( const populateOrganizationPrefixInput = useCallback(
({ ({
@ -791,6 +789,7 @@ const GeneralInitForm = forwardRef<
<InputWithRef <InputWithRef
input={ input={
<OutlinedInputWithLabel <OutlinedInputWithLabel
disableAutofill
id="striker-init-general-admin-password" id="striker-init-general-admin-password"
inputProps={{ inputProps={{
inputProps: { inputProps: {
@ -801,19 +800,6 @@ const GeneralInitForm = forwardRef<
inputs: { [IT_IDS.adminPassword]: { value } }, inputs: { [IT_IDS.adminPassword]: { value } },
}); });
}, },
onPasswordVisibilityAppend: (inputType) => {
const localIsConfirmAdminPassword =
inputType === INPUT_TYPES.password;
testInputToToggleSubmitDisabled({
isExcludeConfirmAdminPassword:
!localIsConfirmAdminPassword,
});
setIsConfirmAdminPassword(
localIsConfirmAdminPassword,
);
setConfirmAdminPasswordInputMessage();
},
}} }}
inputLabelProps={{ isNotifyRequired: true }} inputLabelProps={{ isNotifyRequired: true }}
label="Admin password" label="Admin password"
@ -835,42 +821,41 @@ const GeneralInitForm = forwardRef<
ref={adminPasswordInputRef} ref={adminPasswordInputRef}
/> />
</MUIGrid> </MUIGrid>
{isConfirmAdminPassword && ( <MUIGrid item xs={1}>
<MUIGrid item xs={1}> <InputWithRef
<InputWithRef input={
input={ <OutlinedInputWithLabel
<OutlinedInputWithLabel disableAutofill
id="striker-init-general-confirm-admin-password" id="striker-init-general-confirm-admin-password"
inputProps={{ inputProps={{
inputProps: { inputProps: {
type: INPUT_TYPES.password, type: INPUT_TYPES.password,
}, },
onBlur: ({ target: { value } }) => { onBlur: ({ target: { value } }) => {
testInput({ testInput({
inputs: {
[IT_IDS.confirmAdminPassword]: { value },
},
});
},
}}
inputLabelProps={{
isNotifyRequired: isConfirmAdminPassword,
}}
label="Confirm password"
onChange={({ target: { value } }) => {
testInputToToggleSubmitDisabled({
inputs: { inputs: {
[IT_IDS.confirmAdminPassword]: { value }, [IT_IDS.confirmAdminPassword]: { value },
}, },
}); });
setConfirmAdminPasswordInputMessage(); },
}} }}
/> inputLabelProps={{
} isNotifyRequired: true,
ref={confirmAdminPasswordInputRef} }}
/> label="Confirm password"
</MUIGrid> onChange={({ target: { value } }) => {
)} testInputToToggleSubmitDisabled({
inputs: {
[IT_IDS.confirmAdminPassword]: { value },
},
});
setConfirmAdminPasswordInputMessage();
}}
/>
}
ref={confirmAdminPasswordInputRef}
/>
</MUIGrid>
</MUIGrid> </MUIGrid>
</MUIGrid> </MUIGrid>
</MUIGrid> </MUIGrid>

@ -285,6 +285,7 @@ const AnNetworkInputGroup = <M extends MapToInputTestID>({
data-network-type={networkType} data-network-type={networkType}
/> />
<Grid <Grid
columns={{ xs: 1, sm: 2, md: 3 }}
layout={{ layout={{
[inputCellIdIp]: { [inputCellIdIp]: {
children: ( children: (

@ -888,6 +888,7 @@ const NetworkInitForm = forwardRef<
setDnsInputMessage(); setDnsInputMessage();
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -908,6 +909,7 @@ const NetworkInitForm = forwardRef<
setGatewayInputMessage(); setGatewayInputMessage();
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -1020,6 +1022,7 @@ const NetworkInitForm = forwardRef<
setNetworkIfacesInputMessage(); setNetworkIfacesInputMessage();
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -1048,6 +1051,7 @@ const NetworkInitForm = forwardRef<
setNetworkIPAddressInputMessage(); setNetworkIPAddressInputMessage();
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
@ -1068,6 +1072,7 @@ const NetworkInitForm = forwardRef<
}; };
tests[IT_IDS.networkName(inputTestPrefix)] = { tests[IT_IDS.networkName(inputTestPrefix)] = {
defaults: { value: name }, defaults: { value: name },
isRequired: true,
tests: [{ test: testNotBlank }], tests: [{ test: testNotBlank }],
}; };
tests[inputTestIDSubnetMask] = { tests[inputTestIDSubnetMask] = {
@ -1077,6 +1082,7 @@ const NetworkInitForm = forwardRef<
setNetworkSubnetMaskInputMessage(); setNetworkSubnetMaskInputMessage();
}, },
}, },
isRequired: true,
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {

@ -20,7 +20,6 @@ import OutlinedInputLabel, {
} from './OutlinedInputLabel'; } from './OutlinedInputLabel';
type OutlinedInputWithLabelOptionalPropsWithDefault = { type OutlinedInputWithLabelOptionalPropsWithDefault = {
fillRow?: boolean;
formControlProps?: Partial<MUIFormControlProps>; formControlProps?: Partial<MUIFormControlProps>;
helpMessageBoxProps?: Partial<MessageBoxProps>; helpMessageBoxProps?: Partial<MessageBoxProps>;
id?: string; id?: string;
@ -53,7 +52,6 @@ type OutlinedInputWithLabelProps = Pick<
const OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS: Required<OutlinedInputWithLabelOptionalPropsWithDefault> & const OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS: Required<OutlinedInputWithLabelOptionalPropsWithDefault> &
OutlinedInputWithLabelOptionalPropsWithoutDefault = { OutlinedInputWithLabelOptionalPropsWithoutDefault = {
baseInputProps: undefined, baseInputProps: undefined,
fillRow: false,
formControlProps: {}, formControlProps: {},
helpMessageBoxProps: {}, helpMessageBoxProps: {},
id: '', id: '',
@ -70,7 +68,6 @@ const OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS: Required<OutlinedInputWithLabelOp
const OutlinedInputWithLabel: FC<OutlinedInputWithLabelProps> = ({ const OutlinedInputWithLabel: FC<OutlinedInputWithLabelProps> = ({
baseInputProps, baseInputProps,
disableAutofill, disableAutofill,
fillRow: isFillRow = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.fillRow,
formControlProps = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.formControlProps, formControlProps = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.formControlProps,
helpMessageBoxProps = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.helpMessageBoxProps, helpMessageBoxProps = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.helpMessageBoxProps,
id = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.id, id = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.id,
@ -91,15 +88,10 @@ const OutlinedInputWithLabel: FC<OutlinedInputWithLabelProps> = ({
type, type,
value = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.value, value = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.value,
}) => { }) => {
const { sx: formControlSx, ...restFormControlProps } = formControlProps;
const { text: helpText = '' } = helpMessageBoxProps; const { text: helpText = '' } = helpMessageBoxProps;
const [isShowHelp, setIsShowHelp] = useState<boolean>(false); const [isShowHelp, setIsShowHelp] = useState<boolean>(false);
const formControlWidth = useMemo(
() => (isFillRow ? '100%' : undefined),
[isFillRow],
);
const helpElement = useMemo( const helpElement = useMemo(
() => () =>
isShowHelp && ( isShowHelp && (
@ -136,11 +128,7 @@ const OutlinedInputWithLabel: FC<OutlinedInputWithLabelProps> = ({
const handleHelp = useMemo(createHelpHandler, [createHelpHandler]); const handleHelp = useMemo(createHelpHandler, [createHelpHandler]);
return ( return (
<MUIFormControl <MUIFormControl fullWidth {...formControlProps}>
fullWidth
{...restFormControlProps}
sx={{ width: formControlWidth, ...formControlSx }}
>
<OutlinedInputLabel <OutlinedInputLabel
htmlFor={id} htmlFor={id}
isNotifyRequired={required} isNotifyRequired={required}

@ -18,6 +18,7 @@ import api from '../lib/api';
import Autocomplete from './Autocomplete'; import Autocomplete from './Autocomplete';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import ContainedButton from './ContainedButton'; import ContainedButton from './ContainedButton';
import FlexBox from './FlexBox';
import { dsize, dsizeToByte } from '../lib/format_data_size_wrappers'; import { dsize, dsizeToByte } from '../lib/format_data_size_wrappers';
import IconButton, { IconButtonProps } from './IconButton'; import IconButton, { IconButtonProps } from './IconButton';
import MessageBox, { MessageBoxProps } from './MessageBox'; import MessageBox, { MessageBoxProps } from './MessageBox';
@ -901,6 +902,8 @@ const ProvisionServerDialog = ({
const [allAnvils, setAllAnvils] = useState< const [allAnvils, setAllAnvils] = useState<
OrganizedAnvilDetailMetadataForProvisionServer[] OrganizedAnvilDetailMetadataForProvisionServer[]
>([]); >([]);
// Provision is impossible when one of anvil node list, file list, or storage
// group list is empty.
const [anvilUUIDMapToData, setAnvilUUIDMapToData] = const [anvilUUIDMapToData, setAnvilUUIDMapToData] =
useState<AnvilUUIDMapToData>({}); useState<AnvilUUIDMapToData>({});
const [fileUUIDMapToData, setFileUUIDMapToData] = useState<FileUUIDMapToData>( const [fileUUIDMapToData, setFileUUIDMapToData] = useState<FileUUIDMapToData>(
@ -1480,6 +1483,15 @@ const ProvisionServerDialog = ({
); );
}; };
const hasResource = useMemo<Record<string, boolean>>(
() => ({
'anvil node': Boolean(Object.keys(anvilUUIDMapToData).length),
file: Boolean(Object.keys(fileUUIDMapToData).length),
'storage group': Boolean(Object.keys(storageGroupUUIDMapToData).length),
}),
[anvilUUIDMapToData, fileUUIDMapToData, storageGroupUUIDMapToData],
);
useEffect(() => { useEffect(() => {
api api
.get('/anvil', { .get('/anvil', {
@ -1594,6 +1606,17 @@ const ProvisionServerDialog = ({
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
</PanelHeader> </PanelHeader>
<FlexBox spacing=".6em">
{Object.entries(hasResource).map(
([resource, has]) =>
!has && (
<MessageBox type="warning">
No {resource} available yet. Try refreshing after the resource
gets created.
</MessageBox>
),
)}
</FlexBox>
{isProvisionServerDataReady ? ( {isProvisionServerDataReady ? (
<Box <Box
sx={{ sx={{

@ -103,10 +103,19 @@ const SelectWithLabel = <
const menuItemElements = useMemo( const menuItemElements = useMemo(
() => () =>
selectItems.map((item) => { selectItems.map((item) => {
const { value, displayValue }: SelectItem<Value, Display> = /**
typeof item === 'object' * Cases:
? item * 1. item is string
: { displayValue: item as Display, value: item as Value }; * 2. item is SelectItem with only value
* 3. item is SelectItem with both value, and displayValue
*/
if (typeof item === 'string') return createMenuItem(item, item);
const {
value,
displayValue = String(value),
}: SelectItem<Value, Display> = item;
return createMenuItem(value, displayValue); return createMenuItem(value, displayValue);
}), }),

@ -58,7 +58,7 @@ const AddPeerDialog = forwardRef<
const [formValidity, setFormValidity] = useState<{ const [formValidity, setFormValidity] = useState<{
[inputTestID: string]: boolean; [inputTestID: string]: boolean;
}>({}); }>({});
const [isEnablePingTest, setIsEnablePingTest] = useState<boolean>(false); const [isEnablePingTest, setIsEnablePingTest] = useState<boolean>(true);
const [isSubmittingAddPeer, setIsSubmittingAddPeer] = const [isSubmittingAddPeer, setIsSubmittingAddPeer] =
useState<boolean>(false); useState<boolean>(false);
@ -142,7 +142,6 @@ const AddPeerDialog = forwardRef<
<InputWithRef <InputWithRef
input={ input={
<OutlinedInputWithLabel <OutlinedInputWithLabel
fillRow
id="add-peer-password-input" id="add-peer-password-input"
label={LABEL.password} label={LABEL.password}
type={INPUT_TYPES.password} type={INPUT_TYPES.password}

@ -52,6 +52,7 @@ const StrikerInitForm: FC = () => {
const [hostDetail, setHostDetail] = useState<APIHostDetail | undefined>(); const [hostDetail, setHostDetail] = useState<APIHostDetail | undefined>();
// Make sure the fetch for host detail only happens once.
const allowGetHostDetail = useRef<boolean>(true); const allowGetHostDetail = useRef<boolean>(true);
const generalInitFormRef = useRef<GeneralInitFormForwardedRefContent>({}); const generalInitFormRef = useRef<GeneralInitFormForwardedRefContent>({});
@ -60,6 +61,8 @@ const StrikerInitForm: FC = () => {
const jobIconRef = useRef<IconWithIndicatorForwardedRefContent>({}); const jobIconRef = useRef<IconWithIndicatorForwardedRefContent>({});
const jobSummaryRef = useRef<JobSummaryForwardedRefContent>({}); const jobSummaryRef = useRef<JobSummaryForwardedRefContent>({});
const [panelTitle, setPanelTitle] = useState<string>('Loading...');
const reconfig = useMemo<boolean>(() => Boolean(re), [re]); const reconfig = useMemo<boolean>(() => Boolean(re), [re]);
const buildSubmitSection = useMemo( const buildSubmitSection = useMemo(
@ -88,43 +91,36 @@ const StrikerInitForm: FC = () => {
[isDisableSubmit, isSubmittingForm], [isDisableSubmit, isSubmittingForm],
); );
const panelTitle = useMemo(() => {
let title = 'Loading...';
if (isReady) {
title = reconfig
? `Reconfigure ${hostDetail?.shortHostName ?? 'striker'}`
: 'Initialize striker';
}
return title;
}, [hostDetail?.shortHostName, isReady, reconfig]);
const toggleSubmitDisabled = useCallback((...testResults: boolean[]) => { const toggleSubmitDisabled = useCallback((...testResults: boolean[]) => {
setIsDisableSubmit(!testResults.every((testResult) => testResult)); setIsDisableSubmit(!testResults.every((testResult) => testResult));
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isReady) { if (!isReady) return;
if (reconfig && allowGetHostDetail.current) {
allowGetHostDetail.current = false;
api
.get<APIHostDetail>('/host/local')
.then(({ data }) => {
setHostDetail(data);
})
.catch((error) => {
const emsg = handleAPIError(error);
emsg.children = ( if (!reconfig) {
<>Failed to get host detail data. {emsg.children}</> setPanelTitle('Initialize striker');
);
setSubmitMessage(emsg); return;
});
}
} }
if (!allowGetHostDetail.current) return;
allowGetHostDetail.current = false;
api
.get<APIHostDetail>('/host/local')
.then(({ data }) => {
setHostDetail(data);
setPanelTitle(`Reconfigure ${data.shortHostName}`);
})
.catch((error) => {
const emsg = handleAPIError(error);
emsg.children = <>Failed to get host detail data. {emsg.children}</>;
setSubmitMessage(emsg);
});
}, [isReady, reconfig, setHostDetail]); }, [isReady, reconfig, setHostDetail]);
return ( return (
@ -249,7 +245,7 @@ const StrikerInitForm: FC = () => {
} }
return ( return (
<Grid container key={key} item> <Grid columns={{ xs: 2 }} container key={key} item>
<Grid item xs={1}> <Grid item xs={1}>
<BodyText>{`Link ${ifaceIndex + 1}`}</BodyText> <BodyText>{`Link ${ifaceIndex + 1}`}</BodyText>
</Grid> </Grid>

@ -1 +0,0 @@
self.__BUILD_MANIFEST=function(s,c,a,e,t,i,n,f,b,u,d,k,h,j,g,r,l,_,o,m){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,a,e,i,n,f,u,"static/chunks/6-dbef3ba2a090cb05.js",c,t,b,d,r,l,"static/chunks/pages/index-0e23f6af1e089a97.js"],"/_error":["static/chunks/pages/_error-a9572f84d60f21da.js"],"/anvil":[s,a,e,i,n,f,u,"static/chunks/924-2a2fdb45d3e02493.js",c,t,b,d,r,"static/chunks/pages/anvil-38307a04a51f8094.js"],"/config":[s,a,e,n,k,j,c,t,h,"static/chunks/pages/config-144ed56943e89e8c.js"],"/file-manager":[s,a,e,i,f,k,g,"static/chunks/486-45903b907cd7ece3.js",c,t,b,"static/chunks/pages/file-manager-8a23bb0baebac7f6.js"],"/init":[s,a,i,n,f,u,j,_,c,t,b,h,o,"static/chunks/pages/init-210f96453904f447.js"],"/login":[s,a,e,n,c,t,h,"static/chunks/pages/login-1d03dfaf2cb572f8.js"],"/mail-config":[s,a,e,i,n,f,u,k,g,c,t,b,d,m,"static/chunks/pages/mail-config-77c70d16ef879e90.js"],"/manage-element":[s,a,e,i,n,f,u,k,g,j,_,"static/chunks/814-6420b976d086fe20.js",c,t,b,d,h,o,m,"static/chunks/pages/manage-element-7ac129e45d98ff58.js"],"/server":[s,e,i,c,l,"static/chunks/pages/server-d81577dd0b817ba2.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/mail-config","/manage-element","/server"]}}("static/chunks/494-413067ecdf8ec8f0.js","static/chunks/775-3f1c58f77437bd5d.js","static/chunks/804-a6d43595270ed0d2.js","static/chunks/416-b31c470a96d10e58.js","static/chunks/675-9a50fb0ae255b835.js","static/chunks/50-af452066db73e3df.js","static/chunks/263-5784adae0d1d8513.js","static/chunks/213-67c4f0768a44e039.js","static/chunks/633-900b9341a6a3bc53.js","static/chunks/310-4edb13985847ab25.js","static/chunks/733-a945bbb3c5f55f74.js","static/chunks/461-c4e18a515455805e.js","static/chunks/556-0c0cace3593e5307.js","static/chunks/203-ea1ab9b7c3c7694b.js","static/chunks/264-683b93ad6e70a8fb.js","static/chunks/750-9f873f4e10dbcacd.js","static/chunks/302-6490e226661e8e00.js","static/chunks/197-c291e38a27168218.js","static/chunks/270-9058c1049a825f7d.js","static/chunks/17-0593dc8fdd9c512e.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

@ -0,0 +1 @@
self.__BUILD_MANIFEST=function(s,a,c,e,t,i,n,f,b,u,k,h,j,d,g,r,l,_,o,m){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,c,e,i,n,f,u,"static/chunks/6-dbef3ba2a090cb05.js",a,t,b,k,r,l,"static/chunks/pages/index-0e23f6af1e089a97.js"],"/_error":["static/chunks/pages/_error-a9572f84d60f21da.js"],"/anvil":[s,c,e,i,n,f,u,"static/chunks/924-2a2fdb45d3e02493.js",a,t,b,k,r,"static/chunks/pages/anvil-38307a04a51f8094.js"],"/config":[s,c,e,n,h,d,a,t,j,"static/chunks/pages/config-a837144441f39a8e.js"],"/file-manager":[s,c,e,i,f,h,g,"static/chunks/486-45903b907cd7ece3.js",a,t,b,"static/chunks/pages/file-manager-8a23bb0baebac7f6.js"],"/init":[s,c,i,n,f,u,d,_,a,t,b,j,o,"static/chunks/pages/init-3c6b0fb0f99640a3.js"],"/login":[s,c,e,n,a,t,j,"static/chunks/pages/login-1d03dfaf2cb572f8.js"],"/mail-config":[s,c,e,i,n,f,u,h,g,a,t,b,k,m,"static/chunks/pages/mail-config-77c70d16ef879e90.js"],"/manage-element":[s,c,e,i,n,f,u,h,g,d,_,"static/chunks/814-6420b976d086fe20.js",a,t,b,k,j,o,m,"static/chunks/pages/manage-element-bb1b216f332e08a0.js"],"/server":[s,e,i,a,l,"static/chunks/pages/server-d81577dd0b817ba2.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/mail-config","/manage-element","/server"]}}("static/chunks/494-413067ecdf8ec8f0.js","static/chunks/775-3f1c58f77437bd5d.js","static/chunks/804-a6d43595270ed0d2.js","static/chunks/416-b31c470a96d10e58.js","static/chunks/675-852f892156e3dddd.js","static/chunks/50-af452066db73e3df.js","static/chunks/263-5784adae0d1d8513.js","static/chunks/213-67c4f0768a44e039.js","static/chunks/633-057bae89c8ebfecf.js","static/chunks/310-4edb13985847ab25.js","static/chunks/733-a945bbb3c5f55f74.js","static/chunks/461-c4e18a515455805e.js","static/chunks/556-0c0cace3593e5307.js","static/chunks/203-ea1ab9b7c3c7694b.js","static/chunks/264-683b93ad6e70a8fb.js","static/chunks/750-56f9c4fd6f13ad2b.js","static/chunks/302-56ef8ca7e64551d5.js","static/chunks/197-c291e38a27168218.js","static/chunks/270-1756b18f0d4b1547.js","static/chunks/17-0593dc8fdd9c512e.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -4169,9 +4169,9 @@
"dev": true "dev": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.5", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -10196,9 +10196,9 @@
"dev": true "dev": true
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.15.5", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
}, },
"format-data-size": { "format-data-size": {
"version": "0.1.0", "version": "0.1.0",

@ -192,6 +192,16 @@ sub access_chain
if ($is_last_key) if ($is_last_key)
{ {
# Allow operators on hash children. Mainly used for cleaning up before refreshing children of $anvil->data.
my $op = $chain_args->[0] // "";
if ($op eq "=")
{
my $op_value = $chain_args->[1];
$intermediate->{$key} = $op_value if (defined $op_value);
}
@results = ($intermediate->{$key}); @results = ($intermediate->{$key});
last; last;
@ -208,6 +218,20 @@ sub access_chain
# On the last key of the chain; try to execute the subroutine if it exists # On the last key of the chain; try to execute the subroutine if it exists
if ($is_last_key) if ($is_last_key)
{ {
# Go through each argument and replace any 'anvil->' strings with their real value.
# At the time of writing, the only expected use case is calling $anvil->_make_hash_reference
for my $arg_i (0 .. $#{$chain_args})
{
my $arg = $chain_args->[$arg_i];
if ($arg =~ s/^anvil(->|\.)//)
{
my ($replacement) = access_chain({ chain => $arg, debug => $debug });
$chain_args->[$arg_i] = $replacement;
}
}
# Trailing 1 means the eval block will return success if the subroutine and assign succeeded # Trailing 1 means the eval block will return success if the subroutine and assign succeeded
eval { (@results) = $intermediate->${key}(@$chain_args); 1; }; eval { (@results) = $intermediate->${key}(@$chain_args); 1; };

Loading…
Cancel
Save