mirror of https://github.com/rancher/dashboard.git
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:
parent
6a2acd6e56
commit
6e65615cb8
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -668,7 +668,7 @@ labels:
|
|||
addLabel: Add Label
|
||||
addSetLabel: Add/Set Label
|
||||
addAnnotation: Add Annotation
|
||||
label:
|
||||
labels:
|
||||
title: Labels
|
||||
annotations:
|
||||
title: Annotations
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue