mirror of https://github.com/rancher/dashboard.git
parent
b530db815f
commit
6e9cbd5b55
|
|
@ -185,46 +185,7 @@ $transition-duration: 150ms;
|
|||
border-color: transparent;
|
||||
}
|
||||
&.vs--open .vs__selected {
|
||||
position: absolute;
|
||||
opacity: .4;
|
||||
}
|
||||
&.vs--searching .vs__selected {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.vs__selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid var(--dropdown-border);
|
||||
border-radius: 4px;
|
||||
color: #333;
|
||||
margin: 4px 2px 0px 2px;
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
|
||||
.vs__deselect {
|
||||
display: inline-flex;
|
||||
appearance: none;
|
||||
margin-left: 4px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
fill: rgba(60,60,60,0.26);
|
||||
text-shadow: 0 1px 0 #fff
|
||||
}
|
||||
|
||||
/* States */
|
||||
|
||||
.vs--single {
|
||||
.vs__selected {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
&.vs--open .vs__selected {
|
||||
position: absolute;
|
||||
// position: absolute;
|
||||
opacity: .4;
|
||||
}
|
||||
&.vs--searching .vs__selected {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
export default {
|
||||
computed: {
|
||||
...mapState('auth', ['principal']),
|
||||
podConfig() {
|
||||
const config = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Pod',
|
||||
metadata: { name: this.expectedPodName },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'ubuntu xenial',
|
||||
image: 'ubuntu:xenial',
|
||||
imagePullPolicy: 'Always',
|
||||
stdin: true
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
expectedPodName() {
|
||||
const userName = this.principal.name.toLowerCase();
|
||||
// TODO make pod different clusters
|
||||
const cluster = 'local';
|
||||
|
||||
return `manage-${ cluster }-${ userName }`;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
findPod() {
|
||||
return fetch(`${ window.location.origin }/api/v1/namespaces/default/pods/${ this.expectedPodName }`)
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
} else {
|
||||
throw (res);
|
||||
}
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('error getting pod: ', err);
|
||||
|
||||
return this.makePod();
|
||||
});
|
||||
},
|
||||
makePod() {
|
||||
return fetch(`${ window.location.origin }/api/v1/namespaces/default/pods`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.podConfig )
|
||||
}).then(res => res.json())
|
||||
.then((json) => {
|
||||
return json;
|
||||
});
|
||||
},
|
||||
deletePod() {
|
||||
fetch(`${ window.location.origin }/api/v1/namespaces/default/pods/${ this.expectedPodName }`, { method: 'DELETE' }).then(res => res.json()).then(json => console.log(json));
|
||||
},
|
||||
async openModal() {
|
||||
const resource = await this.findPod();
|
||||
|
||||
console.log('opening term for: ', resource);
|
||||
this.$store.dispatch('shell/defineSocket', { resource, action: 'openShell' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn-sm bg-secondary" @click="openModal">
|
||||
≥ kubectl
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<script>
|
||||
import { saveAs } from 'file-saver';
|
||||
export default {
|
||||
props: { backlog: { type: Array, default: () => [] } },
|
||||
data() {
|
||||
return { showLast: false };
|
||||
},
|
||||
computed: {
|
||||
prettyLines() {
|
||||
return this.backlog.map(line => this.withLocale(line));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
withLocale(line) {
|
||||
const matches = line.match(/^\[?([^ \]]+)\]?\s?/) || [];
|
||||
let date, message, localDate;
|
||||
|
||||
if (matches[1] && this.isDate(matches[1]) ) {
|
||||
date = new Date(matches[1]);
|
||||
message = line.substr(matches[1].length);
|
||||
localDate = `${ date.toLocaleDateString() } ${ date.toLocaleTimeString() }`;
|
||||
}
|
||||
|
||||
return { localDate, message };
|
||||
},
|
||||
isDate(date) {
|
||||
return new Date(date) !== 'Invalid Date' && !isNaN(new Date(date));
|
||||
},
|
||||
// toggleLast(e) {
|
||||
// this.$store.dispatch('shell/closeSocket')
|
||||
// .then(() => {
|
||||
// this.$store.dispatch('shell/defineSocket', { showLast: !this.showLast });
|
||||
// });
|
||||
// },
|
||||
scrollToTop() {
|
||||
const viewPort = this.$refs.logs;
|
||||
|
||||
viewPort.scrollTop = 0;
|
||||
},
|
||||
scrollToBottom() {
|
||||
const viewPort = this.$refs.logs;
|
||||
|
||||
viewPort.scrollTop = viewPort.scrollHeight;
|
||||
},
|
||||
clear() {
|
||||
this.$store.commit('shell/clearBacklog');
|
||||
},
|
||||
download() {
|
||||
const blob = new Blob(this.backlog, { type: 'text/plain;charset=utf-8' });
|
||||
const fileName = `${ this.$store.state.shell.container.name }.log`;
|
||||
|
||||
saveAs(blob, fileName);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div ref="logs" class="logs terminal">
|
||||
<div
|
||||
v-for="(line, i) in prettyLines"
|
||||
:key="`${line}--${i}`"
|
||||
>
|
||||
<span class="datestring">{{ line.localDate }}</span>{{ line.message }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls-bottom">
|
||||
<button class="btn btn-sm bg-primary" type="button" @click="scrollToTop">
|
||||
Scroll to Top
|
||||
</button>
|
||||
<button class="btn btn-sm bg-primary" type="button" @click="scrollToBottom">
|
||||
Scroll to Bottom
|
||||
</button>
|
||||
<button class="btn btn-sm bg-primary" @click="clear">
|
||||
Clear
|
||||
</button>
|
||||
<button class="btn btn-sm bg-primary" @click="download">
|
||||
Download Logs
|
||||
</button>
|
||||
</div>
|
||||
<!-- <div class="toggle">
|
||||
<input id="togglelast" :value="showLast" type="checkbox" @click="toggleLast">
|
||||
<label for="togglelast">previous container </label>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang='scss'>
|
||||
.logs{
|
||||
height: 80%;
|
||||
overflow-y: scroll;
|
||||
max-height: 80vh;
|
||||
min-height: 500px;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.controls-bottom{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
& button{
|
||||
margin: 5px;
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
.toggle{
|
||||
color: var( --input-label);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/* eslint-disable vue/html-quotes */
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { base64Encode } from '@/utils/crypto';
|
||||
import Terminal from '@/components/ContainerExec/Terminal';
|
||||
import Logs from '@/components/ContainerExec/Logs';
|
||||
export default {
|
||||
components: { Terminal, Logs },
|
||||
data() {
|
||||
return { terminal: null };
|
||||
},
|
||||
computed: {
|
||||
...mapState('shell', ['containers', 'container', 'socket', 'mode', 'backlog']),
|
||||
isOpen() {
|
||||
return this.socket.readyState === 1;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
mode(newProp) {
|
||||
if (newProp) {
|
||||
this.$modal.show('terminal');
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hide() {
|
||||
this.$store.dispatch('shell/closeSocket');
|
||||
this.$store.commit('shell/closeModal');
|
||||
},
|
||||
|
||||
selectContainer(container) {
|
||||
this.$store.dispatch('shell/closeSocket')
|
||||
.then(() => {
|
||||
this.$store.dispatch('shell/defineSocket', { container });
|
||||
});
|
||||
},
|
||||
resized({ rows, cols }) {
|
||||
if (this.isOpen) {
|
||||
const message = `4${ base64Encode(JSON.stringify({
|
||||
Width: cols,
|
||||
Height: rows
|
||||
})) }`;
|
||||
|
||||
this.socket.send(message);
|
||||
}
|
||||
},
|
||||
terminalReady(payload) {
|
||||
this.terminal = payload.terminal;
|
||||
if (this.isOpen) {
|
||||
this.backlog.forEach(log => payload.terminal.write(log));
|
||||
}
|
||||
},
|
||||
terminalInput(input) {
|
||||
this.$store.commit('shell/sendInput', { input });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<modal
|
||||
ref="modal"
|
||||
name="terminal"
|
||||
:resizable="true"
|
||||
:adaptive="true"
|
||||
height="auto"
|
||||
@before-close="hide"
|
||||
>
|
||||
<div v-if="containers.length>1" class="controls-top">
|
||||
<span>
|
||||
≥
|
||||
</span>
|
||||
<v-select
|
||||
:clearable="false"
|
||||
:value="container"
|
||||
:options="containers"
|
||||
label="name"
|
||||
@input="selectContainer"
|
||||
>
|
||||
</v-select>
|
||||
</div>
|
||||
<div v-if="containers.length<=1" class="label-top">
|
||||
<span>
|
||||
≥ {{ container.name }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- use bound key here to make the terminal re-render every time container changes -->
|
||||
<Terminal
|
||||
v-if="mode==='openShell'"
|
||||
:key="container.name"
|
||||
:lines="backlog"
|
||||
:class=" { disconnected: !isOpen }"
|
||||
:is-open="isOpen"
|
||||
@clearBacklog="$store.commit('shell/clearBacklog')"
|
||||
@resized="resized"
|
||||
@input="terminalInput"
|
||||
@terminalReady="terminalReady"
|
||||
/>
|
||||
<Logs v-if="mode==='openLogs'" :class=" { disconnected: !isOpen }" :backlog="backlog" />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/node_modules/xterm/css/xterm.css';
|
||||
.v--modal-box{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 20px 60px -2px rgba(0,0,0,0.3);
|
||||
|
||||
& > :nth-child(2){
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
.terminal, .xterm, .xterm-viewport{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--nav-bg );
|
||||
|
||||
& .datestring {
|
||||
color: var(--input-label)
|
||||
}
|
||||
|
||||
& div {
|
||||
color: rgba(39,170,94,1);
|
||||
}
|
||||
}
|
||||
.controls-top, .label-top {
|
||||
color: var(--dropdown-text);
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& .v-select{
|
||||
flex-grow: 1;
|
||||
flex-basis: 0%;
|
||||
border: none;
|
||||
|
||||
& .vs__dropdown-toggle {
|
||||
border: none;
|
||||
}
|
||||
& .vs__selected{
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
color: var(--dropdown-text)
|
||||
}
|
||||
}}
|
||||
|
||||
.label-top{
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.v--modal {
|
||||
background-color: var(--box-bg);
|
||||
}
|
||||
|
||||
.disconnected{
|
||||
& > :nth-child(1) {
|
||||
border: 1px solid red;
|
||||
}
|
||||
}
|
||||
.socket-status{
|
||||
// border: 1px dashed red;
|
||||
align-self: flex-end;
|
||||
position: relative;
|
||||
top: 20px;
|
||||
right: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
allowInput: { type: Boolean, default: true },
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
cursorblink: true,
|
||||
useStyle: true,
|
||||
fontSize: 12,
|
||||
|
||||
};
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
background: '#141419',
|
||||
cursor: 'rgba(39,170,94,1)',
|
||||
foreground: 'rgba(39,170,94,1)'
|
||||
};
|
||||
}
|
||||
},
|
||||
lines: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
terminal: null,
|
||||
fitAddon: null,
|
||||
xterm: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
lines(newLines) {
|
||||
if (this.terminal && newLines.length) {
|
||||
newLines.forEach(line => this.terminal.write(line));
|
||||
this.$emit('clearBacklog');
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.terminal.dispose();
|
||||
},
|
||||
mounted() {
|
||||
// dynamically import xterm in mounted() to avoid problems with ssr
|
||||
import('xterm')
|
||||
.then((xterm) => {
|
||||
console.log('xterm imported');
|
||||
this.xterm = xterm;
|
||||
})
|
||||
.then(() => {
|
||||
return import('xterm-addon-fit');
|
||||
})
|
||||
.then((fitAddon) => {
|
||||
this.fitAddon = new fitAddon.FitAddon();
|
||||
})
|
||||
.then(() => {
|
||||
this.drawTerminal();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
drawTerminal() {
|
||||
const vm = this;
|
||||
const terminal = new this.xterm.Terminal({
|
||||
...vm.config,
|
||||
disableStdin: !vm.allowInput,
|
||||
theme: this.theme
|
||||
});
|
||||
|
||||
this.terminal = terminal;
|
||||
this.terminal.loadAddon(this.fitAddon);
|
||||
this.terminal.open(this.$refs.terminal);
|
||||
this.fitTerminal();
|
||||
this.lines.forEach(line => this.terminal.write(line));
|
||||
this.$emit('clearBacklog');
|
||||
terminal.onData((input) => {
|
||||
this.$emit('input', input);
|
||||
});
|
||||
},
|
||||
fitTerminal(arg) {
|
||||
if (this.fitAddon) {
|
||||
this.fitAddon.fit();
|
||||
this.$emit('resized', this.fitAddon.proposeDimensions());
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="terminal-container">
|
||||
<div id="terminal" ref="terminal" class="terminal">
|
||||
</div>
|
||||
<resize-observer @notify="fitTerminal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#terminal-container {
|
||||
padding: 5px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
& .xterm-viewport {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -7,6 +7,8 @@ import { mapPref, THEME, EXPANDED_GROUPS } from '@/store/prefs';
|
|||
import ActionMenu from '@/components/ActionMenu';
|
||||
import NamespaceFilter from '@/components/nav/NamespaceFilter';
|
||||
import ClusterSwitcher from '@/components/nav/ClusterSwitcher';
|
||||
import ShellSocket from '@/components/ContainerExec/ShellSocket';
|
||||
import LaunchKubectl from '@/components/ContainerExec/LaunchKubectl';
|
||||
import Group from '@/components/nav/Group';
|
||||
import { COUNT } from '@/config/types';
|
||||
|
||||
|
|
@ -15,7 +17,9 @@ export default {
|
|||
ClusterSwitcher,
|
||||
NamespaceFilter,
|
||||
ActionMenu,
|
||||
Group
|
||||
Group,
|
||||
ShellSocket,
|
||||
LaunchKubectl
|
||||
},
|
||||
|
||||
middleware: ['authenticated'],
|
||||
|
|
@ -117,6 +121,7 @@ export default {
|
|||
</div>
|
||||
|
||||
<div class="header-middle">
|
||||
<LaunchKubectl />
|
||||
</div>
|
||||
|
||||
<v-popover
|
||||
|
|
@ -172,7 +177,7 @@ export default {
|
|||
<main>
|
||||
<nuxt />
|
||||
</main>
|
||||
|
||||
<ShellSocket />
|
||||
<ActionMenu />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ module.exports = {
|
|||
'~/plugins/vue-clipboard2',
|
||||
'~/plugins/v-select',
|
||||
'~/plugins/transitions',
|
||||
{ src: '~plugins/vue-js-modal' },
|
||||
{ src: '~/plugins/js-yaml', ssr: false },
|
||||
{ src: '~/plugins/codemirror', ssr: false },
|
||||
{ src: '~/plugins/resize', ssr: false },
|
||||
|
|
@ -164,7 +165,6 @@ function onProxyReqWs(proxyReq, req, socket, options, head) {
|
|||
proxyReq.setHeader('origin', options.target.href);
|
||||
proxyReq.setHeader('x-forwarded-host', req.headers['host']);
|
||||
proxyReq.setHeader('x-api-host', req.headers['host']);
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error('Proxy WS Error:', err);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,12 +53,15 @@
|
|||
"v-tooltip": "^2.0.2",
|
||||
"vue-clipboard2": "^0.3.1",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-js-modal": "^1.3.31",
|
||||
"vue-multiselect": "^2.1.6",
|
||||
"vue-native-websocket": "^2.0.13",
|
||||
"vue-resize": "^0.4.5",
|
||||
"vue-select": "^3.1.0",
|
||||
"vue2-transitions": "^0.3.0",
|
||||
"vuex-persistedstate": "^2.5.4",
|
||||
"xterm": "^4.0.2",
|
||||
"xterm-addon-fit": "^0.2.1",
|
||||
"yaml-js": "^0.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
import Vue from 'vue';
|
||||
import VModal from 'vue-js-modal/dist/ssr.index';
|
||||
|
||||
Vue.use(VModal);
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -79,9 +79,26 @@ export const getters = {
|
|||
}
|
||||
}
|
||||
|
||||
const openShell = {
|
||||
action: 'openShell',
|
||||
enabled: true,
|
||||
icon: 'icon icon-fw icon-chevron-right',
|
||||
label: 'Execute Shell',
|
||||
total: 1,
|
||||
};
|
||||
const openLogs = {
|
||||
action: 'openLogs',
|
||||
enabled: true,
|
||||
icon: 'icon icon-fw icon-chevron-right',
|
||||
label: 'View Logs',
|
||||
total: 1,
|
||||
};
|
||||
|
||||
const out = _filter(map);
|
||||
|
||||
return out;
|
||||
return selected[0].kind === 'Pod' ? {
|
||||
...out, openShell, openLogs
|
||||
} : { ...out };
|
||||
},
|
||||
|
||||
isSelected: state => (resource) => {
|
||||
|
|
@ -146,8 +163,12 @@ export const actions = {
|
|||
return _execute(state.tableSelected, action, args);
|
||||
},
|
||||
|
||||
execute({ state }, { action, args }) {
|
||||
return _execute(state.resources, action, args);
|
||||
execute({ state, dispatch }, { action, args }) {
|
||||
if (action.action === 'openShell' || action.action === 'openLogs') {
|
||||
dispatch('shell/defineSocket', { resource: state.resources[0], action: action.action }, { root: true });
|
||||
} else {
|
||||
return _execute(state.resources, action, args);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -192,7 +213,6 @@ function _filter(map, disableAll = false) {
|
|||
|
||||
function _execute(resources, action, args) {
|
||||
args = args || [];
|
||||
|
||||
if ( resources.length > 1 && action.bulkAction ) {
|
||||
const fn = resources[0][action.bulkAction];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
import { base64Decode, base64Encode } from '@/utils/crypto/index';
|
||||
|
||||
export const state = () => {
|
||||
return {
|
||||
socket: {},
|
||||
containers: [],
|
||||
container: {},
|
||||
resource: null,
|
||||
mode: null,
|
||||
backlog: [],
|
||||
toSend: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
closeSocket({ commit, state }) {
|
||||
return new Promise((resolve) => {
|
||||
if (state.socket) {
|
||||
state.socket.close();
|
||||
commit('clearBacklog');
|
||||
commit('socketOpened', { socket: {} });
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
defineSocket({ dispatch, state }, payload) {
|
||||
const resource = payload.resource ? payload.resource : state.resource;
|
||||
const action = payload.action ? payload.action : state.mode;
|
||||
const containers = resource.spec.containers.filter((container) => {
|
||||
return container.name !== 'istio';
|
||||
});
|
||||
const currentContainer = payload.container ? payload.container : containers[0];
|
||||
const showLast = payload.showLast ? payload.showLast : false;
|
||||
let protocol = null;
|
||||
let url = null;
|
||||
|
||||
switch (action) {
|
||||
case 'openShell':
|
||||
protocol = 'base64.channel.k8s.io';
|
||||
url = `${ window.location.origin.replace('https', 'wss') }/api/v1/namespaces/${ resource.metadata.namespace }/pods/${ resource.metadata.name }/exec?container=${ currentContainer.name }&stdout=1&stdin=1&stderr=1&tty=1&command=sh`;
|
||||
break;
|
||||
case 'openLogs':
|
||||
protocol = 'base64.binary.k8s.io';
|
||||
url = `${ window.location.origin.replace('https', 'wss') }/api/v1/namespaces/${ resource.metadata.namespace }/pods/${ resource.metadata.name }/log?container=${ currentContainer.name }&tailLines=500&follow=true×tamps=true&previous=${ showLast }`;
|
||||
break;
|
||||
default:
|
||||
protocol = 'base64.channel.k8s.io';
|
||||
url = `${ window.location.origin.replace('https', 'wss') }/api/v1/namespaces/${ resource.metadata.namespace }/pods/${ resource.metadata.name }/exec?container=${ currentContainer.name }`;
|
||||
}
|
||||
console.log('socket url: ', url);
|
||||
dispatch('openSocket', {
|
||||
url,
|
||||
resource,
|
||||
containers,
|
||||
container: currentContainer,
|
||||
mode: action,
|
||||
protocol,
|
||||
showLast
|
||||
});
|
||||
},
|
||||
openSocket({ commit, state }, payload) {
|
||||
commit('socketOpened', { ...payload });
|
||||
const socket = new WebSocket(payload.url, payload.protocol);
|
||||
|
||||
socket.onmessage = (e) => {
|
||||
decodeMsg(socket.protocol, e.data).then((message) => {
|
||||
commit('addBacklog', { log: message });
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
};
|
||||
socket.onopen = () => {
|
||||
state.toSend.forEach(msg => socket.send(msg));
|
||||
commit('socketOpened', { socket, ...payload });
|
||||
};
|
||||
socket.onclose = (msg) => {
|
||||
console.log('socket closed: ', msg);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
closeModal(state) {
|
||||
state.mode = null;
|
||||
},
|
||||
socketOpened(state, payload) {
|
||||
for (const prop in payload) {
|
||||
state[prop] = payload[prop];
|
||||
}
|
||||
},
|
||||
addBacklog(state, payload) {
|
||||
state.backlog.push(payload.log);
|
||||
},
|
||||
clearBacklog(state) {
|
||||
state.backlog = [];
|
||||
},
|
||||
sendInput(state, payload) {
|
||||
if (state.socket.readyState === 1) {
|
||||
state.socket.send(0 + base64Encode(payload.input));
|
||||
} else {
|
||||
state.toSend.push(0 + base64Encode(payload.input));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const decodeMsg = (protocol, msg) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (protocol === 'base64.binary.k8s.io') {
|
||||
resolve(base64Decode(msg));
|
||||
} else {
|
||||
const type = msg[0];
|
||||
const message = base64Decode(msg.slice(1));
|
||||
|
||||
if (type === '2') {
|
||||
reject(message);
|
||||
}
|
||||
resolve(message);
|
||||
}
|
||||
});
|
||||
};
|
||||
15
yarn.lock
15
yarn.lock
|
|
@ -11399,6 +11399,11 @@ vue-hot-reload-api@^2.3.0:
|
|||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
|
||||
|
||||
vue-js-modal@^1.3.31:
|
||||
version "1.3.31"
|
||||
resolved "https://registry.yarnpkg.com/vue-js-modal/-/vue-js-modal-1.3.31.tgz#fdece823d4f2816c8b1075c1fd8f667df11f5a42"
|
||||
integrity sha512-gwt2904sWbMUuUcHwKQ510IEs4G7S3bqVWLYeTOc2eEyWMmmnT9UmojDsXIexFnPVM7cZTua37z3Jm/h0i0y8Q==
|
||||
|
||||
vue-loader@^15.7.1:
|
||||
version "15.7.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.1.tgz#6ccacd4122aa80f69baaac08ff295a62e3aefcfd"
|
||||
|
|
@ -11854,6 +11859,16 @@ xtend@^4.0.0, xtend@~4.0.1:
|
|||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
xterm-addon-fit@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.2.1.tgz#353f43921eb78e3f9ad3f3afbb14e7ac183ca738"
|
||||
integrity sha512-BlR57O3t1/bmVcnS81bn9ZnNf+GiGNbeXdNUKSBa9tKEwNUMcU3S+KFLIRv7rm1Ty0D5pMOu0vbz/RDorKRwKQ==
|
||||
|
||||
xterm@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.0.2.tgz#c6a1b9586c0786627625e2ee9e78ad519dbc8c99"
|
||||
integrity sha512-NIr11b6C782TZznU8e6K/IMfmwlWMWRI6ba9GEDG9uX25SadkpjoMnzvxOS0Z/15sfrbn0rghPiarGDmmP0uhQ==
|
||||
|
||||
xxhashjs@^0.2.1:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8"
|
||||
|
|
|
|||
Loading…
Reference in New Issue