Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion js/app_api-adminSettings.js

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions js/app_api-adminSettings.js.license
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: BSD-2-Clause
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-License-Identifier: ( AGPL-3.0-or-later)
SPDX-FileCopyrightText: xiaokai <kexiaokai@gmail.com>
SPDX-FileCopyrightText: rhysd <lin90162@yahoo.co.jp>
SPDX-FileCopyrightText: inline-style-parser developers
Expand Down Expand Up @@ -48,7 +47,6 @@ SPDX-FileCopyrightText: Antoni Andre <antoniandre.web@gmail.com>
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
SPDX-FileCopyrightText: Andris Reinman
SPDX-FileCopyrightText: Andrea Giammarchi
SPDX-FileCopyrightText: Alexander Piskun <bigcat88@icloud.com>
SPDX-FileCopyrightText: @nextcloud/dialogs developers


Expand Down Expand Up @@ -434,6 +432,6 @@ This file is generated from multiple sources. Included packages:
- zwitch
- version: 2.0.4
- license: MIT
- app_api
- nextcloud
- version: 1.0.0
- license: ( AGPL-3.0-or-later)
- license: AGPL-3.0-or-later
2 changes: 1 addition & 1 deletion js/app_api-adminSettings.js.map

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions js/app_api-filesplugin.js.license
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ SPDX-License-Identifier: MIT
SPDX-License-Identifier: ISC
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-License-Identifier: ( AGPL-3.0-or-later)
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Roeland Jago Douma
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Matt Zabriskie
SPDX-FileCopyrightText: James Halliday
SPDX-FileCopyrightText: GitHub Inc.
SPDX-FileCopyrightText: Feross Aboukhadijeh
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Alkemics
SPDX-FileCopyrightText: Alexander Piskun <bigcat88@icloud.com>


This file is generated from multiple sources. Included packages:
Expand Down Expand Up @@ -65,6 +65,6 @@ This file is generated from multiple sources. Included packages:
- webpack
- version: 5.94.0
- license: MIT
- app_api
- nextcloud
- version: 1.0.0
- license: ( AGPL-3.0-or-later)
- license: AGPL-3.0-or-later
17 changes: 16 additions & 1 deletion lib/DeployActions/DockerActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,12 @@ public function deployExAppHarp(ExApp $exApp, DaemonConfig $daemonConfig, array
'restart_policy' => $this->appConfig->getValueString(Application::APP_ID, 'container_restart_policy', 'unless-stopped', lazy: true),
'compute_device' => $computeDevice,
'mount_points' => $mountPoints,
'start_container' => true,
'start_container' => true
];

if (isset($params['container_params']['resourceLimits']) && !empty($params['container_params']['resourceLimits'])) {
$createPayload['resource_limits'] = $params['container_params']['resourceLimits'];
}

$this->logger->debug(sprintf('Payload for /docker/exapp/create for %s: %s', $exAppName, json_encode($createPayload)));
try {
Expand Down Expand Up @@ -556,6 +560,16 @@ public function createContainer(string $dockerUrl, string $imageId, DaemonConfig
);
}

if (isset($params['resourceLimits'])) {
if (isset($params['resourceLimits']['memory']) && $params['resourceLimits']['memory'] > 0) {
// memory in bytes
$containerParams['HostConfig']['Memory'] = $params['resourceLimits']['memory'];
Comment thread
Hephi2 marked this conversation as resolved.
}
if (isset($params['resourceLimits']['nanoCPUs']) && $params['resourceLimits']['nanoCPUs'] > 0) {
$containerParams['HostConfig']['NanoCPUs'] = $params['resourceLimits']['nanoCPUs'];
}
}

$url = $this->buildApiUrl($dockerUrl, sprintf('containers/create?name=%s', urlencode($this->buildExAppContainerName($params['name']))));
try {
$options['json'] = $containerParams;
Expand Down Expand Up @@ -1107,6 +1121,7 @@ public function buildDeployParams(DaemonConfig $daemonConfig, array $appInfo): a
'devices' => $devices,
'deviceRequests' => $deviceRequests,
'mounts' => $appInfo['external-app']['mounts'] ?? [],
'resourceLimits' => $deployConfig['resourceLimits'] ?? []
];

