Merge pull request #677 from ylei-tsubame/dependabot/58-59braces

Web UI: patches 650, 663, 642, 661, 670, 400, and dependabot/58, 59
main
Digimer 6 months ago committed by GitHub
commit ab79237240
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      striker-ui-api/out/index.js
  2. 28
      striker-ui-api/package-lock.json
  3. 1
      striker-ui-api/src/lib/consts/WS_GUID.ts
  4. 2
      striker-ui-api/src/lib/consts/index.ts
  5. 70
      striker-ui-api/src/lib/request_handlers/command/getHostSSH.ts
  6. 79
      striker-ui-api/src/lib/request_handlers/host/getHostConnection.ts
  7. 2
      striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts
  8. 2
      striker-ui-api/src/lib/request_handlers/ssh-key/getSSHKeyConflict.ts
  9. 66
      striker-ui-api/src/middlewares/proxyServerVnc.ts
  10. 2
      striker-ui-api/src/types/ApiCommand.d.ts
  11. 1
      striker-ui-api/src/types/ApiHost.d.ts
  12. 2
      striker-ui-api/src/types/ApiSshKey.d.ts
  13. 5
      striker-ui-api/src/types/ErrorResponse.d.ts
  14. 5
      striker-ui/components/CrudList.tsx
  15. 33
      striker-ui/components/Dialog/DialogHeader.tsx
  16. 5
      striker-ui/components/Dialog/DialogWithHeader.tsx
  17. 84
      striker-ui/components/Display/FullSize.tsx
  18. 35
      striker-ui/components/Display/VncDisplay.tsx
  19. 3
      striker-ui/components/Files/schema.ts
  20. 5
      striker-ui/components/FormSummary.tsx
  21. 11
      striker-ui/components/ManageHost/ManageHost.tsx
  22. 75
      striker-ui/components/ManageHost/TestAccessForm.tsx
  23. 10
      striker-ui/components/ManageHost/schema.ts
  24. 9
      striker-ui/components/ManageHost/testAccessSchema.ts
  25. 7
      striker-ui/components/ManageMailRecipient/schema.ts
  26. 3
      striker-ui/components/ManageMailServer/schema.ts
  27. 2
      striker-ui/components/ManageManifest/AddManifestInputGroup.tsx
  28. 11
      striker-ui/components/ManageManifest/AnHostInputGroup.tsx
  29. 44
      striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx
  30. 2
      striker-ui/components/ManageManifest/EditManifestInputGroup.tsx
  31. 76
      striker-ui/components/ManageManifest/ManageManifestPanel.tsx
  32. 28
      striker-ui/components/ManageManifest/RunManifestInputGroup.tsx
  33. 3
      striker-ui/components/ProvisionServerDialog.tsx
  34. 21
      striker-ui/components/StrikerConfig/ConfigPeersForm.tsx
  35. 44
      striker-ui/hooks/useCookieJar.ts
  36. 17
      striker-ui/lib/yupMatches.ts
  37. 1
      striker-ui/out/_next/static/4RB26cd2zGZyKBQyjB24P/_buildManifest.js
  38. 1
      striker-ui/out/_next/static/SxEOmK8s3UBkjPP7eOep7/_buildManifest.js
  39. 0
      striker-ui/out/_next/static/SxEOmK8s3UBkjPP7eOep7/_ssgManifest.js
  40. 1
      striker-ui/out/_next/static/chunks/16-633a4da2be332451.js
  41. 1
      striker-ui/out/_next/static/chunks/16-8f130ff153ed09e1.js
  42. 2
      striker-ui/out/_next/static/chunks/466-40a89715cb183656.js
  43. 1
      striker-ui/out/_next/static/chunks/512-56563c67ec35f070.js
  44. 1
      striker-ui/out/_next/static/chunks/724.74a0b8e0158ff12a.js
  45. 1
      striker-ui/out/_next/static/chunks/724.9b0f3b59b9f819ec.js
  46. 1
      striker-ui/out/_next/static/chunks/762-6137fd9eb5f130da.js
  47. 1
      striker-ui/out/_next/static/chunks/762-c3bdcfb38ea6ff94.js
  48. 1
      striker-ui/out/_next/static/chunks/845-b3d5dd7a156a9380.js
  49. 2
      striker-ui/out/_next/static/chunks/pages/_app-979a3ab1fd6debc5.js
  50. 1
      striker-ui/out/_next/static/chunks/pages/config-396facf1669ffe17.js
  51. 1
      striker-ui/out/_next/static/chunks/pages/config-cab528473cc20327.js
  52. 1
      striker-ui/out/_next/static/chunks/pages/file-manager-1086cc9ed94415ae.js
  53. 1
      striker-ui/out/_next/static/chunks/pages/file-manager-a836cefe1c1c7d5f.js
  54. 2
      striker-ui/out/_next/static/chunks/pages/login-9acb46ff70465046.js
  55. 1
      striker-ui/out/_next/static/chunks/pages/mail-config-4ecabfd783a4abaf.js
  56. 1
      striker-ui/out/_next/static/chunks/pages/mail-config-cc0f4d97fffbb64c.js
  57. 1
      striker-ui/out/_next/static/chunks/pages/manage-element-0a2d309344524020.js
  58. 1
      striker-ui/out/_next/static/chunks/pages/manage-element-3d0a368d3c926f1d.js
  59. 2
      striker-ui/out/_next/static/chunks/pages/server-9fd04502dddda042.js
  60. 2
      striker-ui/out/_next/static/chunks/webpack-a4ad8f3183d9bd89.js
  61. 2
      striker-ui/out/anvil.html
  62. 2
      striker-ui/out/config.html
  63. 2
      striker-ui/out/file-manager.html
  64. 2
      striker-ui/out/index.html
  65. 2
      striker-ui/out/init.html
  66. 2
      striker-ui/out/login.html
  67. 2
      striker-ui/out/mail-config.html
  68. 2
      striker-ui/out/manage-element.html
  69. 2
      striker-ui/out/server.html
  70. 28
      striker-ui/package-lock.json
  71. 1
      striker-ui/types/APICommand.d.ts
  72. 5
      striker-ui/types/APIError.d.ts
  73. 1
      striker-ui/types/APIHost.d.ts
  74. 1
      striker-ui/types/ConfigPeerForm.d.ts
  75. 3
      striker-ui/types/CrudList.d.ts
  76. 1
      striker-ui/types/Dialog.d.ts
  77. 1
      striker-ui/types/ManageHost.d.ts
  78. 2
      striker-ui/types/ManageManifest.d.ts
  79. 4
      striker-ui/types/VncDisplay.d.ts
  80. 1
      striker-ui/types/novnc__novnc.d.ts
  81. 29
      tools/anvil-manage-vnc-pipe

File diff suppressed because one or more lines are too long

