Switching Ingress over to using the ArrayListGroup component

Made a few changes to improve the performance of the page. I experienced noticeable lag while editing the paths.
Updated KeyValue to address spacing and protip change requests.
This commit is contained in:
Cody Jackson 2020-11-05 14:28:17 -07:00 committed by Cody Jackson
parent 6a2acd6e56
commit 6e65615cb8
12 changed files with 167 additions and 164 deletions

View File

@ -119,3 +119,40 @@
-ms-transform: scale($horiz, $vert);
transform: scale($horiz, $vert);
}
@mixin input-status-color {
&:not(.focused) {
&.success {
border: solid 1px var(--success);
input, .selected {
color: var(--success);
}
.vs__actions:after {
color: var(--success);
}
}
&.warning {
border: solid 1px var(--warning);
input, .selected {
color: var(--warning);
}
.vs__actions:after {
color: var(--warning);
}
}
&.error {
border: solid 1px var(--error);
input, .selected {
color: var(--error);
}
.vs__actions:after {
color: var(--error);
}
}
}
}

View File

@ -24,40 +24,7 @@ TEXTAREA,
border: solid var(--outline-width) var(--input-border);
color: var(--input-text);
&:not(.focused) {
&.success {
border: solid 1px var(--success);
input, .selected {
color: var(--success);
}
.vs__actions:after {
color: var(--success);
}
}
&.warning {
border: solid 1px var(--warning);
input, .selected {
color: var(--warning);
}
.vs__actions:after {
color: var(--warning);
}
}
&.error {
border: solid 1px var(--error);
input, .selected {
color: var(--error);
}
.vs__actions:after {
color: var(--error);
}
}
}
@include input-status-color;
&:not(.view) {
&:hover {

View File

@ -668,7 +668,7 @@ labels:
addLabel: Add Label
addSetLabel: Add/Set Label
addAnnotation: Add Annotation
label:
labels:
title: Labels
annotations:
title: Annotations

View File

@ -13,7 +13,7 @@ import { clone } from '@/utils/object';
- Concealed value
*/
const DEFAULT_PROTIP = 'ProTip: Paste lines into any list field for easy bulk entry';
const DEFAULT_PROTIP = 'Paste lines into any list field for easy bulk entry';
export default {
components: { TextAreaAutoGrow },
@ -337,7 +337,7 @@ export default {
.value {
flex: 1;
INPUT {
height: 50px;
height: $input-height;
}
}
}

View File

@ -7,7 +7,7 @@ export default { components: { ArrayList, InfoBox } };
<template>
<ArrayList class="array-list-grouped" v-bind="$attrs" @input="$emit('input', $event)">
<template v-slot:columns="scope">
<InfoBox>
<InfoBox class="pt-40">
<slot v-bind="scope"></slot>
</InfoBox>
</template>

View File

@ -52,7 +52,7 @@ export default {
},
protip: {
type: [String, Boolean],
default: 'ProTip: Paste lines of <code>key=value</code> or <code>key: value</code> into any key field for easy bulk entry',
default: 'Paste lines of <em>key=value</em> or <em>key: value</em> into any key field for easy bulk entry',
},
padLeft: {
@ -397,7 +397,7 @@ export default {
<div class="key-value" :class="mode">
<div v-if="title" class="clearfix">
<slot name="title">
<h3>
<h3 class="mb-0">
{{ title }}
<button v-if="titleAdd && showAdd" type="button" class="btn btn-xs role-tertiary p-5 ml-10" style="position: relative; top: -3px;" @click="add()">
<i class="icon icon-plus icon-lg icon-fw" />
@ -524,7 +524,7 @@ export default {
</template>
</div>
<div v-if="!titleAdd && (showAdd || showRead)" class="footer">
<div v-if="!titleAdd && (showAdd || showRead)" class="footer mt-10">
<slot name="add" :add="add">
<button v-if="showAdd" type="button" class="btn btn-sm role-secondary add" @click="add()">
{{ addLabel }}

View File

@ -3,10 +3,12 @@ import { createPopper } from '@popperjs/core';
import { get } from '@/utils/object';
import LabeledFormElement from '@/mixins/labeled-form-element';
import VueSelectOverrides from '@/mixins/vue-select-overrides';
import LabeledTooltip from '@/components/form/LabeledTooltip';
export default {
components: { LabeledTooltip },
mixins: [LabeledFormElement, VueSelectOverrides],
props: {
props: {
disabled: {
default: false,
type: Boolean,
@ -49,6 +51,14 @@ export default {
},
type: Function,
},
tooltip: {
type: String,
default: null,
},
hoverTooltip: {
type: Boolean,
default: false,
},
searchable: {
default: false,
type: Boolean,
@ -174,5 +184,18 @@ export default {
<slot :name="slot" v-bind="scope" />
</template>
</v-select>
<LabeledTooltip
v-if="tooltip && !focused"
:hover="hoverTooltip"
:value="tooltip"
:status="status"
/>
</div>
</template>
<style lang="scss" scoped>
.unlabeled-select {
position: relative;
@include input-status-color;
}
</style>

View File

@ -1,12 +1,11 @@
<script>
import LabeledInput from '@/components/form/LabeledInput';
import LabeledSelect from '@/components/form/LabeledSelect';
import InfoBox from '@/components/InfoBox';
import ArrayList from '@/components/form/ArrayList';
const DEFAULT_CERT_VALUE = '__[[DEFAULT_CERT]]__';
export default {
components: {
InfoBox, LabeledInput, LabeledSelect
},
components: { ArrayList, LabeledSelect },
props: {
value: {
type: Object,
@ -22,7 +21,7 @@ export default {
data() {
const defaultCert = {
label: this.t('ingress.certificates.defaultCertLabel'),
value: null,
value: DEFAULT_CERT_VALUE,
};
const { hosts = [''], secretName = defaultCert.value } = this.value;
@ -34,7 +33,15 @@ export default {
},
computed: {
certsWithDefault() {
return [this.defaultCert, ...this.certs];
return [this.defaultCert, ...this.certs.map(c => ({ label: c, value: c }))];
},
certificateStatus() {
const isValueAnOption = !this.secretName || this.certsWithDefault.find(cert => this.secretName === cert.value);
return isValueAnOption ? null : 'warning';
},
certificateTooltip() {
return this.certificateStatus === 'warning' ? this.t('ingress.certificates.certificate.doesntExist') : null;
},
},
methods: {
@ -51,64 +58,47 @@ export default {
update() {
const out = { hosts: this.hosts };
if (this.secretName !== this.defaultCert) {
out.secretName = this.secretName;
out.secretName = this.secretName;
if (out.secretName === DEFAULT_CERT_VALUE) {
out.secretName = null;
}
this.$emit('input', out);
},
onSecretInput(e) {
this.secretName = e && typeof e === 'object' ? e.label : e;
this.update();
},
onHostsInput(e) {
this.hosts = e;
this.update();
}
},
};
</script>
<template>
<InfoBox class="cert" @input="update">
<div class="row">
<div class="col span-6">
<LabeledSelect
class="secret-name"
:value="secretName"
:options="certsWithDefault"
:label="t('ingress.certificates.certificate.label')"
required
@input="
(e) => {
secretName = e;
update();
}
"
/>
</div>
<div class="col span-6">
<div v-for="(host, i) in hosts" :key="i" class="row mb-10">
<div :style="{ 'margin-right': '0px' }" class="col span-10">
<LabeledInput
:value="host"
:label="t('ingress.certificates.host.label')"
:placeholder="t('ingress.certificates.host.placeholder')"
@input="(e) => $set(hosts, i, e)"
/>
</div>
<div class="col span-2">
<button
class="btn btn-sm role-link col"
@click="(e) => remove(e, i)"
>
{{ t("ingress.certificates.removeHost") }}
</button>
</div>
</div>
<button
class="btn role-tertiary add"
@click="addHost"
>
{{ t("ingress.certificates.addHost") }}
</button>
</div>
<div class="cert row" @input="update">
<div class="col span-6">
<LabeledSelect
v-model="secretName"
class="secret-name"
:options="certsWithDefault"
:label="t('ingress.certificates.certificate.label')"
required
:status="certificateStatus"
:taggable="true"
:tooltip="certificateTooltip"
:hover-tooltip="true"
:searchable="true"
@input="onSecretInput"
/>
</div>
<button class="btn role-link close" @click="$emit('remove')">
<i class="icon icon-2x icon-x" />
</button>
</InfoBox>
<div class="col span-6">
<ArrayList :value="hosts" :add-label="t('ingress.certificates.addHost')" @input="onHostsInput" />
</div>
</div>
</template>
<style lang="scss" scoped>

View File

@ -3,9 +3,13 @@ import SortableTable from '@/components/SortableTable';
import { _EDIT, _VIEW } from '@/config/query-params';
import { SECRET } from '@/config/types';
import { TYPES } from '@/models/secret';
import ArrayListGrouped from '@/components/form/ArrayListGrouped';
import Certificate from './Certificate';
export default {
components: { Certificate, SortableTable },
components: {
ArrayListGrouped, Certificate, SortableTable
},
props: {
value: {
type: Object,
@ -74,12 +78,6 @@ export default {
},
},
methods: {
addCert() {
this.tls.push({});
},
removeCert(idx) {
this.tls.splice(idx, 1);
},
filterByNamespace(list) {
const namespaces = this.$store.getters['namespaces']();
@ -101,18 +99,15 @@ export default {
:row-actions="false"
/>
<div v-else>
<Certificate
v-for="(cert,i) in tls"
:key="i"
class="mb-20"
:mode="mode"
:certs="certificates"
:value="cert"
@input="e=>$set(tls, i, e)"
@remove="e=>removeCert(i)"
/>
<button class="btn role-tertiary add mt-10" type="button" @click="addCert">
{{ t('ingress.certificates.addCertificate') }}
</button>
<ArrayListGrouped v-model="value.spec.tls" :add-label="t('ingress.certificates.addCertificate')" :default-add-value="{}">
<template #default="props">
<Certificate
v-model="props.row.value"
class="mb-20"
:mode="mode"
:certs="certificates"
/>
</template>
</ArrayListGrouped>
</div>
</template>

View File

@ -71,11 +71,6 @@ export default {
/>
</div>
<div id="host" class="col span-5"></div>
<div class="col span-1">
<button class="btn role-link close" @click="removeRule">
<i class="icon icon-2x icon-x" />
</button>
</div>
</div>
<div class="rule-path-headings row">
<div class="col" :class="{'span-6': ingress.showPathType, 'span-4': !ingress.showPathType}">
@ -111,13 +106,6 @@ export default {
</template>
<style lang="scss" scoped>
.rule {
background: var(--tabbed-container-bg);
border: 1px solid var(--tabbed-border);
border-radius: var(--border-radius);
padding: 20px;
margin-top: 20px;
}
#host {
align-self: center;
}

View File

@ -31,12 +31,18 @@ export default {
'Exact',
'ImplementationSpecific'
];
const { backend = {}, path = '', pathType = pathTypes[0] } = this.value;
const serviceName = get(backend, this.ingress.serviceNamePath) || '';
const servicePort = get(backend, this.ingress.servicePortPath) || '';
set(this.value, 'backend', this.value.backend || {});
set(this.value, 'path', this.value.path || '');
set(this.value, 'pathType', this.value.pathType || pathTypes[0]);
set(this.value.backend, this.ingress.serviceNamePath, get(this.value.backend, this.ingress.serviceNamePath) || '');
set(this.value.backend, this.ingress.servicePortPath, get(this.value.backend, this.ingress.servicePortPath) || '');
const serviceName = get(this.value.backend, this.ingress.serviceNamePath);
const servicePort = get(this.value.backend, this.ingress.servicePortPath);
return {
serviceName, servicePort, path, pathType, pathTypes
pathTypes, serviceName, servicePort
};
},
computed: {
@ -46,13 +52,16 @@ export default {
return service?.ports || [];
},
serviceTargetStatus() {
const isValueAnOption = !this.serviceName || this.serviceTargets.find(target => this.serviceName === target.value);
const serviceName = this.serviceName.label || this.serviceName;
const isValueAnOption = !serviceName || this.serviceTargets.find(target => serviceName === target.value);
return isValueAnOption ? null : 'warning';
},
serviceTargetTooltip() {
console.log('notop', this.serviceTargetStatus);
return this.serviceTargetStatus === 'warning' ? this.t('ingress.rules.target.doesntExist') : null;
},
}
},
created() {
this.queueUpdate = debounce(this.update, 500);
@ -83,10 +92,11 @@ export default {
<div class="rule-path row">
<div v-if="ingress.showPathType" class="col span-6">
<InputWithSelect
class="path-type"
:options="pathTypes"
:placeholder="t('ingress.rules.path.placeholder', undefined, true)"
:select-value="pathType"
:text-value="path"
:select-value="value.pathType"
:text-value="value.path"
:searchable="false"
@input="queueUpdatePathTypeAndPath"
/>
@ -102,6 +112,7 @@ export default {
:options="serviceTargets"
:status="serviceTargetStatus"
:taggable="true"
:searchable="true"
:tooltip="serviceTargetTooltip"
:hover-tooltip="true"
@input="queueUpdate(); servicePort = ''"
@ -110,7 +121,7 @@ export default {
<div class="col" :class="{'span-2': ingress.showPathType, 'span-3': !ingress.showPathType}" :style="{'margin-right': '0px'}">
<LabeledInput
v-if="portOptions.length === 0"
v-model="servicePort"
:v-model="servicePort"
:placeholder="t('ingress.rules.port.placeholder')"
@input="queueUpdate"
/>
@ -129,6 +140,12 @@ export default {
</template>
<style lang="scss" scoped>
.rule-path ::v-deep {
.path-type {
.unlabeled-select {
min-width: 200px;
}
}
&, .input-container {
height: $input-height;
}

View File

@ -3,11 +3,12 @@ import { WORKLOAD_TYPES } from '@/config/types';
import Loading from '@/components/Loading';
import SortableTable from '@/components/SortableTable';
import { _VIEW } from '@/config/query-params';
import ArrayListGrouped from '@/components/form/ArrayListGrouped';
import Rule from './Rule';
export default {
components: {
Loading, Rule, SortableTable
ArrayListGrouped, Loading, Rule, SortableTable
},
props: {
@ -31,10 +32,6 @@ export default {
await Promise.all(Object.values(WORKLOAD_TYPES).map(type => this.$store.dispatch('cluster/findAll', { type })));
},
data() {
return { rules: this.value.spec.rules };
},
computed: {
workloads() {
return Object.values(WORKLOAD_TYPES).flatMap(type => this.$store.getters['cluster/all'](type));
@ -83,15 +80,6 @@ export default {
rows() {
return this.value.createRulesForListPage(this.workloads);
}
},
methods: {
addRule() {
this.rules.push({});
},
removeRule(idx) {
this.rules.splice(idx, 1);
}
}
};
</script>
@ -109,16 +97,14 @@ export default {
/>
</div>
<div v-else>
<Rule
v-for="(_, i) in rules"
:key="i"
v-model="rules[i]"
:service-targets="serviceTargets"
:ingress="value"
@remove="e=>removeRule(i)"
/>
<button class="btn role-tertiary add mt-10" type="button" @click="addRule">
{{ t('ingress.rules.addRule') }}
</button>
<ArrayListGrouped v-model="value.spec.rules" :add-label="t('ingress.rules.addRule')" :default-add-value="{}">
<template #default="props">
<Rule
v-model="props.row.value"
:service-targets="serviceTargets"
:ingress="value"
/>
</template>
</ArrayListGrouped>
</div>
</template>