Merge pull request #427 from ylei-tsubame/tmp/rebuild

Web UI: fixes to resolve #410, #418, #421, #426, and #429
main
Digimer 1 year ago committed by GitHub
commit 2c747600b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      striker-ui-api/out/index.js
  2. 2
      striker-ui-api/src/lib/consts/REG_EXP_PATTERNS.ts
  3. 50
      striker-ui-api/src/lib/request_handlers/host/buildQueryHostDetail.ts
  4. 1
      striker-ui-api/src/lib/request_handlers/host/prepareNetwork.ts
  5. 2
      striker-ui/components/Display/Preview.tsx
  6. 2
      striker-ui/components/Hosts/AnvilHost.tsx
  7. 2
      striker-ui/components/Hosts/index.tsx
  8. 24
      striker-ui/components/NetworkInitForm.tsx
  9. 6
      striker-ui/components/PrepareHostForm.tsx
  10. 24
      striker-ui/components/ProvisionServerDialog.tsx
  11. 29
      striker-ui/components/StrikerConfig/AddPeerDialog.tsx
  12. 18
      striker-ui/lib/consts/HOST_STATUS.ts
  13. 18
      striker-ui/lib/consts/NODES.ts
  14. 2
      striker-ui/lib/consts/REG_EXP_PATTERNS.ts
  15. 1
      striker-ui/out/_next/static/8B2qFvgX8u8D8ZWgE33V8/_buildManifest.js
  16. 1
      striker-ui/out/_next/static/E2IG-69g4AQF1Cz2zUpmh/_buildManifest.js
  17. 0
      striker-ui/out/_next/static/E2IG-69g4AQF1Cz2zUpmh/_middlewareManifest.js
  18. 0
      striker-ui/out/_next/static/E2IG-69g4AQF1Cz2zUpmh/_ssgManifest.js
  19. 2
      striker-ui/out/_next/static/chunks/692-e32ff331a198ffb5.js
  20. 2
      striker-ui/out/_next/static/chunks/825-f20d177b43a24683.js
  21. 1
      striker-ui/out/_next/static/chunks/899-83e9de2a35c6bcf0.js
  22. 1
      striker-ui/out/_next/static/chunks/899-ec535b0f0a173e21.js
  23. 2
      striker-ui/out/_next/static/chunks/94-e103c3735f0e061b.js
  24. 1
      striker-ui/out/_next/static/chunks/pages/anvil-c0917177269e4c45.js
  25. 1
      striker-ui/out/_next/static/chunks/pages/anvil-c1177b17efcafc34.js
  26. 1
      striker-ui/out/_next/static/chunks/pages/config-0ecdb2b2b3f8c089.js
  27. 1
      striker-ui/out/_next/static/chunks/pages/config-af002475ca739544.js
  28. 2
      striker-ui/out/_next/static/chunks/pages/index-7c2cb48473145987.js
  29. 2
      striker-ui/out/_next/static/chunks/pages/manage-element-3b79eff498885bcb.js
  30. 2
      striker-ui/out/anvil.html
  31. 2
      striker-ui/out/config.html
  32. 2
      striker-ui/out/file-manager.html
  33. 2
      striker-ui/out/index.html
  34. 2
      striker-ui/out/init.html
  35. 2
      striker-ui/out/login.html
  36. 2
      striker-ui/out/manage-element.html
  37. 2
      striker-ui/out/server.html
  38. 2
      striker-ui/pages/index.tsx
  39. 2
      striker-ui/pages/manage-element/index.tsx
  40. 20
      striker-ui/types/APIHost.d.ts

File diff suppressed because one or more lines are too long

