mirror of https://github.com/rancher/dashboard.git
440 lines
9.6 KiB
Vue
440 lines
9.6 KiB
Vue
<script>
|
|
import ExplainPanel from './ExplainPanel';
|
|
import { KEY } from '@shell/utils/platform';
|
|
import { expandOpenAPIDefinition, getOpenAPISchemaName, makeOpenAPIBreadcrumb } from '../open-api-utils.ts';
|
|
|
|
const HEADER_HEIGHT = 55;
|
|
|
|
export default {
|
|
components: { ExplainPanel },
|
|
|
|
props: {
|
|
schema: {
|
|
type: Object,
|
|
default: () => {}
|
|
}
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
isOpen: false,
|
|
definition: undefined,
|
|
busy: true,
|
|
error: false,
|
|
expandAll: false,
|
|
isResizing: false,
|
|
resizeLeft: '',
|
|
resizePosition: 'absolute',
|
|
width: '33%',
|
|
right: '-33%',
|
|
breadcrumbs: undefined,
|
|
definitions: {},
|
|
noResource: false,
|
|
notFound: false,
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
|
|
top() {
|
|
const banner = document.getElementById('banner-header');
|
|
let height = HEADER_HEIGHT;
|
|
|
|
if (banner) {
|
|
height += banner.clientHeight;
|
|
}
|
|
|
|
return `${ height }px`;
|
|
},
|
|
|
|
height() {
|
|
return `calc(100vh - ${ this.top })`;
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
open() {
|
|
this.busy = true;
|
|
this.isOpen = true;
|
|
this.addCloseKeyHandler();
|
|
this.right = '0';
|
|
},
|
|
|
|
close() {
|
|
this.isOpen = false;
|
|
this.removeCloseKeyHandler();
|
|
this.right = `-${ this.width }`;
|
|
},
|
|
|
|
scrollTop() {
|
|
this.$refs.main.$el.scrollTop = 0;
|
|
},
|
|
|
|
addCloseKeyHandler() {
|
|
document.addEventListener('keyup', this.closeKeyHandler);
|
|
},
|
|
|
|
removeCloseKeyHandler() {
|
|
document.removeEventListener('keyup', this.closeKeyHandler);
|
|
},
|
|
|
|
closeKeyHandler(e) {
|
|
if (e.keyCode === KEY.ESCAPE ) {
|
|
this.close();
|
|
}
|
|
},
|
|
|
|
toggleAll() {
|
|
this.expandAll = !this.expandAll;
|
|
},
|
|
|
|
load(data, schema, error) {
|
|
this.noResource = false;
|
|
this.error = false;
|
|
this.notFound = false;
|
|
|
|
if (!schema) {
|
|
this.busy = false;
|
|
this.noResource = true;
|
|
this.notFound = true;
|
|
|
|
return;
|
|
}
|
|
|
|
if (error || !data) {
|
|
this.busy = false;
|
|
this.error = true;
|
|
|
|
return;
|
|
}
|
|
|
|
let name = getOpenAPISchemaName(schema);
|
|
|
|
// Manual fix ups where the schema group does not match the Open API one
|
|
|
|
// Schemas like 'ingress' seem to have the wrong group - so try the other one with 'api'
|
|
if (!data.definitions[name]) {
|
|
name = name.replace(/io\.k8s\./g, 'io.k8s.api.');
|
|
}
|
|
|
|
// RBAC (e.g Role): io.k8s.api.authorization.rbac.v1.* -> io.k8s.api.rbac.v1.*
|
|
if (!data.definitions[name]) {
|
|
name = name.replace(/io\.k8s\.api\.authorization\.rbac/g, 'io.k8s.api.rbac');
|
|
}
|
|
|
|
if (name) {
|
|
this.definitions = data.definitions;
|
|
this.navigate([makeOpenAPIBreadcrumb(name)]);
|
|
} else {
|
|
this.definition = undefined;
|
|
}
|
|
|
|
this.busy = false;
|
|
},
|
|
|
|
startPanelResize(ev) {
|
|
this.isResizing = true;
|
|
this.$refs.resizer.setPointerCapture(ev.pointerId);
|
|
},
|
|
|
|
doPanelResize(ev) {
|
|
if (this.isResizing) {
|
|
this.resizePosition = 'fixed';
|
|
this.resizeLeft = `${ ev.clientX }px`;
|
|
}
|
|
},
|
|
|
|
endPanelResize(ev) {
|
|
this.isResizing = false;
|
|
this.$refs.resizer.releasePointerCapture(ev.pointerId);
|
|
|
|
const width = window.innerWidth - ev.clientX + 2;
|
|
|
|
this.resizePosition = 'absolute';
|
|
this.resizeLeft = '';
|
|
|
|
this.width = `${ width }px`;
|
|
},
|
|
|
|
navigate(breadcrumbs) {
|
|
const goto = breadcrumbs[breadcrumbs.length - 1];
|
|
|
|
this.breadcrumbs = breadcrumbs;
|
|
this.definition = this.definitions[goto.id];
|
|
this.expanded = {};
|
|
this.expandAll = false;
|
|
this.notFound = false;
|
|
|
|
if (!this.definition) {
|
|
this.noResource = true;
|
|
this.notFound = true;
|
|
|
|
return;
|
|
}
|
|
|
|
expandOpenAPIDefinition(this.definitions, this.definition, this.breadcrumbs);
|
|
|
|
setTimeout(() => this.scrollTop(), 100);
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div
|
|
class="slide-in-glass"
|
|
:class="{ 'slide-in-glass-open': isOpen }"
|
|
@click="close()"
|
|
/>
|
|
<div
|
|
class="slide-in"
|
|
:class="{ 'slide-in-open': isOpen }"
|
|
:style="{ width, right, top, height }"
|
|
data-testid="slide-in-panel"
|
|
>
|
|
<div
|
|
ref="resizer"
|
|
class="panel-resizer"
|
|
:style="{ position: resizePosition, left: resizeLeft }"
|
|
@pointerdown="startPanelResize"
|
|
@pointermove="doPanelResize"
|
|
@pointerup="endPanelResize"
|
|
/>
|
|
<div class="main-panel">
|
|
<div class="header">
|
|
<div
|
|
v-if="breadcrumbs"
|
|
class="breadcrumbs"
|
|
>
|
|
<div v-if="noResource">
|
|
{{ t('kubectl-explain.title') }}
|
|
</div>
|
|
<div
|
|
v-for="(b, i) in breadcrumbs"
|
|
v-else
|
|
:key="b.id"
|
|
>
|
|
<span
|
|
v-if="i > 0"
|
|
class="ml-5 mr-5"
|
|
>></span>
|
|
<span
|
|
v-if="i === breadcrumbs.length - 1"
|
|
>{{ b.name }}
|
|
</span>
|
|
<a
|
|
v-else
|
|
href="#"
|
|
class="breadcrumb-link"
|
|
@click="navigate(breadcrumbs.slice(0, i + 1))"
|
|
>{{ b.name }}</a>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-else
|
|
@click="scrollTop()"
|
|
>
|
|
{{ t('kubectl-explain.title') }}
|
|
</div>
|
|
<i
|
|
v-if="!busy && !noResource && definition"
|
|
class="icon icon-sort mr-10"
|
|
@click="toggleAll()"
|
|
/>
|
|
<i
|
|
class="icon icon-close"
|
|
data-testid="slide-in-panel-close"
|
|
@click="close"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="busy"
|
|
class="loading panel-loading"
|
|
>
|
|
<div>
|
|
<i class="icon icon-lg icon-spinner icon-spin" />
|
|
</div>
|
|
</div>
|
|
<ExplainPanel
|
|
v-if="!noResource && definition"
|
|
ref="main"
|
|
:expand-all="expandAll"
|
|
:definition="definition"
|
|
class="explain-panel"
|
|
@navigate="navigate"
|
|
/>
|
|
<div
|
|
v-if="error"
|
|
class="select-resource"
|
|
>
|
|
<i class="icon icon-error" />
|
|
<div>
|
|
{{ t('kubectl-explain.errors.load') }}
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="noResource"
|
|
class="select-resource"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
height="24"
|
|
viewBox="0 -960 960 960"
|
|
width="24"
|
|
>
|
|
<path d="M260-320q47 0 91.5 10.5T440-278v-394q-41-24-87-36t-93-12q-36 0-71.5 7T120-692v396q35-12 69.5-18t70.5-6Zm260 42q44-21 88.5-31.5T700-320q36 0 70.5 6t69.5 18v-396q-33-14-68.5-21t-71.5-7q-47 0-93 12t-87 36v394Zm-40 118q-48-38-104-59t-116-21q-42 0-82.5 11T100-198q-21 11-40.5-1T40-234v-482q0-11 5.5-21T62-752q46-24 96-36t102-12q58 0 113.5 15T480-740q51-30 106.5-45T700-800q52 0 102 12t96 36q11 5 16.5 15t5.5 21v482q0 23-19.5 35t-40.5 1q-37-20-77.5-31T700-240q-60 0-116 21t-104 59ZM280-494Z" />
|
|
</svg>
|
|
<div v-if="notFound">
|
|
{{ t('kubectl-explain.errors.notFound') }}
|
|
</div>
|
|
<div v-else>
|
|
{{ t('kubectl-explain.prompt') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
$slidein-width: 33%;
|
|
|
|
.panel-resizer {
|
|
position: absolute;
|
|
height: 100%;
|
|
border: 2px solid transparent;
|
|
|
|
&:hover {
|
|
border: 2px solid var(--primary);
|
|
cursor: col-resize;
|
|
}
|
|
}
|
|
|
|
.main-panel {
|
|
padding-left: 4px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: auto;
|
|
|
|
.select-resource {
|
|
font-size: 16px;
|
|
margin: 40px;
|
|
text-align: center;
|
|
|
|
> svg, i {
|
|
margin-bottom: 20px;
|
|
opacity: 0.5;
|
|
height: 64px;
|
|
width: 64px;
|
|
}
|
|
|
|
// Ensure the icon uses the correct color for the theme
|
|
> svg {
|
|
fill: var(--body-text);
|
|
}
|
|
|
|
> i {
|
|
font-size: 64px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.header {
|
|
align-items: center;
|
|
display: flex;
|
|
padding: 4px;
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
.breadcrumbs {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
|
|
.breadcrumb-link {
|
|
color: var(--text);
|
|
|
|
&:hover {
|
|
color: var(--link);
|
|
}
|
|
}
|
|
}
|
|
|
|
> :first-child {
|
|
flex: 1;
|
|
font-weight: bold;
|
|
}
|
|
|
|
> i {
|
|
padding: 8px;
|
|
opacity: 0.7;
|
|
|
|
&:hover {
|
|
background-color: var(--primary);
|
|
color: var(--primary-text);
|
|
cursor: pointer;
|
|
opacity: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.loading {
|
|
align-items: center;
|
|
display: flex;
|
|
flex: 1;
|
|
justify-content: center;
|
|
|
|
.icon-spinner {
|
|
font-size: 24px;
|
|
}
|
|
}
|
|
|
|
.glass {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
}
|
|
|
|
.slide-in {
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: fixed;
|
|
top: 0;
|
|
z-index: 2000;
|
|
width: $slidein-width;
|
|
background-color: var(--body-bg);
|
|
right: -$slidein-width;
|
|
transition: right 0.5s;
|
|
border-left: 1px solid var(--border);
|
|
}
|
|
|
|
.slide-in-open {
|
|
right: 0;
|
|
}
|
|
|
|
.explain-panel {
|
|
padding: 10px;
|
|
}
|
|
|
|
.slide-in-glass {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
height :100vh;
|
|
width: 100vw;
|
|
|
|
&.slide-in-glass-open {
|
|
background-color: var(--body-bg);
|
|
display: block;
|
|
opacity: 0.5;
|
|
z-index: 1000;
|
|
}
|
|
}
|
|
|
|
.panel-loading {
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|