Skip to content

Commit

Permalink
WIP: Advanced deploy options (2)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Borysenko <[email protected]>
  • Loading branch information
andrey18106 committed Jan 17, 2025
1 parent a2867ed commit fe30885
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 158 deletions.
294 changes: 136 additions & 158 deletions apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,156 +10,127 @@
<div class="modal__content">
<h2 id="form-name">{{ t('settings', 'Advanced deploy options') }}</h2>
<p class="description" style="text-align: center;">
{{ t('settings', 'Edit ExApp deploy options before installation') }}
{{ configuredDeployOptions === null ? t('settings', 'Edit ExApp deploy options before installation') : t('settings', 'Configured ExApp deploy options. Can be set only during installation') }}.
<a href="https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AdvancedDeployOptions.html">Learn more</a>
</p>

<h3 v-if="environmentVariables.length > 0">{{ t('settings', 'Environment variables') }}</h3>
<div v-for="envVar in environmentVariables" :key="envVar.envName"
class="deploy-option">
<NcTextField :label="envVar.displayName" :value.sync="deployOptions.environment_variables[envVar.envName]" />
<p class="description">{{ envVar.description }}</p>
</div>

<h3 v-if="environmentVariables.length > 0 || (configuredDeployOptions !== null && configuredDeployOptions.environment_variables.length > 0)">
{{ t('settings', 'Environment variables') }}
</h3>
<template v-if="configuredDeployOptions === null">
<div v-for="envVar in environmentVariables" :key="envVar.envName"

Check failure on line 21 in apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

':key' should be on a new line
class="deploy-option">

Check failure on line 22 in apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected "\t" character, but found " " character
<NcTextField :label="envVar.displayName" :value.sync="deployOptions.environment_variables[envVar.envName]" />
<p class="description">{{ envVar.description }}</p>
</div>
</template>
<template v-else-if="Object.keys(configuredDeployOptions).length > 0">
<p class="description">{{ t('settings', 'ExApp container environment variables (ExApp-reserved envs are excluded)') }}</p>
<ul class="envs">
<li v-for="envVar in Object.keys(configuredDeployOptions.environment_variables)" :key="envVar">
<NcTextField
:label="configuredDeployOptions.environment_variables[envVar].displayName ?? envVar"

Check failure on line 32 in apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected no linebreak before this attribute
:value="configuredDeployOptions.environment_variables[envVar].value" readonly />

Check failure on line 33 in apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'readonly' should be on a new line
<p class="description">{{ configuredDeployOptions.environment_variables[envVar].description }}</p>
</li>
</ul>
</template>
<template v-else>
<p class="description">{{ t('settings', 'No environment variables defined') }}</p>
</template>

<h3>{{ t('settings', 'Mounts') }}</h3>
<p class="description">{{ t('settings', 'Define host folder mounts to bind to ExApp container') }}</p>
<p class="warning">{{ t('settings', 'Must exist prior to installing the ExApp') }}</p>

<div v-for="mount in deployOptions.mounts"
class="deploy-option"
style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;">
<NcTextField :label="t('settings', 'Host path')" :value.sync="mount.hostPath" />
<NcTextField :label="t('settings', 'Container path')" :value.sync="mount.containerPath" />
<NcCheckboxRadioSwitch :checked.sync="mount.readonly">
{{ t('settings', 'Read-only') }}
</NcCheckboxRadioSwitch>
<NcButton
:aria-label="t('settings', 'Remove mount')"
style="margin-top: 6px;"
@click="removeMount(mount)">
<template #icon>
<NcIconSvgWrapper :path="mdiDelete" />
</template>
</NcButton>
</div>

<div v-if="addingMount" class="deploy-option">
<h4>{{ t('settings', 'New mount') }}</h4>
<div style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;">
<NcTextField
:label="t('settings', 'Host path')"
:aria-label="t('settings', 'Enter path to host folder')"
:value.sync="newMountPoint.hostPath" />
<NcTextField
:label="t('settings', 'Container path')"
:aria-label="t('settings', 'Enter path to container folder')"
:value.sync="newMountPoint.containerPath" />
<NcCheckboxRadioSwitch
:checked.sync="newMountPoint.readonly"
:aria-label="t('settings', 'Toggle read-only mode')">
<template v-if="configuredDeployOptions === null">
<p class="description">{{ t('settings', 'Define host folder mounts to bind to ExApp container') }}</p>
<p class="warning">{{ t('settings', 'Must exist on Deploy daemon host prior to installing the ExApp') }}</p>
<div v-for="mount in deployOptions.mounts"

Check failure on line 46 in apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Elements in iteration expect to have 'v-bind:key' directives
class="deploy-option"

Check failure on line 47 in apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected "\t" character, but found " " character
style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;">

Check failure on line 48 in apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected "\t" character, but found " " character
<NcTextField :label="t('settings', 'Host path')" :value.sync="mount.hostPath" />
<NcTextField :label="t('settings', 'Container path')" :value.sync="mount.containerPath" />
<NcCheckboxRadioSwitch :checked.sync="mount.readonly">
{{ t('settings', 'Read-only') }}
</NcCheckboxRadioSwitch>
</div>
<div style="display: flex; align-items: center; margin-top: 4px;">
<NcButton
:aria-label="t('settings', 'Confirm adding new mount')"
@click="addMountPoint">
:aria-label="t('settings', 'Remove mount')"
style="margin-top: 6px;"
@click="removeMount(mount)">
<template #icon>
<NcIconSvgWrapper :path="mdiCheck" />
<NcIconSvgWrapper :path="mdiDelete" />
</template>
{{ t('settings', 'Confirm') }}
</NcButton>
<NcButton
:aria-label="t('settings', 'Cancel adding mount')"
style="margin-left: 4px;"
@click="cancelAddMountPoint">
<template #icon>
<NcIconSvgWrapper :path="mdiClose" />
</template>
{{ t('settings', 'Cancel') }}
</NcButton>
</div>
</div>
<NcButton
v-if="!addingMount"
:aria-label="t('settings', 'Add mount')"
style="margin-top: 5px;"
@click="() => addingMount = true">
<template #icon>
<NcIconSvgWrapper :path="mdiPlus" />
</template>
{{ t('settings', 'Add mount') }}
</NcButton>


<h3>{{ t('settings', 'Port bindings') }}</h3>
<p class="description">{{ t('settings', 'Define ports to expose from ExApp container') }}</p>

<div v-for="port in deployOptions.ports"
class="deploy-option"
style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;">
<NcTextField :label="t('settings', 'Host port')" :value.sync="port.hostPort" />
<NcTextField :label="t('settings', 'Host IP')" :value.sync="port.hostIp" />
<NcTextField :label="t('settings', 'Container port')" :value.sync="port.containerPort" />
<div v-if="addingMount" class="deploy-option">
<h4>{{ t('settings', 'New mount') }}</h4>
<div style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;">
<NcTextField
ref="newMountHostPath"
:label="t('settings', 'Host path')"
:aria-label="t('settings', 'Enter path to host folder')"
:value.sync="newMountPoint.hostPath" />
<NcTextField
:label="t('settings', 'Container path')"
:aria-label="t('settings', 'Enter path to container folder')"
:value.sync="newMountPoint.containerPath" />
<NcCheckboxRadioSwitch
:checked.sync="newMountPoint.readonly"
:aria-label="t('settings', 'Toggle read-only mode')">
{{ t('settings', 'Read-only') }}
</NcCheckboxRadioSwitch>
</div>
<div style="display: flex; align-items: center; margin-top: 4px;">
<NcButton
:aria-label="t('settings', 'Confirm adding new mount')"
@click="addMountPoint">
<template #icon>
<NcIconSvgWrapper :path="mdiCheck" />
</template>
{{ t('settings', 'Confirm') }}
</NcButton>
<NcButton
:aria-label="t('settings', 'Cancel adding mount')"
style="margin-left: 4px;"
@click="cancelAddMountPoint">
<template #icon>
<NcIconSvgWrapper :path="mdiClose" />
</template>
{{ t('settings', 'Cancel') }}
</NcButton>
</div>
</div>
<NcButton
:aria-label="t('settings', 'Remove port binding')"
style="margin-top: 6px;"
@click="removePortBinding(port)">
v-if="!addingMount"
:aria-label="t('settings', 'Add mount')"
style="margin-top: 5px;"
@click="() => {
addingMount = true
$nextTick(() => {
this.$refs.newMountHostPath.focus()
})
}">
<template #icon>
<NcIconSvgWrapper :path="mdiDelete" />
<NcIconSvgWrapper :path="mdiPlus" />
</template>
{{ t('settings', 'Add mount') }}
</NcButton>
</div>

