mirror of https://github.com/rancher/dashboard.git
Merge pull request #528 from codyrancher/ingress
Fixing up ingress pages
This commit is contained in:
commit
f7aa3d29a1
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
</div>
|
||||
<div v-else class="selected" :class="{'no-label':!label}" :style="{display:selectedDisplay}">
|
||||
{{ currentLabel }}
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }`;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue