209209 :aria-label =" t('app_api', 'Compute device')"
210210 :options =" computeDevices" />
211211 </div >
212+ <div class =" external-label" :aria-label =" t('app_api', 'Memory limit')" >
213+ <label for =" memory-limit" >
214+ {{ t('app_api', 'Memory limit (in MiB)') }}
215+ <InfoTooltip :text =" t('app_api', 'Maximum memory that the ExApp container can use in mebibytes')" />
216+ </label >
217+ <NcInputField
218+ id =" memory-limit"
219+ ref =" memory-limit"
220+ class =" ex-input-field"
221+ :value.sync =" memoryLimit"
222+ :placeholder =" t('app_api', 'Memory limit (in MiB)')"
223+ :aria-label =" t('app_api', 'Memory limit (in MiB)')"
224+ :error =" isMemoryLimitValid === false"
225+ :helper-text =" isMemoryLimitValid === false ? t('app_api', 'Must be a positive integer') : ''" />
226+ </div >
227+ <div class =" external-label" :aria-label =" t('app_api', 'CPU limit')" >
228+ <label for =" cpu-limit" >
229+ {{ t('app_api', 'CPU limit') }}
230+ <InfoTooltip :text =" t('app_api', 'Maximum CPU cores that the ExApp container can use (e.g. 0.5 for half a core, 2 for two cores)')" />
231+ </label >
232+ <NcInputField
233+ id =" cpu-limit"
234+ ref =" cpu-limit"
235+ class =" ex-input-field"
236+ :value.sync =" cpuLimit"
237+ :placeholder =" t('app_api', 'CPU limit as decimal value')"
238+ :aria-label =" t('app_api', 'CPU limit')"
239+ :error =" isCpuLimitValid === false"
240+ :helper-text =" isCpuLimitValid === false ? t('app_api', 'Must be a positive number') : ''" />
241+ </div >
212242 <template v-if =" additionalOptions .length > 0 " >
213243 <div class =" row" style =" flex-direction : column ;" >
214244 <div
@@ -413,12 +443,31 @@ export default {
413443 data .defaultDaemon = this .isDefaultDaemon
414444 data .additionalOptions = Object .entries (this .daemon .deploy_config .additional_options ?? {}).map (([key , value ]) => ({ key, value }))
415445 data .deployConfigSettingsOpened = true
446+ if (data .deployConfig .resourceLimits ) {
447+ if (data .deployConfig .resourceLimits .memory ) {
448+ // memory in bytes
449+ data .deployConfig .resourceLimits .memoryMiB = data .deployConfig .resourceLimits .memory / (1024 * 1024 )
450+ delete data .deployConfig .resourceLimits .memory
451+ } else {
452+ data .deployConfig .resourceLimits .memoryMiB = null
453+ }
454+ if (data .deployConfig .resourceLimits .nanoCPUs ) {
455+ data .deployConfig .resourceLimits .cpus = data .deployConfig .resourceLimits .nanoCPUs / 1000000000
456+ delete data .deployConfig .resourceLimits .nanoCPUs
457+ } else {
458+ data .deployConfig .resourceLimits .cpus = null
459+ }
460+ }
416461 }
417462 if (! data .deployConfig .harp ) {
418463 data .deployConfig .harp = null
419464 data .deployConfigSettingsOpened = false
420465 }
421466
467+ if (! data .deployConfig .resourceLimits ) {
468+ data .deployConfig .resourceLimits = { memoryMiB: null , cpus: null }
469+ }
470+
422471 return data
423472 },
424473 computed: {
@@ -437,6 +486,32 @@ export default {
437486 daemonProtocol () {
438487 return this .httpsEnabled ? ' https' : ' http'
439488 },
489+ memoryLimit: {
490+ get () {
491+ return this .deployConfig .resourceLimits .memoryMiB || ' '
492+ },
493+ set (value ) {
494+ this .deployConfig .resourceLimits .memoryMiB = value === ' ' ? null : value
495+ },
496+ },
497+ cpuLimit: {
498+ get () {
499+ return this .deployConfig .resourceLimits .cpus || ' '
500+ },
501+ set (value ) {
502+ this .deployConfig .resourceLimits .cpus = value === ' ' ? null : value
503+ },
504+ },
505+ isMemoryLimitValid () {
506+ if (this .memoryLimit === ' ' || this .memoryLimit === null ) return true
507+ const str = String (this .memoryLimit ).trim ()
508+ return / ^ [1-9 ] \d * $ / .test (str)
509+ },
510+ isCpuLimitValid () {
511+ if (this .cpuLimit === ' ' || this .cpuLimit === null ) return true
512+ const str = String (this .cpuLimit ).trim ()
513+ return / ^ \d * \. ? \d + $ / .test (str)
514+ },
440515 isDaemonNameInvalid () {
441516 return this .daemons .some (daemon => daemon .name === this .name && daemon .name !== this .daemon ? .name )
442517 },
@@ -463,7 +538,7 @@ export default {
463538 return t (' app_api' , ' The docker network that the deployed ex-apps would use.' )
464539 },
465540 cannotRegister () {
466- return this .isDaemonNameInvalid === true || this .isHaProxyPasswordValid === false || (this .isHarp && ! this .deployConfig .net )
541+ return this .isDaemonNameInvalid === true || this .isHaProxyPasswordValid === false || (this .isHarp && ! this .deployConfig .net ) || this . isMemoryLimitValid === false || this . isCpuLimitValid === false
467542 },
468543 isAdditionalOptionValid () {
469544 return this .additionalOption .key .trim () !== ' ' && this .additionalOption .value .trim () !== ' '
@@ -619,6 +694,17 @@ export default {
619694 registries: this .deployConfig .registries || null ,
620695 },
621696 }
697+
698+ const resourceLimits = {}
699+ if (this .deployConfig .resourceLimits .memoryMiB && this .isMemoryLimitValid ) {
700+ // memory in bytes
701+ resourceLimits .memory = Number (this .deployConfig .resourceLimits .memoryMiB ) * 1024 * 1024
702+ }
703+ if (this .deployConfig .resourceLimits .cpus && this .isCpuLimitValid ) {
704+ resourceLimits .nanoCPUs = Number (this .deployConfig .resourceLimits .cpus ) * 1000000000
705+ }
706+ params .deploy_config .resourceLimits = resourceLimits
707+
622708 if (this .additionalOptions .length > 0 ) {
623709 params .deploy_config .additional_options = this .additionalOptions .reduce ((acc , option ) => {
624710 acc[option .key ] = option .value
0 commit comments