@ -3022,11 +3022,11 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -4144,9 +4144,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@ -8827,11 +8827,11 @@
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"requires": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
}
},
"browserslist": {
@ -9657,9 +9657,9 @@
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"requires": {
"to-regex-range": "^5.0.1"
}

@ -0,0 +1 @@
export const WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

@ -6,7 +6,9 @@ export * from './AN_VARIABLE_NAME_LIST';
export * from './DELETED';
export * from './ENV';
export * from './EXIT_CODE_LIST';
export * from './HOST_KEY_CHANGED_PREFIX';
export * from './LOCAL';
export * from './NODE_AND_DR_RESERVED_MEMORY_SIZE';
export * from './OS_LIST';
export * from './REG_EXP_PATTERNS';
export * from './WS_GUID';

@ -1,16 +1,19 @@
import assert from 'assert';
import { RequestHandler } from 'express';
import { REP_IPV4, REP_PEACEFUL_STRING } from '../../consts';
import { HOST_KEY_CHANGED_PREFIX } from '../../consts/HOST_KEY_CHANGED_PREFIX';
import {
HOST_KEY_CHANGED_PREFIX,
REP_IPV4,
REP_PEACEFUL_STRING,
} from '../../consts';
import { getLocalHostUUID, getPeerData, query } from '../../accessModule';
import { getPeerData, query } from '../../accessModule';
import { sanitize } from '../../sanitize';
import { perr } from '../../shell';
export const getHostSSH: RequestHandler<
unknown,
GetHostSshResponseBody,
GetHostSshResponseBody | ErrorResponseBody,
GetHostSshRequestBody
> = async (request, response) => {
const {
@ -42,36 +45,61 @@ export const getHostSSH: RequestHandler<
return response.status(400).send();
}
const localHostUUID = getLocalHostUUID();
let rsbody: GetHostSshResponseBody;
try {
rsbody = await getPeerData(target, { password, port });
} catch (subError) {
perr(`Failed to get peer data; CAUSE: ${subError}`);
} catch (error) {
const emsg = `Failed to get peer data; CAUSE: ${error}`;
perr(emsg);
const rserror: ErrorResponseBody = {
code: 'fe14fb1',
message: emsg,
name: 'AccessError',
};
return response.status(500).send();
return response.status(500).send(rserror);
}
if (!rsbody.isConnected) {
const rows: [stateNote: string, stateUUID: string][] = await query(`
SELECT sta.state_note, sta.state_uuid
FROM states AS sta
WHERE sta.state_host_uuid = '${localHostUUID}'
AND sta.state_name = '${HOST_KEY_CHANGED_PREFIX}${target}';`);
let states: [string, string][];
if (rows.length > 0) {
rsbody.badSSHKeys = rows.reduce<DeleteSshKeyConflictRequestBody>(
(previous, [, stateUUID]) => {
previous[localHostUUID].push(stateUUID);
try {
states = await query<[stateUuid: string, hostUuid: string][]>(`
SELECT a.state_uuid, a.state_host_uuid
FROM states AS a
WHERE a.state_name = '${HOST_KEY_CHANGED_PREFIX}${target}';`);
} catch (error) {
const emsg = `Failed to list SSH key conflicts; CAUSE: ${error}`;
perr(emsg);
const rserror: ErrorResponseBody = {
code: 'd5a2acf',
message: emsg,
name: 'AccessError',
};
return response.status(500).send(rserror);
}
if (states.length > 0) {
rsbody.badSshKeys = states.reduce<DeleteSshKeyConflictRequestBody>(
(previous, state) => {
const [stateUuid, hostUuid] = state;
const { [hostUuid]: list = [] } = previous;
list.push(stateUuid);
previous[hostUuid] = list;
return previous;
},
{ [localHostUUID]: [] },
{},
);
}
}
response.status(200).send(rsbody);
};

@ -1,6 +1,7 @@
import { getDatabaseConfigData, getLocalHostUUID } from '../../accessModule';
import { buildUnknownIDCondition } from '../../buildCondition';
import buildGetRequestHandler from '../buildGetRequestHandler';
import { buildQueryResultReducer } from '../../buildQueryResultModifier';
import { toLocal } from '../../convertHostUUID';
import { match } from '../../match';
import { pout, poutvar } from '../../shell';
@ -55,7 +56,7 @@ export const getHostConnection = buildGetRequestHandler(
let rawDatabaseData: AnvilDataDatabaseHash;
const hostUUIDField = 'ip_add.ip_address_host_uuid';
const hostUUIDField = 'a.ip_address_host_uuid';
const localHostUUID: string = getLocalHostUUID();
const { after: condHostUUIDs, before: beforeBuildIDCond } =
buildUnknownIDCondition(rawHostUUIDs, hostUUIDField, {
@ -88,52 +89,56 @@ export const getHostConnection = buildGetRequestHandler(
poutvar(connections, 'connections=');
if (buildQueryOptions) {
buildQueryOptions.afterQueryReturn = (queryStdout) => {
let result = queryStdout;
if (queryStdout instanceof Array) {
queryStdout.forEach(
([ipAddressUUID, hostUUID, ipAddress, network]) => {
const [, networkType, rawNetworkNumber, rawNetworkLinkNumber] =
match(network, /^([^\s]+)(\d+)_[^\s]+(\d+)$/);
const connectionKey = getConnectionKey(hostUUID);
buildQueryOptions.afterQueryReturn = buildQueryResultReducer(
(previous, row) => {
const [ipUuid, hostUuid, ip, ifaceId] = row;
connections[connectionKey].inbound.ipAddress[ipAddress] = {
hostUUID,
ipAddress,
ipAddressUUID,
networkLinkNumber: Number(rawNetworkLinkNumber),
networkNumber: Number(rawNetworkNumber),
const [, networkType, rNetworkNumber, rNetworkLinkNumber] = match(
ifaceId,
/^(.*n)(\d+)_link(\d+)$/,
);
const connectionKey = getConnectionKey(hostUuid);
connections[connectionKey].inbound.ipAddress[ip] = {
hostUUID: hostUuid,
ifaceId,
ipAddress: ip,
ipAddressUUID: ipUuid,
networkLinkNumber: Number(rNetworkLinkNumber),
networkNumber: Number(rNetworkNumber),
networkType,
};
return previous;
},
connections,
);
result = connections;
}
return result;
};
}
return `SELECT
ip_add.ip_address_uuid,
ip_add.ip_address_host_uuid,
ip_add.ip_address_address,
a.ip_address_uuid,
a.ip_address_host_uuid,
a.ip_address_address,
CASE
WHEN a.ip_address_on_type = 'interface'
THEN (
CASE
WHEN ip_add.ip_address_on_type = 'interface'
THEN net_int.network_interface_name
ELSE bon.bond_active_interface
WHEN b.network_interface_name ~* '.*n\\d+_link\\d+'
THEN b.network_interface_name
ELSE b.network_interface_device
END
)
ELSE d.bond_active_interface
END AS network_name
FROM ip_addresses AS ip_add
LEFT JOIN network_interfaces AS net_int
ON ip_add.ip_address_on_uuid = net_int.network_interface_uuid
LEFT JOIN bridges AS bri
ON ip_add.ip_address_on_uuid = bri.bridge_uuid
LEFT JOIN bonds AS bon
ON bri.bridge_uuid = bon.bond_bridge_uuid
OR ip_add.ip_address_on_uuid = bon.bond_uuid
FROM ip_addresses AS a
LEFT JOIN network_interfaces AS b
ON a.ip_address_on_uuid = b.network_interface_uuid
LEFT JOIN bridges AS c
ON a.ip_address_on_uuid = c.bridge_uuid
LEFT JOIN bonds AS d
ON c.bridge_uuid = d.bond_bridge_uuid
OR a.ip_address_on_uuid = d.bond_uuid
WHERE ${condHostUUIDs}
AND ip_add.ip_address_note != 'DELETED';`;
AND a.ip_address_note != 'DELETED';`;
},
);

@ -235,6 +235,8 @@ export const buildManifest = async (
`Fence name must be a peaceful string; got [${fenceName}]`,
);
if (!port) return;
assert(
REP_PEACEFUL_STRING.test(port),
`Port of ${fenceName} must be a peaceful string; got [${port}]`,

@ -1,4 +1,4 @@
import { HOST_KEY_CHANGED_PREFIX } from '../../consts/HOST_KEY_CHANGED_PREFIX';
import { HOST_KEY_CHANGED_PREFIX } from '../../consts';
import { getLocalHostUUID } from '../../accessModule';
import buildGetRequestHandler from '../buildGetRequestHandler';

@ -1,22 +1,24 @@
import { createHash } from 'crypto';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { P_UUID } from '../lib/consts';
import { P_UUID, WS_GUID } from '../lib/consts';
import { perr, pout } from '../lib/shell';
import { getVncinfo } from '../lib/accessModule';
import { cname } from '../lib/cname';
import { perr, pout, poutvar } from '../lib/shell';
const WS_SVR_VNC_URL_PREFIX = '/ws/server/vnc';
const getServerUuid = (url = '') =>
url.replace(new RegExp(`^${WS_SVR_VNC_URL_PREFIX}/(${P_UUID})`), '$1');
export const proxyServerVnc = createProxyMiddleware({
changeOrigin: true,
pathFilter: `${WS_SVR_VNC_URL_PREFIX}/*`,
router: async (request) => {
const { url = '' } = request;
const { url } = request;
const serverUuid = url.replace(
new RegExp(`^${WS_SVR_VNC_URL_PREFIX}/(${P_UUID})`),
'$1',
);
const serverUuid = getServerUuid(url);
pout(`Got param [${serverUuid}] from [${url}]`);
@ -38,17 +40,55 @@ export const proxyServerVnc = createProxyMiddleware({
error: (error, request, response) => {
perr(`VNC proxy error: ${error}`);
let resType: string;
if (!response) {
perr(`Missing response; got [${response}]`);
return;
}
const serverUuid = getServerUuid(request.url);
const errapiName = cname(`vncerror.${serverUuid}`);
const errapiObj: ErrorResponseBody = {
code: '72c969b',
message: error.message,
name: error.name,
};
const errapiStr = JSON.stringify(errapiObj);
const errapiValue = encodeURIComponent(errapiStr);
const errapiCookie = `${errapiName}=j:${errapiValue}; Path=/server; SameSite=Lax; Max-Age=3`;
poutvar({ errapiCookie }, 'Error cookie: ');
if ('writeHead' in response) {
resType = 'ServerResponse';
pout('Found ServerResponse object');
response.writeHead(500).end();
} else {
resType = 'Socket';
return response
.writeHead(500, {
'Set-Cookie': `${errapiCookie}`,
})
.end();
}
pout(`Response type = ${resType}`);
pout(`Found Socket object`);
const {
headers: { 'sec-websocket-key': wskey },
} = request;
const wsaccept = createHash('sha1')
.update(wskey + WS_GUID, 'binary')
.digest('base64');
const headers = [
'HTTP/1.1 101 Switching Protocols',
'Connection: upgrade',
`Sec-WebSocket-Accept: ${wsaccept}`,
`Set-Cookie: ${errapiCookie}`,
'Upgrade: websocket',
];
response.end(`${headers.join('\r\n')}\r\n`, 'utf-8');
},
},
ws: true,

@ -5,7 +5,7 @@ type GetHostSshRequestBody = {
};
type GetHostSshResponseBody = {
badSSHKeys?: DeleteSshKeyConflictRequestBody;
badSshKeys?: DeleteSshKeyConflictRequestBody;
hostName: string;
hostOS: string;
hostUUID: string;

@ -19,6 +19,7 @@ type HostConnectionOverview = {
ipAddress: {
[ipAddress: string]: {
hostUUID: string;
ifaceId: string;
ipAddress: string;
ipAddressUUID: string;
networkLinkNumber: number;

@ -9,4 +9,4 @@ type SshKeyConflict = {
};
};
type DeleteSshKeyConflictRequestBody = { [hostUUID: string]: string[] };
type DeleteSshKeyConflictRequestBody = Record<string, string[]>;

@ -0,0 +1,5 @@
type ErrorResponseBody = {
code: string;
message: string;
name: string;
};

@ -25,6 +25,7 @@ const CrudList = <
addHeader: rAddHeader,
editHeader: rEditHeader,
entriesUrl,
formDialogProps,
getAddLoading,
getDeleteErrorMessage,
getDeleteHeader,
@ -184,6 +185,8 @@ const CrudList = <
loading={getAddLoading?.call(null)}
ref={addDialogRef}
showClose
{...formDialogProps?.common}
{...formDialogProps?.add}
>
{renderAddForm(formTools)}
</DialogWithHeader>
@ -192,6 +195,8 @@ const CrudList = <
loading={getEditLoading(loadingEntry)}
ref={editDialogRef}
showClose
{...formDialogProps?.common}
{...formDialogProps?.edit}
>
{renderEditForm(formTools, entry)}
</DialogWithHeader>

@ -1,4 +1,4 @@
import { FC, ReactNode, useContext, useMemo } from 'react';
import { FC, ReactNode, useCallback, useContext, useMemo } from 'react';
import { DialogContext } from './Dialog';
import IconButton from '../IconButton';
@ -7,10 +7,29 @@ import sxstring from '../../lib/sxstring';
import { HeaderText } from '../Text';
const DialogHeader: FC<DialogHeaderProps> = (props) => {
const { children, showClose } = props;
const {
children,
onClose = ({ handlers: { base } }, ...args) => base?.call(null, ...args),
showClose,
} = props;
const dialogContext = useContext(DialogContext);
const closeHandler = useCallback<ButtonClickEventHandler>(
(...args) =>
onClose(
{
handlers: {
base: () => {
dialogContext?.setOpen(false);
},
},
},
...args,
),
[dialogContext, onClose],
);
const title = useMemo<ReactNode>(
() => sxstring(children, HeaderText),
[children],
@ -19,15 +38,9 @@ const DialogHeader: FC<DialogHeaderProps> = (props) => {
const close = useMemo<ReactNode>(
() =>
showClose && (
<IconButton
mapPreset="close"
onClick={() => {
dialogContext?.setOpen(false);
}}
size="small"
/>
<IconButton mapPreset="close" onClick={closeHandler} size="small" />
),
[dialogContext, showClose],
[closeHandler, showClose],
);
return (

@ -18,6 +18,7 @@ const DialogWithHeader: ForwardRefExoticComponent<
dialogProps,
header,
loading,
onClose,
openInitially,
showClose,
wide,
@ -31,7 +32,9 @@ const DialogWithHeader: ForwardRefExoticComponent<
ref={ref}
wide={wide}
>
<DialogHeader showClose={showClose}>{header}</DialogHeader>
<DialogHeader onClose={onClose} showClose={showClose}>
{header}
</DialogHeader>
{children}
</Dialog>
);

@ -15,7 +15,8 @@ import MenuItem from '../MenuItem';
import { Panel, PanelHeader } from '../Panels';
import ServerMenu from '../ServerMenu';
import Spinner from '../Spinner';
import { HeaderText } from '../Text';
import { BodyText, HeaderText } from '../Text';
import useCookieJar from '../../hooks/useCookieJar';
import useIsFirstRender from '../../hooks/useIsFirstRender';
const PREFIX = 'FullSize';
@ -43,7 +44,12 @@ const StyledDiv = styled('div')(() => ({
const VncDisplay = dynamic(() => import('./VncDisplay'), { ssr: false });
// Unit: seconds
const DEFAULT_VNC_RECONNECT_TIMER_START = 5;
const DEFAULT_VNC_RECONNECT_TIMER_START = 10;
const MAP_TO_WSCODE_MSG: Record<number, string> = {
1000: 'in-use by another process?',
1006: 'destination is down?',
};
const buildServerVncUrl = (host: string, serverUuid: string) =>
`ws://${host}/ws/server/vnc/${serverUuid}`;
@ -54,6 +60,7 @@ const FullSize: FC<FullSizeProps> = ({
serverName,
vncReconnectTimerStart = DEFAULT_VNC_RECONNECT_TIMER_START,
}): JSX.Element => {
const { buildCookieJar } = useCookieJar();
const isFirstRender = useIsFirstRender();
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
@ -61,7 +68,15 @@ const FullSize: FC<FullSizeProps> = ({
Partial<RfbConnectArgs> | undefined
>(undefined);
const [vncConnecting, setVncConnecting] = useState<boolean>(false);
const [vncError, setVncError] = useState<boolean>(false);
const [vncWsErrorMessage, setVncWsErrorMessage] = useState<
string | undefined
>();
const [vncApiErrorMessage, setVncApiErrorMessage] = useState<
string | undefined
>();
const [vncReconnectTimer, setVncReconnectTimer] = useState<number>(
vncReconnectTimerStart,
);
@ -138,17 +153,61 @@ const FullSize: FC<FullSizeProps> = ({
// 'disconnect' event emits when a connection fails,
// OR when a user closes the existing connection.
const rfbDisconnectEventHandler = useCallback(
({ detail: { clean } }) => {
if (!clean) {
(event) => {
const { detail } = event;
const { clean } = detail;
if (clean) return;
setVncConnecting(false);
setVncError(true);
updateVncReconnectTimer();
}
},
[updateVncReconnectTimer],
);
const wsCloseEventHandler = useCallback(
(event?: WebsockCloseEvent): void => {
if (!event) {
setVncWsErrorMessage(undefined);
return;
}
const { code: wscode, reason } = event;
let wsmsg = `ws: ${wscode}`;
const guess = MAP_TO_WSCODE_MSG[wscode];
if (guess) {
wsmsg += ` (${guess})`;
}
if (reason) {
wsmsg += `, ${reason}`;
}
setVncWsErrorMessage(wsmsg);
const vncerror = buildCookieJar()[
`suiapi.vncerror.${serverUUID}`
] as APIError;
if (!vncerror) {
setVncApiErrorMessage(undefined);
return;
}
const { code: apicode, message } = vncerror;
setVncApiErrorMessage(`api: ${apicode}, ${message}`);
},
[buildCookieJar, serverUUID],
);
const showScreen = useMemo(
() => !vncConnecting && !vncError,
[vncConnecting, vncError],
@ -281,27 +340,26 @@ const FullSize: FC<FullSizeProps> = ({
<VncDisplay
onConnect={rfbConnectEventHandler}
onDisconnect={rfbDisconnectEventHandler}
onWsClose={wsCloseEventHandler}
rfb={rfb}
rfbConnectArgs={rfbConnectArgs}
rfbScreen={rfbScreen}
/>
</Box>
{!showScreen && (
<Box display="flex" className={classes.spinnerBox}>
<Box display="flex" className={classes.spinnerBox} textAlign="center">
{vncConnecting && (
<>
<HeaderText textAlign="center">
Connecting to {serverName}.
</HeaderText>
<HeaderText>Connecting to {serverName}.</HeaderText>
<Spinner />
</>
)}
{vncError && (
<>
<HeaderText textAlign="center">
There was a problem connecting to the server.
</HeaderText>
<HeaderText textAlign="center" mt="1em">
<HeaderText>Can&apos;t connect to the server.</HeaderText>
<BodyText>{vncApiErrorMessage}</BodyText>
<BodyText>{vncWsErrorMessage}</BodyText>
<HeaderText mt=".5em">
Retrying in {vncReconnectTimer}.
</HeaderText>
</>

@ -1,4 +1,5 @@
import RFB from '@novnc/novnc/core/rfb';
import Websock from '@novnc/novnc/core/websock';
import { useEffect } from 'react';
const rfbConnect: RfbConnectFunction = ({
@ -9,6 +10,8 @@ const rfbConnect: RfbConnectFunction = ({
focusOnClick = false,
onConnect,
onDisconnect,
onWsClose,
onWsError,
qualityLevel = 6,
resizeSession = true,
rfb,
@ -45,6 +48,23 @@ const rfbConnect: RfbConnectFunction = ({
if (onDisconnect) {
rfb.current.addEventListener('disconnect', onDisconnect);
}
/* eslint-disable no-underscore-dangle */
const ws: typeof Websock = rfb.current._sock;
const socketClose = ws._eventHandlers.close;
const socketError = ws._eventHandlers.error;
ws.on('close', (e?: WebsockCloseEvent) => {
socketClose(e);
onWsClose?.call(null, e);
});
ws.on('error', (e: Event) => {
socketError(e);
onWsError?.call(null, e);
});
/* eslint-enable no-underscore-dangle */
};
const rfbDisconnect: RfbDisconnectFunction = (rfb) => {
@ -58,6 +78,8 @@ const VncDisplay = (props: VncDisplayProps): JSX.Element => {
const {
onConnect,
onDisconnect,
onWsClose,
onWsError,
rfb,
rfbConnectArgs,
rfbScreen,
@ -73,6 +95,8 @@ const VncDisplay = (props: VncDisplayProps): JSX.Element => {
const args: RfbConnectArgs = {
onConnect,
onDisconnect,
onWsClose,
onWsError,
rfb,
rfbScreen,
url,
@ -83,7 +107,16 @@ const VncDisplay = (props: VncDisplayProps): JSX.Element => {
} else {
rfbDisconnect(rfb);
}
}, [initUrl, onConnect, onDisconnect, rfb, rfbConnectArgs, rfbScreen]);
}, [
initUrl,
onConnect,
onDisconnect,
onWsClose,
onWsError,
rfb,
rfbConnectArgs,
rfbScreen,
]);
useEffect(
() => () => {

@ -1,6 +1,7 @@
import * as yup from 'yup';
import buildYupDynamicObject from '../../lib/buildYupDynamicObject';
import { yupLaxUuid } from '../../lib/yupMatches';
const fileLocationSchema = yup.object({ active: yup.boolean().required() });
@ -19,7 +20,7 @@ const fileSchema = yup.object({
}),
name: yup.string().required(),
type: yup.string().oneOf(['iso', 'other', 'script']),
uuid: yup.string().uuid().required(),
uuid: yupLaxUuid().required(),
});
const fileListSchema = yup.lazy((files) =>

@ -24,7 +24,10 @@ const renderEntryValueWithPassword: RenderFormValueFunction = (args) => {
const renderEntryValueBase: RenderFormValueFunction = (args) => {
const { entry, hasPassword } = args;
if (['', null, undefined].some((bad) => entry === bad)) {
if (
['', null, undefined].some((bad) => entry === bad) ||
Number.isNaN(entry)
) {
return <BodyText>none</BodyText>;
}

@ -16,6 +16,15 @@ const ManageHost: FC = () => {
return (
<CrudList<APIHostOverview, APIHostDetail>
formDialogProps={{
common: {
onClose: ({ handlers: { base } }, ...args) => {
base?.call(null, ...args);
// Delay to avoid visual changes until dialog is fully closed.
setTimeout(setInquireHostResponse, 500);
},
},
}}
addHeader="Initialize host"
editHeader=""
entriesUrl="/host?types=dr,node"
@ -38,7 +47,7 @@ const ManageHost: FC = () => {
}}
renderAddForm={(tools) => (
<>
<TestAccessForm setResponse={setInquireHostResponse} />
<TestAccessForm setResponse={setInquireHostResponse} tools={tools} />
{inquireHostResponse && (
<PrepareHostForm host={inquireHostResponse} tools={tools} />
)}

@ -10,17 +10,19 @@ import UncontrolledInput from '../UncontrolledInput';
import useFormikUtils from '../../hooks/useFormikUtils';
import Spinner from '../Spinner';
import schema from './testAccessSchema';
import { BodyText } from '../Text';
const TestAccessForm: FC<TestAccessFormProps> = (props) => {
const { setResponse } = props;
const { setResponse, tools } = props;
const messageGroupRef = useRef<MessageGroupForwardedRefContent>(null);
const [loadingInquiry, setLoadingInquiry] = useState<boolean>(false);
const [moreActions, setMoreActions] = useState<ContainedButtonProps[]>([]);
const setApiMessage = useCallback(
(message?: Message) =>
messageGroupRef?.current?.setMessage?.call(null, 'api', message),
messageGroupRef.current?.setMessage?.call(null, 'api', message),
[],
);
@ -33,6 +35,7 @@ const TestAccessForm: FC<TestAccessFormProps> = (props) => {
onSubmit: (values, { setSubmitting }) => {
setApiMessage();
setLoadingInquiry(true);
setMoreActions([]);
setResponse(undefined);
const { ip, password } = values;
@ -43,7 +46,72 @@ const TestAccessForm: FC<TestAccessFormProps> = (props) => {
password,
})
.then(({ data }) => {
const { isConnected } = data;
const { badSshKeys, isConnected } = data;
if (badSshKeys) {
setApiMessage({
children: (
<>
Host identification at {ip} changed. If this is valid,
please delete the conflicting SSH host key.
</>
),
type: 'warning',
});
setMoreActions([
{
background: 'red',
children: 'Delete keys',
onClick: () => {
tools.confirm.prepare({
actionProceedText: 'Delete',
content: (
<BodyText>
There&apos;s a different host key on {ip}, which could
mean a MITM attack. But if this change is expected,
you can delete the known host key(s) to resolve the
conflict.
</BodyText>
),
onProceedAppend: () => {
tools.confirm.loading(true);
api
.delete('/ssh-key/conflict', {
data: badSshKeys,
})
.then(() => {
tools.confirm.finish('Success', {
children: (
<>Started job to delete host key(s) for {ip}.</>
),
});
setMoreActions([]);
})
.catch((error) => {
const emsg = handleAPIError(error);
emsg.children = (
<>Failed to delete host key(s). {emsg.children}</>
);
tools.confirm.finish('Error', emsg);
});
},
proceedColour: 'red',
titleText: `Delete all known SSH host key(s) for ${ip}?`,
});
tools.confirm.open(true);
},
type: 'button',
},
]);
return;
}
if (!isConnected) {
setApiMessage({
@ -141,6 +209,7 @@ const TestAccessForm: FC<TestAccessFormProps> = (props) => {
) : (
<ActionGroup
actions={[
...moreActions,
{
background: 'blue',
children: 'Test access',

@ -1,13 +1,11 @@
import * as yup from 'yup';
import { REP_IPV4 } from '../../lib/consts/REG_EXP_PATTERNS';
import { yupIpv4, yupLaxUuid } from '../../lib/yupMatches';
const schema = yup.object().shape(
{
enterpriseKey: yup.string().uuid().optional(),
ip: yup.string().matches(REP_IPV4, {
message: 'Expected IP address to be a valid IPv4 address.',
}),
enterpriseKey: yupLaxUuid().optional(),
ip: yupIpv4().required(),
name: yup.string().required(),
redhatConfirmPassword: yup
.string()
@ -27,7 +25,7 @@ const schema = yup.object().shape(
String(redhatPassword).length > 0 ? field.required() : field.optional(),
),
type: yup.string().oneOf(['dr', 'subnode']).required(),
uuid: yup.string().uuid().required(),
uuid: yupLaxUuid().required(),
},
[['redhatUsername', 'redhatPassword']],
);

@ -1,14 +1,9 @@
import * as yup from 'yup';
import { REP_IPV4 } from '../../lib/consts/REG_EXP_PATTERNS';
import { yupIpv4 } from '../../lib/yupMatches';
const schema = yup.object({
ip: yup
.string()
.matches(REP_IPV4, {
message: 'Expected IP address to be a valid IPv4 address.',
})
.required(),
ip: yupIpv4().required(),
password: yup.string().required(),
});

@ -1,6 +1,7 @@
import * as yup from 'yup';
import buildYupDynamicObject from '../../lib/buildYupDynamicObject';
import { yupLaxUuid } from '../../lib/yupMatches';
const alertLevelSchema = yup.number().oneOf([0, 1, 2, 3, 4]);
@ -9,9 +10,9 @@ const alertOverrideSchema = yup.object({
level: alertLevelSchema.required(),
target: yup.object({
type: yup.string().oneOf(['node', 'subnode']).required(),
uuid: yup.string().uuid().required(),
uuid: yupLaxUuid().required(),
}),
uuid: yup.string().uuid().optional(),
uuid: yupLaxUuid().optional(),
});
const alertOverrideListSchema = yup.lazy((entries) =>
@ -24,7 +25,7 @@ const mailRecipientSchema = yup.object({
language: yup.string().oneOf(['en_CA']).optional(),
level: alertLevelSchema.required(),
name: yup.string().required(),
uuid: yup.string().uuid().optional(),
uuid: yupLaxUuid().optional(),
});
const mailRecipientListSchema = yup.lazy((entries) =>

@ -1,6 +1,7 @@
import * as yup from 'yup';
import buildYupDynamicObject from '../../lib/buildYupDynamicObject';
import { yupLaxUuid } from '../../lib/yupMatches';
const mailServerSchema = yup.object({
address: yup.string().required(),
@ -17,7 +18,7 @@ const mailServerSchema = yup.object({
port: yup.number().required(),
security: yup.string().oneOf(['none', 'starttls', 'tls-ssl']),
username: yup.string().optional(),
uuid: yup.string().uuid().required(),
uuid: yupLaxUuid().required(),
});
const mailServerListSchema = yup.lazy((mailServers) =>

@ -8,7 +8,6 @@ import AnIdInputGroup, {
} from './AnIdInputGroup';
import AnNetworkConfigInputGroup, {
INPUT_ID_ANC_DNS,
INPUT_ID_ANC_MTU,
INPUT_ID_ANC_NTP,
} from './AnNetworkConfigInputGroup';
import FlexBox from '../FlexBox';
@ -41,7 +40,6 @@ const AddManifestInputGroup = <
| typeof INPUT_ID_AI_PREFIX
| typeof INPUT_ID_AI_SEQUENCE
| typeof INPUT_ID_ANC_DNS
| typeof INPUT_ID_ANC_MTU
| typeof INPUT_ID_ANC_NTP]: string;
},
>({

@ -3,6 +3,7 @@ import { ReactElement, useMemo } from 'react';
import FlexBox from '../FlexBox';
import Grid from '../Grid';
import InputWithRef from '../InputWithRef';
import MessageBox from '../MessageBox';
import OutlinedInputWithLabel from '../OutlinedInputWithLabel';
import { InnerPanel, InnerPanelBody, InnerPanelHeader } from '../Panels';
import SwitchWithLabel from '../SwitchWithLabel';
@ -202,7 +203,6 @@ const AnHostInputGroup = <M extends MapToInputTestID>({
},
)}
onFirstRender={buildInputFirstRenderFunction(inputId)}
required
/>
),
};
@ -345,6 +345,15 @@ const AnHostInputGroup = <M extends MapToInputTestID>({
<Grid
columns={GRID_COLUMNS}
layout={{
'fence-message': {
children: (
<MessageBox>
It is recommended to provide 2 fence device ports.
</MessageBox>
),
width: '100%',
xs: 0,
},
...networkListGridLayout,
[inputCellIdAHIpmiIp]: {
children: (

@ -10,21 +10,16 @@ import Grid from '../Grid';
import IconButton from '../IconButton';
import InputWithRef from '../InputWithRef';
import OutlinedInputWithLabel from '../OutlinedInputWithLabel';
import {
buildIpCsvTestBatch,
buildNumberTestBatch,
} from '../../lib/test_input';
import { buildIpCsvTestBatch } from '../../lib/test_input';
const INPUT_ID_PREFIX_AN_NETWORK_CONFIG = 'an-network-config-input';
const INPUT_CELL_ID_PREFIX_ANC = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-cell`;
const INPUT_ID_ANC_DNS = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-dns`;
const INPUT_ID_ANC_MTU = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-mtu`;
const INPUT_ID_ANC_NTP = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-ntp`;
const INPUT_LABEL_ANC_DNS = 'DNS';
const INPUT_LABEL_ANC_MTU = 'MTU';
const INPUT_LABEL_ANC_NTP = 'NTP';
const DEFAULT_DNS_CSV = '8.8.8.8,8.8.4.4';
@ -73,17 +68,13 @@ const guessNetworkMinIp = ({
const AnNetworkConfigInputGroup = <
M extends MapToInputTestID & {
[K in
| typeof INPUT_ID_ANC_DNS
| typeof INPUT_ID_ANC_MTU
| typeof INPUT_ID_ANC_NTP]: string;
[K in typeof INPUT_ID_ANC_DNS | typeof INPUT_ID_ANC_NTP]: string;
},
>({
formUtils,
networkListEntries,
previous: {
dnsCsv: previousDnsCsv = DEFAULT_DNS_CSV,
mtu: previousMtu,
ntpCsv: previousNtpCsv,
} = {},
setNetworkList,
@ -448,41 +439,12 @@ const AnNetworkConfigInputGroup = <
/>
),
},
'an-network-config-input-cell-mtu': {
children: (
<InputWithRef
input={
<OutlinedInputWithLabel
id={INPUT_ID_ANC_MTU}
inputProps={{ placeholder: '1500' }}
label={INPUT_LABEL_ANC_MTU}
value={previousMtu}
/>
}
inputTestBatch={buildNumberTestBatch(
INPUT_LABEL_ANC_MTU,
() => {
setMessage(INPUT_ID_ANC_MTU);
},
{
onFinishBatch:
buildFinishInputTestBatchFunction(INPUT_ID_ANC_MTU),
},
(message) => {
setMessage(INPUT_ID_ANC_MTU, { children: message });
},
)}
onFirstRender={buildInputFirstRenderFunction(INPUT_ID_ANC_MTU)}
valueType="number"
/>
),
},
}}
spacing="1em"
/>
);
};
export { INPUT_ID_ANC_DNS, INPUT_ID_ANC_MTU, INPUT_ID_ANC_NTP };
export { INPUT_ID_ANC_DNS, INPUT_ID_ANC_NTP };
export default AnNetworkConfigInputGroup;

@ -7,7 +7,6 @@ import {
} from './AnIdInputGroup';
import {
INPUT_ID_ANC_DNS,
INPUT_ID_ANC_MTU,
INPUT_ID_ANC_NTP,
} from './AnNetworkConfigInputGroup';
import AddManifestInputGroup from './AddManifestInputGroup';
@ -19,7 +18,6 @@ const EditManifestInputGroup = <
| typeof INPUT_ID_AI_PREFIX
| typeof INPUT_ID_AI_SEQUENCE
| typeof INPUT_ID_ANC_DNS
| typeof INPUT_ID_ANC_MTU
| typeof INPUT_ID_ANC_NTP]: string;
},
>({

@ -18,7 +18,6 @@ import {
} from './AnNetworkInputGroup';
import {
INPUT_ID_ANC_DNS,
INPUT_ID_ANC_MTU,
INPUT_ID_ANC_NTP,
} from './AnNetworkConfigInputGroup';
import api from '../../lib/api';
@ -30,6 +29,7 @@ import FormSummary from '../FormSummary';
import handleAPIError from '../../lib/handleAPIError';
import IconButton from '../IconButton';
import List from '../List';
import MessageBox from '../MessageBox';
import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup';
import { Panel, PanelHeader } from '../Panels';
import periodicFetch from '../../lib/fetchers/periodicFetch';
@ -65,14 +65,10 @@ const getFormData = (
const { value: dnsCsv } = elements.namedItem(
INPUT_ID_ANC_DNS,
) as HTMLInputElement;
const { value: rawMtu } = elements.namedItem(
INPUT_ID_ANC_MTU,
) as HTMLInputElement;
const { value: ntpCsv } = elements.namedItem(
INPUT_ID_ANC_NTP,
) as HTMLInputElement;
const mtu = Number.parseInt(rawMtu, 10);
const sequence = Number.parseInt(rawSequence, 10);
return Object.values(elements).reduce<APIBuildManifestRequestBody>(
@ -104,7 +100,6 @@ const getFormData = (
hostConfig: { hosts: {} },
networkConfig: {
dnsCsv,
mtu,
networks: {},
ntpCsv,
},
@ -192,7 +187,6 @@ const ManageManifestPanel: FC = () => {
INPUT_ID_AI_PREFIX,
INPUT_ID_AI_SEQUENCE,
INPUT_ID_ANC_DNS,
INPUT_ID_ANC_MTU,
INPUT_ID_ANC_NTP,
],
messageGroupRef,
@ -243,6 +237,62 @@ const ManageManifestPanel: FC = () => {
[manifestTemplate],
);
const countHostFences = useCallback(
(
body: APIBuildManifestRequestBody,
): { counts: Record<string, number>; messages: React.ReactNode[] } => {
const {
hostConfig: { hosts },
} = body;
const counts = Object.values(hosts).reduce<Record<string, number>>(
(previous, host) => {
const { fences, hostType, hostNumber } = host;
const hostName = `${hostType.replace(
/node/,
'subnode',
)}${hostNumber}`;
if (!fences) {
previous[hostName] = 0;
return previous;
}
previous[hostName] = Object.values(fences).reduce<number>(
(count, fence) => {
const { fencePort } = fence;
const diff = fencePort.length ? 1 : 0;
return count + diff;
},
0,
);
return previous;
},
{},
);
const messages = Object.entries(counts).map((entry) => {
const [hostName, fenceCount] = entry;
return fenceCount ? (
<></>
) : (
<MessageBox key={`${hostName}-no-fence-port-message`}>
No fence device port specified for {hostName}.
</MessageBox>
);
});
return { counts, messages };
},
[],
);
const addManifestFormDialogProps = useMemo<ConfirmDialogProps>(
() => ({
actionProceedText: 'Add',
@ -260,6 +310,7 @@ const ManageManifestPanel: FC = () => {
),
onSubmitAppend: (...args) => {
const body = getFormData(...args);
const { messages } = countHostFences(body);
setConfirmDialogProps({
actionProceedText: 'Add',
@ -276,6 +327,7 @@ const ManageManifestPanel: FC = () => {
url: '/manifest',
});
},
preActionArea: <FlexBox spacing=".3em">{messages}</FlexBox>,
titleText: `Add install manifest?`,
});
@ -284,6 +336,7 @@ const ManageManifestPanel: FC = () => {
titleText: 'Add an install manifest',
}),
[
countHostFences,
formUtils,
getManifestOverviews,
knownFences,
@ -309,6 +362,7 @@ const ManageManifestPanel: FC = () => {
),
onSubmitAppend: (...args) => {
const body = getFormData(...args);
const { messages } = countHostFences(body);
setConfirmDialogProps({
actionProceedText: 'Edit',
@ -325,6 +379,7 @@ const ManageManifestPanel: FC = () => {
url: `/manifest/${mdetailUuid}`,
});
},
preActionArea: <FlexBox spacing=".3em">{messages}</FlexBox>,
titleText: `Update install manifest ${mdetailName}?`,
});
@ -334,16 +389,17 @@ const ManageManifestPanel: FC = () => {
titleText: `Update install manifest ${mdetailName}`,
}),
[
countHostFences,
formUtils,
getManifestOverviews,
isLoadingManifestDetail,
knownFences,
knownUpses,
manifestDetail,
isLoadingManifestDetail,
mdetailName,
mdetailUuid,
setConfirmDialogProps,
submitForm,
mdetailUuid,
getManifestOverviews,
],
);

@ -45,12 +45,7 @@ const RunManifestInputGroup = <M extends MapToInputTestID>({
const passwordRef = useRef<InputForwardedRefContent<'string'>>({});
const { hosts: initHostList = {} } = hostConfig;
const {
dnsCsv,
mtu,
networks: initNetworkList = {},
ntpCsv = MANIFEST_PARAM_NONE,
} = networkConfig;
const { dnsCsv, networks: initNetworkList = {}, ntpCsv } = networkConfig;
const hostListEntries = useMemo(
() => Object.entries(initHostList),
@ -195,12 +190,10 @@ const RunManifestInputGroup = <M extends MapToInputTestID>({
};
hostListEntries.forEach(([hostId, { networks = {} }]) => {
const {
[networkId]: { networkIp: ip = MANIFEST_PARAM_NONE } = {},
} = networks;
const { [networkId]: { networkIp: ip = '' } = {} } = networks;
hostNetworks[`${idPrefix}-${hostId}-ip`] = {
children: <MonoText>{ip}</MonoText>,
children: <MonoText>{ip || MANIFEST_PARAM_NONE}</MonoText>,
};
});
@ -237,11 +230,10 @@ const RunManifestInputGroup = <M extends MapToInputTestID>({
};
hostListEntries.forEach(([hostId, { fences = {} }]) => {
const { [fenceName]: { fencePort = MANIFEST_PARAM_NONE } = {} } =
fences;
const { [fenceName]: { fencePort = '' } = {} } = fences;
previous[`${idPrefix}-${hostId}-port`] = {
children: <MonoText>{fencePort}</MonoText>,
children: <MonoText>{fencePort || MANIFEST_PARAM_NONE}</MonoText>,
};
});
@ -426,19 +418,13 @@ const RunManifestInputGroup = <M extends MapToInputTestID>({
children: <BodyText>DNS</BodyText>,
},
'run-manifest-dns-csv-cell': {
children: <EndMono>{dnsCsv}</EndMono>,
children: <EndMono>{dnsCsv || MANIFEST_PARAM_NONE}</EndMono>,
},
'run-manifest-ntp-csv-cell-header': {
children: <BodyText>NTP</BodyText>,
},
'run-manifest-ntp-csv-cell': {
children: <EndMono>{ntpCsv}</EndMono>,
},
'run-manifest-mtu-cell-header': {
children: <BodyText>MTU</BodyText>,
},
'run-manifest-mtu-cell': {
children: <EndMono>{mtu}</EndMono>,
children: <EndMono>{ntpCsv || MANIFEST_PARAM_NONE}</EndMono>,
},
}}
spacing="0.4em"

@ -1578,6 +1578,9 @@ const ProvisionServerDialog = ({
);
setIsProvisionServerDataReady(true);
})
.catch(() => {
// Ignore for now; the 'no resources' message would trigger.
});
}, [initLimits]);

@ -67,12 +67,16 @@ const ConfigPeersForm: FC<ConfigPeerFormProps> = ({
Object.entries(ipAddressList).reduce<InboundConnectionList>(
(
nyu,
[ipAddress, { networkLinkNumber, networkNumber, networkType }],
[
ipAddress,
{ ifaceId, networkLinkNumber, networkNumber, networkType },
],
) => {
nyu[ipAddress] = {
...previous[ipAddress],
dbPort,
dbUser,
ifaceId,
ipAddress,
networkLinkNumber,
networkNumber,
@ -135,13 +139,20 @@ const ConfigPeersForm: FC<ConfigPeerFormProps> = ({
listItems={inboundConnections}
renderListItem={(
ipAddress,
{ dbPort, dbUser, networkNumber, networkType },
) => (
{ dbPort, dbUser, ifaceId, networkNumber, networkType },
) => {
const network: string =
NETWORK_TYPES[networkType] && networkNumber
? `${NETWORK_TYPES[networkType]} ${networkNumber}`
: `Unknown network; interface: ${ifaceId}`;
return (
<FlexBox spacing={0} sx={{ width: '100%' }}>
<MonoText>{`${dbUser}@${ipAddress}:${dbPort}`}</MonoText>
<SmallText>{`${NETWORK_TYPES[networkType]} ${networkNumber}`}</SmallText>
<SmallText>{network}</SmallText>
</FlexBox>
)}
);
}}
/>
</Grid>
<Grid item xs={1}>

@ -4,6 +4,7 @@ import useIsFirstRender from './useIsFirstRender';
const useCookieJar = (): {
cookieJar: CookieJar;
buildCookieJar: () => CookieJar;
getCookie: <T>(key: string) => T | undefined;
getSession: () => SessionCookie | undefined;
getSessionUser: () => SessionCookieUser | undefined;
@ -12,25 +13,10 @@ const useCookieJar = (): {
const [cookieJar, setCookieJar] = useState<CookieJar>({});
const getCookie = useCallback(
<T>(key: string, prefix = 'suiapi.') =>
cookieJar[`${prefix}${key}`] as T | undefined,
[cookieJar],
);
const getSession = useCallback(
() => getCookie<SessionCookie>('session'),
[getCookie],
);
const getSessionUser = useCallback(() => getSession()?.user, [getSession]);
useEffect(() => {
if (isFirstRender) {
const buildCookieJar = useCallback(() => {
const lines = document.cookie.split(/\s*;\s*/);
setCookieJar(
lines.reduce<CookieJar>((previous, line) => {
const jar = lines.reduce<CookieJar>((previous, line) => {
const [key, value] = line.split('=', 2);
const decoded = decodeURIComponent(value);
@ -50,13 +36,33 @@ const useCookieJar = (): {
previous[key] = result;
return previous;
}, {}),
}, {});
return jar;
}, []);
const getCookie = useCallback(
<T>(key: string, prefix = 'suiapi.') =>
cookieJar[`${prefix}${key}`] as T | undefined,
[cookieJar],
);
const getSession = useCallback(
() => getCookie<SessionCookie>('session'),
[getCookie],
);
const getSessionUser = useCallback(() => getSession()?.user, [getSession]);
useEffect(() => {
if (isFirstRender) {
setCookieJar(buildCookieJar());
}
}, [isFirstRender]);
}, [buildCookieJar, isFirstRender]);
return {
cookieJar,
buildCookieJar,
getCookie,
getSession,
getSessionUser,

@ -0,0 +1,17 @@
import * as yup from 'yup';
import { REP_IPV4, REP_UUID } from './consts/REG_EXP_PATTERNS';
/**
* This is OK because yup uses the template string syntax internally to access
* the field name.
*/
/* eslint-disable no-template-curly-in-string */
export const yupLaxUuid = () =>
yup.string().matches(REP_UUID, { message: '${path} must be a valid UUID' });
export const yupIpv4 = () =>
yup
.string()
.matches(REP_IPV4, { message: '${path} must be a valid IPv4 address' });

@ -1 +0,0 @@
self.__BUILD_MANIFEST=function(s,c,a,e,t,i,n,f,b,u,k,h,j,g,r,d,l,_){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,a,e,t,i,n,b,"static/chunks/715-31c42cd57b463d49.js",c,f,u,r,d,"static/chunks/pages/index-f7680dbed4474b4b.js"],"/_error":["static/chunks/pages/_error-8447282b6bcee29e.js"],"/anvil":[s,a,e,t,i,n,b,"static/chunks/680-2258b21ffebaf50b.js",c,f,r,"static/chunks/pages/anvil-c29ee8fc3eea3417.js"],"/config":[s,a,e,i,k,h,c,u,"static/chunks/pages/config-cab528473cc20327.js"],"/file-manager":[s,a,e,t,n,k,j,"static/chunks/579-6ba9d1157accb8a7.js",c,f,g,"static/chunks/pages/file-manager-1086cc9ed94415ae.js"],"/init":[s,a,t,i,n,b,h,l,c,f,_,"static/chunks/pages/init-afbc75b7ee36cb21.js"],"/login":[s,a,e,i,c,u,"static/chunks/pages/login-f5cfbd1de52c490d.js"],"/mail-config":[s,a,e,t,i,n,b,k,j,c,f,g,"static/chunks/pages/mail-config-cc0f4d97fffbb64c.js"],"/manage-element":[s,a,e,t,i,n,b,k,j,h,l,"static/chunks/858-f6bfa9b45bc673cc.js",c,f,u,g,_,"static/chunks/pages/manage-element-0a2d309344524020.js"],"/server":[s,e,t,c,d,"static/chunks/pages/server-5cd5f165d40a1eaa.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/mail-config","/manage-element","/server"]}}("static/chunks/572-b5c29784d1349ae1.js","static/chunks/616-c4d59f8a6d39d5a4.js","static/chunks/442-b751672afa3cc53f.js","static/chunks/318-35524f40e72b9bd4.js","static/chunks/341-bdaf9b2461a83319.js","static/chunks/514-4ce501d9fa08982c.js","static/chunks/242-912372df2bb37c32.js","static/chunks/762-c3bdcfb38ea6ff94.js","static/chunks/74-9720e9bc600a2719.js","static/chunks/761-7379298625e9125e.js","static/chunks/461-8504faeaf244aab6.js","static/chunks/982-a80463e6b63f11a0.js","static/chunks/602-32dbc2a66990c0a6.js","static/chunks/845-b3d5dd7a156a9380.js","static/chunks/466-6093dd3c9e9ea062.js","static/chunks/16-633a4da2be332451.js","static/chunks/161-e5c89be90a214bca.js","static/chunks/784-0aa3ea101d582664.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

@ -0,0 +1 @@
self.__BUILD_MANIFEST=function(s,c,a,e,t,i,n,f,b,u,k,h,j,d,g,r,l,_){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,a,e,t,i,n,b,"static/chunks/715-31c42cd57b463d49.js",c,f,u,g,r,"static/chunks/pages/index-f7680dbed4474b4b.js"],"/_error":["static/chunks/pages/_error-8447282b6bcee29e.js"],"/anvil":[s,a,e,t,i,n,b,"static/chunks/680-2258b21ffebaf50b.js",c,f,g,"static/chunks/pages/anvil-c29ee8fc3eea3417.js"],"/config":[s,a,e,i,k,h,c,u,"static/chunks/pages/config-396facf1669ffe17.js"],"/file-manager":[s,a,e,t,n,k,j,"static/chunks/579-6ba9d1157accb8a7.js",c,f,d,"static/chunks/pages/file-manager-a836cefe1c1c7d5f.js"],"/init":[s,a,t,i,n,b,h,l,c,f,_,"static/chunks/pages/init-afbc75b7ee36cb21.js"],"/login":[s,a,e,i,c,u,"static/chunks/pages/login-9acb46ff70465046.js"],"/mail-config":[s,a,e,t,i,n,b,k,j,c,f,d,"static/chunks/pages/mail-config-4ecabfd783a4abaf.js"],"/manage-element":[s,a,e,t,i,n,b,k,j,h,l,"static/chunks/858-f6bfa9b45bc673cc.js",c,f,u,d,_,"static/chunks/pages/manage-element-3d0a368d3c926f1d.js"],"/server":[s,e,t,c,r,"static/chunks/pages/server-9fd04502dddda042.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/mail-config","/manage-element","/server"]}}("static/chunks/572-b5c29784d1349ae1.js","static/chunks/616-c4d59f8a6d39d5a4.js","static/chunks/442-b751672afa3cc53f.js","static/chunks/318-35524f40e72b9bd4.js","static/chunks/341-bdaf9b2461a83319.js","static/chunks/514-4ce501d9fa08982c.js","static/chunks/242-912372df2bb37c32.js","static/chunks/762-6137fd9eb5f130da.js","static/chunks/74-9720e9bc600a2719.js","static/chunks/761-7379298625e9125e.js","static/chunks/461-8504faeaf244aab6.js","static/chunks/982-a80463e6b63f11a0.js","static/chunks/602-32dbc2a66990c0a6.js","static/chunks/512-56563c67ec35f070.js","static/chunks/466-40a89715cb183656.js","static/chunks/16-8f130ff153ed09e1.js","static/chunks/161-e5c89be90a214bca.js","static/chunks/784-0aa3ea101d582664.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

@ -0,0 +1 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[724],{38724:function(e,r,n){n.r(r);var t=n(85893),c=n(94460),u=n(67294);let l=e=>{let{background:r="",clipViewport:n=!1,compressionLevel:t=2,dragViewport:u=!1,focusOnClick:l=!1,onConnect:o,onDisconnect:s,onWsClose:i,onWsError:a,qualityLevel:d=6,resizeSession:v=!0,rfb:f,rfbScreen:E,scaleViewport:p=!0,showDotCursor:m=!1,url:w,viewOnly:h=!1}=e;if(!(null==E?void 0:E.current)||(null==f?void 0:f.current))return;E.current.innerHTML="",f.current=new c.Z(E.current,w),f.current.background=r,f.current.clipViewport=n,f.current.compressionLevel=t,f.current.dragViewport=u,f.current.focusOnClick=l,f.current.qualityLevel=d,f.current.resizeSession=v,f.current.scaleViewport=p,f.current.showDotCursor=m,f.current.viewOnly=h,o&&f.current.addEventListener("connect",o),s&&f.current.addEventListener("disconnect",s);let k=f.current._sock,_=k._eventHandlers.close,L=k._eventHandlers.error;k.on("close",e=>{_(e),null==i||i.call(null,e)}),k.on("error",e=>{L(e),null==a||a.call(null,e)})},o=e=>{(null==e?void 0:e.current)&&(e.current.disconnect(),e.current=null)},s=e=>{let{onConnect:r,onDisconnect:n,onWsClose:c,onWsError:s,rfb:i,rfbConnectArgs:a,rfbScreen:d,url:v}=e;return(0,u.useEffect)(()=>{if(a){let{url:e=v}=a;e&&l({onConnect:r,onDisconnect:n,onWsClose:c,onWsError:s,rfb:i,rfbScreen:d,url:e,...a})}else o(i)},[v,r,n,c,s,i,a,d]),(0,u.useEffect)(()=>()=>{o(i)},[i]),(0,t.jsx)("div",{style:{width:"100%",height:"75vh"},ref:d,onMouseEnter:()=>{document.activeElement&&document.activeElement instanceof HTMLElement&&document.activeElement.blur(),(null==i?void 0:i.current)&&i.current.focus()}})};s.displayName="VncDisplay",r.default=s}}]);

@ -1 +0,0 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[724],{38724:function(e,r,n){n.r(r);var t=n(85893),c=n(94460),u=n(67294);let l=e=>{let{background:r="",clipViewport:n=!1,compressionLevel:t=2,dragViewport:u=!1,focusOnClick:l=!1,onConnect:i,onDisconnect:s,qualityLevel:o=6,resizeSession:d=!0,rfb:a,rfbScreen:v,scaleViewport:f=!0,showDotCursor:E=!1,url:p,viewOnly:m=!1}=e;(null==v?void 0:v.current)&&(null==a||!a.current)&&(v.current.innerHTML="",a.current=new c.Z(v.current,p),a.current.background=r,a.current.clipViewport=n,a.current.compressionLevel=t,a.current.dragViewport=u,a.current.focusOnClick=l,a.current.qualityLevel=o,a.current.resizeSession=d,a.current.scaleViewport=f,a.current.showDotCursor=E,a.current.viewOnly=m,i&&a.current.addEventListener("connect",i),s&&a.current.addEventListener("disconnect",s))},i=e=>{(null==e?void 0:e.current)&&(e.current.disconnect(),e.current=null)},s=e=>{let{onConnect:r,onDisconnect:n,rfb:c,rfbConnectArgs:s,rfbScreen:o,url:d}=e;return(0,u.useEffect)(()=>{if(s){let{url:e=d}=s;e&&l({onConnect:r,onDisconnect:n,rfb:c,rfbScreen:o,url:e,...s})}else i(c)},[d,r,n,c,s,o]),(0,u.useEffect)(()=>()=>{i(c)},[c]),(0,t.jsx)("div",{style:{width:"100%",height:"75vh"},ref:o,onMouseEnter:()=>{document.activeElement&&document.activeElement instanceof HTMLElement&&document.activeElement.blur(),(null==c?void 0:c.current)&&c.current.focus()}})};s.displayName="VncDisplay",r.default=s}}]);

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

@ -1 +1 @@
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){return"static/chunks/"+e+"."+({460:"5494ba1e4d778d0d",724:"9b0f3b59b9f819ec"})[e]+".js"},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/_next/",i={272:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}else i[e]=0}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){return"static/chunks/"+e+"."+({460:"5494ba1e4d778d0d",724:"74a0b8e0158ff12a"})[e]+".js"},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/_next/",i={272:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}else i[e]=0}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();

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

@ -2842,12 +2842,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -4149,9 +4149,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@ -9247,12 +9247,12 @@
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
}
},
"browserslist": {
@ -10238,9 +10238,9 @@
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"

@ -1,4 +1,5 @@
type APICommandInquireHostResponseBody = {
badSshKeys?: Record<string, string[]>;
hostName: string;
hostOS: string;
hostUUID: string;

@ -0,0 +1,5 @@
type APIError = {
code: string;
message: string;
name: string;
};

@ -4,6 +4,7 @@ type APIHostConnectionOverviewList = {
ipAddress: {
[ipAddress: string]: {
hostUUID: string;
ifaceId: string;
ipAddress: string;
ipAddressUUID: string;
networkLinkNumber: number;

@ -2,6 +2,7 @@ type InboundConnectionList = {
[ipAddress: string]: {
dbPort: number;
dbUser: string;
ifaceId: string;
ipAddress: string;
networkLinkNumber: number;
networkNumber: number;

@ -25,6 +25,9 @@ type DeletePromiseChainGetter<T> = (
type CrudListOptionalProps<Overview> = {
entryUrlPrefix?: string;
formDialogProps?: Partial<
Record<'add' | 'common' | 'edit', Partial<DialogWithHeaderProps>>
>;
getAddLoading?: (previous?: boolean) => boolean;
getDeletePromiseChain?: <T>(
base: DeletePromiseChainGetter<T>,

@ -39,6 +39,7 @@ type DialogActionGroupProps = DialogActionGroupOptionalProps;
/** DialogHeader */
type DialogHeaderOptionalProps = {
onClose?: ExtendableEventHandler<ButtonClickEventHandler>;
showClose?: boolean;
};

@ -14,6 +14,7 @@ type TestAccessFormProps = {
setResponse: React.Dispatch<
React.SetStateAction<InquireHostResponse | undefined>
>;
tools: CrudListFormTools;
};
/** PrepareHostForm */

@ -18,8 +18,6 @@ type ManifestNetworkList = {
type ManifestNetworkConfig = {
dnsCsv: string;
/** Max Transmission Unit (MTU); unit: bytes */
mtu: number;
networks: ManifestNetworkList;
ntpCsv: string;
};

@ -4,6 +4,8 @@ type RfbRef = import('react').MutableRefObject<
type RfbScreenRef = import('react').MutableRefObject<HTMLDivElement | null>;
type WebsockCloseEvent = Event & { code: number; reason: string };
type RfbConnectArgs = {
background?: string;
clipViewport?: boolean;
@ -12,6 +14,8 @@ type RfbConnectArgs = {
focusOnClick?: boolean;
onConnect?: () => void;
onDisconnect?: (event: { detail: { clean: boolean } }) => void;
onWsClose?: (event?: WebsockCloseEvent) => void;
onWsError?: (event: Event) => void;
qualityLevel?: number;
resizeSession?: boolean;
rfb: RfbRef;

@ -1 +1,2 @@
declare module '@novnc/novnc/core/rfb';
declare module '@novnc/novnc/core/websock';

@ -46,7 +46,7 @@ my $server_vnc_port = $anvil->data->{switches}{'server-vnc-port'};
if (defined $server)
{
$server_uuid //= is_uuid_v4($server) ? $server : $anvil->Get->server_uuid_from_name({ server_name => $server });
$server_uuid //= $anvil->Validate->uuid({ uuid => $server }) ? $server : $anvil->Get->server_uuid_from_name({ server_name => $server });
}
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $switch_debug, list => {
@ -99,8 +99,7 @@ sub build_find_available_port_call
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "build_find_available_port_call" });
return (1) if ( (not $step_operator =~ /^[+-]$/)
|| (not is_int($step_size)) || ($step_size < 1) );
return (1) if ( (not $step_operator =~ /^[+-]$/) || (not $anvil->Validate->positive_integer({ number => $step_size })) );
my $call = "ss_output=\$($ss -ant) && port=${start} && while $grep -Eq \":\${port}[[:space:]]+[^[:space:]]+\" <<<\$ss_output; do (( port ${step_operator}= $step_size )); done && $echo \$port";
@ -203,7 +202,7 @@ sub find_server_vnc_port
return (1) if (not defined $svr_uuid);
return (0, $svr_vnc_port) if (is_int($svr_vnc_port));
return (0, $svr_vnc_port) if ($anvil->Validate->positive_integer({ number => $svr_vnc_port }));
# If we don't have the server's VNC port, find it in its qemu-kvm process.
@ -253,16 +252,6 @@ sub find_ws_processes
return (0, $result);
}
sub is_int
{
return defined $_[0] && $_[0] =~ /^\d+$/;
}
sub is_uuid_v4
{
return defined $_[0] && $_[0] =~ /[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/;
}
sub prettify
{
my $var_value = shift;
@ -325,7 +314,7 @@ sub set_vncinfo_variable
variable_value => "${local_host_name}:${end_port}",
});
return (1) if (not is_uuid_v4($variable_uuid));
return (1) if (not $anvil->Validate->uuid({ uuid => $variable_uuid }));
return (0);
}
@ -377,7 +366,7 @@ sub start_pipe
my $svr_uuid = $parameters->{svr_uuid};
my $svr_vnc_port = $parameters->{svr_vnc_port};
return (1, __LINE__.": [$svr_uuid]") if (not is_uuid_v4($svr_uuid));
return (1, __LINE__.": [$svr_uuid]") if (not $anvil->Validate->uuid({ uuid => $svr_uuid }));
my $common_params = { debug => $debug };
@ -416,8 +405,8 @@ sub start_ws
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "start_ws" });
return (1) if ( (not defined $ws_processes)
|| (not is_int($svr_vnc_port))
|| (not is_int($ws_sport_offset)) );
|| (not $anvil->Validate->positive_integer({ number => $svr_vnc_port }))
|| (not $anvil->Validate->positive_integer({ number => $ws_sport_offset })) );
my $existing_ws_pids = $ws_processes->{targets}{$svr_vnc_port};
@ -462,7 +451,7 @@ sub stop_pipe
my $svr_uuid = $parameters->{svr_uuid};
my $svr_vnc_port = $parameters->{svr_vnc_port};
return (1, __LINE__.": [$svr_uuid]") if (not is_uuid_v4($svr_uuid));
return (1, __LINE__.": [$svr_uuid]") if (not $anvil->Validate->uuid({ uuid => $svr_uuid }));
my $common_params = { debug => $debug };
@ -511,7 +500,7 @@ sub stop_ws
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "stop_ws" });
return (1) if ( (not is_int($ws_pid)) || (not defined $ws_processes) );
return (1) if ( (not $anvil->Validate->positive_integer({ number => $ws_pid })) || (not defined $ws_processes) );
call({ debug => $debug, call => "$kill $ws_pid || $kill -9 $ws_pid" });

Loading…
Cancel
Save