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 MB)') }}
215+ <InfoTooltip :text =" t (' app_api' , ' Maximum memory that the ExApp container can use in megabytes' )" />
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 MB' )"
223+ :aria-label =" t (' app_api' , ' Memory limit in MB' )"
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,26 @@ 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+ data .deployConfig .resourceLimits .memoryMB = data .deployConfig .resourceLimits .memory / (1024 * 1024 )
449+ delete data .deployConfig .resourceLimits .memory
450+ }
451+ if (data .deployConfig .resourceLimits .nanoCPUs ) {
452+ data .deployConfig .resourceLimits .cpus = data .deployConfig .resourceLimits .nanoCPUs / 1000000000
453+ delete data .deployConfig .resourceLimits .nanoCPUs
454+ }
455+ }
416456 }
417457 if (! data .deployConfig .harp ) {
418458 data .deployConfig .harp = null
419459 data .deployConfigSettingsOpened = false
420460 }
421461
462+ if (! data .deployConfig .resourceLimits ) {
463+ data .deployConfig .resourceLimits = { memoryMB: null , cpus: null }
464+ }
465+
422466 return data
423467 },
424468 computed: {
@@ -437,6 +481,32 @@ export default {
437481 daemonProtocol () {
438482 return this .httpsEnabled ? ' https' : ' http'
439483 },
484+ memoryLimit: {
485+ get () {
486+ return this .deployConfig .resourceLimits .memoryMB || ' '
487+ },
488+ set (value ) {
489+ this .deployConfig .resourceLimits .memoryMB = value === ' ' ? null : value
490+ },
491+ },
492+ cpuLimit: {
493+ get () {
494+ return this .deployConfig .resourceLimits .cpus || ' '
495+ },
496+ set (value ) {
497+ this .deployConfig .resourceLimits .cpus = value === ' ' ? null : value
498+ },
499+ },
500+ isMemoryLimitValid () {
501+ if (this .memoryLimit === ' ' || this .memoryLimit === null ) return true
502+ const str = String (this .memoryLimit ).trim ()
503+ return / ^ [1-9 ] \d * $ / .test (str)
504+ },
505+ isCpuLimitValid () {
506+ if (this .cpuLimit === ' ' || this .cpuLimit === null ) return true
507+ const str = String (this .cpuLimit ).trim ()
508+ return / ^ \d * \. ? \d + $ / .test (str)
509+ },
440510 isDaemonNameInvalid () {
441511 return this .daemons .some (daemon => daemon .name === this .name && daemon .name !== this .daemon ? .name )
442512 },
@@ -463,7 +533,7 @@ export default {
463533 return t (' app_api' , ' The docker network that the deployed ex-apps would use.' )
464534 },
465535 cannotRegister () {
466- return this .isDaemonNameInvalid === true || this .isHaProxyPasswordValid === false || (this .isHarp && ! this .deployConfig .net )
536+ return this .isDaemonNameInvalid === true || this .isHaProxyPasswordValid === false || (this .isHarp && ! this .deployConfig .net ) || this . isMemoryLimitValid === false || this . isCpuLimitValid === false
467537 },
468538 isAdditionalOptionValid () {
469539 return this .additionalOption .key .trim () !== ' ' && this .additionalOption .value .trim () !== ' '
@@ -619,6 +689,16 @@ export default {
619689 registries: this .deployConfig .registries || null ,
620690 },
621691 }
692+
693+ const resourceLimits = {}
694+ if (this .deployConfig .resourceLimits .memoryMB && this .isMemoryLimitValid ) {
695+ resourceLimits .memory = Number (this .deployConfig .resourceLimits .memoryMB ) * 1024 * 1024
696+ }
697+ if (this .deployConfig .resourceLimits .cpus && this .isCpuLimitValid ) {
698+ resourceLimits .nanoCPUs = Number (this .deployConfig .resourceLimits .cpus ) * 1000000000
699+ }
700+ params .deploy_config .resourceLimits = resourceLimits
701+
622702 if (this .additionalOptions .length > 0 ) {
623703 params .deploy_config .additional_options = this .additionalOptions .reduce ((acc , option ) => {
624704 acc[option .key ] = option .value
0 commit comments