mirror of https://github.com/rancher/dashboard.git
commit
97de65d3cd
|
|
@ -0,0 +1,45 @@
|
|||
<script>
|
||||
import ResourceYaml from '@/components/ResourceYaml';
|
||||
|
||||
export async function asyncData(ctx) {
|
||||
const { resource, namespace, id } = ctx.params;
|
||||
const fqid = (namespace ? `${ namespace }/` : '') + id;
|
||||
|
||||
const obj = await ctx.store.dispatch('cluster/find', { type: resource, id: fqid });
|
||||
const value = await obj.followLink('view', { headers: { accept: 'application/yaml' } });
|
||||
|
||||
return {
|
||||
obj,
|
||||
value: value.data
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { ResourceYaml },
|
||||
|
||||
props: {
|
||||
asyncData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return { ...this.asyncData };
|
||||
},
|
||||
|
||||
computed: {
|
||||
doneRoute() {
|
||||
const name = this.$route.name.replace(/(-namespace)?-id$/, '');
|
||||
|
||||
return name;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ResourceYaml :obj="obj" :value="value" :done-route="doneRoute" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<script>
|
||||
import CreateEditView from '@/mixins/create-edit-view';
|
||||
import ResourceYaml from '@/components/ResourceYaml';
|
||||
import { FRIENDLY } from '@/config/friendly';
|
||||
import {
|
||||
MODE, _VIEW, _EDIT, EDIT_YAML, _FLAGGED
|
||||
} from '@/config/query-params';
|
||||
|
||||
export async function asyncData({ store, params, route }) {
|
||||
const { resource, namespace, id } = params;
|
||||
const type = FRIENDLY[resource].type;
|
||||
const asYaml = route.query[EDIT_YAML] === _FLAGGED;
|
||||
const schema = store.getters['cluster/schemaFor'](type);
|
||||
|
||||
let fqid = id;
|
||||
|
||||
if ( schema.attributes.namespaced ) {
|
||||
fqid = `${ namespace }/${ fqid }`;
|
||||
}
|
||||
|
||||
const obj = await store.dispatch('cluster/find', { type, id: fqid });
|
||||
const model = await store.dispatch('cluster/clone', obj);
|
||||
const view = await obj.followLink('view', { headers: { accept: 'application/yaml' } });
|
||||
|
||||
const out = {
|
||||
asYaml,
|
||||
resource,
|
||||
model,
|
||||
yaml: view.data,
|
||||
originalModel: obj
|
||||
};
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export const watchQuery = [MODE, EDIT_YAML];
|
||||
|
||||
export default {
|
||||
components: { ResourceYaml },
|
||||
mixins: { CreateEditView },
|
||||
|
||||
props: {
|
||||
asyncData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
const mode = this.$route.query.mode || _VIEW;
|
||||
|
||||
return {
|
||||
mode,
|
||||
...this.asyncData
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isView() {
|
||||
return this.mode === _VIEW;
|
||||
},
|
||||
|
||||
isEdit() {
|
||||
return this.mode === _EDIT;
|
||||
},
|
||||
|
||||
type() {
|
||||
return FRIENDLY[this.resource].type;
|
||||
},
|
||||
|
||||
doneRoute() {
|
||||
const name = this.$route.name.replace(/(-namespace)?-id$/, '');
|
||||
|
||||
return name;
|
||||
},
|
||||
|
||||
doneParams() {
|
||||
return this.$route.params;
|
||||
},
|
||||
|
||||
parentLink() {
|
||||
const name = this.doneRoute;
|
||||
const params = this.donneParams;
|
||||
const out = this.$router.resolve({ name, params }).href;
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
cruComponent() {
|
||||
return () => import(`@/components/cru/${ this.type }`);
|
||||
},
|
||||
|
||||
typeDisplay() {
|
||||
return FRIENDLY[this.resource].singular;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
showActions() {
|
||||
this.$store.commit('selection/show', {
|
||||
resources: this.originalModel,
|
||||
elem: this.$refs.actions,
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ResourceYaml
|
||||
v-if="asYaml"
|
||||
:obj="model"
|
||||
:value="yaml"
|
||||
:done-route="doneRoute"
|
||||
:parent-route="doneRoute"
|
||||
:parent-params="doneParams"
|
||||
/>
|
||||
<template v-else>
|
||||
<header>
|
||||
<h1 v-trim-whitespace>
|
||||
<span v-if="isEdit">Edit</span>
|
||||
<nuxt-link v-trim-whitespace :to="parentLink">
|
||||
{{ typeDisplay }}
|
||||
</nuxt-link>: {{ originalModel.nameDisplay }}
|
||||
</h1>
|
||||
<div v-if="isView" class="actions">
|
||||
<button ref="actions" class="btn btn-sm bg-primary actions" @click="showActions">
|
||||
<i class="icon icon-actions" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<component
|
||||
:is="cruComponent"
|
||||
v-model="model"
|
||||
:original-value="originalModel"
|
||||
:done-route="doneRoute"
|
||||
:done-params="doneParams"
|
||||
:parent-route="doneRoute"
|
||||
:parent-params="doneParams"
|
||||
:namespace-suffix-on-create="true"
|
||||
:type-label="typeDisplay"
|
||||
:mode="mode"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
<script>
|
||||
import { findBy } from '../../utils/array';
|
||||
import { TLS } from '../../models/core.v1.secret';
|
||||
import LoadDeps from '@/mixins/load-deps';
|
||||
import Loading from '@/components/Loading';
|
||||
import CreateEditView from '@/mixins/create-edit-view';
|
||||
import NameNsDescription from '@/components/form/NameNsDescription';
|
||||
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||
import Footer from '@/components/form/Footer';
|
||||
import { RIO, SECRET } from '@/config/types';
|
||||
import { groupAndFilterOptions } from '@/utils/group';
|
||||
import { allHash } from '@/utils/promise';
|
||||
|
||||
const KIND_LABELS = {
|
||||
'router': 'A router',
|
||||
'app': 'All versions of a service',
|
||||
'version': 'A single version of a service',
|
||||
};
|
||||
|
||||
const SECRET_LABELS = {
|
||||
'auto': 'Automatically generate a certificate',
|
||||
'secret': 'Choose a secret in the rio-system namespace',
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'CruPublicDomain',
|
||||
|
||||
components: {
|
||||
Loading,
|
||||
NameNsDescription,
|
||||
LabeledSelect,
|
||||
Footer,
|
||||
},
|
||||
mixins: [CreateEditView, LoadDeps],
|
||||
|
||||
data() {
|
||||
let spec = this.value.spec;
|
||||
|
||||
if ( !this.value.spec ) {
|
||||
spec = {};
|
||||
this.value.spec = spec;
|
||||
}
|
||||
|
||||
let kind, targetApp, targetVersion, targetRouter, secretKind, secret;
|
||||
|
||||
if ( spec.targetVersion ) {
|
||||
targetApp = spec.targetApp;
|
||||
targetVersion = spec.targetVersion;
|
||||
kind = 'version';
|
||||
} else if ( spec.targetApp ) {
|
||||
const matchingRouter = findBy(this.allRouters, 'app', spec.targetApp );
|
||||
|
||||
if ( matchingRouter ) {
|
||||
targetRouter = spec.targetApp;
|
||||
kind = 'router';
|
||||
} else {
|
||||
targetApp = spec.targetApp;
|
||||
kind = 'app';
|
||||
}
|
||||
} else {
|
||||
kind = 'router';
|
||||
}
|
||||
|
||||
if ( spec.secret ) {
|
||||
secret = spec.secret;
|
||||
secretKind = 'secret';
|
||||
} else {
|
||||
secretKind = 'auto';
|
||||
}
|
||||
|
||||
return {
|
||||
allServices: null,
|
||||
allRouters: null,
|
||||
allSecrets: null,
|
||||
targetNamespace: spec.targetNamespace || null,
|
||||
|
||||
kind,
|
||||
targetApp,
|
||||
targetVersion,
|
||||
targetRouter,
|
||||
secretKind,
|
||||
secret,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
appOptions() {
|
||||
return groupAndFilterOptions(this.allServices, null, { itemValueKey: 'namespaceApp', itemLabelKey: 'app' });
|
||||
},
|
||||
|
||||
routerOptions() {
|
||||
return groupAndFilterOptions(this.allRouters, null, { itemValueKey: 'namespaceApp', itemLabelKey: 'app' });
|
||||
},
|
||||
|
||||
secretOptions() {
|
||||
return groupAndFilterOptions(this.allSecrets, {
|
||||
'metadata.namespace': RIO.SYSTEM_NAMESPACE,
|
||||
'secretType': TLS,
|
||||
}, {
|
||||
groupBy: null,
|
||||
itemValueKey: 'metadata.name',
|
||||
});
|
||||
},
|
||||
|
||||
versionOptions() {
|
||||
const namespaceApp = this.targetApp;
|
||||
|
||||
if ( !namespaceApp ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return groupAndFilterOptions(this.allServices, { namespaceApp }, {
|
||||
groupBy: null,
|
||||
itemValueKey: 'version',
|
||||
itemLabelKey: 'versionWithDateDisplay',
|
||||
itemSortKey: 'metadata.creationTimestamp:desc'
|
||||
});
|
||||
},
|
||||
|
||||
kindLabels() {
|
||||
return KIND_LABELS;
|
||||
},
|
||||
|
||||
kindOptions() {
|
||||
return Object.keys(KIND_LABELS).map((k) => {
|
||||
return { label: KIND_LABELS[k], value: k };
|
||||
});
|
||||
},
|
||||
|
||||
secretKindLabels() {
|
||||
return SECRET_LABELS;
|
||||
},
|
||||
|
||||
secretKindOptions() {
|
||||
return Object.keys(SECRET_LABELS).map((k) => {
|
||||
return { label: SECRET_LABELS[k], value: k };
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
kind() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
secretKind() {
|
||||
this.update();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadDeps() {
|
||||
const hash = await allHash({
|
||||
services: this.$store.dispatch('cluster/findAll', { type: RIO.SERVICE }),
|
||||
routers: this.$store.dispatch('cluster/findAll', { type: RIO.ROUTER }),
|
||||
secrets: this.$store.dispatch('cluster/findAll', { type: SECRET }),
|
||||
});
|
||||
|
||||
this.allServices = hash.services;
|
||||
this.allRouters = hash.routers;
|
||||
this.allSecrets = hash.secrets;
|
||||
},
|
||||
|
||||
update() {
|
||||
const spec = this.value.spec;
|
||||
|
||||
spec.targetNamespace = null;
|
||||
spec.targetRouter = null;
|
||||
spec.targetApp = null;
|
||||
spec.targetVersion = null;
|
||||
|
||||
switch ( this.kind ) {
|
||||
case 'router':
|
||||
if ( this.targetRouter ) {
|
||||
const [ns, router] = this.targetRouter.split(':', 2);
|
||||
|
||||
this.targetNamespace = ns;
|
||||
spec.targetNamespace = ns;
|
||||
spec.targetRouter = router;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'app':
|
||||
case 'version':
|
||||
if ( this.targetApp ) {
|
||||
const [ns, app] = this.targetApp.split(':', 2);
|
||||
|
||||
this.targetNamespace = ns;
|
||||
spec.targetNamespace = ns;
|
||||
spec.targetApp = app;
|
||||
}
|
||||
|
||||
if ( this.kind === 'version' ) {
|
||||
if ( this.targetVersion ) {
|
||||
spec.targetVersion = this.targetVersion;
|
||||
} else if ( this.versionOptions.length ) {
|
||||
this.targetVersion = this.versionOptions[0].value;
|
||||
spec.targetVersion = this.targetVersion;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ( this.secretKind === 'secret' ) {
|
||||
if ( this.secret ) {
|
||||
spec.secretName = this.secret;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form>
|
||||
<Loading ref="loader" />
|
||||
<div v-if="loading">
|
||||
</div>
|
||||
<template v-else>
|
||||
<NameNsDescription
|
||||
:namespaced="false"
|
||||
:value="value"
|
||||
:mode="mode"
|
||||
name-label="Public Domain Name"
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-11-of-23">
|
||||
<h4>Target</h4>
|
||||
<div v-if="mode === 'view'">
|
||||
{{ kindLabels[kind] }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="opt in kindOptions" :key="opt.value">
|
||||
<label class="radio">
|
||||
<input v-model="kind" type="radio" :value="opt.value" />
|
||||
{{ opt.label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="kind === 'router'" class="mt-20">
|
||||
<LabeledSelect
|
||||
v-model="targetRouter"
|
||||
:options="routerOptions"
|
||||
:grouped="true"
|
||||
:mode="mode"
|
||||
label="Target Router"
|
||||
placeholder="Select a Router..."
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="kind === 'app' || kind === 'version'" class="mt-20">
|
||||
<LabeledSelect
|
||||
v-model="targetApp"
|
||||
:mode="mode"
|
||||
label="Target App"
|
||||
:options="appOptions"
|
||||
:grouped="true"
|
||||
placeholder="Select a service"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="kind === 'version'" class="mt-20">
|
||||
<LabeledSelect
|
||||
v-model="targetVersion"
|
||||
label="Target Version"
|
||||
:mode="mode"
|
||||
:options="versionOptions"
|
||||
placeholder="Select a version"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col span-1-of-23" style="position: relative; overflow: hidden">
|
||||
<hr class="vertical" />
|
||||
</div>
|
||||
|
||||
<div class="col span-11-of-23">
|
||||
<h4>Certificate</h4>
|
||||
<div v-if="mode === 'view'">
|
||||
{{ secretKindLabels[kind] }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="opt in secretKindOptions" :key="opt.value">
|
||||
<label class="radio">
|
||||
<input v-model="secretKind" type="radio" :value="opt.value" />
|
||||
{{ opt.label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="secretKind === 'secret'" class="mt-20">
|
||||
<LabeledSelect
|
||||
v-model="secret"
|
||||
:mode="mode"
|
||||
label="Secret Name"
|
||||
:options="secretOptions"
|
||||
placeholder="Select a Certificate Secret..."
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer :mode="mode" :errors="errors" @save="save" @done="done" />
|
||||
</template>
|
||||
</form>
|
||||
</template>
|
||||
|
|
@ -10,6 +10,12 @@ import Footer from '@/components/form/Footer';
|
|||
import { RIO } from '@/config/types';
|
||||
import { groupAndFilterOptions } from '@/utils/group';
|
||||
|
||||
const KIND_LABELS = {
|
||||
'service': 'Another service',
|
||||
'ip': 'A list of IP Addresses',
|
||||
'fqdn': 'A DNS name',
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'CruExternalService',
|
||||
|
||||
|
|
@ -48,10 +54,6 @@ export default {
|
|||
targetService = `${ spec.targetServiceNamespace }/${ spec.targetServiceName }`;
|
||||
}
|
||||
|
||||
if ( typeof window !== 'undefined' ) {
|
||||
window.v = this.value;
|
||||
}
|
||||
|
||||
return {
|
||||
kind,
|
||||
allServices: null,
|
||||
|
|
@ -65,6 +67,16 @@ export default {
|
|||
serviceOptions() {
|
||||
return groupAndFilterOptions(this.allServices);
|
||||
},
|
||||
|
||||
kindLabels() {
|
||||
return KIND_LABELS;
|
||||
},
|
||||
|
||||
kindOptions() {
|
||||
return Object.keys(KIND_LABELS).map((k) => {
|
||||
return { label: KIND_LABELS[k], value: k };
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
|
@ -128,26 +140,25 @@ export default {
|
|||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<h4>Target</h4>
|
||||
<div>
|
||||
<label class="radio">
|
||||
<input v-model="kind" type="radio" value="service" /> Another service
|
||||
</label>
|
||||
<div v-if="mode === 'view'">
|
||||
{{ kindLabels[kind] }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="radio">
|
||||
<input v-model="kind" type="radio" value="fqdn" /> A DNS name
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="radio">
|
||||
<input v-model="kind" type="radio" value="ip" /> One or more IP addresses
|
||||
</label>
|
||||
<div v-else>
|
||||
<div v-for="opt in kindOptions" :key="opt.value">
|
||||
<label class="radio">
|
||||
<input v-model="kind" type="radio" :value="opt.value" />
|
||||
{{ opt.label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div v-if="kind === 'service'" class="col span-6">
|
||||
<select v-model="targetService">
|
||||
<template v-if="isView">
|
||||
{{ targetService }}
|
||||
</template>
|
||||
<select v-else v-model="targetService">
|
||||
<option disabled value="">
|
||||
Select a Service...
|
||||
</option>
|
||||
|
|
@ -159,7 +170,7 @@ export default {
|
|||
</select>
|
||||
</div>
|
||||
<div v-if="kind === 'fqdn'" class="col span-6">
|
||||
<LabeledInput v-model="fqdn" label="DNS FQDN" @input="update" />
|
||||
<LabeledInput v-model="fqdn" :mode="mode" label="DNS FQDN" @input="update" />
|
||||
</div>
|
||||
<div v-if="kind === 'ip'" class="col span-6">
|
||||
<ArrayList
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import LabeledInput from '@/components/form/LabeledInput';
|
||||
import InputWithSelect from '@/components/form/InputWithSelect';
|
||||
import RadioGroup from '@/components/form/RadioGroup';
|
||||
|
||||
export default {
|
||||
components: { InputWithSelect },
|
||||
props: {
|
||||
|
|
@ -21,11 +20,13 @@ export default {
|
|||
default: () => {}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
types: this.options || ['exact', 'prefix', 'regexp'], value: Object.values(this.spec)[0] || '', type: Object.keys(this.spec)[0] || 'exact'
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectType(type) {
|
||||
this.type = type;
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export default {
|
|||
<div class="row">
|
||||
<div class="col span-6">
|
||||
<UnitInput
|
||||
v-model="spec.memory"
|
||||
v-model.number="spec.memoryBytes"
|
||||
:mode="mode"
|
||||
:increment="1024"
|
||||
:input-exponent="2"
|
||||
|
|
@ -96,7 +96,7 @@ export default {
|
|||
</div>
|
||||
<div class="col span-6">
|
||||
<UnitInput
|
||||
v-model="spec.cpu"
|
||||
v-model="spec.cpuMillis"
|
||||
:mode="mode"
|
||||
label="CPU Reservation"
|
||||
:increment="1000"
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export default {
|
|||
return KIND_LABELS;
|
||||
},
|
||||
|
||||
kindChoices() {
|
||||
kindOptions() {
|
||||
return Object.keys(KIND_LABELS).map((k) => {
|
||||
return { label: KIND_LABELS[k], value: k };
|
||||
});
|
||||
|
|
@ -163,7 +163,7 @@ export default {
|
|||
{{ kindLabels[kind] }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="opt in kindChoices" :key="opt.value">
|
||||
<div v-for="opt in kindOptions" :key="opt.value">
|
||||
<label class="radio">
|
||||
<input v-model="kind" type="radio" :value="opt.value" />
|
||||
{{ opt.label }}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default {
|
|||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
key="increment"
|
||||
v-model="spec.weight"
|
||||
v-model.number="spec.weight"
|
||||
:mode="mode"
|
||||
type="number"
|
||||
min="0"
|
||||
|
|
@ -43,7 +43,7 @@ export default {
|
|||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
key="increment"
|
||||
v-model="spec.rollout.increment"
|
||||
v-model.number="spec.rollout.increment"
|
||||
:mode="mode"
|
||||
type="number"
|
||||
min="1"
|
||||
|
|
@ -52,7 +52,7 @@ export default {
|
|||
</div>
|
||||
<div class="col span-6">
|
||||
<UnitInput
|
||||
v-model="spec.rollout.interval"
|
||||
v-model.number="spec.rollout.intervalSeconds"
|
||||
:mode="mode"
|
||||
label="Rollout Interval"
|
||||
suffix="sec"
|
||||
|
|
@ -64,7 +64,7 @@ export default {
|
|||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
key="maxUnavailable"
|
||||
v-model="spec.maxUnavailable"
|
||||
v-model.number="spec.maxUnavailable"
|
||||
:mode="mode"
|
||||
type="number"
|
||||
min="0"
|
||||
|
|
@ -74,7 +74,7 @@ export default {
|
|||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
key="maxSurge"
|
||||
v-model="spec.maxSurge"
|
||||
v-model.number="spec.maxSurge"
|
||||
:mode="mode"
|
||||
type="number"
|
||||
min="0"
|
||||
|
|
|
|||
|
|
@ -65,10 +65,6 @@ export default {
|
|||
spec.imagePullPolicy = 'Always';
|
||||
}
|
||||
|
||||
if ( typeof window !== 'undefined' ) {
|
||||
window.v = this.value;
|
||||
}
|
||||
|
||||
return {
|
||||
multipleContainers,
|
||||
nameResource,
|
||||
|
|
|
|||
|
|
@ -10,11 +10,23 @@ export default {
|
|||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
grouped: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentLabel() {
|
||||
const entry = findBy(this.options || [], 'value', this.value);
|
||||
let entry;
|
||||
|
||||
if ( this.grouped ) {
|
||||
for ( let i = 0 ; i < this.options.length && !entry ; i++ ) {
|
||||
entry = findBy(this.options[i].items || [], 'value', this.value);
|
||||
}
|
||||
} else {
|
||||
entry = findBy(this.options || [], 'value', this.value);
|
||||
}
|
||||
|
||||
if ( entry ) {
|
||||
return entry.label;
|
||||
|
|
@ -65,7 +77,14 @@ export default {
|
|||
{{ placeholder }}
|
||||
</option>
|
||||
<slot name="options" :options="options">
|
||||
<option v-for="opt in options" :key="opt.value" :value="opt.value">
|
||||
<template v-if="grouped">
|
||||
<optgroup v-for="grp in options" :key="grp.group" :label="grp.group">
|
||||
<option v-for="opt in grp.items" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</template>
|
||||
<option v-for="opt in options" v-else :key="opt.value" :value="opt.value">
|
||||
<slot name="label" :opt="opt">
|
||||
{{ opt.label }}
|
||||
</slot>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
threeColumn: {
|
||||
namespaced: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
extraColumn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
|
@ -81,6 +85,13 @@ export default {
|
|||
|
||||
notView() {
|
||||
return this.mode !== _VIEW;
|
||||
},
|
||||
|
||||
colSpan() {
|
||||
const cols = 1 + (this.namespaced ? 1 : 0) + (this.extraColumn ? 1 : 0);
|
||||
const span = 12 / cols;
|
||||
|
||||
return `span-${ span }`;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -89,7 +100,7 @@ export default {
|
|||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div :class="{col: true, 'span-6': !threeColumn, 'span-4': threeColumn}">
|
||||
<div :class="{col: true, [colSpan]: true}">
|
||||
<slot name="name">
|
||||
<LabeledInput
|
||||
key="name"
|
||||
|
|
@ -105,7 +116,7 @@ export default {
|
|||
</LabeledInput>
|
||||
</slot>
|
||||
</div>
|
||||
<div :class="{col: true, 'span-6': !threeColumn, 'span-4': threeColumn}">
|
||||
<div v-if="namespaced" :class="{col: true, [colSpan]: true}">
|
||||
<slot name="namespace">
|
||||
<LabeledSelect
|
||||
key="namespace"
|
||||
|
|
@ -118,7 +129,7 @@ export default {
|
|||
/>
|
||||
</slot>
|
||||
</div>
|
||||
<div v-if="threeColumn" class="col span-4">
|
||||
<div v-if="extraColumn" :class="{col: true, [colSpan]: true}">
|
||||
<slot name="right">
|
||||
</slot>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default {
|
|||
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
col: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
{{ value }}%
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<script>
|
||||
import { RIO } from '@/config/types';
|
||||
import { filterBy } from '@/utils/array';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
col: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
total() {
|
||||
const services = this.$store.getters['cluster/all'](RIO.SERVICE);
|
||||
const forThisApp = filterBy(services, 'app', this.row.app);
|
||||
|
||||
let desired = 0;
|
||||
let current = 0;
|
||||
|
||||
for ( const service of forThisApp ) {
|
||||
const weights = service.weights;
|
||||
|
||||
desired += weights.desired || 0;
|
||||
current += weights.current || 0;
|
||||
}
|
||||
|
||||
desired = Math.max(1, desired);
|
||||
current = Math.max(1, current);
|
||||
|
||||
return { desired, current };
|
||||
},
|
||||
|
||||
desired() {
|
||||
const desired = this.row.weights.desired;
|
||||
const total = this.total.desired;
|
||||
|
||||
return Math.round(desired / total * 1000) / 10;
|
||||
},
|
||||
|
||||
current() {
|
||||
const current = this.row.weights.current;
|
||||
const total = this.total.current;
|
||||
|
||||
if ( total === 0 ) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return Math.round(current / total * 1000) / 10;
|
||||
},
|
||||
|
||||
showDesired() {
|
||||
return this.current !== this.desired;
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p v-trim-whitespace :class="{'text-muted': current === 100 && desired === 100}">
|
||||
{{ current }}%
|
||||
</p>
|
||||
<div v-if="showDesired">
|
||||
<i class="icon icon-chevron-right" />
|
||||
{{ desired }}%
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { CONFIG_MAP, SECRET, RIO } from '@/config/types';
|
||||
import {
|
||||
STATE, NAMESPACE_NAME, RIO_IMAGE, SCALE, AGE, KEYS, TARGET, TARGET_KIND,
|
||||
STATE, NAME, NAMESPACE_NAME, AGE,
|
||||
RIO_IMAGE, WEIGHT, SCALE,
|
||||
KEYS,
|
||||
TARGET, TARGET_KIND, TARGET_SECRET,
|
||||
} from '@/config/table-headers';
|
||||
|
||||
export const FRIENDLY = {
|
||||
|
|
@ -31,8 +34,17 @@ export const FRIENDLY = {
|
|||
'public-domains': {
|
||||
singular: 'Public Domain',
|
||||
plural: 'Public Domains',
|
||||
type: RIO.PUBLIC_DOMAIN
|
||||
type: RIO.PUBLIC_DOMAIN,
|
||||
headers: [
|
||||
STATE,
|
||||
NAME,
|
||||
TARGET_KIND,
|
||||
TARGET,
|
||||
TARGET_SECRET,
|
||||
AGE,
|
||||
],
|
||||
},
|
||||
|
||||
services: {
|
||||
singular: 'Service',
|
||||
plural: 'Services',
|
||||
|
|
@ -41,20 +53,24 @@ export const FRIENDLY = {
|
|||
STATE,
|
||||
NAMESPACE_NAME,
|
||||
RIO_IMAGE,
|
||||
WEIGHT,
|
||||
SCALE,
|
||||
AGE,
|
||||
]
|
||||
},
|
||||
|
||||
stack: {
|
||||
singular: 'Stack',
|
||||
plural: 'Stacks',
|
||||
type: RIO.STACK
|
||||
},
|
||||
|
||||
routers: {
|
||||
singular: 'Router',
|
||||
plural: 'Routers',
|
||||
type: RIO.ROUTER
|
||||
},
|
||||
|
||||
secrets: {
|
||||
singular: 'Secret',
|
||||
plural: 'Secrets',
|
||||
|
|
|
|||
|
|
@ -74,12 +74,14 @@ export const SCALE = {
|
|||
formatter: 'Scale',
|
||||
align: 'center',
|
||||
};
|
||||
|
||||
export const WEIGHT = {
|
||||
name: 'weight',
|
||||
label: 'Weight',
|
||||
sort: false,
|
||||
width: 60,
|
||||
value: 'status.computedWeight',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
formatter: 'Weight',
|
||||
};
|
||||
|
||||
export const SUCCESS = {
|
||||
|
|
@ -114,7 +116,7 @@ export const KEYS = {
|
|||
};
|
||||
|
||||
export const TARGET_KIND = {
|
||||
name: 'targetKind',
|
||||
name: 'target-kind',
|
||||
label: 'Target Type',
|
||||
value: 'kindDisplay',
|
||||
width: 100,
|
||||
|
|
@ -126,6 +128,13 @@ export const TARGET = {
|
|||
value: 'targetDisplay',
|
||||
};
|
||||
|
||||
export const TARGET_SECRET = {
|
||||
name: 'secret-name',
|
||||
label: 'Secret',
|
||||
value: 'secretName',
|
||||
sort: ['secretName', 'targetApp', 'targetVersion', 'id'],
|
||||
};
|
||||
|
||||
export function headersFor(schema) {
|
||||
const out = [];
|
||||
const columns = schema.attributes.columns;
|
||||
|
|
|
|||
|
|
@ -15,13 +15,15 @@ export const RIO = {
|
|||
CLUSTER_DOMAIN: 'admin.rio.cattle.io.v1.clusterdomain',
|
||||
FEATURE: 'admin.rio.cattle.io.v1.feature',
|
||||
INFO: 'admin.rio.cattle.io.v1.rioinfo',
|
||||
PUBLIC_DOMAIN: 'admin.rio.cattle.io.v1.publicdomain',
|
||||
|
||||
APP: 'rio.cattle.io.v1.app',
|
||||
EXTERNAL_SERVICE: 'rio.cattle.io.v1.externalservice',
|
||||
PUBLIC_DOMAIN: 'admin.rio.cattle.io.v1.publicdomain',
|
||||
STACK: 'rio.cattle.io.v1.stack',
|
||||
ROUTER: 'rio.cattle.io.v1.router',
|
||||
SERVICE: 'rio.cattle.io.v1.service',
|
||||
APP: 'rio.cattle.io.v1.app',
|
||||
EXTERNAL_SERVICE: 'rio.cattle.io.v1.externalservice',
|
||||
STACK: 'rio.cattle.io.v1.stack',
|
||||
ROUTER: 'rio.cattle.io.v1.router',
|
||||
SERVICE: 'rio.cattle.io.v1.service',
|
||||
|
||||
SYSTEM_NAMESPACE: 'rio-system',
|
||||
};
|
||||
|
||||
export const RANCHER = {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ export default {
|
|||
},
|
||||
|
||||
data() {
|
||||
if ( typeof window !== 'undefined' ) {
|
||||
window.v = this.value;
|
||||
}
|
||||
|
||||
return { errors: null };
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
export default {
|
||||
targetKind() {
|
||||
const spec = this.spec;
|
||||
|
||||
if ( !spec ) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
if ( spec.targetRouter && spec.targetNamespace ) {
|
||||
return 'router';
|
||||
}
|
||||
|
||||
if ( spec.targetApp && spec.targetVersion && spec.targetNamespace ) {
|
||||
return 'version';
|
||||
}
|
||||
|
||||
if ( spec.targetApp && spec.targetNamespace ) {
|
||||
return 'app';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
},
|
||||
|
||||
kindDisplay() {
|
||||
// Satisfy eslint that it's a string...
|
||||
const kind = `${ this.targetKind }`;
|
||||
|
||||
switch ( kind ) {
|
||||
case 'router':
|
||||
return 'Router';
|
||||
case 'version':
|
||||
return 'Service version';
|
||||
case 'app':
|
||||
return 'Service';
|
||||
case 'unknown':
|
||||
return '?';
|
||||
}
|
||||
},
|
||||
|
||||
targetDisplay() {
|
||||
// Satisfy eslint that it's a string...
|
||||
const kind = `${ this.targetKind }`;
|
||||
|
||||
switch ( kind ) {
|
||||
case 'router':
|
||||
return `${ this.spec.targetNamespace }/${ this.spec.targetRouter }`;
|
||||
case 'version':
|
||||
return `${ this.spec.targetNamespace }/${ this.spec.targetApp }@${ this.spec.targetVersion }`;
|
||||
case 'app':
|
||||
return `${ this.spec.targetNamespace }/${ this.spec.targetApp }`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -1,13 +1,23 @@
|
|||
export const OPAQUE = 'Opaque';
|
||||
export const SERVICE_ACCT = 'kubernetes.io/service-account-token';
|
||||
export const DOCKER = 'kubernetes.io/dockercfg';
|
||||
export const DOCKER_JSON = 'kubernetes.io/dockerconfigjson';
|
||||
export const BASIC = 'kubernetes.io/basic-auth';
|
||||
export const SSH = 'kubernetes.io/ssh-auth';
|
||||
export const TLS = 'kubernetes.io/tls';
|
||||
export const BOOTSTRAP = 'bootstrap.kubernetes.io/token';
|
||||
export const ISTIO_TLS = 'istio.io/key-and-cert';
|
||||
|
||||
const DISPLAY_TYPES = {
|
||||
Opaque: 'Opaque',
|
||||
'kubernetes.io/service-account-token': 'Service Acct',
|
||||
'kubernetes.io/dockercfg': 'Dockercfg',
|
||||
'kubernetes.io/dockerconfigjson': 'Docker JSON',
|
||||
'kubernetes.io/basic-auth': 'Basic Auth',
|
||||
'kubernetes.io/ssh-auth': 'SSH',
|
||||
'kubernetes.io/tls': 'TLS',
|
||||
'bootstrap.kubernetes.io/token': 'Bootstrap Token',
|
||||
'istio.io/key-and-cert': 'TLS (Istio)',
|
||||
[OPAQUE]: 'Opaque',
|
||||
[SERVICE_ACCT]: 'Service Acct',
|
||||
[DOCKER]: 'Dockercfg',
|
||||
[DOCKER_JSON]: 'Docker JSON',
|
||||
[BASIC]: 'Basic Auth',
|
||||
[SSH]: 'SSH',
|
||||
[TLS]: 'TLS',
|
||||
[BOOTSTRAP]: 'Bootstrap Token',
|
||||
[ISTIO_TLS]: 'TLS (Istio)',
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
@ -28,6 +38,10 @@ export default {
|
|||
return keys.join(', ');
|
||||
},
|
||||
|
||||
secretType() {
|
||||
return this._type;
|
||||
},
|
||||
|
||||
typeDisplay() {
|
||||
const mapped = DISPLAY_TYPES[this._type];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
export default {
|
||||
kind() {
|
||||
targetKind() {
|
||||
const spec = this.spec;
|
||||
|
||||
if ( !spec ) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
if ( spec.targetServiceName && spec.targetServiceNamespace ) {
|
||||
return 'service';
|
||||
}
|
||||
|
|
@ -19,7 +23,7 @@ export default {
|
|||
|
||||
kindDisplay() {
|
||||
// Satisfy eslint that it's a string...
|
||||
const kind = `${ this.kind }`;
|
||||
const kind = `${ this.targetKind }`;
|
||||
|
||||
switch ( kind ) {
|
||||
case 'service':
|
||||
|
|
@ -35,7 +39,7 @@ export default {
|
|||
|
||||
targetDisplay() {
|
||||
// Satisfy eslint that it's a string...
|
||||
const kind = `${ this.kind }`;
|
||||
const kind = `${ this.targetKind }`;
|
||||
|
||||
switch ( kind ) {
|
||||
case 'service':
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
export default {
|
||||
app() {
|
||||
return this.spec.app || this.status.computedApp || this.metadata.name;
|
||||
},
|
||||
|
||||
namespaceApp() {
|
||||
return `${ this.metadata.namespace }:${ this.app }`;
|
||||
},
|
||||
};
|
||||
|
|
@ -1,21 +1,31 @@
|
|||
import day from 'dayjs';
|
||||
import { insertAt } from '@/utils/array';
|
||||
import { ADD_SIDECAR, _FLAGGED } from '@/config/query-params';
|
||||
import { escapeHtml } from '@/utils/string';
|
||||
import { DATE_FORMAT, TIME_FORMAT } from '@/store/prefs';
|
||||
|
||||
export default {
|
||||
appKey() {
|
||||
return `${ this.spec.namespace }/${ this.appName }`;
|
||||
app() {
|
||||
return this.spec.app || this.status.computedApp || this.metadata.name;
|
||||
},
|
||||
|
||||
appName() {
|
||||
return this.spec.app || this.metadata.name;
|
||||
},
|
||||
|
||||
versionName() {
|
||||
return this.spec.version || 'v0';
|
||||
version() {
|
||||
return this.spec.version || this.status.computedVersion;
|
||||
},
|
||||
|
||||
nameDisplay() {
|
||||
return `${ this.appName }:${ this.versionName }`;
|
||||
return `${ this.app } (${ this.version })`;
|
||||
},
|
||||
|
||||
namespaceNameDisplay() {
|
||||
const namespace = this.metadata.namespace;
|
||||
const name = this.metadata.name || this.id;
|
||||
|
||||
return `${ namespace }:${ name }`;
|
||||
},
|
||||
|
||||
namespaceApp() {
|
||||
return `${ this.metadata.namespace }:${ this.app }`;
|
||||
},
|
||||
|
||||
imageDisplay() {
|
||||
|
|
@ -26,6 +36,17 @@ export default {
|
|||
.replace(/localhost:5442\/(.*)/i, '$1 (local)');
|
||||
},
|
||||
|
||||
createdDisplay() {
|
||||
const dateFormat = escapeHtml( this.$rootGetters['prefs/get'](DATE_FORMAT));
|
||||
const timeFormat = escapeHtml( this.$rootGetters['prefs/get'](TIME_FORMAT));
|
||||
|
||||
return day(this.metadata.creationTimestamp).format(`${ dateFormat } ${ timeFormat }`);
|
||||
},
|
||||
|
||||
versionWithDateDisplay() {
|
||||
return `${ this.version } (${ this.createdDisplay })`;
|
||||
},
|
||||
|
||||
scales() {
|
||||
const status = this.status || {};
|
||||
let scaleStatus = status.scaleStatus;
|
||||
|
|
@ -37,10 +58,11 @@ export default {
|
|||
}
|
||||
|
||||
const spec = (typeof this.spec.replicas === 'undefined' ? 1 : this.spec.replicas || 0);
|
||||
const global = this.spec.global === true;
|
||||
const current = status.computedReplicas || 0;
|
||||
const available = scaleStatus.available || 0;
|
||||
const current = (typeof this.status.computedReplicas === 'undefined' ? available : status.computedReplicas || 0);
|
||||
const unavailable = scaleStatus.unavailable || 0;
|
||||
const global = this.spec.global === true;
|
||||
|
||||
let desired = spec;
|
||||
|
||||
if ( global ) {
|
||||
|
|
@ -177,6 +199,32 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
weights() {
|
||||
let current = 0;
|
||||
let desired = 0;
|
||||
const spec = this.spec.weight;
|
||||
|
||||
if ( !this.status ) {
|
||||
return { current, desired };
|
||||
}
|
||||
|
||||
const status = this.status.computedWeight;
|
||||
|
||||
if ( typeof status === 'number' ) {
|
||||
current = status;
|
||||
} else if ( typeof spec === 'number' ) {
|
||||
current = spec;
|
||||
}
|
||||
|
||||
if ( typeof spec === 'number' ) {
|
||||
desired = spec;
|
||||
} else if ( typeof status === 'number' ) {
|
||||
desired = status;
|
||||
}
|
||||
|
||||
return { current, desired };
|
||||
},
|
||||
|
||||
async pauseOrResume(pause = true) {
|
||||
try {
|
||||
await this.patch({
|
||||
|
|
@ -236,110 +284,4 @@ export default {
|
|||
return this.goToEdit({ [ADD_SIDECAR]: _FLAGGED });
|
||||
};
|
||||
},
|
||||
|
||||
// @TODO fake
|
||||
/*
|
||||
pods() {
|
||||
const out = [];
|
||||
const status = this.status.scaleStatus;
|
||||
|
||||
if ( !status ) {
|
||||
return out;
|
||||
}
|
||||
|
||||
let idx = 1;
|
||||
|
||||
for ( let i = 0 ; i < status.ready ; i++ ) {
|
||||
let state = 'active';
|
||||
let transitioning = 'no';
|
||||
|
||||
if ( i >= this.spec.scale ) {
|
||||
state = 'removing';
|
||||
transitioning = 'yes';
|
||||
}
|
||||
|
||||
out.push(store.createRecord({
|
||||
type: 'pod',
|
||||
name: `${ this.nameDisplay }-${ idx }`,
|
||||
state,
|
||||
transitioning,
|
||||
|
||||
containers: [
|
||||
store.createRecord({
|
||||
type: 'container',
|
||||
name: `container${ idx }`,
|
||||
state,
|
||||
transitioning,
|
||||
})
|
||||
]
|
||||
}));
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
for ( let i = 0 ; i < status.available ; i++ ) {
|
||||
out.push(store.createRecord({
|
||||
type: 'pod',
|
||||
name: `${ get(this, 'nameDisplay') }-${ idx }`,
|
||||
state: 'not-ready',
|
||||
transitioning: 'no',
|
||||
|
||||
containers: [
|
||||
store.createRecord({
|
||||
type: 'container',
|
||||
name: `container${ idx }`,
|
||||
state: 'not-ready',
|
||||
transitioning: 'no',
|
||||
})
|
||||
]
|
||||
}));
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
for ( let i = 0 ; i < status.unavailable ; i++ ) {
|
||||
out.push(store.createRecord({
|
||||
type: 'pod',
|
||||
name: `${ get(this, 'nameDisplay') }-${ idx }`,
|
||||
state: 'creating',
|
||||
transitioning: 'yes',
|
||||
|
||||
containers: [
|
||||
store.createRecord({
|
||||
type: 'container',
|
||||
name: `container${ idx }`,
|
||||
state: 'transitioning',
|
||||
transitioning: 'yes',
|
||||
})
|
||||
]
|
||||
}));
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
partiallyUpdated: computed('scale', 'scaleStatus.updated', function() {
|
||||
let scale = get(this, 'scale');
|
||||
let status = get(this, 'scaleStatus');
|
||||
|
||||
if ( !status ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( scale > 0 && status.updated > 0 && scale > status.updated ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}),
|
||||
|
||||
updatedPercent: computed('scale', 'scaleStatus.updated', function() {
|
||||
let scale = get(this, 'scale');
|
||||
let status = get(this, 'scaleStatus');
|
||||
|
||||
return formatPercent(100 * status.updated / scale);
|
||||
}),
|
||||
*/
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { trimWhitespaceSsr } from './plugins/trim-whitespace';
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
|
|
@ -74,6 +75,8 @@ module.exports = {
|
|||
cssSourceMap: true
|
||||
},
|
||||
|
||||
render: { bundleRenderer: { directives: { trimWhitespace: trimWhitespaceSsr } } },
|
||||
|
||||
modern: true,
|
||||
|
||||
generate: { dir: outputDir },
|
||||
|
|
|
|||
|
|
@ -1,34 +1,13 @@
|
|||
<script>
|
||||
import ResourceYaml from '@/components/ResourceYaml';
|
||||
import ExplorerDetail, { asyncData } from '@/components/ExplorerDetail';
|
||||
|
||||
export default {
|
||||
components: { ResourceYaml },
|
||||
|
||||
computed: {
|
||||
doneRoute() {
|
||||
const name = this.$route.name.replace(/(-namespace)?-id$/, '');
|
||||
|
||||
return name;
|
||||
}
|
||||
},
|
||||
|
||||
async asyncData(ctx) {
|
||||
const { resource, namespace, id } = ctx.params;
|
||||
const fqid = (namespace ? `${ namespace }/` : '') + id;
|
||||
|
||||
const obj = await ctx.store.dispatch('cluster/find', { type: resource, id: fqid });
|
||||
const value = await obj.followLink('view', { headers: { accept: 'application/yaml' } });
|
||||
|
||||
return {
|
||||
obj,
|
||||
value: value.data
|
||||
};
|
||||
}
|
||||
name: 'ExplorerGroupResourceId',
|
||||
components: { ExplorerDetail },
|
||||
asyncData
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ResourceYaml :obj="obj" :value="value" :done-route="doneRoute" />
|
||||
</div>
|
||||
<ExplorerDetail :async-data="_data" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
<script>
|
||||
export { default } from '../_id.vue';
|
||||
import ExplorerDetail, { asyncData } from '@/components/ExplorerDetail';
|
||||
|
||||
export default {
|
||||
name: 'ExplorerGroupResourceNamespaceId',
|
||||
components: { ExplorerDetail },
|
||||
asyncData
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ResourceYaml :obj="obj" :value="value" :done-route="doneRoute" />
|
||||
</div>
|
||||
<ExplorerDetail :async-data="_data" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
import RioDetail, { watchQuery, asyncData } from '@/components/RioDetail';
|
||||
|
||||
export default {
|
||||
name: 'RioResourceId',
|
||||
components: { RioDetail },
|
||||
asyncData,
|
||||
watchQuery,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RioDetail :async-data="_data" />
|
||||
</template>
|
||||
|
|
@ -1,130 +1,14 @@
|
|||
<script>
|
||||
import CreateEditView from '@/mixins/create-edit-view';
|
||||
import ResourceYaml from '@/components/ResourceYaml';
|
||||
import { FRIENDLY } from '@/config/friendly';
|
||||
import {
|
||||
MODE, _VIEW, _EDIT, EDIT_YAML, _FLAGGED
|
||||
} from '@/config/query-params';
|
||||
import RioDetail, { watchQuery, asyncData } from '@/components/RioDetail';
|
||||
|
||||
export default {
|
||||
components: { ResourceYaml },
|
||||
mixins: { CreateEditView },
|
||||
watchQuery: [MODE, EDIT_YAML],
|
||||
|
||||
data() {
|
||||
const mode = this.$route.query.mode || _VIEW;
|
||||
|
||||
return { mode };
|
||||
},
|
||||
|
||||
computed: {
|
||||
isView() {
|
||||
return this.mode === _VIEW;
|
||||
},
|
||||
|
||||
isEdit() {
|
||||
return this.mode === _EDIT;
|
||||
},
|
||||
|
||||
type() {
|
||||
return FRIENDLY[this.resource].type;
|
||||
},
|
||||
|
||||
doneRoute() {
|
||||
const name = this.$route.name.replace(/(-namespace)?-id$/, '');
|
||||
|
||||
return name;
|
||||
},
|
||||
|
||||
doneParams() {
|
||||
return this.$route.params;
|
||||
},
|
||||
|
||||
parentLink() {
|
||||
const name = this.doneRoute;
|
||||
const params = this.donneParams;
|
||||
const out = this.$router.resolve({ name, params }).href;
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
cruComponent() {
|
||||
return () => import(`@/components/cru/${ this.type }`);
|
||||
},
|
||||
|
||||
typeDisplay() {
|
||||
return FRIENDLY[this.resource].singular;
|
||||
},
|
||||
},
|
||||
|
||||
async asyncData({ store, params, route }) {
|
||||
const { resource, namespace, id } = params;
|
||||
const fqid = `${ namespace }/${ id }`;
|
||||
const type = FRIENDLY[resource].type;
|
||||
const asYaml = route.query[EDIT_YAML] === _FLAGGED;
|
||||
|
||||
const obj = await store.dispatch('cluster/find', { type, id: fqid });
|
||||
const model = await store.dispatch('cluster/clone', obj);
|
||||
const view = await obj.followLink('view', { headers: { accept: 'application/yaml' } });
|
||||
|
||||
const out = {
|
||||
asYaml,
|
||||
resource,
|
||||
model,
|
||||
yaml: view.data,
|
||||
originalModel: obj
|
||||
};
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
methods: {
|
||||
showActions() {
|
||||
this.$store.commit('selection/show', {
|
||||
resources: this.originalModel,
|
||||
elem: this.$refs.actions,
|
||||
});
|
||||
},
|
||||
}
|
||||
name: 'RioNamespaceResourceId',
|
||||
components: { RioDetail },
|
||||
asyncData,
|
||||
watchQuery
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ResourceYaml
|
||||
v-if="asYaml"
|
||||
:obj="model"
|
||||
:value="yaml"
|
||||
:done-route="doneRoute"
|
||||
:parent-route="doneRoute"
|
||||
:parent-params="doneParams"
|
||||
/>
|
||||
<template v-else>
|
||||
<header>
|
||||
<h1 v-trim-whitespace>
|
||||
<span v-if="isEdit">Edit</span>
|
||||
<nuxt-link v-trim-whitespace :to="parentLink">
|
||||
{{ typeDisplay }}
|
||||
</nuxt-link>: {{ originalModel.nameDisplay }}
|
||||
</h1>
|
||||
<div v-if="isView" class="actions">
|
||||
<button ref="actions" class="btn btn-sm bg-primary actions" @click="showActions">
|
||||
<i class="icon icon-actions" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<component
|
||||
:is="cruComponent"
|
||||
v-model="model"
|
||||
:original-value="originalModel"
|
||||
:done-route="doneRoute"
|
||||
:done-params="doneParams"
|
||||
:parent-route="doneRoute"
|
||||
:parent-params="doneParams"
|
||||
:namespace-suffix-on-create="true"
|
||||
:type-label="typeDisplay"
|
||||
:mode="mode"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<RioDetail :async-data="_data" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export default {
|
|||
|
||||
return {
|
||||
resource,
|
||||
schema,
|
||||
type,
|
||||
model,
|
||||
};
|
||||
|
|
@ -79,7 +80,7 @@ export default {
|
|||
v-model="model"
|
||||
:done-route="doneRoute"
|
||||
:done-params="doneParams"
|
||||
:namespace-suffix-on-create="true"
|
||||
:namespace-suffix-on-create="schema.attributes.namespaced"
|
||||
:type-label="typeDisplay"
|
||||
mode="create"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export default {
|
|||
const name = this.metadata.name || this.id;
|
||||
|
||||
if ( namespace ) {
|
||||
return `${ namespace }/${ name }`;
|
||||
return `${ namespace }:${ name }`;
|
||||
}
|
||||
|
||||
return name;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,34 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
function trimEmptyTextNodes(el) {
|
||||
export function trimWhitespace(el, dir) {
|
||||
for (const node of el.childNodes) {
|
||||
if (node.nodeType === Node.TEXT_NODE && node.data.trim() === '') {
|
||||
node.remove();
|
||||
if (node.nodeType === Node.TEXT_NODE ) {
|
||||
const trimmed = node.data.trim();
|
||||
|
||||
if ( trimmed === '') {
|
||||
node.remove();
|
||||
} else if ( trimmed !== node.data ) {
|
||||
node.data = trimmed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function trimWhitespaceSsr(el, dir) {
|
||||
for ( const node of (el.children || []) ) {
|
||||
if ( node.text ) {
|
||||
const trimmed = node.text.trim();
|
||||
|
||||
if ( trimmed !== node.text ) {
|
||||
node.text = trimmed;
|
||||
}
|
||||
} else if ( node.children ) {
|
||||
trimWhitespaceSsr(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.directive('trim-whitespace', {
|
||||
inserted: trimEmptyTextNodes,
|
||||
componentUpdated: trimEmptyTextNodes
|
||||
inserted: trimWhitespace,
|
||||
componentUpdated: trimWhitespace
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import { get } from '@/utils/object';
|
|||
import { filterBy } from '@/utils/array';
|
||||
import { sortBy } from '@/utils/sort';
|
||||
|
||||
// groupAndFilterBy(services, 'default')
|
||||
export function groupAndFilterOptions(ary, filterValue, {
|
||||
filterKey = 'metadata.namespace',
|
||||
const NOT_GROUPED = 'none';
|
||||
|
||||
groupKey = 'metadata.namespace',
|
||||
// groupAndFilterBy(services, 'default')
|
||||
export function groupAndFilterOptions(ary, filter, {
|
||||
defaultFilterKey = 'metadata.namespace',
|
||||
|
||||
groupBy = 'metadata.namespace',
|
||||
groupPrefix = 'Namespace: ',
|
||||
|
||||
itemLabelKey = 'nameDisplay',
|
||||
|
|
@ -15,8 +17,11 @@ export function groupAndFilterOptions(ary, filterValue, {
|
|||
} = {}) {
|
||||
let matching;
|
||||
|
||||
if ( filterKey && filterValue ) {
|
||||
matching = filterBy((ary || []), filterKey, filterValue);
|
||||
if ( filter && typeof filter === 'object' ) {
|
||||
matching = filterBy((ary || []), filter);
|
||||
} else if ( filter ) {
|
||||
// If you want to filter on a value that's false-y (false, null, undefined) use the object version of filter
|
||||
matching = filterBy((ary || []), defaultFilterKey, filter);
|
||||
} else {
|
||||
matching = ary;
|
||||
}
|
||||
|
|
@ -24,31 +29,42 @@ export function groupAndFilterOptions(ary, filterValue, {
|
|||
const groups = {};
|
||||
|
||||
for ( const match of matching ) {
|
||||
const name = get(match, groupKey);
|
||||
const name = groupBy ? get(match, groupBy) : NOT_GROUPED;
|
||||
let entry = groups[name];
|
||||
|
||||
if ( !entry ) {
|
||||
entry = {
|
||||
group: `${ groupPrefix }${ name }`,
|
||||
items: [],
|
||||
items: {},
|
||||
};
|
||||
|
||||
groups[name] = entry;
|
||||
}
|
||||
|
||||
entry.items.push({
|
||||
obj: match,
|
||||
label: get(match, itemLabelKey),
|
||||
value: get(match, itemValueKey),
|
||||
});
|
||||
const value = get(match, itemValueKey);
|
||||
|
||||
if ( !entry.items[value] ) {
|
||||
entry.items[value] = {
|
||||
obj: match,
|
||||
label: get(match, itemLabelKey),
|
||||
value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const out = Object.keys(groups).map((name) => {
|
||||
const entry = groups[name];
|
||||
|
||||
entry.items = sortBy(entry.items, `obj.${ itemSortKey }`);
|
||||
entry.items = sortBy(Object.values(entry.items), `obj.${ itemSortKey }`);
|
||||
|
||||
return entry;
|
||||
});
|
||||
|
||||
return sortBy(out, 'group');
|
||||
if ( groupBy ) {
|
||||
return sortBy(out, 'group');
|
||||
} else if ( out.length ) {
|
||||
return out[0].items;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue