Fixup port bindings in location view
This commit is contained in:
@@ -138,6 +138,16 @@ defineExpose({
|
||||
tcpPorts.value = a.manifest.tcpPorts;
|
||||
udpPorts.value = a.manifest.udpPorts;
|
||||
|
||||
// ensure we have value property
|
||||
for (const p in tcpPorts.value) {
|
||||
tcpPorts.value[p].value = tcpPorts.value[p].value || tcpPorts.value[p].defaultValue;
|
||||
tcpPorts.value[p].enabled = false;
|
||||
}
|
||||
for (const p in udpPorts.value) {
|
||||
udpPorts.value[p].value = udpPorts.value[p].value || udpPorts.value[p].defaultValue;
|
||||
udpPorts.value[p].enabled = false;
|
||||
}
|
||||
|
||||
secondaryDomains.value = a.manifest.httpPorts;
|
||||
for (const p in secondaryDomains.value) {
|
||||
const port = secondaryDomains.value[p];
|
||||
@@ -200,7 +210,7 @@ defineExpose({
|
||||
<TextInput id="upstreamUri" v-model="upstreamUri" />
|
||||
</FormGroup>
|
||||
|
||||
<PortBindings v-model:tcp-ports="tcpPorts" v-model:udp-ports="udpPorts" :error="formError"/>
|
||||
<PortBindings v-model:tcp="tcpPorts" v-model:udp="udpPorts" :error="formError"/>
|
||||
<AccessControl v-model:option="accessRestrictionOption" v-model:acl="accessRestrictionAcl" :manifest="manifest"/>
|
||||
|
||||
<Button style="margin-top: 15px" @click="submit" icon="fa-solid fa-circle-down" :disabled="!formValid" :loading="busy">Install {{ manifest.title }}</Button>
|
||||
|
||||
@@ -2,21 +2,11 @@
|
||||
|
||||
import { FormGroup, Checkbox, NumberInput } from 'pankow';
|
||||
|
||||
const props = defineProps([ 'tcpPorts', 'udpPorts', 'error' ]);
|
||||
defineEmits([ 'update:tcpPorts', 'update:udpPorts' ]);
|
||||
defineProps([ 'error' ]);
|
||||
|
||||
// copy value so we can use value as model value
|
||||
for (const p in props.tcpPorts) {
|
||||
const port = props.tcpPorts[p];
|
||||
port.value = port.defaultValue;
|
||||
port.enabled = true;
|
||||
}
|
||||
|
||||
for (const p in props.udpPorts) {
|
||||
const port = props.udpPorts[p];
|
||||
port.value = port.defaultValue;
|
||||
port.enabled = true;
|
||||
}
|
||||
// all ports require a property called 'value' for the model to work
|
||||
const tcpPorts = defineModel('tcp');
|
||||
const udpPorts = defineModel('udp');
|
||||
|
||||
</script>
|
||||
|
||||
@@ -26,7 +16,7 @@ for (const p in props.udpPorts) {
|
||||
<Checkbox :label="port.title" v-model="port.enabled" />
|
||||
<small>{{ port.description + '.' + (port.portCount >=1 ? (port.portCount + ' ports. ') : '') }}</small>
|
||||
<small v-show="port.readOnly">{{ $t('appstore.installDialog.portReadOnly') }}</small>
|
||||
<small class="has-error" v-show="error.port === port.value">Port already taken</small>
|
||||
<small class="has-error" v-if="error.port === port.value">Port already taken {{ port }}</small>
|
||||
<NumberInput v-model="port.value" :disabled="!port.enabled" :min="1"/>
|
||||
<!-- TODO <p class="text-small text-warning text-bold" ng-show="appInstall.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p> -->
|
||||
</FormGroup>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Button, SingleSelect, InputGroup, FormGroup, TextInput } from 'pankow';
|
||||
import PortBindings from '../PortBindings.vue';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
import DomainsModel from '../../models/DomainsModel.js';
|
||||
|
||||
@@ -13,31 +14,48 @@ const domainsModel = DomainsModel.create();
|
||||
const domains = ref([]);
|
||||
const busy = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const errorObject = ref({});
|
||||
const overwriteDns = ref(false);
|
||||
const needsOverwriteDns = ref(false);
|
||||
const domain = ref('');
|
||||
const subdomain = ref('');
|
||||
const provider = computed(() => {
|
||||
const d = domains.value.find(d => d.domain === domain.value);
|
||||
return d ? d.provider : '';
|
||||
});
|
||||
const secondaryDomains = ref({});
|
||||
const aliasDomains = ref([]);
|
||||
const aliases = ref([]);
|
||||
const redirects =ref([]);
|
||||
const tcpPorts = ref({});
|
||||
const udpPorts = ref({});
|
||||
|
||||
function onAddAliasDomain() {
|
||||
aliasDomains.value.push({
|
||||
function isNoopOrManual(domain) {
|
||||
const tmp = domains.value.find(d => d.domain === domain);
|
||||
return tmp ? (tmp.provider === 'noop' || tmp.provider === 'manual') : false;
|
||||
}
|
||||
|
||||
function onAddAlias() {
|
||||
aliases.value.push({
|
||||
domain: '',
|
||||
subdomain: ''
|
||||
});
|
||||
}
|
||||
|
||||
function onRemoveAliasDomain(index) {
|
||||
aliasDomains.value.splice(index, 1);
|
||||
function onRemoveAlias(index) {
|
||||
aliases.value.splice(index, 1);
|
||||
}
|
||||
|
||||
function onAddRedirect() {
|
||||
redirects.value.push({
|
||||
domain: '',
|
||||
subdomain: ''
|
||||
});
|
||||
}
|
||||
|
||||
function onRemoveRedirect(index) {
|
||||
redirects.value.splice(index, 1);
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
busy.value = true;
|
||||
errorMessage.value = '';
|
||||
errorObject.value = {};
|
||||
needsOverwriteDns.value = false;
|
||||
|
||||
const checkForDomains = [{
|
||||
@@ -46,7 +64,8 @@ async function onSubmit() {
|
||||
}];
|
||||
|
||||
for (const d in secondaryDomains.value) checkForDomains.push({ domain: secondaryDomains.value[d].domain, subdomain: secondaryDomains.value[d].subdomain });
|
||||
for (const d of aliasDomains.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
|
||||
for (const d of aliases.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
|
||||
for (const d of redirects.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
|
||||
|
||||
for (const d of checkForDomains) {
|
||||
const [error, result] = await domainsModel.checkRecords(d.domain, d.subdomain);
|
||||
@@ -58,14 +77,23 @@ async function onSubmit() {
|
||||
if (result.needsOverwrite) return needsOverwriteDns.value = true;
|
||||
}
|
||||
|
||||
// only use enabled ports
|
||||
const ports = {};
|
||||
const portsCombined = Object.assign(tcpPorts.value, udpPorts.value);
|
||||
for (const env in portsCombined) {
|
||||
if (portsCombined[env].enabled) {
|
||||
ports[env] = portsCombined[env].value;
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
overwriteDns: overwriteDns.value,
|
||||
subdomain: subdomain.value,
|
||||
domain: domain.value,
|
||||
ports: {}, // TODO
|
||||
ports: ports,
|
||||
secondaryDomains: secondaryDomains.value,
|
||||
redirectDomains: [], // TODO
|
||||
aliasDomains: aliasDomains.value,
|
||||
redirectDomains: redirects.value,
|
||||
aliasDomains: aliases.value,
|
||||
};
|
||||
|
||||
const [error] = await appsModel.configure(props.app.id, 'location', data);
|
||||
@@ -95,7 +123,32 @@ onMounted(async () => {
|
||||
secondaryDomains.value[d.environmentVariable].subdomain = d.subdomain;
|
||||
}
|
||||
|
||||
aliasDomains.value = props.app.aliasDomains;
|
||||
aliases.value = props.app.aliasDomains;
|
||||
redirects.value = props.app.redirectDomains;
|
||||
|
||||
tcpPorts.value = props.app.manifest.tcpPorts;
|
||||
udpPorts.value = props.app.manifest.udpPorts;
|
||||
|
||||
// ensure we have value property
|
||||
for (const p in tcpPorts.value) {
|
||||
tcpPorts.value[p].value = tcpPorts.value[p].value || tcpPorts.value[p].defaultValue;
|
||||
tcpPorts.value[p].enabled = false;
|
||||
}
|
||||
for (const p in udpPorts.value) {
|
||||
udpPorts.value[p].value = udpPorts.value[p].value || udpPorts.value[p].defaultValue;
|
||||
udpPorts.value[p].enabled = false;
|
||||
}
|
||||
|
||||
for (const p in props.app.portBindings) {
|
||||
if (tcpPorts.value[p]) {
|
||||
tcpPorts.value[p].value = props.app.portBindings[p].hostPort;
|
||||
tcpPorts.value[p].enabled = true;
|
||||
} else if (udpPorts.value[p]) {
|
||||
udpPorts.value[p].value = props.app.portBindings[p].hostPort;
|
||||
udpPorts.value[p].enabled = true;
|
||||
}
|
||||
else console.error(`Portbinding ${p} not known in manifest!`);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@@ -108,40 +161,66 @@ onMounted(async () => {
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('app.location.location') }}</label>
|
||||
<div class="has-error" v-if="errorMessage">{{ errorMessage }}</div>
|
||||
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" v-model="subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="domain" option-key="domain" option-label="domain"/>
|
||||
</InputGroup>
|
||||
<p class="text-warning" v-if="isNoopOrManual(domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((subdomain ? subdomain + '.' : '') + domain) })"></p>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-for="secondaryDomain in secondaryDomains" :key="secondaryDomain.containerPort">
|
||||
<label :for="'secondaryDomainInput' + secondaryDomain.containerPort">{{ secondaryDomain.title }}</label>
|
||||
<small>{{ secondaryDomain.description }}</small>
|
||||
<FormGroup v-for="item in secondaryDomains" :key="item.containerPort">
|
||||
<label :for="'secondaryDomainInput' + item.containerPort">{{ item.title }}</label>
|
||||
<small>{{ item.description }}</small>
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" :id="'secondaryDomainInput' + secondaryDomain.containerPort" v-model="secondaryDomain.subdomain" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="secondaryDomain.domain" option-key="domain" option-label="domain"/>
|
||||
<TextInput style="flex-grow: 1" :id="'secondaryDomainInput' + item.containerPort" v-model="item.subdomain" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="domain"/>
|
||||
</InputGroup>
|
||||
<p class="text-warning" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></p>
|
||||
</FormGroup>
|
||||
|
||||
<PortBindings v-model:tcp="tcpPorts" v-model:udp="udpPorts" :error="errorObject"/>
|
||||
|
||||
<div v-if="app.manifest.multiDomain">
|
||||
<label>{{ $t('app.location.aliases') }} <sup><a href="https://docs.cloudron.io/apps/#aliases" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 10px" v-for="(aliasDomain, index) in aliasDomains" :key="aliasDomain">
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" v-model="aliasDomain.subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="aliasDomain.domain" option-key="domain" option-label="domain"/>
|
||||
</InputGroup>
|
||||
<Button danger tool :disabled="busy" icon="fa-solid fa-trash" @click="onRemoveAliasDomain(index)"/>
|
||||
<div v-if="aliases.length === 0">{{ $t('app.location.noAliases') }}</div>
|
||||
<div v-for="(item, index) in aliases" :key="item" style="margin-bottom: 10px">
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" v-model="item.subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="domain"/>
|
||||
</InputGroup>
|
||||
<Button danger tool :disabled="busy" icon="fa-solid fa-trash" @click="onRemoveAlias(index)"/>
|
||||
</div>
|
||||
<p class="text-warning" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></p>
|
||||
</div>
|
||||
|
||||
<div class="actionable" v-if="!busy" @click="onAddAliasDomain()">{{ $t('app.location.addAliasAction') }}</div>
|
||||
<div class="actionable" v-if="!busy" @click="onAddAlias()">{{ $t('app.location.addAliasAction') }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>{{ $t('app.location.redirections') }} <sup><a href="https://docs.cloudron.io/apps/#redirections" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
|
||||
<div v-if="redirects.length === 0">{{ $t('app.location.noRedirections') }}</div>
|
||||
<div v-for="(item, index) in redirects" :key="item" style="margin-bottom: 10px;">
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" v-model="item.subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="domain"/>
|
||||
</InputGroup>
|
||||
<Button danger tool :disabled="busy" icon="fa-solid fa-trash" @click="onRemoveRedirect(index)"/>
|
||||
</div>
|
||||
<p class="text-warning" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></p>
|
||||
</div>
|
||||
|
||||
<div class="actionable" v-if="!busy" @click="onAddRedirect()">{{ $t('app.location.addRedirectionAction') }}</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<p class="text-bold text-warning" v-if="provider === 'noop' || provider === 'manual'" v-html="$t('appstore.installDialog.manualWarning', { location: ((subdomain ? subdomain + '.' : '') + domain) })"></p>
|
||||
<div class="has-error" v-if="errorMessage">{{ errorMessage }}</div>
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user