return [
Expand Down
24 changes: 24 additions & 0 deletions src/components/DaemonConfig/DaemonConfigDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p><b>{{ t('app_api', 'Name') }}: </b>{{ daemon.name }}</p>
<p><b>{{ t('app_api', 'Protocol') }}: </b>{{ daemon.protocol }}</p>
<p><b>{{ t('app_api', 'Host') }}: </b>{{ daemon.host }}</p>
<p v-if="daemon.deploy_config.harp"><b>{{ t('app_api', 'ExApp direct communication (FRP disabled)') }}: </b>{{ daemon.deploy_config.harp.exapp_direct ?? false }}</p>

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / node-build

Expected 1 line break before closing tag (`</p>`), but no line breaks found

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / node-build

Expected 1 line break after opening tag (`<p>`), but no line breaks found

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

Expected 1 line break before closing tag (`</p>`), but no line breaks found

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

Expected 1 line break after opening tag (`<p>`), but no line breaks found

<h3>{{ t('app_api', 'Deploy config') }}</h3>
<p><b>{{ t('app_api', 'Docker network') }}: </b>{{ daemon.deploy_config.net }}</p>
Expand All @@ -41,6 +41,9 @@
<p v-if="daemon.deploy_config.computeDevice">
<b>{{ t('app_api', 'Compute device') }}:</b> {{ daemon.deploy_config?.computeDevice?.label }}
</p>
<p><b>{{ t('app_api', 'Memory limit') }}:</b> {{ formatMemoryLimit(daemon.deploy_config?.resourceLimits?.memory) }}</p>

<p><b>{{ t('app_api', 'CPU limit') }}:</b> {{ formatCpuLimit(daemon.deploy_config?.resourceLimits?.nanoCPUs) }}</p>

<div v-if="daemon.deploy_config.additional_options" class="additional-options">
<h3>{{ t('app_api', 'Additional options') }}</h3>
Expand Down Expand Up @@ -129,6 +132,27 @@
console.debug(err)
})
},
formatMemoryLimit(memoryBytes) {
if (!memoryBytes) {
return t('app_api', 'Unlimited')
}
const memoryMiB = memoryBytes / (1024 * 1024)
if (memoryMiB >= 1024) {
const memoryGiB = memoryMiB / 1024
return t('app_api', '{size} GiB', { size: memoryGiB.toFixed(1) })
}
return t('app_api', '{size} MiB', { size: Math.round(memoryMiB) })
},
formatCpuLimit(nanoCpus) {
if (!nanoCpus) {
return t('app_api', 'Unlimited')
}
const cpus = nanoCpus / 1000000000
if (cpus === 1) {
return t('app_api', '1 CPU')
}
return t('app_api', '{cpus} CPUs', { cpus: cpus.toFixed(2) })
},
},
}
</script>
Expand Down
88 changes: 87 additions & 1 deletion src/components/DaemonConfig/ManageDaemonConfigModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,36 @@
:aria-label="t('app_api', 'Compute device')"
:options="computeDevices" />
</div>
<div class="external-label" :aria-label="t('app_api', 'Memory limit')">
<label for="memory-limit">
{{ t('app_api', 'Memory limit (in MiB)') }}
<InfoTooltip :text="t('app_api', 'Maximum memory that the ExApp container can use in mebibytes')" />
</label>
<NcInputField
id="memory-limit"
ref="memory-limit"
class="ex-input-field"
:value.sync="memoryLimit"
:placeholder="t('app_api', 'Memory limit (in MiB)')"
:aria-label="t('app_api', 'Memory limit (in MiB)')"
:error="isMemoryLimitValid === false"
:helper-text="isMemoryLimitValid === false ? t('app_api', 'Must be a positive integer') : ''" />
</div>
<div class="external-label" :aria-label="t('app_api', 'CPU limit')">
<label for="cpu-limit">
{{ t('app_api', 'CPU limit') }}
<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)')" />
</label>
<NcInputField
id="cpu-limit"
ref="cpu-limit"
class="ex-input-field"
:value.sync="cpuLimit"
:placeholder="t('app_api', 'CPU limit as decimal value')"
:aria-label="t('app_api', 'CPU limit')"
:error="isCpuLimitValid === false"
:helper-text="isCpuLimitValid === false ? t('app_api', 'Must be a positive number') : ''" />
</div>
<template v-if="additionalOptions.length > 0">
<div class="row" style="flex-direction: column;">
<div
Expand Down Expand Up @@ -413,12 +443,31 @@ export default {
data.defaultDaemon = this.isDefaultDaemon
data.additionalOptions = Object.entries(this.daemon.deploy_config.additional_options ?? {}).map(([key, value]) => ({ key, value }))
data.deployConfigSettingsOpened = true
if (data.deployConfig.resourceLimits) {
if (data.deployConfig.resourceLimits.memory) {
// memory in bytes
data.deployConfig.resourceLimits.memoryMiB = data.deployConfig.resourceLimits.memory / (1024 * 1024)
delete data.deployConfig.resourceLimits.memory
} else {
data.deployConfig.resourceLimits.memoryMiB = null
}
if (data.deployConfig.resourceLimits.nanoCPUs) {
data.deployConfig.resourceLimits.cpus = data.deployConfig.resourceLimits.nanoCPUs / 1000000000
delete data.deployConfig.resourceLimits.nanoCPUs
} else {
data.deployConfig.resourceLimits.cpus = null
}
}
}
if (!data.deployConfig.harp) {
data.deployConfig.harp = null
data.deployConfigSettingsOpened = false
}

