From 42c43c2ebca67f04bff90aed179a77f94d6a09a8 Mon Sep 17 00:00:00 2001 From: Steven Oh Date: Mon, 31 Jul 2023 14:10:45 -0500 Subject: [PATCH 1/4] Added ability to build a system through Tapis UI --- src/tapis-api/systems/index.ts | 1 + src/tapis-api/systems/makeNewSystem.ts | 21 + src/tapis-app/Systems/_Layout/Layout.tsx | 2 + .../CreateSystemModal.module.scss | 16 + .../CreateSystemModal/CreateSystemModal.tsx | 407 ++++++++++++++++++ .../Settings/AdvancedSettings.tsx | 103 +++++ .../Batch/BatchLogicalQueuesSettings.tsx | 175 ++++++++ .../Settings/Batch/BatchSettings.tsx | 82 ++++ .../Settings/CmdSettings.tsx | 25 ++ .../Settings/DtnSettings.tsx | 64 +++ .../Settings/Job/JobCapabilitiesSettings.tsx | 134 ++++++ .../Settings/Job/JobEnvVariableSettings.tsx | 101 +++++ .../Settings/Job/JobSettings.tsx | 38 ++ .../Settings/ProxySettings.tsx | 49 +++ .../Settings/TagsSettings.tsx | 76 ++++ .../SystemToolbar/CreateSystemModal/index.ts | 3 + .../SystemToolbar/SystemToolbar.module.scss | 20 + .../SystemToolbar/SystemToolbar.tsx | 67 +++ .../_components/SystemToolbar/index.ts | 3 + src/tapis-hooks/systems/index.ts | 1 + src/tapis-hooks/systems/queryKeys.ts | 1 + src/tapis-hooks/systems/useMakeNewSystem.ts | 47 ++ 22 files changed, 1436 insertions(+) create mode 100644 src/tapis-api/systems/makeNewSystem.ts create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.module.scss create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/AdvancedSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchLogicalQueuesSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/CmdSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/DtnSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobCapabilitiesSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobEnvVariableSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/ProxySettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/TagsSettings.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/index.ts create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.module.scss create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.tsx create mode 100644 src/tapis-app/Systems/_components/SystemToolbar/index.ts create mode 100644 src/tapis-hooks/systems/useMakeNewSystem.ts diff --git a/src/tapis-api/systems/index.ts b/src/tapis-api/systems/index.ts index 6ad778a61..b3fd53cab 100644 --- a/src/tapis-api/systems/index.ts +++ b/src/tapis-api/systems/index.ts @@ -1,3 +1,4 @@ export { default as list } from './list'; export { default as details } from './details'; export { default as listSchedulerProfiles } from './schedulerProfiles'; +export { default as makeNewSystem } from './makeNewSystem'; \ No newline at end of file diff --git a/src/tapis-api/systems/makeNewSystem.ts b/src/tapis-api/systems/makeNewSystem.ts new file mode 100644 index 000000000..49dbe97b0 --- /dev/null +++ b/src/tapis-api/systems/makeNewSystem.ts @@ -0,0 +1,21 @@ +import { Systems } from '@tapis/tapis-typescript'; +import { apiGenerator, errorDecoder } from 'tapis-api/utils'; + +const makeNewSystem = ( + reqCreateSystem: Systems.ReqCreateSystem, + basePath: string, + jwt: string, + skipCredentialCheck?: boolean +) => { + const api: Systems.SystemsApi = apiGenerator( + Systems, + Systems.SystemsApi, + basePath, + jwt + ); + return errorDecoder(() => + api.createSystem({ reqCreateSystem, skipCredentialCheck }) + ); +}; + +export default makeNewSystem; diff --git a/src/tapis-app/Systems/_Layout/Layout.tsx b/src/tapis-app/Systems/_Layout/Layout.tsx index 12dad5bce..67b1d1c8a 100644 --- a/src/tapis-app/Systems/_Layout/Layout.tsx +++ b/src/tapis-app/Systems/_Layout/Layout.tsx @@ -6,12 +6,14 @@ import { LayoutNavWrapper, } from 'tapis-ui/_common'; import { SystemsNav } from '../_components'; +import SystemToolbar from '../_components/SystemToolbar'; import { Router } from '../_Router'; const Layout: React.FC = () => { const header = (
System List
+
); diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.module.scss b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.module.scss new file mode 100644 index 000000000..04ba38e91 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.module.scss @@ -0,0 +1,16 @@ +.modal-settings { + max-height: 38rem; + overflow-y: scroll; +} + +.item { + border: 1px dotted lightgray; + padding: 0.5em 0.5em 0.5em 0.5em; + margin-bottom: 1em; +} + +.array { + border: 1px solid gray; + padding: 0.5em 0.5em 0.5em 0.5em; + margin-bottom: 0.5em; +} diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx new file mode 100644 index 000000000..9498586f8 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx @@ -0,0 +1,407 @@ +import { Button, Input, FormGroup, Label } from 'reactstrap'; +import { GenericModal } from 'tapis-ui/_common'; +import { SubmitWrapper } from 'tapis-ui/_wrappers'; +import { ToolbarModalProps } from '../SystemToolbar'; +import { Form, Formik } from 'formik'; +import { FormikInput } from 'tapis-ui/_common'; +import { FormikSelect, FormikCheck } from 'tapis-ui/_common/FieldWrapperFormik'; +import { useMakeNewSystem } from 'tapis-hooks/systems'; +import { useEffect, useCallback, useState } from 'react'; +import styles from './CreateSystemModal.module.scss'; +import * as Yup from 'yup'; +import { + SystemTypeEnum, + AuthnEnum, + RuntimeTypeEnum, + SchedulerTypeEnum, + LogicalQueue, + Capability, + KeyValuePair, +} from '@tapis/tapis-typescript-systems'; +import { useQueryClient } from 'react-query'; +import { default as queryKeys } from 'tapis-hooks/systems/queryKeys'; +import AdvancedSettings from './Settings/AdvancedSettings'; + +//Arrays that are used in the drop-down menus +const systemTypes = Object.values(SystemTypeEnum); +const authnMethods = Object.values(AuthnEnum); + +const CreateSystemModal: React.FC = ({ toggle }) => { + //Allows the system list to update without the user having to refresh the page + const queryClient = useQueryClient(); + const onSuccess = useCallback(() => { + queryClient.invalidateQueries(queryKeys.list); + }, [queryClient]); + + const { makeNewSystem, isLoading, error, isSuccess, reset } = + useMakeNewSystem(); + + useEffect(() => { + reset(); + }, [reset]); + + //used for the advanced checkbox + const [simplified, setSimplified] = useState(false); + const onChange = useCallback(() => { + setSimplified(!simplified); + }, [setSimplified, simplified]); + + const validationSchema = Yup.object({ + sysname: Yup.string() + .min(1) + .max(80, 'System name should not be longer than 80 characters') + .matches( + /^[a-zA-Z0-9_.-]+$/, + "Must contain only alphanumeric characters and the following: '.', '_', '-'" + ) + .required('System name is a required field'), + description: Yup.string().max( + 2048, + 'Description schould not be longer than 2048 characters' + ), + host: Yup.string() + .min(1) + .max(256, 'Host name should not be longer than 256 characters') + .matches( + /^[a-zA-Z0-9_.-]+$/, + "Must contain only alphanumeric characters and the following: '.', '_', '-'" + ) + .required('Host name is a required field'), + rootDir: Yup.string() + .min(1) + .max(4096, 'Root Directory should not be longer than 4096 characters'), + jobWorkingDir: Yup.string() + .min(1) + .max( + 4096, + 'Job Working Directory should not be longer than 4096 characters' + ), + effectiveUserId: Yup.string().max( + 60, + 'Effective User ID should not be longer than 60 characters' + ), + batchSchedulerProfile: Yup.string().max( + 80, + 'Batch Scheduler Profile should not be longer than 80 characters' + ), + batchDefaultLogicalQueue: Yup.string().max( + 128, + 'Batch Default Logical Queue should not be longer than 128 characters' + ), + proxyHost: Yup.string().max( + 256, + 'Proxy Host should not be longer than 256 characters' + ), + dtnSystemId: Yup.string().max( + 80, + 'DTN System ID should not be longer than 80 characters' + ), + mpiCmd: Yup.string().max( + 126, + 'mpiCmd should not be longer than 126 characters' + ), + }); + + const initialValues = { + sysname: '', + description: undefined, + systemType: SystemTypeEnum.Linux, + host: 'stampede2.tacc.utexas.edu', + defaultAuthnMethod: AuthnEnum.Password, + canExec: true, + rootDir: '/', + jobRuntimes: RuntimeTypeEnum.Singularity, + version: undefined, + effectiveUserId: '${apiUserId}', + bucketName: undefined, + + //batch + canRunBatch: true, + batchScheduler: SchedulerTypeEnum.Slurm, + batchSchedulerProfile: 'tacc', + batchDefaultLogicalQueue: 'tapisNormal', + + batchLogicalQueues: [ + { + name: 'tapisNormal', + hpcQueueName: 'normal', + maxJobs: 50, + maxJobsPerUser: 10, + minNodeCount: 1, + maxNodeCount: 16, + minCoresPerNode: 1, + maxCoresPerNode: 68, + minMemoryMB: 1, + maxMemoryMB: 16384, + minMinutes: 1, + maxMinutes: 60, + }, + ], + + //proxy + useProxy: false, + proxyHost: undefined, + proxyPort: 0, + + //dtn + isDtn: false, + dtnSystemId: undefined, + dtnMountPoint: undefined, + dtnMountSourcePath: undefined, + + //cmd + enableCmdPrefix: false, + mpiCmd: undefined, + + jobWorkingDir: 'HOST_EVAL($SCRATCH)', + jobMaxJobs: undefined, + jobMaxJobsPerUser: undefined, + jobCapabilities: [], + jobEnvVariables: [], + + tags: [], + }; + + const onSubmit = ({ + sysname, + description, + systemType, + host, + defaultAuthnMethod, + canExec, + rootDir, + jobWorkingDir, + jobRuntimes, + version, + effectiveUserId, + bucketName, + + //batch + canRunBatch, + batchScheduler, + batchSchedulerProfile, + batchDefaultLogicalQueue, + batchLogicalQueues, + + //proxy + useProxy, + proxyHost, + proxyPort, + + //dtn + isDtn, + dtnSystemId, + dtnMountPoint, + dtnMountSourcePath, + + //cmd + enableCmdPrefix, + mpiCmd, + + jobMaxJobs, + jobMaxJobsPerUser, + jobCapabilities, + jobEnvVariables, + + tags, + }: { + sysname: string; + description: string | undefined; + systemType: SystemTypeEnum; + host: string; + defaultAuthnMethod: AuthnEnum; + canExec: boolean; + rootDir: string; + jobWorkingDir: string; + jobRuntimes: RuntimeTypeEnum; + version: string | undefined; + effectiveUserId: string; + bucketName: string | undefined; + + //batch + canRunBatch: boolean; + batchScheduler: SchedulerTypeEnum; + batchSchedulerProfile: string; + batchDefaultLogicalQueue: string; + batchLogicalQueues: Array; + + //proxy + useProxy: boolean; + proxyHost: string | undefined; + proxyPort: number; + + //dtn + isDtn: boolean; + dtnSystemId: string | undefined; + dtnMountPoint: string | undefined; + dtnMountSourcePath: string | undefined; + + //cmd + enableCmdPrefix: boolean; + mpiCmd: string | undefined; + + jobMaxJobs: number | undefined; + jobMaxJobsPerUser: number | undefined; + jobCapabilities: Array; + jobEnvVariables: Array; + + tags: Array | undefined; + }) => { + //Converting the string into a boolean value + const jobRuntimesArray = [{ runtimeType: jobRuntimes, version }]; + + makeNewSystem( + { + id: sysname, + description, + systemType, + host, + defaultAuthnMethod, + canExec, + rootDir, + jobWorkingDir, + jobRuntimes: jobRuntimesArray, + effectiveUserId, + bucketName, + + //batch + canRunBatch, + batchScheduler, + batchSchedulerProfile, + batchDefaultLogicalQueue, + batchLogicalQueues, + + //proxy + useProxy, + proxyHost, + proxyPort, + + //dtn + isDtn, + dtnSystemId, + dtnMountPoint, + dtnMountSourcePath, + + //cmd + // enableCmdPrefix, + // mpiCmd, + + jobMaxJobs, + jobMaxJobsPerUser, + jobCapabilities, + jobEnvVariables, + + tags, + }, + true, + { onSuccess } + ); + }; + + return ( + + + {() => ( +
+ + + + + + + + {systemTypes.map((values) => { + return ; + })} + + + + + {authnMethods.map((values) => { + return ; + })} + + {true ? ( + + ) : null} + + + + )} +
+ + } + footer={ + + + + } + /> + ); +}; + +export default CreateSystemModal; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/AdvancedSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/AdvancedSettings.tsx new file mode 100644 index 000000000..d140a3dc4 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/AdvancedSettings.tsx @@ -0,0 +1,103 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import { FormikSelect } from 'tapis-ui/_common/FieldWrapperFormik'; +import { RuntimeTypeEnum } from '@tapis/tapis-typescript-systems'; +import { Systems } from '@tapis/tapis-typescript'; +import { useMemo } from 'react'; +import { SystemTypeEnum } from '@tapis/tapis-typescript-systems'; +import { useFormikContext } from 'formik'; +import styles from '../CreateSystemModal.module.scss'; +import BatchSettings from './Batch/BatchSettings'; +import ProxySettings from './ProxySettings'; +import DtnSettings from './DtnSettings'; +import CmdSettings from './CmdSettings'; +import TagsSettings from './TagsSettings'; +import JobSettings from './Job/JobSettings'; + +//Array that is used in the drop-down menus +const runtimeTypes = Object.values(RuntimeTypeEnum); + +type AdvancedSettingsProp = { + simplified: boolean; +}; + +const AdvancedSettings: React.FC = ({ simplified }) => { + //used when trying to read the current value of a parameter + const { values } = useFormikContext(); + + //reading if the systemType is S3 at its current state + const isS3 = useMemo( + () => + (values as Partial).systemType === + SystemTypeEnum.S3, + [values] + ); + + //reading the runtimeType at its current state + const runtimeType = (values as Partial).jobRuntimes; + + if (simplified) { + return ( + + + + + {runtimeTypes.map((values) => { + return ; + })} + + + + {isS3 ? ( + + ) : null} + + + + + + + + ); + } else { + return null; + } +}; + +export default AdvancedSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchLogicalQueuesSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchLogicalQueuesSettings.tsx new file mode 100644 index 000000000..ec04157a6 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchLogicalQueuesSettings.tsx @@ -0,0 +1,175 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import styles from '../../CreateSystemModal.module.scss'; +import { Systems } from '@tapis/tapis-typescript'; +import { Button } from 'reactstrap'; +import { FieldArray, useFormikContext, FieldArrayRenderProps } from 'formik'; +import { LogicalQueue } from '@tapis/tapis-typescript-systems'; + +type BatchLQFieldProps = { + item: LogicalQueue; + index: number; + remove: (index: number) => Systems.ReqCreateSystem | undefined; +}; + +const BatchLogicalQueuesField: React.FC = ({ + item, + index, + remove, +}) => { + return ( + <> + + + + + + + + + + + + + + + + + ); +}; + +const BatchLogicalQueuesInputs: React.FC<{ + arrayHelpers: FieldArrayRenderProps; +}> = ({ arrayHelpers }) => { + const { values } = useFormikContext(); + + const batchLogicalQueues = + (values as Partial)?.batchLogicalQueues ?? []; + + return ( + 0} + title="Batch Logical Queues" + note={`${batchLogicalQueues.length} items`} + className={styles['array']} + > + {batchLogicalQueues.map((batchLogicalQueuesInput, index) => ( + + ))} + + + ); +}; + +export const BatchLogicalQueuesSettings: React.FC = () => { + return ( +
+ { + return ( + <> + + + ); + }} + /> +
+ ); +}; + +export default BatchLogicalQueuesSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchSettings.tsx new file mode 100644 index 000000000..56ced0abf --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Batch/BatchSettings.tsx @@ -0,0 +1,82 @@ +import { useFormikContext } from 'formik'; +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import { FormikSelect, FormikCheck } from 'tapis-ui/_common/FieldWrapperFormik'; +import { + SchedulerTypeEnum, + SystemTypeEnum, +} from '@tapis/tapis-typescript-systems'; +import styles from '../../CreateSystemModal.module.scss'; +import { useMemo } from 'react'; +import { Systems } from '@tapis/tapis-typescript'; +import BatchLogicalQueuesSettings from './BatchLogicalQueuesSettings'; + +//Array that is used in the drop-down menus +const schedulerTypes = Object.values(SchedulerTypeEnum); + +const BatchSettings: React.FC = () => { + //used when trying to read the current value of a parameter + const { values } = useFormikContext(); + + //reading the canRunBatch at its current state + const canRunBatch = useMemo( + () => (values as Partial).canRunBatch, + [values] + ); + //reading if the systemType is Linux at its current state + const isLinux = useMemo( + () => + (values as Partial).systemType === + SystemTypeEnum.Linux, + [values] + ); + + return ( +
+ {isLinux ? ( + + + {canRunBatch ? ( +
+ + + {schedulerTypes.map((values) => { + return ; + })} + + + + +
+ ) : null} +
+ ) : null} +
+ ); +}; + +export default BatchSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/CmdSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/CmdSettings.tsx new file mode 100644 index 000000000..c3f3c3468 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/CmdSettings.tsx @@ -0,0 +1,25 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import { FormikCheck } from 'tapis-ui/_common/FieldWrapperFormik'; +import styles from '../CreateSystemModal.module.scss'; + +const CmdSettings: React.FC = () => { + return ( + + + + + ); +}; + +export default CmdSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/DtnSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/DtnSettings.tsx new file mode 100644 index 000000000..2c16b59db --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/DtnSettings.tsx @@ -0,0 +1,64 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import { FormikCheck } from 'tapis-ui/_common/FieldWrapperFormik'; +import { useMemo } from 'react'; +import { Systems } from '@tapis/tapis-typescript'; +import { useFormikContext } from 'formik'; +import styles from '../CreateSystemModal.module.scss'; + +const DtnSettings: React.FC = () => { + //used when trying to read the current value of a parameter + const { values } = useFormikContext(); + + //reading the isDtn at its current state + const isDtn = useMemo( + () => (values as Partial).isDtn, + [values] + ); + //reading the canExec at its current state + const canExec = useMemo( + () => (values as Partial).canExec, + [values] + ); + + return ( +
+ {!canExec ? ( + + + {isDtn ? ( +
+ + + +
+ ) : null} +
+ ) : null} +
+ ); +}; + +export default DtnSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobCapabilitiesSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobCapabilitiesSettings.tsx new file mode 100644 index 000000000..b340bda77 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobCapabilitiesSettings.tsx @@ -0,0 +1,134 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import { FormikSelect } from 'tapis-ui/_common/FieldWrapperFormik'; +import styles from '../../CreateSystemModal.module.scss'; +import { Systems } from '@tapis/tapis-typescript'; +import { Button } from 'reactstrap'; +import { FieldArray, useFormikContext, FieldArrayRenderProps } from 'formik'; +import { + Capability, + CategoryEnum, + DatatypeEnum, +} from '@tapis/tapis-typescript-systems'; + +const categories = Object.values(CategoryEnum); +const datatypes = Object.values(DatatypeEnum); + +type JobCapabilitiesFieldProps = { + item: Capability; + index: number; + remove: (index: number) => Systems.ReqCreateSystem | undefined; +}; + +const JobCapabilitiesField: React.FC = ({ + item, + index, + remove, +}) => { + return ( + <> + + + + {categories.map((values) => { + return ; + })} + + + + + {datatypes.map((values) => { + return ; + })} + + + + + + + ); +}; + +const JobCapabilitiesInputs: React.FC<{ arrayHelpers: FieldArrayRenderProps }> = + ({ arrayHelpers }) => { + const { values } = useFormikContext(); + + const jobCapabilities = + (values as Partial)?.jobCapabilities ?? []; + + return ( + 0} + title="Job Capabilities" + note={`${jobCapabilities.length} items`} + className={styles['array']} + > + {jobCapabilities.map((jobCapabilitiesInput, index) => ( + + ))} + + + ); + }; + +export const JobCapabilitiesSettings: React.FC = () => { + return ( +
+ { + return ( + <> + + + ); + }} + /> +
+ ); +}; + +export default JobCapabilitiesSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobEnvVariableSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobEnvVariableSettings.tsx new file mode 100644 index 000000000..8c08a1815 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobEnvVariableSettings.tsx @@ -0,0 +1,101 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import styles from '../../CreateSystemModal.module.scss'; +import { Systems } from '@tapis/tapis-typescript'; +import { Button } from 'reactstrap'; +import { FieldArray, useFormikContext, FieldArrayRenderProps } from 'formik'; +import { KeyValuePair } from '@tapis/tapis-typescript-systems'; + +type JobEnvVariablesFieldProps = { + item: KeyValuePair; + index: number; + remove: (index: number) => Systems.ReqCreateSystem | undefined; +}; + +const JobEnvVariablesField: React.FC = ({ + item, + index, + remove, +}) => { + return ( + <> + + + + + + + + ); +}; + +const JobEnvVariablesInputs: React.FC<{ arrayHelpers: FieldArrayRenderProps }> = + ({ arrayHelpers }) => { + const { values } = useFormikContext(); + + const jobEnvVariables = + (values as Partial)?.jobEnvVariables ?? []; + + return ( + 0} + title="Job Environment Variables" + note={`${jobEnvVariables.length} items`} + className={styles['array']} + > + {jobEnvVariables.map((jobEnvVariablesInput, index) => ( + + ))} + + + ); + }; + +export const JobEnvVariablesSettings: React.FC = () => { + return ( +
+ { + return ( + <> + + + ); + }} + /> +
+ ); +}; + +export default JobEnvVariablesSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobSettings.tsx new file mode 100644 index 000000000..debd70221 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/Job/JobSettings.tsx @@ -0,0 +1,38 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import styles from '../../CreateSystemModal.module.scss'; +import JobEnvVariablesSettings from './JobEnvVariableSettings'; +import JobCapabilitiesSettings from './JobCapabilitiesSettings'; + +const JobSettings: React.FC = () => { + return ( + + + + + + + + ); +}; + +export default JobSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/ProxySettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/ProxySettings.tsx new file mode 100644 index 000000000..cec77fc1a --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/ProxySettings.tsx @@ -0,0 +1,49 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import { FormikCheck } from 'tapis-ui/_common/FieldWrapperFormik'; +import { useMemo } from 'react'; +import { Systems } from '@tapis/tapis-typescript'; +import { useFormikContext } from 'formik'; +import styles from '../CreateSystemModal.module.scss'; + +const ProxySettings: React.FC = () => { + //used when trying to read the current value of a parameter + const { values } = useFormikContext(); + + //reading the useProxy at its current state + const useProxy = useMemo( + () => (values as Partial).useProxy, + [values] + ); + + return ( + + + {useProxy ? ( +
+ + +
+ ) : null} +
+ ); +}; + +export default ProxySettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/TagsSettings.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/TagsSettings.tsx new file mode 100644 index 000000000..e3775e882 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/Settings/TagsSettings.tsx @@ -0,0 +1,76 @@ +import { FormikInput, Collapse } from 'tapis-ui/_common'; +import styles from '../CreateSystemModal.module.scss'; +import { Systems } from '@tapis/tapis-typescript'; +import { Button } from 'reactstrap'; +import { FieldArray, useFormikContext, FieldArrayRenderProps } from 'formik'; + +type TagsFieldProps = { + item: string; + index: number; + remove: (index: number) => Systems.ReqCreateSystem | undefined; +}; +const TagsField: React.FC = ({ item, index, remove }) => { + return ( + <> + + + + + + ); +}; + +const TagsInputs: React.FC<{ arrayHelpers: FieldArrayRenderProps }> = ({ + arrayHelpers, +}) => { + const { values } = useFormikContext(); + + const tags = (values as Partial)?.tags ?? []; + + return ( + 0} + title="Tags" + note={`${tags.length} items`} + className={styles['array']} + > + {tags.map((tagInput, index) => ( + + ))} + + + ); +}; + +export const TagsSettings: React.FC = () => { + return ( +
+ { + return ( + <> + + + ); + }} + /> +
+ ); +}; + +export default TagsSettings; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/index.ts b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/index.ts new file mode 100644 index 000000000..140e243e2 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/index.ts @@ -0,0 +1,3 @@ +import CreateSystemModal from './CreateSystemModal'; + +export default CreateSystemModal; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.module.scss b/src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.module.scss new file mode 100644 index 000000000..9d0a6e26d --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.module.scss @@ -0,0 +1,20 @@ +.toolbar-wrapper { + padding: 0; + margin: 0; + margin-top: 0.5em; + display: flex !important; +} + +.toolbar-btn { + height: 2rem; + align-items: center; + margin-left: 0.5em; + font-size: 0.7em !important; + border-radius: 0 !important; + background-color: #f4f4f4 !important; + color: #333333 !important; +} + +.toolbar-btn:disabled { + color: #999999 !important; +} diff --git a/src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.tsx b/src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.tsx new file mode 100644 index 000000000..66bc02701 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/SystemToolbar.tsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { Button } from 'reactstrap'; +import { Icon } from 'tapis-ui/_common'; +import styles from './SystemToolbar.module.scss'; +import { useLocation } from 'react-router-dom'; +import CreateSystemModal from './CreateSystemModal'; + +type ToolbarButtonProps = { + text: string; + icon: string; + onClick: () => void; + disabled: boolean; +}; + +export type ToolbarModalProps = { + toggle: () => void; +}; + +export const ToolbarButton: React.FC = ({ + text, + icon, + onClick, + disabled = true, + ...rest +}) => { + return ( +
+ +
+ ); +}; + +const SystemToolbar: React.FC = () => { + const [modal, setModal] = useState(undefined); + const { pathname } = useLocation(); + + const toggle = () => { + setModal(undefined); + }; + return ( +
+ {pathname && ( +
+ setModal('createsystem')} + aria-label="createSystem" + /> + + {modal === 'createsystem' && } +
+ )} +
+ ); +}; + +export default SystemToolbar; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/index.ts b/src/tapis-app/Systems/_components/SystemToolbar/index.ts new file mode 100644 index 000000000..4aa5e2da4 --- /dev/null +++ b/src/tapis-app/Systems/_components/SystemToolbar/index.ts @@ -0,0 +1,3 @@ +import SystemToolbar from './SystemToolbar'; + +export default SystemToolbar; diff --git a/src/tapis-hooks/systems/index.ts b/src/tapis-hooks/systems/index.ts index e3ffe4b66..bb0be117c 100644 --- a/src/tapis-hooks/systems/index.ts +++ b/src/tapis-hooks/systems/index.ts @@ -1,3 +1,4 @@ export { default as useList } from './useList'; export { default as useDetails } from './useDetails'; export { default as useSchedulerProfiles } from './useSchedulerProfiles'; +export { default as useMakeNewSystem } from './useMakeNewSystem'; \ No newline at end of file diff --git a/src/tapis-hooks/systems/queryKeys.ts b/src/tapis-hooks/systems/queryKeys.ts index fce9ed78c..31027e7cb 100644 --- a/src/tapis-hooks/systems/queryKeys.ts +++ b/src/tapis-hooks/systems/queryKeys.ts @@ -2,6 +2,7 @@ const QueryKeys = { list: 'systems/list', details: 'systems/details', listSchedulerProfiles: 'systems/listScehdulerProfiles', + makeNewSystem: 'systems/makeNewSystem', }; export default QueryKeys; diff --git a/src/tapis-hooks/systems/useMakeNewSystem.ts b/src/tapis-hooks/systems/useMakeNewSystem.ts new file mode 100644 index 000000000..3e9747c84 --- /dev/null +++ b/src/tapis-hooks/systems/useMakeNewSystem.ts @@ -0,0 +1,47 @@ +import { useMutation, MutateOptions } from 'react-query'; +import { Systems } from '@tapis/tapis-typescript'; +import { makeNewSystem } from '../../tapis-api/systems'; +import { useTapisConfig } from '../context'; +import QueryKeys from './queryKeys'; + +type MkNewSystemHookParams = { + reqPostSystem: Systems.ReqCreateSystem; + skipCredentialCheck: boolean; +}; + +const useMakeNewSystem = () => { + const { basePath, accessToken } = useTapisConfig(); + const jwt = accessToken?.access_token || ''; + + // The useMutation react-query hook is used to call operations that make server-side changes + // (Other hooks would be used for data retrieval) + // + // In this case, mkdir helper is called to perform the operation + const { mutate, isLoading, isError, isSuccess, data, error, reset } = + useMutation( + [QueryKeys.makeNewSystem, basePath, jwt], + ({ reqPostSystem, skipCredentialCheck }) => + makeNewSystem(reqPostSystem, basePath, jwt, skipCredentialCheck) + ); + + // Return hook object with loading states and login function + return { + isLoading, + isError, + isSuccess, + data, + error, + reset, + makeNewSystem: ( + reqPostSystem: Systems.ReqCreateSystem, + skipCredentialCheck: boolean = true, + // react-query options to allow callbacks such as onSuccess + options?: MutateOptions + ) => { + // Call mutate to trigger a single post-like API operation + return mutate({ reqPostSystem, skipCredentialCheck }, options); + }, + }; +}; + +export default useMakeNewSystem; From 01184cde17442e144996cc4a22e4be7d63b6139f Mon Sep 17 00:00:00 2001 From: Steven Oh Date: Fri, 15 Sep 2023 13:53:54 -0500 Subject: [PATCH 2/4] ran prettier and fixed linting issues --- src/tapis-api/systems/index.ts | 2 +- .../SystemToolbar/CreateSystemModal/CreateSystemModal.tsx | 4 ++-- src/tapis-hooks/systems/index.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tapis-api/systems/index.ts b/src/tapis-api/systems/index.ts index b3fd53cab..871cc4ae1 100644 --- a/src/tapis-api/systems/index.ts +++ b/src/tapis-api/systems/index.ts @@ -1,4 +1,4 @@ export { default as list } from './list'; export { default as details } from './details'; export { default as listSchedulerProfiles } from './schedulerProfiles'; -export { default as makeNewSystem } from './makeNewSystem'; \ No newline at end of file +export { default as makeNewSystem } from './makeNewSystem'; diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx index 9498586f8..9700be78d 100644 --- a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx @@ -112,7 +112,7 @@ const CreateSystemModal: React.FC = ({ toggle }) => { rootDir: '/', jobRuntimes: RuntimeTypeEnum.Singularity, version: undefined, - effectiveUserId: '${apiUserId}', + effectiveUserId: undefined, bucketName: undefined, //batch @@ -215,7 +215,7 @@ const CreateSystemModal: React.FC = ({ toggle }) => { jobWorkingDir: string; jobRuntimes: RuntimeTypeEnum; version: string | undefined; - effectiveUserId: string; + effectiveUserId: string | undefined; bucketName: string | undefined; //batch diff --git a/src/tapis-hooks/systems/index.ts b/src/tapis-hooks/systems/index.ts index bb0be117c..1d0aa477f 100644 --- a/src/tapis-hooks/systems/index.ts +++ b/src/tapis-hooks/systems/index.ts @@ -1,4 +1,4 @@ export { default as useList } from './useList'; export { default as useDetails } from './useDetails'; export { default as useSchedulerProfiles } from './useSchedulerProfiles'; -export { default as useMakeNewSystem } from './useMakeNewSystem'; \ No newline at end of file +export { default as useMakeNewSystem } from './useMakeNewSystem'; From 19b398be51ea9075cc12ac2b5fe271a2af5ac054 Mon Sep 17 00:00:00 2001 From: Steven Oh Date: Fri, 15 Sep 2023 14:26:45 -0500 Subject: [PATCH 3/4] added a comment to the effectiveUserId --- .../SystemToolbar/CreateSystemModal/CreateSystemModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx index 9700be78d..088be859a 100644 --- a/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx +++ b/src/tapis-app/Systems/_components/SystemToolbar/CreateSystemModal/CreateSystemModal.tsx @@ -112,7 +112,7 @@ const CreateSystemModal: React.FC = ({ toggle }) => { rootDir: '/', jobRuntimes: RuntimeTypeEnum.Singularity, version: undefined, - effectiveUserId: undefined, + effectiveUserId: undefined, //default = ${apiUserId} bucketName: undefined, //batch From 79bdf4b2429f979e602c5da6f6b9d4a0437b1656 Mon Sep 17 00:00:00 2001 From: Steven Oh Date: Wed, 25 Oct 2023 17:46:19 -0500 Subject: [PATCH 4/4] fixed JobLauncher taking in inputs as string and not number --- .../components/jobs/JobLauncher/steps/ExecOptions.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tapis-ui/components/jobs/JobLauncher/steps/ExecOptions.tsx b/src/tapis-ui/components/jobs/JobLauncher/steps/ExecOptions.tsx index e2625dcf8..24551287a 100644 --- a/src/tapis-ui/components/jobs/JobLauncher/steps/ExecOptions.tsx +++ b/src/tapis-ui/components/jobs/JobLauncher/steps/ExecOptions.tsx @@ -223,24 +223,28 @@ const ExecSystemQueueOptions: React.FC = () => { label="Node Count" description="The number of nodes to use for this job" required={false} + type="number" /> );