xref: /openbmc/webui-vue/src/views/Operations/Kvm/KvmConsole.vue (revision ce7db82c9582c4dac04ac81d9af6b557ae7965e3)
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 font-weight-bold mr-1">
8               {{ $t('pageKvm.status') }}:
9             </dt>
10             <dd class="d-inline">
11               <status-icon :status="serverStatusIcon" />
12               <span class="d-none d-md-inline"> {{ serverStatus }}</span>
13             </dd>
14           </dl>
15         </b-col>
16 
17         <b-col class="d-flex justify-content-end pr-1">
18           <b-button
19             v-if="isConnected"
20             variant="link"
21             type="button"
22             @click="sendCtrlAltDel"
23           >
24             <icon-arrow-down />
25             {{ $t('pageKvm.buttonCtrlAltDelete') }}
26           </b-button>
27           <b-button
28             v-if="!isFullWindow"
29             variant="link"
30             type="button"
31             @click="openConsoleWindow()"
32           >
33             <icon-launch />
34             {{ $t('pageKvm.openNewTab') }}
35           </b-button>
36         </b-col>
37       </b-row>
38     </div>
39     <div id="terminal-kvm" ref="panel" :class="terminalClass"></div>
40   </div>
41 </template>
42 
43 <script>
44 import RFB from '@novnc/novnc/core/rfb';
45 import StatusIcon from '@/components/Global/StatusIcon';
46 import IconLaunch from '@carbon/icons-vue/es/launch/20';
47 import IconArrowDown from '@carbon/icons-vue/es/arrow--down/16';
48 import { throttle } from 'lodash';
49 import { useI18n } from 'vue-i18n';
50 import i18n from '@/i18n';
51 
52 const Connecting = 0;
53 const Connected = 1;
54 const Disconnected = 2;
55 
56 export default {
57   name: 'KvmConsole',
58   components: { StatusIcon, IconLaunch, IconArrowDown },
59   props: {
60     isFullWindow: {
61       type: Boolean,
62       default: true,
63     },
64   },
65   data() {
66     return {
67       $t: useI18n().t,
68       rfb: null,
69       isConnected: false,
70       terminalClass: this.isFullWindow ? 'full-window' : '',
71       marginClass: this.isFullWindow ? 'margin-left-full-window' : '',
72       status: Connecting,
73       convasRef: null,
74       resizeKvmWindow: null,
75     };
76   },
77   computed: {
78     serverStatusIcon() {
79       if (this.status === Connected) {
80         return 'success';
81       } else if (this.status === Disconnected) {
82         return 'danger';
83       }
84       return 'secondary';
85     },
86     serverStatus() {
87       if (this.status === Connected) {
88         return i18n.global.t('pageKvm.connected');
89       } else if (this.status === Disconnected) {
90         return i18n.global.t('pageKvm.disconnected');
91       }
92       return i18n.global.t('pageKvm.connecting');
93     },
94   },
95   created() {
96     this.$store.dispatch('global/getSystemInfo');
97   },
98   mounted() {
99     this.openTerminal();
100   },
101   beforeUnmount() {
102     window.removeEventListener('resize', this.resizeKvmWindow);
103     this.closeTerminal();
104   },
105   methods: {
106     sendCtrlAltDel() {
107       this.rfb.sendCtrlAltDel();
108     },
109     closeTerminal() {
110       this.rfb.disconnect();
111       this.rfb = null;
112     },
113     openTerminal() {
114       const token = this.$store.getters['authentication/token'];
115       this.rfb = new RFB(
116         this.$refs.panel,
117         `wss://${window.location.host}/kvm/0`,
118         { wsProtocols: [token] },
119       );
120 
121       this.rfb.scaleViewport = true;
122       this.rfb.clipViewport = true;
123       const that = this;
124 
125       this.resizeKvmWindow = throttle(() => {
126         setTimeout(that.setWidthToolbar, 0);
127       }, 1000);
128       window.addEventListener('resize', this.resizeKvmWindow);
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 lang="scss" scoped>
180 @import '@/assets/styles/bmc/helpers/_index.scss';
181 @import '@/assets/styles/bootstrap/_helpers.scss';
182 
183 .button-ctrl-alt-delete {
184   float: right;
185 }
186 
187 .kvm-status {
188   padding-top: $spacer / 2;
189   padding-left: $spacer / 4;
190   display: inline-block;
191 }
192 
193 .margin-left-full-window {
194   margin-left: 5px;
195 }
196 </style>
197