Merge pull request #528 from codyrancher/ingress

Fixing up ingress pages
This commit is contained in:
Vincent Fiduccia 2020-04-21 14:10:37 -07:00 committed by GitHub
commit f7aa3d29a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 232 additions and 164 deletions

View File

@ -118,72 +118,73 @@ export default {
border-left: 0;
margin-left: -1px;
}
}
.in-input {
.in-input {
margin-right: 0;
border-radius: var(--border-radius) 0 0 var(--border-radius);
& .v-select{
height: 100%;
&.v-select {
height: initial;
.vs__selected {
margin: 0;
color: var(--input-text)
}
.vs__dropdown-menu {
min-width: 0px;
.vs__dropdown-option {
padding: 3px 5px;
.vs__selected {
margin: 0;
color: var(--input-text)
}
}
.vs__dropdown-toggle {
background-color: var(--accent-btn);
border-color: var(--primary);
border-right: solid 2px;
color: var(--primary) !important;
height: 100%;
padding: none;
display: flex;
align-items: stretch;
padding: 0 8px 0 8px;
border-radius: var(--border-radius) 0 0 var(--border-radius);
& * {
padding: 0
}
}
.vs__selected-options {
display: -webkit-box;
& .labeled-input {
top:10px;
& LABEL, .selected {
color: var(--primary);
}
.vs__dropdown-menu {
min-width: 0px;
.vs__dropdown-option {
padding: 3px 5px;
}
}
}
.vs__actions {
padding: 2px;;
}
.vs__search {
background-color: var(--default-text);
width: 0px;
padding: 0;
align-self: center;
border: 0;
}
.vs__dropdown-toggle {
background-color: var(--accent-btn);
border-color: var(--primary);
border-right: solid 2px;
color: var(--primary) !important;
height: 100%;
padding: none;
display: flex;
align-items: stretch;
padding: 0 8px 0 8px;
border-radius: var(--border-radius) 0 0 var(--border-radius);
& * {
padding: 0
}
}
.vs__open-indicator{
fill: var(--primary);
transform: scale(0.75);
}
.vs__selected-options {
display: -webkit-box;
& .labeled-input {
top:10px;
& LABEL, .selected {
color: var(--primary);
}
}
}
&.vs--open .vs__open-indicator {
transform: rotate(180deg) scale(0.75);
}
.vs__actions {
padding: 2px;;
}
.vs__search {
background-color: var(--default-text);
width: 0px;
padding: 0;
align-self: center;
border: 0;
}
}
.vs__open-indicator{
fill: var(--primary);
transform: scale(0.75);
}
&.vs--open .vs__open-indicator {
transform: rotate(180deg) scale(0.75);
}
}
}
}
</style>

View File

@ -103,9 +103,40 @@ export default {
</script>
<template>
<div>
<div v-if="isView">
<div :class="{'labeled-input': true, raised, focused, empty, [mode]: true}">
<div v-if="isView">
<div :class="{'labeled-input': true, raised, focused, empty, [mode]: true}">
<label>
{{ label }}
<span v-if="required && !value" class="required">*</span>
</label>
<label class="corner">
<slot name="corner" />
</label>
<div class="selected" :class="{'no-label':!label}" :style="{display:selectedDisplay}">
{{ currentLabel }}
</div>
</div>
</div>
<v-select
v-else
ref="input"
class="inline"
:disabled="isView || disabled"
:value="value"
:options="options"
:multiple="multiple"
:get-option-label="opt=>getOptionLabel(opt)"
:get-option-key="opt=>optionKey ? get(opt, optionKey) : getOptionLabel(opt)"
:label="optionLabel"
:reduce="x => reduce(x)"
@input="x => $emit('input', reduce(x))"
@search:focus="searchFocus"
@search:blur="searchBlur"
@focus="onFocus"
@blur="onBlur"
>
<template v-slot:selected-option-container>
<div :class="{'labeled-input': true, raised, focused, empty, [mode]: true}" :style="{border:'none'}">
<label>
{{ label }}
<span v-if="required && !value" class="required">*</span>
@ -113,48 +144,15 @@ export default {
<label class="corner">
<slot name="corner" />
</label>
<div class="selected" :class="{'no-label':!label}" :style="{display:selectedDisplay}">
<div v-if="isView">
{{ currentLabel }}
</div>
</div>
</div>
<v-select
v-else
ref="input"
class="inline"
:disabled="isView || disabled"
:value="value"
:options="options"
:multiple="multiple"
:get-option-label="opt=>getOptionLabel(opt)"
:get-option-key="opt=>optionKey ? get(opt, optionKey) : getOptionLabel(opt)"
:label="optionLabel"
:reduce="x => reduce(x)"
@input="x => $emit('input', reduce(x))"
@search:focus="searchFocus"
@search:blur="searchBlur"
@focus="onFocus"
@blur="onBlur"
>
<template v-slot:selected-option-container>
<div :class="{'labeled-input': true, raised, focused, empty, [mode]: true}" :style="{border:'none'}">
<label>
{{ label }}
<span v-if="required && !value" class="required">*</span>
</label>
<label class="corner">
<slot name="corner" />
</label>
<div v-if="isView">
{{ currentLabel }}
</div>
<div v-else class="selected" :class="{'no-label':!label}" :style="{display:selectedDisplay}">
{{ currentLabel }}&nbsp;
</div>
<div v-else class="selected" :class="{'no-label':!label}" :style="{display:selectedDisplay}">
{{ currentLabel }}&nbsp;
</div>
</template>
</v-select>
</div>
</div>
</template>
</v-select>
</template>
<style lang='scss'>

View File

@ -15,7 +15,7 @@ export default {
}
},
data() {
const serviceName = this.value?.http?.paths[0]?.backend?.serviceName;
const serviceName = this.value?.rules?.[0].http?.paths[0]?.backend?.serviceName || this.value?.backend?.serviceName || '';
const targetsWorkload = !serviceName.startsWith('ingress-');
let name; let params;
@ -34,16 +34,37 @@ export default {
return {
name, params, targetsWorkload
};
},
computed: {
showHost() {
return !!this.host;
},
host() {
return this.value?.rules?.[0].host;
},
pathServiceName() {
return this.value?.rules?.[0]?.http?.paths?.[0]?.backend?.serviceName;
},
backendServiceName() {
return this.value?.backend?.serviceName;
}
}
};
</script>
<template>
<div>
<a rel="nofollow noopener noreferrer" target="_blank" :href="'http://'+value.host">{{ value.host }}</a>
<i class="icon icon-chevron-right" />
<nuxt-link :to="{name, params}">
{{ value.http.paths[0].backend.serviceName }}
</nuxt-link>
<div v-if="value">
<div v-if="pathServiceName">
<a v-if="showHost" rel="nofollow noopener noreferrer" target="_blank" :href="'http://' + host">{{ host }}</a>
<i v-if="showHost" class="icon icon-chevron-right" />
<nuxt-link :to="{name, params}">
{{ pathServiceName }}
</nuxt-link>
</div>
<div v-else-if="backendServiceName">
<nuxt-link :to="{name, params}">
{{ backendServiceName }}
</nuxt-link>
</div>
</div>
</template>

View File

@ -392,8 +392,7 @@ export const API_GROUP = {
export const INGRESS_TARGET = {
name: 'ingressTarget',
label: 'Target',
value: "$['spec']['rules'][0]",
value: "$['spec']",
formatter: 'IngressTarget',
sort: "$['spec']['rules'][0].host",
}
;
};

View File

@ -5,7 +5,7 @@ import DetailTop from '@/components/DetailTop';
import SortableTable from '@/components/SortableTable';
import Tabbed from '@/components/Tabbed';
import Tab from '@/components/Tabbed/Tab';
import KVTable from '@/components/KVTable';
import Labels from '@/components/form/Labels';
export default {
components: {
@ -13,7 +13,7 @@ export default {
SortableTable,
Tabbed,
Tab,
KVTable
Labels
},
mixins: [CreateEditView],
props: {
@ -28,8 +28,8 @@ export default {
computed: {
detailTopColumns() {
const firstRule = this.value?.spec?.rules[0] || {};
const firstPath = firstRule?.http?.paths[0] || {};
const firstRule = this.firstRule || {};
const firstPath = firstRule?.http?.paths?.[0] || {};
const columns = [
{
@ -75,6 +75,22 @@ export default {
];
},
backendHeaders() {
return [
{
name: 'target',
label: 'Target',
value: 'serviceName',
width: 200
},
{
name: 'port',
label: 'Port',
value: 'servicePort'
}
];
},
certHeaders() {
return [
{
@ -113,6 +129,14 @@ export default {
return cert;
});
},
firstRule() {
return this.value?.spec?.rules?.[0];
},
ruleRows() {
return this.withUrl(this.firstRule?.http?.paths || []);
}
},
methods: {
@ -150,20 +174,35 @@ export default {
<template>
<div>
<DetailTop :columns="detailTopColumns" />
<DetailTop class="mb-20" :columns="detailTopColumns" />
<div>
<h3 class="mb-20">
Rules
</h3>
<div v-for="(rule, i) in value.spec.rules" :key="i" class="rule mb-20">
<div class="label-col mb-40">
<span>Hostname</span>
<code> {{ rule.host }}</code>
<div v-if="value.spec.rules">
<div v-for="(rule, i) in value.spec.rules" :key="i" class="rule mb-20">
<div class="label-col mb-40">
<span>Hostname</span>
<code> {{ rule.host }}</code>
</div>
<SortableTable
:rows="ruleRows"
:headers="ruleHeaders"
key-field="path"
:search="false"
:table-actions="false"
:row-actions="false"
/>
</div>
</div>
<div v-else class="rule">
<div class="mb-10">
Use as the default backend
</div>
<SortableTable
:rows="withUrl(rule.http.paths)"
:headers="ruleHeaders"
key-field="path"
:rows="[value.spec.backend]"
:headers="backendHeaders"
key-field="service"
:search="false"
:table-actions="false"
:row-actions="false"
@ -172,7 +211,7 @@ export default {
</div>
<Tabbed default-tab="labels">
<Tab name="labels" label="Labels">
<KVTable :rows="value.metadata.labels" />
<Labels :spec="value" :mode="mode" />
</Tab>
<Tab name="certificates" label="Certificates">
<SortableTable

View File

@ -29,7 +29,7 @@ export default {
const { paths = [{ id: random32(1) }] } = http;
return {
host, paths, ruleMode: 'setHost'
host, paths, ruleMode: this.value.asDefault ? 'asDefault' : 'setHost'
};
},
@ -93,7 +93,7 @@ export default {
@remove="e=>removePath(i)"
/>
</template>
<button :style="{'padding':'0px 0px 0px 5px'}" class="btn btn-sm role-link" @click="addPath">
<button v-if="ruleMode === 'setHost'" :style="{'padding':'0px 0px 0px 5px'}" class="btn btn-sm role-link" @click="addPath">
add path
</button>
</div>

View File

@ -1,8 +1,6 @@
<script>
import Certificate from './Certificate';
import Rule from './Rule';
import { clone } from '@/utils/object';
import { allHash } from '@/utils/promise';
import { SECRET, TLS_CERT, WORKLOAD_TYPES } from '@/config/types';
import NameNsDescription from '@/components/form/NameNsDescription';
@ -43,18 +41,7 @@ export default {
},
data() {
const { metadata = {}, spec = {} } = clone(this.value);
if (!spec.rules) {
spec.rules = [{}];
}
if (!spec.tls) {
spec.tls = [{ }];
}
return {
metadata, spec, allSecrets: [], allWorkloads: []
};
return { allSecrets: [], allWorkloads: [] };
},
computed: {
@ -75,6 +62,31 @@ export default {
},
},
created() {
if (!this.value.spec) {
this.value.spec = {};
}
if (!this.value.spec.rules) {
this.value.spec.rules = [{}];
}
if (this.value.spec.backend) {
if (!this.value.spec.rules[0].http) {
this.value.spec.rules[0].http = { paths: [] };
}
this.value.spec.rules[0].http.paths.push({ backend: this.value.spec.backend });
this.value.spec.rules[0].asDefault = true;
}
if (!this.value.spec.tls) {
this.value.spec.tls = [{ }];
}
this.registerBeforeHook(this.willSave, 'willSave');
},
methods: {
async loadDeps() {
const hash = await allHash({
@ -99,30 +111,30 @@ export default {
},
addRule() {
this.spec.rules = [...this.spec.rules, {}];
this.value.spec.rules = [...this.value.spec.rules, {}];
},
removeRule(idx) {
const neu = [...this.spec.rules];
const neu = [...this.value.spec.rules];
neu.splice(idx, 1);
this.$set(this.spec, 'rules', neu);
this.$set(this.value.spec, 'rules', neu);
},
updateRule(neu, idx) {
this.$set(this.spec.rules, idx, neu);
this.$set(this.value.spec.rules, idx, neu);
},
addCert() {
this.spec.tls = [...this.spec.tls, {}];
this.value.spec.tls = [...this.value.spec.tls, {}];
},
removeCert(idx) {
const neu = [...this.spec.tls];
const neu = [...this.value.spec.tls];
neu.splice(idx, 1);
this.$set(this.spec, 'tls', neu);
this.$set(this.value.spec, 'tls', neu);
},
// filter a given list of resources by currently selected namespaces
@ -149,38 +161,31 @@ export default {
});
},
saveIngress(cb) {
const defaultRule = this.spec.rules.filter(rule => rule.asDefault)[0];
const nonDefaultRules = this.spec.rules.filter(rule => !rule.asDefault);
willSave() {
const defaultRule = this.value.spec.rules.filter(rule => rule.asDefault)[0];
const defaultBackend = defaultRule?.http?.paths[0]?.backend;
const nonDefaultRules = this.value.spec.rules.filter(rule => !rule.asDefault);
nonDefaultRules.forEach(rule => delete rule.asDefault);
this.value.spec.rules = nonDefaultRules;
if (defaultBackend ) {
this.$set(this.spec, 'backend', defaultBackend);
this.$set(this.value.spec, 'backend', defaultBackend);
}
this.spec.rules = nonDefaultRules;
this.$set(this.value, 'spec', this.spec);
this.$set(this.value, 'metadata', this.metadata);
const saveUrl = this.value.urlFromAttrs;
this.save(cb, saveUrl);
}
},
}
};
</script>
<template>
<form>
<NameNsDescription :value="{metadata}" :mode="mode" @input="e=>metadata=e" />
<NameNsDescription v-model="value" :mode="mode" @input="e=>metadata=e" />
<div>
<h3>
Rules
</h3>
<Rule
v-for="(rule, i) in spec.rules"
v-for="(rule, i) in value.spec.rules"
:key="i"
:value="rule"
:workloads="workloads"
@ -194,15 +199,15 @@ export default {
<div>
<Tabbed :default-tab="'labels'">
<Tab name="labels" label="Labels">
<Labels :spec="{metadata:{}}" mode="create" />
<Labels :spec="value" mode="create" />
</Tab>
<Tab label="Certificates" name="certificates">
<Certificate
v-for="(cert,i) in spec.tls"
v-for="(cert,i) in value.spec.tls"
:key="i"
:certs="certificates"
:value="cert"
@input="e=>$set(spec.tls, i, e)"
@input="e=>$set(value.spec.tls, i, e)"
@remove="e=>removeCert(i)"
/>
<button class="btn btn-sm role-primary mt-20 " type="button" @click="addCert">
@ -211,6 +216,6 @@ export default {
</Tab>
</Tabbed>
</div>
<Footer :errors="errors" :mode="mode" @save="saveIngress" @done="done" />
<Footer :errors="errors" :mode="mode" @save="save" @done="done" />
</form>
</template>

View File

@ -774,7 +774,12 @@ export default {
const { metadata:{ namespace = 'default' } } = this;
let url = schema.links.collection;
const [group, resource] = schema?.attributes;
const attributes = schema?.attributes;
if (!attributes) {
throw new Error('Attributes must be present on the schema');
}
const { group, resource } = attributes;
url = `${ url.slice(0, url.indexOf('/v1')) }/apis/${ group }/namespaces/${ namespace }/${ resource }`;