Added target and terminal support in Dashboard
[src/xds/xds-agent.git] / webapp / src / app / pages / targets / terminals / terminal.component.ts
1 /**
2 * @license
3 * Copyright (C) 2018 "IoT.bzh"
4 * Author Sebastien Douheret <sebastien@iot.bzh>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 import { Component, ElementRef, ViewChild, Input, Output, HostListener, EventEmitter, AfterViewInit } from '@angular/core';
20 import { Observable } from 'rxjs/Observable';
21
22 import { Terminal } from 'xterm';
23 import * as fit from 'xterm/lib/addons/fit/fit';
24
25 export interface ITerminalFont {
26   fontFamily: string;
27   fontSize: string;
28   lineHeight: number;
29   charWidth: number;
30   charHeight: number;
31 }
32
33
34 @Component({
35   selector: 'xds-terminal',
36   styles: [],
37   template: `
38     <div #terminalContainer></div>
39   `,
40 })
41 export class TerminalComponent implements AfterViewInit {
42
43   private _xterm: Terminal;
44   private _initDone: boolean;
45
46   @ViewChild('terminalContainer') termContainer: ElementRef;
47
48   @Output() stdin = new EventEmitter<any>();
49   @Output() resize = new EventEmitter<{ cols: number, rows: number }>();
50
51
52   constructor() {
53     this._initDone = false;
54     Terminal.applyAddon(fit);
55
56     this._xterm = new Terminal({
57       cursorBlink: true,
58       // useStyle: true,
59       scrollback: 1000,
60       rows: 24,
61       cols: 80,
62     });
63   }
64
65   // getting the nativeElement only possible after view init
66   ngAfterViewInit() {
67
68     // this now finds the #terminal element
69     this._xterm.open(this.termContainer.nativeElement);
70
71     // the number of rows will determine the size of the terminal screen
72     (<any>this._xterm).fit();
73
74     // Bind input key
75     this._xterm.on('data', (data) => {
76       // console.log(data.charCodeAt(0));
77       this.stdin.emit(this._sanitizeInput(data));
78       return false;
79     });
80
81     this._initDone = true;
82   }
83
84   @Input('stdout')
85   set writeData(data) {
86     if (this._initDone && data !== undefined) {
87       this._xterm.write(data);
88     }
89   }
90
91   @Input('disable')
92   set disable(value: boolean) {
93     if (!this._initDone) {
94       return;
95     }
96
97     this._xterm.setOption('disableStdin', value);
98
99     if (value) {
100       this._xterm.blur();
101     } else {
102       this._xterm.focus();
103     }
104     this._resize();
105   }
106
107   @HostListener('window:resize', ['$event'])
108   onWindowResize(event) {
109     this._resize();
110   }
111
112   /*** Private functions ***/
113
114   private _sanitizeInput(d) {
115     // TODO sanitize ?
116     return d;
117   }
118
119   private _resize() {
120     const geom = fit.proposeGeometry(this._xterm);
121
122     // console.log('DEBUG  cols ' + String(geom.cols) + ' rows ' + String(geom.rows));
123
124     if (geom.cols < 0 || geom.cols > 2000 || geom.rows < 0 || geom.rows > 2000) {
125       return;
126     }
127
128     // Update xterm size
129     this._xterm.resize(geom.cols, geom.rows);
130
131     // Send resize event to update remote terminal
132     this.resize.emit({ cols: geom.cols, rows: geom.rows });
133   }
134
135 }