if (!data.deployConfig.resourceLimits) {
data.deployConfig.resourceLimits = { memoryMiB: null, cpus: null }
}

return data
},
computed: {
Expand All @@ -437,6 +486,32 @@ export default {
daemonProtocol() {
return this.httpsEnabled ? 'https' : 'http'
},
memoryLimit: {
get() {
return this.deployConfig.resourceLimits.memoryMiB || ''
},
set(value) {
this.deployConfig.resourceLimits.memoryMiB = value === '' ? null : value
},
},
cpuLimit: {
get() {
return this.deployConfig.resourceLimits.cpus || ''
},
set(value) {
this.deployConfig.resourceLimits.cpus = value === '' ? null : value
},
},
isMemoryLimitValid() {
if (this.memoryLimit === '' || this.memoryLimit === null) return true
const str = String(this.memoryLimit).trim()
return /^[1-9]\d*$/.test(str)
},
isCpuLimitValid() {
if (this.cpuLimit === '' || this.cpuLimit === null) return true
const str = String(this.cpuLimit).trim()
return /^\d*\.?\d+$/.test(str)
},
Comment thread
kyteinsky marked this conversation as resolved.
isDaemonNameInvalid() {
return this.daemons.some(daemon => daemon.name === this.name && daemon.name !== this.daemon?.name)
},
Expand All @@ -463,7 +538,7 @@ export default {
return t('app_api', 'The docker network that the deployed ex-apps would use.')
},
cannotRegister() {
return this.isDaemonNameInvalid === true || this.isHaProxyPasswordValid === false || (this.isHarp && !this.deployConfig.net)
return this.isDaemonNameInvalid === true || this.isHaProxyPasswordValid === false || (this.isHarp && !this.deployConfig.net) || this.isMemoryLimitValid === false || this.isCpuLimitValid === false
},
isAdditionalOptionValid() {
return this.additionalOption.key.trim() !== '' && this.additionalOption.value.trim() !== ''
Expand Down Expand Up @@ -619,6 +694,17 @@ export default {
registries: this.deployConfig.registries || null,
},
}

const resourceLimits = {}
if (this.deployConfig.resourceLimits.memoryMiB && this.isMemoryLimitValid) {
// memory in bytes
resourceLimits.memory = Number(this.deployConfig.resourceLimits.memoryMiB) * 1024 * 1024
}
if (this.deployConfig.resourceLimits.cpus && this.isCpuLimitValid) {
resourceLimits.nanoCPUs = Number(this.deployConfig.resourceLimits.cpus) * 1000000000
}
params.deploy_config.resourceLimits = resourceLimits

if (this.additionalOptions.length > 0) {
params.deploy_config.additional_options = this.additionalOptions.reduce((acc, option) => {
acc[option.key] = option.value
Expand Down
32 changes: 32 additions & 0 deletions src/constants/daemonTemplates.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'localhost:8782',
docker_socket_port: 24000,
Expand All @@ -43,6 +47,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'appapi-harp:8782',
docker_socket_port: 24000,
Expand All @@ -67,6 +75,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'nextcloud-aio-harp:8782',
docker_socket_port: 24000,
Expand All @@ -91,6 +103,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'localhost:8782',
docker_socket_port: 24000,
Expand All @@ -115,6 +131,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand All @@ -135,6 +155,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand All @@ -155,6 +179,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand All @@ -175,6 +203,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand Down
Loading