<div v-if="addingPortBinding" class="deploy-option">
<h4>{{ t('settings', 'New port binding') }}</h4>
<div style="display: flex; align-items: center; justify-content: space-between; flex-direction: column; width: 100%;">
<NcTextField
:label="t('settings', 'Host port (e.g. 80, 443, 80/tcp, 443/udp')"
:aria-label="t('settings', 'Enter host port (e.g. 80, 443, 80/tcp, 443/udp')"
:value.sync="newPortBinding.hostPort" />
<NcTextField
:label="t('settings', 'Optional Host IP (e.g. 0.0.0.0, 127.0.0.1)')"
:aria-label="t('settings', 'Optional: Enter host IP (e.g. 0.0.0.0, 127.0.0.1)')"
:value.sync="newPortBinding.hostIp" />
<NcTextField
:label="t('settings', 'Port inside container (e.g. 8080)')"
:aria-label="t('settings', 'Enter port inside container (e.g. 8080)')"
:value.sync="newPortBinding.containerPort" />
</div>
<div style="display: flex; align-items: center; margin-top: 4px;">
<NcButton
:aria-label="t('settings', 'Confirm adding new port binding')"
@click="addPortBinding">
<template #icon>
<NcIconSvgWrapper :path="mdiCheck" />
</template>
{{ t('settings', 'Confirm') }}
</NcButton>
<NcButton
:aria-label="t('settings', 'Cancel adding port binding')"
style="margin-left: 4px;"
@click="cancelAddPortBinding">
<template #icon>
<NcIconSvgWrapper :path="mdiClose" />
</template>
{{ t('settings', 'Cancel') }}
</NcButton>
</template>
<template v-else-if="configuredDeployOptions.mounts.length > 0">
<p class="description">{{ t('settings', 'ExApp container mounts') }}</p>
<div v-for="mount in configuredDeployOptions.mounts"
class="deploy-option"
style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;">
<NcTextField :label="t('settings', 'Host path')" :value.sync="mount.hostPath" readonly />
<NcTextField :label="t('settings', 'Container path')" :value.sync="mount.containerPath" readonly />
<NcCheckboxRadioSwitch :checked.sync="mount.readonly" disabled>
{{ t('settings', 'Read-only') }}
</NcCheckboxRadioSwitch>
</div>
</div>
<NcButton
v-if="!addingPortBinding"
:aria-label="t('settings', 'Add port binding')"
style="margin-top: 5px;"
@click="() => addingPortBinding = true">
<template #icon>
<NcIconSvgWrapper :path="mdiPlus" />
</template>
{{ t('settings', 'Add port binding') }}
</NcButton>

</template>
<template v-else>
<p class="description">{{ t('settings', 'No mounts defined') }}</p>
</template>

<NcButton v-if="!app.active && (app.canInstall || app.isCompatible)"
<NcButton v-if="!app.active && (app.canInstall || app.isCompatible) && configuredDeployOptions === null"
:title="enableButtonTooltip"
:aria-label="enableButtonTooltip"
type="primary"
Expand All @@ -178,6 +149,9 @@
<script>
import { computed, ref } from 'vue'

import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'

import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
Expand Down Expand Up @@ -229,7 +203,6 @@ export default {
return acc
}, {}),
mounts: [],
ports: [],
})

return {
Expand All @@ -243,6 +216,15 @@ export default {
mdiDelete,
}
},
watch: {
show(newShow) {
if (newShow) {
this.fetchExAppDeployOptions()
} else {
this.configuredDeployOptions = null
}
},
},
data() {
return {
addingMount: false,
Expand All @@ -252,11 +234,7 @@ export default {
readonly: false,
},
addingPortBinding: false,
newPortBinding: {
hostPort: '',
hostIp: '',
containerPort: '',
},
configuredDeployOptions: null,
}
},
methods: {
Expand All @@ -280,25 +258,14 @@ export default {
removeMount(mountToRemove) {
this.deployOptions.mounts = this.deployOptions.mounts.filter(mount => mount !== mountToRemove)
},
addPortBinding() {
this.deployOptions.ports.push(this.newPortBinding)
this.newPortBinding = {
hostPort: '',
hostIp: '',
containerPort: '',
}
this.addingPortBinding = false
},
cancelAddPortBinding() {
this.newPortBinding = {
hostPort: '',
hostIp: '',
containerPort: '',
}
this.addingPortBinding = false
},
removePortBinding(portToRemove) {
this.deployOptions.ports = this.deployOptions.ports.filter(port => port !== portToRemove)
async fetchExAppDeployOptions() {
return axios.get(generateUrl(`/apps/app_api/apps/deploy-options/${this.app.id}`))
.then(response => {
this.configuredDeployOptions = response.data
})
.catch(() => {
this.configuredDeployOptions = null
})
},
},
}
Expand All @@ -320,6 +287,17 @@ export default {
align-items: flex-start;
}

.envs {
width: 100%;
overflow: auto;
height: 100%;
max-height: 300px;

li {
margin: 10px 0;
}
}

.description {
margin-top: 4px;
font-size: 0.8em;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@
:disabled="installing || isLoading"
@click="forceEnable(app.id)">
<NcButton
v-if="app?.app_api && (app.canInstall || app.isCompatible)"
class="advanced-deploy-options"
:aria-label="t('settings', 'Advanced deploy options')"
type="secondary"
@click="() => showDeployOptionsModal = true">
<template #icon>
<NcIconSvgWrapper :path="mdiToyBrickPlus" />
</template>
{{ t('settings', 'Deploy options') }}
</NcButton>
</div>
<p v-if="!defaultDeployDaemonAccessible" class="warning">
Expand Down

0 comments on commit fe30885

Please sign in to comment.