@ -3,7 +3,7 @@ export const P_OCTET = '(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]|)[0-9])';
export const P_ALPHANUM = '[a-z0-9]';
export const P_ALPHANUM_DASH = '[a-z0-9-]';
export const P_IPV4 = `(?:${P_OCTET}[.]){3}${P_OCTET}`;
export const P_UUID = `${P_HEX}{8}-${P_HEX}{4}-[1-5]${P_HEX}{3}-[89ab]${P_HEX}{3}-${P_HEX}{12}`;
export const P_UUID = `${P_HEX}{8}-(?:${P_HEX}{4}-){3}${P_HEX}{12}`;
export const REP_DOMAIN = new RegExp(
`^(?:${P_ALPHANUM}(?:${P_ALPHANUM_DASH}{0,61}${P_ALPHANUM})?[.])+${P_ALPHANUM}${P_ALPHANUM_DASH}{0,61}${P_ALPHANUM}$`,

@ -42,45 +42,49 @@ const setCvar = (
export const buildQueryHostDetail: BuildQueryDetailFunction = ({
keys: hostUUIDs = '*',
} = {}) => {
const condHostUUIDs = buildKnownIDCondition(hostUUIDs, 'AND b.host_uuid');
const condHostUUIDs = buildKnownIDCondition(hostUUIDs, 'a.host_uuid');
stdout(`condHostUUIDs=[${condHostUUIDs}]`);
const query = `
SELECT
b.host_name,
b.host_type,
b.host_uuid,
a.variable_name,
a.variable_value,
a.host_name,
a.host_type,
a.host_uuid,
b.variable_name,
b.variable_value,
SUBSTRING(
a.variable_name, '${CVAR_PREFIX_PATTERN}([^:]+)'
b.variable_name, '${CVAR_PREFIX_PATTERN}([^:]+)'
) as cvar_name,
SUBSTRING(
a.variable_name, '${CVAR_PREFIX_PATTERN}([a-z]{2,3})\\d+'
b.variable_name, '${CVAR_PREFIX_PATTERN}([a-z]{2,3})\\d+'
) AS network_type,
SUBSTRING(
a.variable_name, '${CVAR_PREFIX_PATTERN}[a-z]{2,3}\\d+_(link\\d+)'
b.variable_name, '${CVAR_PREFIX_PATTERN}[a-z]{2,3}\\d+_(link\\d+)'
) AS network_link,
c.network_interface_uuid
FROM variables AS a
JOIN hosts AS b
ON a.variable_source_uuid = b.host_uuid
LEFT JOIN network_interfaces AS c
ON a.variable_name LIKE '%link%_mac%'
AND a.variable_value = c.network_interface_mac_address
AND b.host_uuid = c.network_interface_host_uuid
WHERE (
variable_name LIKE '${CVAR_PREFIX}%'
OR variable_name = 'install-target::enabled'
FROM hosts AS a
LEFT JOIN variables AS b
ON b.variable_source_uuid = a.host_uuid
AND (
b.variable_name LIKE '${CVAR_PREFIX}%'
OR b.variable_name = 'install-target::enabled'
)
${condHostUUIDs}
LEFT JOIN network_interfaces AS c
ON b.variable_name LIKE '%link%_mac%'
AND b.variable_value = c.network_interface_mac_address
AND a.host_uuid = c.network_interface_host_uuid
WHERE ${condHostUUIDs}
ORDER BY cvar_name ASC,
a.variable_name ASC;`;
b.variable_name ASC;`;
const afterQueryReturn: QueryResultModifierFunction =
buildQueryResultModifier((output) => {
const [hostName, hostType, hostUUID] = output[0];
if (output.length === 0) return {};
const {
0: [hostName, hostType, hostUUID],
} = output;
const shortHostName = getShortHostName(hostName);
return output.reduce<
@ -105,6 +109,8 @@ export const buildQueryHostDetail: BuildQueryDetailFunction = ({
networkInterfaceUuid,
],
) => {
if (!variableName) return previous;
const [variablePrefix, ...restVariableParts] =
variableName.split('::');
const keychain = MAP_TO_EXTRACTOR[variablePrefix](restVariableParts);

@ -140,6 +140,7 @@ export const prepareNetwork: RequestHandler<
entries: configEntries,
getValue: ({ value }) => String(value),
}),
job_host_uuid: hostUUID,
job_name: 'configure::network',
job_title: 'job_0001',
job_description: 'job_0071',

@ -121,7 +121,7 @@ const Preview: FC<PreviewProps> = ({
(async () => {
try {
const response = await fetch(
`${API_BASE_URL}/server/${serverUUID}?ss`,
`${API_BASE_URL}/server/${serverUUID}?ss=1`,
{
method: 'GET',
headers: {

@ -2,7 +2,7 @@ import { Box, styled, Switch } from '@mui/material';
import API_BASE_URL from '../../lib/consts/API_BASE_URL';
import { LARGE_MOBILE_BREAKPOINT } from '../../lib/consts/DEFAULT_THEME';
import HOST_STATUS from '../../lib/consts/NODES';
import HOST_STATUS from '../../lib/consts/HOST_STATUS';
import { ProgressBar } from '../Bars';
import Decorator, { Colours } from '../Decorator';

@ -20,7 +20,7 @@ const Hosts = ({ anvil }: { anvil: AnvilListItem[] }): JSX.Element => {
return (
<Panel>
<HeaderText text="Nodes" />
<HeaderText text="Subnodes" />
{!isLoading ? (
<>
{anvilIndex !== -1 && data && (

@ -1317,14 +1317,22 @@ const NetworkInitForm = forwardRef<
networks: pNetworks,
} = hostDetail as APIHostDetail;
if (
[pDns, pGateway, pGatewayInterface, pNetworks].some(
(condition) => !condition,
)
) {
return;
}
dnsCSVInputRef.current.setValue?.call(null, pDns);
gatewayInputRef.current.setValue?.call(null, pGateway);
const applied: string[] = [];
const inputs = Object.values(pNetworks).reduce<NetworkInput[]>(
(previous, { ip, link1Uuid, link2Uuid = '', subnetMask, type }) => {
const typeCount =
getNetworkTypeCount(type, { inputs: previous }) + 1;
const inputs = Object.values(pNetworks as APIHostNetworkList).reduce<
NetworkInput[]
>((previous, { ip, link1Uuid, link2Uuid = '', subnetMask, type }) => {
const typeCount = getNetworkTypeCount(type, { inputs: previous }) + 1;
const isRequired = requiredNetworks[type] === typeCount;
const name = `${NETWORK_TYPES[type]} ${typeCount}`;
@ -1346,11 +1354,9 @@ const NetworkInitForm = forwardRef<
});
return previous;
},
[],
);
}, []);
setGatewayInterface(pGatewayInterface);
setGatewayInterface(pGatewayInterface as string);
setNetworkInterfaceInputMap((previous) => {
const result = { ...previous };
@ -1369,11 +1375,9 @@ const NetworkInitForm = forwardRef<
testInputToToggleSubmitDisabled();
}
}, [
createNetwork,
expectHostDetail,
getNetworkTypeCount,
hostDetail,
networkInputs,
networkInterfaceInputMap,
requiredNetworks,
testInputToToggleSubmitDisabled,

@ -522,7 +522,7 @@ const PrepareHostForm: FC = () => {
setIsShowAccessSection(true);
}}
radioItems={{
node: { label: 'Node', value: 'node' },
node: { label: 'Subnode', value: 'node' },
dr: { label: 'Disaster Recovery (DR) host', value: 'dr' },
}}
/>
@ -549,7 +549,9 @@ const PrepareHostForm: FC = () => {
'preparehost-confirm-host-type-value': {
children: (
<MonoText>
{inputHostType === 'dr' ? 'Disaster Recovery (DR)' : 'Node'}
{inputHostType === 'dr'
? 'Disaster Recovery (DR)'
: 'Subnode'}
</MonoText>
),
},

@ -549,8 +549,8 @@ const filterAnvils: FilterAnvilsFunction = (
let anvilStorageGroupFreeMax: bigint = BIGINT_ZERO;
let anvilStorageGroupFreeTotal: bigint = BIGINT_ZERO;
// Summarize storage groups in this anvil node pair to produce all
// UUIDs, max free space, and total free space.
// Summarize storage groups in this anvil node to produce all UUIDs, max
// free space, and total free space.
storageGroups.forEach(({ storageGroupUUID, storageGroupFree }) => {
if (testIncludeStorageGroup(storageGroupUUID)) {
anvilStorageGroupUUIDs.push(storageGroupUUID);
@ -563,17 +563,17 @@ const filterAnvils: FilterAnvilsFunction = (
});
const usableTests: (() => boolean)[] = [
// Does this anvil node pair have at least one storage group?
// Does this anvil node have at least one storage group?
() => storageGroups.length > 0,
// Does this anvil node pair have enough CPU cores?
// Does this anvil node have enough CPU cores?
() => cpuCores <= anvilTotalCPUCores,
// Does this anvil node pair have enough memory?
// Does this anvil node have enough memory?
() => memory <= anvilTotalAvailableMemory,
// For every virtual disk:
// 1. Does this anvil node pair have the selected storage group which
// 1. Does this anvil node have the selected storage group which
// will contain the VD?
// 2. Does the selected storage group OR any storage group on this
// anvil node pair have enough free space?
// anvil node have enough free space?
() =>
storageGroupUUIDs.every((uuid, index) => {
const vdSize = vdSizes[index] ?? BIGINT_ZERO;
@ -588,15 +588,15 @@ const filterAnvils: FilterAnvilsFunction = (
return hasStorageGroup && hasEnoughStorage;
}),
// Do storage groups on this anvil node pair have enough free space
// to contain multiple VDs?
// Do storage groups on this anvil node have enough free space to
// contain multiple VDs?
() =>
Object.entries(storageGroupTotals).every(([uuid, total]) =>
uuid === 'all'
? total <= anvilStorageGroupFreeTotal
: total <= storageGroupUUIDMapToData[uuid].storageGroupFree,
),
// Does this anvil node pair have access to selected files?
// Does this anvil node have access to selected files?
() =>
fileUUIDs.every(
(fileUUID) =>
@ -1352,7 +1352,7 @@ const ProvisionServerDialog = ({
<Grid direction="row" item xs={gridColumns}>
<BodyText>
Server <InlineMonoText text={inputServerNameValue} /> will be
created on anvil node pair{' '}
created on anvil node{' '}
<InlineMonoText
text={anvilUUIDMapToData[inputAnvilValue].anvilName}
/>{' '}
@ -1696,7 +1696,7 @@ const ProvisionServerDialog = ({
inputLabelProps={{
isNotifyRequired: inputAnvilValue.length === 0,
}}
label="Anvil node pair"
label="Anvil node"
messageBoxProps={inputAnvilMessage}
selectItems={anvilSelectItems}
selectProps={{

@ -1,4 +1,11 @@
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
import {
MutableRefObject,
forwardRef,
useCallback,
useMemo,
useRef,
useState,
} from 'react';
import INPUT_TYPES from '../../lib/consts/INPUT_TYPES';
@ -11,6 +18,7 @@ import ConfirmDialog from '../ConfirmDialog';
import FlexBox from '../FlexBox';
import Grid from '../Grid';
import handleAPIError from '../../lib/handleAPIError';
import IconButton from '../IconButton';
import InputWithRef, { InputForwardedRefContent } from '../InputWithRef';
import { Message } from '../MessageBox';
import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup';
@ -19,7 +27,7 @@ import {
buildIPAddressTestBatch,
buildPeacefulStringTestBatch,
} from '../../lib/test_input';
import { BodyText } from '../Text';
import { BodyText, HeaderText } from '../Text';
import useProtect from '../../hooks/useProtect';
import useProtectedState from '../../hooks/useProtectedState';
@ -305,7 +313,22 @@ const AddPeerDialog = forwardRef<
}}
proceedButtonProps={{ disabled: isFormInvalid }}
ref={ref}
titleText="Add a peer"
titleText={
<>
<HeaderText>Add a peer</HeaderText>
<IconButton
mapPreset="close"
onClick={() => {
if (ref && 'current' in ref) {
(
ref as MutableRefObject<ConfirmDialogForwardedRefContent>
).current.setOpen?.call(null, false);
}
}}
variant="redcontained"
/>
</>
}
/>
);
});

@ -0,0 +1,18 @@
const SUBNODE_STATUS_MESSAGE_MAP: ReadonlyMap<string, string> = new Map([
['message_0222', 'The subnode is in an unknown state.'],
['message_0223', 'The subnode is a full cluster member.'],
[
'message_0224',
'The subnode is coming online; the cluster resource manager is running (step 2/3).',
],
[
'message_0225',
'The subnode is coming online; the subnode is a consensus cluster member (step 1/3).',
],
[
'message_0226',
'The subnode has booted, but it is not (yet) joining the cluster.',
],
]);
export default SUBNODE_STATUS_MESSAGE_MAP;

@ -1,18 +0,0 @@
const NODE_STATUS_MESSAGE_MAP: ReadonlyMap<string, string> = new Map([
['message_0222', 'The node is in an unknown state.'],
['message_0223', 'The node is a full cluster member.'],
[
'message_0224',
'The node is coming online; the cluster resource manager is running (step 2/3).',
],
[
'message_0225',
'The node is coming online; the node is a consensus cluster member (step 1/3).',
],
[
'message_0226',
'The node has booted, but it is not (yet) joining the cluster.',
],
]);
export default NODE_STATUS_MESSAGE_MAP;

@ -17,6 +17,6 @@ export const REP_IPV4_CSV = new RegExp(`^(?:${ipv4}\\s*,\\s*)*${ipv4}$`);
export const REP_PEACEFUL_STRING = /^[^'"/\\><}{]*$/;
export const REP_UUID = new RegExp(
`^${hex}{8}-${hex}{4}-[1-5]${hex}{3}-[89ab]${hex}{3}-${hex}{12}$`,
`^${hex}{8}-(?:${hex}{4}-){3}${hex}{12}$`,
'i',
);

@ -1 +0,0 @@
self.__BUILD_MANIFEST=function(s,c,a,e,t,n,i,f,d,b,u,k,h,j,r,g){return{__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":[s,a,e,i,f,k,"static/chunks/717-8bd60b96d67fd464.js",c,t,n,d,h,j,"static/chunks/pages/index-1f8f0ad3b3894dbc.js"],"/_error":["static/chunks/pages/_error-2280fa386d040b66.js"],"/anvil":[s,a,e,i,f,k,c,t,n,d,h,"static/chunks/pages/anvil-c1177b17efcafc34.js"],"/config":[s,a,e,b,c,t,n,u,"static/chunks/pages/config-0ecdb2b2b3f8c089.js"],"/file-manager":[s,a,e,i,"static/chunks/768-9ee3dcb62beecb53.js",c,t,"static/chunks/pages/file-manager-1a707639a4834587.js"],"/init":[s,a,i,f,b,r,c,t,n,d,g,"static/chunks/pages/init-7428606d331bc1dd.js"],"/login":[s,a,e,c,t,n,u,"static/chunks/pages/login-b5de0cd2f49998d6.js"],"/manage-element":[s,a,e,i,f,b,r,"static/chunks/195-d5fd184cc249f755.js",c,t,n,d,u,g,"static/chunks/pages/manage-element-2b1d8792c2a5bf47.js"],"/server":[s,e,"static/chunks/227-a3756585a7ef09ae.js",c,j,"static/chunks/pages/server-db52258419acacf3.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/manage-element","/server"]}}("static/chunks/382-f51344f6f9208507.js","static/chunks/62-532ed713980da8db.js","static/chunks/483-f8013e38dca1620d.js","static/chunks/894-e57948de523bcf96.js","static/chunks/780-e8b3396d257460a4.js","static/chunks/899-83e9de2a35c6bcf0.js","static/chunks/182-08683bbe95fbb010.js","static/chunks/614-0ce04fd295045ffe.js","static/chunks/140-ec935fb15330b98a.js","static/chunks/644-c7c6e21c71345aed.js","static/chunks/903-dc2a40be612a10c3.js","static/chunks/485-77798bccc4308d0e.js","static/chunks/825-d34974d169ea09cc.js","static/chunks/94-db0af749b6e45543.js","static/chunks/676-6159ce853338cc1f.js","static/chunks/692-64941f28ab144919.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

@ -0,0 +1 @@
self.__BUILD_MANIFEST=function(s,c,a,e,t,n,i,d,f,b,u,k,h,j,r,g){return{__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":[s,a,e,i,d,k,"static/chunks/717-8bd60b96d67fd464.js",c,t,n,f,h,j,"static/chunks/pages/index-7c2cb48473145987.js"],"/_error":["static/chunks/pages/_error-2280fa386d040b66.js"],"/anvil":[s,a,e,i,d,k,c,t,n,f,h,"static/chunks/pages/anvil-c0917177269e4c45.js"],"/config":[s,a,e,b,c,t,n,u,"static/chunks/pages/config-af002475ca739544.js"],"/file-manager":[s,a,e,i,"static/chunks/768-9ee3dcb62beecb53.js",c,t,"static/chunks/pages/file-manager-1a707639a4834587.js"],"/init":[s,a,i,d,b,r,c,t,n,f,g,"static/chunks/pages/init-7428606d331bc1dd.js"],"/login":[s,a,e,c,t,n,u,"static/chunks/pages/login-b5de0cd2f49998d6.js"],"/manage-element":[s,a,e,i,d,b,r,"static/chunks/195-d5fd184cc249f755.js",c,t,n,f,u,g,"static/chunks/pages/manage-element-3b79eff498885bcb.js"],"/server":[s,e,"static/chunks/227-a3756585a7ef09ae.js",c,j,"static/chunks/pages/server-db52258419acacf3.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/manage-element","/server"]}}("static/chunks/382-f51344f6f9208507.js","static/chunks/62-532ed713980da8db.js","static/chunks/483-f8013e38dca1620d.js","static/chunks/894-e57948de523bcf96.js","static/chunks/780-e8b3396d257460a4.js","static/chunks/899-ec535b0f0a173e21.js","static/chunks/182-08683bbe95fbb010.js","static/chunks/614-0ce04fd295045ffe.js","static/chunks/140-ec935fb15330b98a.js","static/chunks/644-c7c6e21c71345aed.js","static/chunks/903-dc2a40be612a10c3.js","static/chunks/485-77798bccc4308d0e.js","static/chunks/825-f20d177b43a24683.js","static/chunks/94-e103c3735f0e061b.js","static/chunks/676-6159ce853338cc1f.js","static/chunks/692-e32ff331a198ffb5.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

File diff suppressed because one or more lines are too long

@ -154,7 +154,7 @@ const Dashboard: FC = () => {
};
fetchJSON<{ screenshot: string }>(
`${API_BASE_URL}/server/${serverUUID}?ss`,
`${API_BASE_URL}/server/${serverUUID}?ss=1`,
)
.then(({ screenshot }) => {
item.screenshot = screenshot;

@ -93,7 +93,7 @@ const PrepareNetworkTabContent: FC = () => {
if (isFirstRender) {
api
.get<APIHostOverviewList>('/host', { params: { types: 'node' } })
.get<APIHostOverviewList>('/host', { params: { types: ['dr', 'node'] } })
.then(({ data }) => {
setHostOverviewList(data);
setHostSubTabId(Object.keys(data)[0]);

@ -39,14 +39,7 @@ type APIHostOverviewList = {
[hostUUID: string]: APIHostOverview;
};
type APIHostDetail = APIHostOverview & {
dns: string;
domain?: string;
gateway: string;
gatewayInterface: string;
installTarget: APIHostInstallTarget;
networks: {
[networkId: string]: {
type APIHostNetwork = {
createBridge?: NumberBoolean;
ip: string;
link1MacToSet: string;
@ -56,7 +49,18 @@ type APIHostDetail = APIHostOverview & {
subnetMask: string;
type: NetworkType;
};
type APIHostNetworkList = {
[networkId: string]: APIHostNetwork;
};
type APIHostDetail = APIHostOverview & {
dns?: string;
domain?: string;
gateway?: string;
gatewayInterface?: string;
installTarget?: APIHostInstallTarget;
networks?: APIHostNetworkList;
organization?: string;
prefix?: string;
sequence?: string;

Loading…
Cancel
Save