xref: /openbmc/webui-vue/src/views/Operations/Kvm/KvmConsole.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1<template>
2  <div :class="marginClass">
3    <div ref="toolbar" class="kvm-toolbar">
4      <b-row class="d-flex">
5        <b-col class="d-flex flex-column justify-content-end" cols="4">
6          <dl class="mb-2" sm="2" md="2">
7            <dt class="d-inline fw-bold me-1">{{ $t('pageKvm.status') }}:</dt>
8            <dd class="d-inline">
9              <status-icon :status="serverStatusIcon" />
10              <span class="d-none d-md-inline"> {{ serverStatus }}</span>
11            </dd>
12          </dl>
13        </b-col>
14
15        <b-col class="d-flex justify-content-end pe-1">
16          <b-button
17            v-if="isConnected"
18            variant="link"
19            type="button"
20            @click="sendCtrlAltDel"
21          >
22            <icon-arrow-down />
23            {{ $t('pageKvm.buttonCtrlAltDelete') }}
24          </b-button>
25          <b-button
26            v-if="!isFullWindow"
27            variant="link"
28            type="button"
29            @click="openConsoleWindow()"
30          >
31            <icon-launch />
32            {{ $t('pageKvm.openNewTab') }}
33          </b-button>
34        </b-col>
35      </b-row>
36    </div>
37    <div id="terminal-kvm" ref="panel" :class="terminalClass"></div>
38  </div>
39</template>
40
41<script>
42import RFB from '@novnc/novnc/core/rfb';
43import StatusIcon from '@/components/Global/StatusIcon';
44import IconLaunch from '@carbon/icons-vue/es/launch/20';
45import IconArrowDown from '@carbon/icons-vue/es/arrow--down/16';
46import { throttle } from 'lodash';
47import { useI18n } from 'vue-i18n';
48import i18n from '@/i18n';
49
50const Connecting = 0;
51const Connected = 1;
52const Disconnected = 2;
53
54export default {
55  name: 'KvmConsole',
56  components: { StatusIcon, IconLaunch, IconArrowDown },
57  props: {
58    isFullWindow: {
59      type: Boolean,
60      default: true,
61    },
62  },
63  data() {
64    return {
65      $t: useI18n().t,
66      rfb: null,
67      isConnected: false,
68      terminalClass: this.isFullWindow ? 'full-window' : '',
69      marginClass: this.isFullWindow ? 'margin-left-full-window' : '',
70      status: Connecting,
71      convasRef: null,
72      resizeKvmWindow: null,
73    };
74  },
75  computed: {
76    serverStatusIcon() {
77      if (this.status === Connected) {
78        return 'success';
79      } else if (this.status === Disconnected) {
80        return 'danger';
81      }
82      return 'secondary';
83    },
84    serverStatus() {
85      if (this.status === Connected) {
86        return i18n.global.t('pageKvm.connected');
87      } else if (this.status === Disconnected) {
88        return i18n.global.t('pageKvm.disconnected');
89      }
90      return i18n.global.t('pageKvm.connecting');
91    },
92  },
93  created() {
94    this.$store.dispatch('global/getSystemInfo');
95  },
96  mounted() {
97    this.openTerminal();
98  },
99  beforeUnmount() {
100    window.removeEventListener('resize', this.resizeKvmWindow);
101    this.closeTerminal();
102  },
103  methods: {
104    sendCtrlAltDel() {
105      this.rfb.sendCtrlAltDel();
106    },
107    closeTerminal() {
108      this.rfb.disconnect();
109      this.rfb = null;
110    },
111    openTerminal() {
112      const token = this.$store.getters['authentication/token'];
113      this.rfb = new RFB(
114        this.$refs.panel,
115        `wss://${window.location.host}/kvm/0`,
116        { wsProtocols: [token] },
117      );
118
119      this.rfb.scaleViewport = true;
120      this.rfb.clipViewport = true;
121      const that = this;
122
123      this.resizeKvmWindow = throttle(() => {
124        setTimeout(that.setWidthToolbar, 0);
125      }, 1000);
126      window.addEventListener('resize', this.resizeKvmWindow, {
127        passive: true,
128      });
129
130      this.rfb.addEventListener('connect', () => {
131        that.isConnected = true;
132        that.status = Connected;
133        that.setWidthToolbar();
134      });
135
136      this.rfb.addEventListener('disconnect', () => {
137        this.isConnected = false;
138        that.status = Disconnected;
139      });
140    },
141    setWidthToolbar() {
142      if (
143        this.$refs.panel.children &&
144        this.$refs.panel.children.length > 0 &&
145        this.$refs.panel.children[0].children.length > 0
146      ) {
147        this.$refs.toolbar.style.width =
148          this.$refs.panel.children[0].children[0].clientWidth - 10 + 'px';
149      }
150    },
151    openConsoleWindow() {
152      // If consoleWindow is not null
153      // Check the newly opened window is closed or not
154      if (this.$eventBus.$consoleWindow) {
155        // If window is not closed set focus to new window
156        // If window is closed, do open new window
157        if (!this.$eventBus.$consoleWindow.closed) {
158          this.$eventBus.$consoleWindow.focus();
159          return;
160        } else {
161          this.openNewWindow();
162        }
163      } else {
164        // If consoleWindow is null, open new window
165        this.openNewWindow();
166      }
167    },
168    openNewWindow() {
169      this.$eventBus.$consoleWindow = window.open(
170        '#/console/kvm',
171        'kvmConsoleWindow',
172        'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=yes,width=700,height=550',
173      );
174    },
175  },
176};
177</script>
178
179<style scoped lang="scss">
180.button-ctrl-alt-delete {
181  float: inline-end;
182}
183
184.kvm-status {
185  padding-top: calc(#{$spacer} / 2);
186  padding-inline-start: calc(#{$spacer} / 4);
187  display: inline-block;
188}
189
190.margin-left-full-window {
191  margin-inline-start: 5px;
192}
193</style>
194