mirror of https://github.com/rancher/dashboard.git
Merge pull request #576 from mantis-toboggan-md/masthead
masthead changes
This commit is contained in:
commit
967c775b81
|
|
@ -0,0 +1,92 @@
|
|||
<script>
|
||||
export default {
|
||||
data() {
|
||||
// make a map of all route names to validate programatically generated names
|
||||
const allRoutes = this.$router.options.routes;
|
||||
const allRouteMap = allRoutes.reduce((all, route) => {
|
||||
all[route.name] = route;
|
||||
|
||||
return all;
|
||||
}, {});
|
||||
const { name, params } = this.$route;
|
||||
|
||||
const crumbPieces = name.split('-');
|
||||
|
||||
let crumbLocations = [];
|
||||
|
||||
crumbPieces.forEach((piece, i) => {
|
||||
let nextName = piece;
|
||||
|
||||
if (crumbLocations[i - 1]) {
|
||||
nextName = ( `${ crumbLocations[i - 1].name }-${ piece }`);
|
||||
}
|
||||
crumbLocations.push({
|
||||
name: nextName,
|
||||
params: this.paramsFor(nextName, params)
|
||||
});
|
||||
});
|
||||
|
||||
// remove root route 'c'
|
||||
crumbLocations.shift();
|
||||
|
||||
// filter invalid routes
|
||||
crumbLocations = crumbLocations.filter((location) => {
|
||||
return (allRouteMap[location.name] && this.displayName(location, params));
|
||||
});
|
||||
|
||||
return {
|
||||
crumbLocations, params, crumbPieces, allRouteMap
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
paramsFor(crumbName, params = this.params) {
|
||||
const pieces = crumbName.split('-');
|
||||
const out = {};
|
||||
|
||||
pieces.forEach((piece) => {
|
||||
if (params[piece]) {
|
||||
out[piece] = params[piece];
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
displayName(location, params = this.params) {
|
||||
const pieces = location.name.split('-');
|
||||
const lastPiece = pieces[pieces.length - 1];
|
||||
|
||||
if (lastPiece === 'resource') {
|
||||
const resourceType = params[lastPiece];
|
||||
const schema = this.$store.getters['cluster/schemaFor'](resourceType);
|
||||
|
||||
if (schema) {
|
||||
return this.$store.getters['type-map/pluralLabelFor'](schema);
|
||||
}
|
||||
}
|
||||
|
||||
return params[lastPiece];
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div v-for="(location, i) in crumbLocations" :key="location.name">
|
||||
<span v-if="i > 0" class="divider">/</span>
|
||||
<span v-if="i===crumbLocations.length-1">{{ displayName(location) }}</span>
|
||||
<nuxt-link v-else :to="location">
|
||||
{{ displayName(location) }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.breadcrumbs .divider {
|
||||
margin: 0px 5px 0px 5px
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<script>
|
||||
import { PROJECT } from '../../config/labels-annotations';
|
||||
import BreadCrumbs from '@/components/BreadCrumbs';
|
||||
import { NAMESPACE, EXTERNAL } from '@/config/types';
|
||||
|
||||
export default {
|
||||
components: { BreadCrumbs },
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
|
||||
realMode: {
|
||||
type: String,
|
||||
default: 'create'
|
||||
},
|
||||
|
||||
doneRoute: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
asYaml: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
hasDetail: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
schema() {
|
||||
return this.$store.getters['cluster/schemaFor']( this.value.type );
|
||||
},
|
||||
|
||||
h1() {
|
||||
const typeLink = this.$router.resolve({
|
||||
name: this.doneRoute,
|
||||
params: this.$route.params
|
||||
}).href;
|
||||
|
||||
const out = this.$store.getters['i18n/t'](`resourceDetail.header.${ this.realMode }`, {
|
||||
typeLink,
|
||||
type: this.$store.getters['type-map/singularLabelFor'](this.schema),
|
||||
name: this.value.nameDisplay,
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
isNamespace() {
|
||||
return this.schema.id === NAMESPACE;
|
||||
},
|
||||
|
||||
namespace() {
|
||||
if (this.value?.metadata?.namespace) {
|
||||
return this.value?.metadata?.namespace;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
namespaceLocation() {
|
||||
if (!this.isNamespace) {
|
||||
return {
|
||||
name: 'c-cluster-resource-id',
|
||||
params: {
|
||||
cluster: this.$route.params.cluster,
|
||||
resource: NAMESPACE,
|
||||
id: this.$route.params.namespace
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
project() {
|
||||
if (this.isNamespace) {
|
||||
const id = (this.value?.metadata?.labels || {})[PROJECT];
|
||||
|
||||
return this.$store.getters['clusterExternal/byId'](EXTERNAL.PROJECT, id);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
showActions() {
|
||||
this.$store.commit('action-menu/show', {
|
||||
resources: this.value,
|
||||
elem: this.$refs.actions,
|
||||
});
|
||||
},
|
||||
|
||||
toggleYaml() {
|
||||
const out = !this.asYaml;
|
||||
|
||||
this.$emit('update:asYaml', out);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<BreadCrumbs class="breadcrumbs" />
|
||||
<div>
|
||||
<h1 v-html="h1" />
|
||||
<!-- //TODO use nuxt-link for an internal project detail page once it exists -->
|
||||
<span v-if="isNamespace">Project: {{ project.nameDisplay }}</span>
|
||||
<span v-else-if="namespace">Namespace: <nuxt-link :to="namespaceLocation">{{ namespace }}</nuxt-link></span>
|
||||
</div>
|
||||
<div v-if="mode==='view'" class="actions">
|
||||
<!-- //TODO remove check for custom detail component once there is a generic detail -->
|
||||
<div v-if="hasDetail" class="yaml-toggle">
|
||||
<button id="yaml-on" :disabled="asYaml" class="btn btn-sm role-primary" @click="toggleYaml">
|
||||
YAML
|
||||
</button>
|
||||
<button id="yaml-off" :disabled="!asYaml" class="btn btn-sm role-primary" @click="toggleYaml">
|
||||
Overview
|
||||
</button>
|
||||
</div>
|
||||
<button ref="actions" aria-haspopup="true" type="button" class="btn btn-sm role-multi-action actions" @click="showActions">
|
||||
<i class="icon icon-actions" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
.yaml-toggle{
|
||||
display: inline-flex;
|
||||
|
||||
& #yaml-on{
|
||||
border-radius: calc(var(--border-radius) * 2) 0px 0px calc(var(--border-radius) * 2);
|
||||
}
|
||||
|
||||
& #yaml-off{
|
||||
border-radius: 0px calc(var(--border-radius) * 2) calc(var(--border-radius) * 2) 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from '@/config/query-params';
|
||||
import { SCHEMA } from '@/config/types';
|
||||
import { createYaml } from '@/utils/create-yaml';
|
||||
import Masthead from '@/components/ResourceDetail/Masthead';
|
||||
|
||||
// Components can't have asyncData, only pages.
|
||||
// So you have to call this in the page and pass it in as a prop.
|
||||
|
|
@ -51,6 +52,7 @@ function realModeFor(query, id) {
|
|||
// e.g. for workload to create a deployment
|
||||
export async function defaultAsyncData(ctx, resource) {
|
||||
const { store, params, route } = ctx;
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { namespace, id } = params;
|
||||
|
||||
|
|
@ -65,7 +67,7 @@ export async function defaultAsyncData(ctx, resource) {
|
|||
|
||||
const hasCustomDetail = store.getters['type-map/hasCustomDetail'](resource);
|
||||
const hasCustomEdit = store.getters['type-map/hasCustomEdit'](resource);
|
||||
const asYaml = (route.query[AS_YAML] === _FLAGGED) || (realMode === _VIEW && !hasCustomDetail) || (realMode !== _VIEW && !hasCustomEdit);
|
||||
const asYamlInit = (route.query[AS_YAML] === _FLAGGED) || (realMode === _VIEW && !hasCustomDetail) || (realMode !== _VIEW && !hasCustomEdit);
|
||||
const schema = store.getters['cluster/schemaFor'](resource);
|
||||
|
||||
let originalModel, model, yaml;
|
||||
|
|
@ -86,9 +88,7 @@ export async function defaultAsyncData(ctx, resource) {
|
|||
originalModel = await store.dispatch('cluster/create', data);
|
||||
model = await store.dispatch('cluster/clone', { resource: originalModel });
|
||||
|
||||
if ( asYaml ) {
|
||||
yaml = createYaml(schemas, resource, data);
|
||||
}
|
||||
yaml = createYaml(schemas, resource, data);
|
||||
} else {
|
||||
let fqid = id;
|
||||
|
||||
|
|
@ -107,11 +107,9 @@ export async function defaultAsyncData(ctx, resource) {
|
|||
model.applyDefaults(ctx, realMode);
|
||||
}
|
||||
|
||||
if ( asYaml ) {
|
||||
const link = originalModel.hasLink('rioview') ? 'rioview' : 'view';
|
||||
const link = originalModel.hasLink('rioview') ? 'rioview' : 'view';
|
||||
|
||||
yaml = (await originalModel.followLink(link, { headers: { accept: 'application/yaml' } })).data;
|
||||
}
|
||||
yaml = (await originalModel.followLink(link, { headers: { accept: 'application/yaml' } })).data;
|
||||
}
|
||||
|
||||
let mode = realMode;
|
||||
|
|
@ -128,11 +126,12 @@ export async function defaultAsyncData(ctx, resource) {
|
|||
hasCustomEdit,
|
||||
resource,
|
||||
model,
|
||||
asYaml,
|
||||
asYamlInit,
|
||||
yaml,
|
||||
originalModel,
|
||||
mode,
|
||||
realMode
|
||||
realMode,
|
||||
route
|
||||
};
|
||||
/*******
|
||||
* Important: these need to be declared below as props too if you want to use them
|
||||
|
|
@ -144,7 +143,7 @@ export async function defaultAsyncData(ctx, resource) {
|
|||
export const watchQuery = [MODE, AS_YAML];
|
||||
|
||||
export default {
|
||||
components: { ResourceYaml },
|
||||
components: { ResourceYaml, Masthead },
|
||||
mixins: { CreateEditView },
|
||||
|
||||
props: {
|
||||
|
|
@ -164,7 +163,7 @@ export default {
|
|||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
asYaml: {
|
||||
asYamlInit: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
|
|
@ -183,6 +182,12 @@ export default {
|
|||
realMode: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
route: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -199,7 +204,11 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
// asYamlInit is taken from route query and passed as prop from _id page; asYaml is saved in local data to be manipulated by Masthead
|
||||
const asYaml = this.asYamlInit;
|
||||
|
||||
return {
|
||||
asYaml,
|
||||
isCustomYamlEditor: false,
|
||||
currentValue: this.value,
|
||||
detailComponent: this.$store.getters['type-map/importDetail'](this.resource),
|
||||
|
|
@ -210,9 +219,6 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
schema() {
|
||||
return this.$store.getters['cluster/schemaFor']( this.model.type );
|
||||
},
|
||||
|
||||
isView() {
|
||||
return this.mode === _VIEW;
|
||||
|
|
@ -251,44 +257,20 @@ export default {
|
|||
|
||||
return null;
|
||||
},
|
||||
|
||||
h1() {
|
||||
const typeLink = this.$router.resolve({
|
||||
name: this.doneRoute,
|
||||
params: this.$route.params
|
||||
}).href;
|
||||
|
||||
const out = this.$store.getters['i18n/t'](`resourceDetail.header.${ this.realMode }`, {
|
||||
typeLink,
|
||||
type: this.$store.getters['type-map/singularLabelFor'](this.schema),
|
||||
name: this.originalModel?.nameDisplay,
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
showActions() {
|
||||
this.$store.commit('action-menu/show', {
|
||||
resources: this.originalModel,
|
||||
elem: this.$refs.actions,
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header>
|
||||
<h1 v-html="h1" />
|
||||
<div v-if="isView" class="actions">
|
||||
<button ref="actions" aria-haspopup="true" type="button" class="btn btn-sm role-multi-action actions" @click="showActions">
|
||||
<i class="icon icon-actions" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<Masthead
|
||||
:value="originalModel"
|
||||
:mode="mode"
|
||||
:done-route="doneRoute"
|
||||
:real-mode="realMode"
|
||||
:as-yaml.sync="asYaml"
|
||||
:has-detail="hasCustomDetail"
|
||||
/>
|
||||
<template v-if="asYaml">
|
||||
<ResourceYaml
|
||||
:model="model"
|
||||
|
|
@ -355,29 +337,4 @@ export default {
|
|||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
.detail-top{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background: var(--box-bg);
|
||||
border: solid thin var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
& > * {
|
||||
margin-right: 20px;
|
||||
padding: 10px 0 10px 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
&:not(:last-child){
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
& >:not(:first-child){
|
||||
color: var(--input-label);
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -107,7 +107,7 @@ export default {
|
|||
/>
|
||||
</div>
|
||||
</section>
|
||||
<ResourceTabs v-model="value" />
|
||||
<ResourceTabs v-model="value" :mode="mode" />
|
||||
|
||||
<Footer
|
||||
:mode="mode"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import Networking from '@/edit/workload/Networking';
|
|||
import Footer from '@/components/form/Footer';
|
||||
import Job from '@/edit/workload/Job';
|
||||
import WorkloadPorts from '@/edit/workload/WorkloadPorts';
|
||||
import { defaultAsyncData } from '@/components/ResourceDetail.vue';
|
||||
import { defaultAsyncData } from '@/components/ResourceDetail';
|
||||
import { _EDIT } from '@/config/query-params';
|
||||
import ResourceTabs from '@/components/form/ResourceTabs';
|
||||
|
||||
|
|
|
|||
|
|
@ -253,7 +253,8 @@ export default {
|
|||
|
||||
HEADER {
|
||||
display: grid;
|
||||
grid-template-areas: "title actions";
|
||||
grid-template-areas: "breadcrumbs breadcrumbs"
|
||||
"title actions";
|
||||
grid-template-columns: "auto min-content";
|
||||
margin-bottom: 20px;
|
||||
|
||||
|
|
@ -271,6 +272,10 @@ export default {
|
|||
grid-area: actions;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
grid-area: breadcrumbs;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@
|
|||
import ResourceTable from '@/components/ResourceTable';
|
||||
import Favorite from '@/components/nav/Favorite';
|
||||
import { AS_YAML, _FLAGGED } from '@/config/query-params';
|
||||
import BreadCrumbs from '@/components/BreadCrumbs';
|
||||
|
||||
export default {
|
||||
components: { ResourceTable, Favorite },
|
||||
components: {
|
||||
ResourceTable,
|
||||
Favorite,
|
||||
BreadCrumbs
|
||||
},
|
||||
|
||||
data() {
|
||||
const params = { ...this.$route.params };
|
||||
|
|
@ -28,6 +33,7 @@ export default {
|
|||
}).href;
|
||||
|
||||
return {
|
||||
route: this.$route,
|
||||
listComponent,
|
||||
formRoute,
|
||||
yamlRoute,
|
||||
|
|
@ -109,6 +115,8 @@ export default {
|
|||
<template>
|
||||
<div>
|
||||
<header>
|
||||
<BreadCrumbs class="breadcrumbs" :route="route" />
|
||||
|
||||
<h1>
|
||||
{{ typeDisplay }} <Favorite :resource="resource" />
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -135,6 +135,10 @@ export function pluralize(count, singular, plural) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
return plural;
|
||||
}
|
||||
|
||||
if (count === 1) {
|
||||
return `${ count } ${ singular }`;
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue