preparing for 0.2.0...

Still a work in progress. Adding menu system and fontawsome
integration.
This commit is contained in:
billchurch 2018-02-08 15:34:33 -05:00
parent 8af3d3c695
commit 207832d318
10 changed files with 10825 additions and 250 deletions

View file

@ -1,6 +1,6 @@
{
"listen": {
"ip": "127.0.0.1",
"ip": "192.168.0.71",
"port": 2222
},
"user": {

View file

@ -34,7 +34,7 @@
"express-session": "^1.15.6",
"morgan": "^1.9.0",
"read-config": "^1.6.0",
"socket.io": "^2.0.0",
"socket.io": "^1.7.4",
"ssh2": "^0.5.5",
"validator": "^9.0.0"
},
@ -48,17 +48,21 @@
"cleanmac": "find . -name '.DS_Store' -type f -delete"
},
"devDependencies": {
"@fortawesome/fontawesome": "^1.1.3",
"@fortawesome/fontawesome-free-solid": "^5.0.6",
"bithound": "^1.7.0",
"clean-webpack-plugin": "^0.1.18",
"copy-webpack-plugin": "^4.3.1",
"css-loader": "^0.28.9",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"nodemon": "^1.11.0",
"snazzy": "^7.0.0",
"snyk": "^1.39.1",
"standard": "^10.0.3",
"style-loader": "^0.20.1",
"uglifyjs-webpack-plugin": "^1.1.8",
"url-loader": "^0.6.2",
"webpack": "^3.10.0",
"webpack-merge": "^4.1.1",
"webpack-stream": "^4.0.0",

View file

@ -9,12 +9,36 @@
<div id="header"></div>
<div id="terminal-container" class="terminal"></div>
<div id="bottomdiv">
<div class="dropup" id="menu">
<i class="fas fa-bars"></i> Menu
<div class="dropup-content">
<a id="aboutBtn" class="About" href="javascript:void(0);"><i class="fas fa-question"></i> About</a>
<a id="toggleLog" class="toggleLog" href="javascript:void(0);"><i class="fas fa-clipboard"></i> Start Log</a>
<a id="downloadLog" href="javascript:void(0);"><i class="fas fa-download"></i> Download Log</a>
<a id="credentialsBtn" href="javascript:void(0);"><i class="fas fa-key"></i> Credentials</a>
</div>
</div>
<div id="footer"></div>
<div id="status"></div>
<div id="credentials"><a class="credentials" href="javascript:void(0);">CREDENTIALS</a></div>
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);">Download Log</a></div>
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);">Start Log</a></div>
</div>
</div>
<!-- The Modal -->
<div id="aboutModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<span class="aboutClose">&times;</span>
<h1>WebSSH2 0.2.0</h1>
</div>
<div class="modal-body">
<p>Copyright 2017 F5 Networks, Inc. See End User License Agreement (EULA) for license terms. Notwithstanding anything to the contrary in the EULA, Licensee may copy and modify this software product for its internal business purposes.</p>
</div>
<div class="modal-footer">
<p>This is pre-release content. This application may change in final release.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -9,12 +9,36 @@
<div id="header"></div>
<div id="terminal-container" class="terminal"></div>
<div id="bottomdiv">
<div class="dropup" id="menu">
<i class="fas fa-bars"></i> Menu
<div class="dropup-content">
<a id="aboutBtn" class="About" href="javascript:void(0);"><i class="fas fa-question"></i> About</a>
<a id="toggleLog" class="toggleLog" href="javascript:void(0);"><i class="fas fa-clipboard"></i> Start Log</a>
<a id="downloadLog" href="javascript:void(0);"><i class="fas fa-download"></i> Download Log</a>
<a id="credentialsBtn" href="javascript:void(0);"><i class="fas fa-key"></i> Credentials</a>
</div>
</div>
<div id="footer"></div>
<div id="status"></div>
<div id="credentials"><a class="credentials" href="javascript:void(0);">CREDENTIALS</a></div>
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);">Download Log</a></div>
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);">Start Log</a></div>
</div>
</div>
<!-- The Modal -->
<div id="aboutModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<span class="aboutClose">&times;</span>
<h1>WebSSH2 0.2.0</h1>
</div>
<div class="modal-body">
<p>Copyright 2017 F5 Networks, Inc. See End User License Agreement (EULA) for license terms. Notwithstanding anything to the contrary in the EULA, Licensee may copy and modify this software product for its internal business purposes.</p>
</div>
<div class="modal-footer">
<p>This is pre-release content. This application may change in final release.</p>
</div>
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -177,12 +177,27 @@ body {
flex: 0 1 30px;
}
#menu {
display: inline-block;
font-size:16px;
color: rgb(255, 255, 255);
padding-left: 10px;
z-index: 100;
}
#menu:hover .dropup-content {
display: block;
}
#footer {
display: inline-block;
color: rgb(240, 240, 240);
background-color: rgb(50, 50, 50);
padding-left: 10px;
padding-right: 10px;
padding-left: 5px;
padding-right: 5px;
border-color: white;
border-style: none none none solid;
border-width: 1px;
text-align: left;
}
@ -198,51 +213,110 @@ body {
text-align: left;
z-index: 100;
}
#credentials {
.dropup {
position: relative;
display: inline-block;
color: rgb(51, 51, 51);
background-color: rgb(255, 127, 0);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none none;
border-width: 1px;
text-align: left;
z-index: 100;
}
a.credentials {
color: rgb(51, 51, 51);
.dropup-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
bottom:16px;
z-index: 1;
}
.dropup-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
#downloadLog {
display: inline-block;
color: rgb(240, 240, 240);
background-color: rgb(255, 127, 0);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none none;
border-width: 1px;
text-align: left;
z-index: 100;
.dropup-content a:hover {background-color: #ccc}
.dropup:hover .dropup-content {
display: block;
}
a.downloadLog {
color: rgb(240, 240, 240);
.dropup:click .dropup-content {
display: block;
}
.dropup:hover .dropbtn {
background-color: #3e8e41;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 100; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(128,128,128); /* Fallback color */
background-color: rgba(128,128,128,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
position: relative;
background-color: #fefefe;
color: #000;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 80%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
/* Add Animation */
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
/* The Close Button */
.aboutClose {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
}
.aboutClose:hover,
.aboutClose:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
#toggleLog {
display: inline-block;
color: rgb(240, 240, 240);
background-color: rgb(0, 127, 0);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none none;
border-width: 1px;
text-align: left;
z-index: 100;
.modal-header {
padding: 2px 16px;
background-color: #5cb85c;
color: white;
}
a.toggleLog {
color: rgb(240, 240, 240);
text-decoration: none;
.modal-body {padding: 2px 16px;}
.modal-footer {
padding: 2px 16px;
background-color: #5cb85c;
color: white;
font-style: italic;
}

View file

@ -9,12 +9,36 @@
<div id="header"></div>
<div id="terminal-container" class="terminal"></div>
<div id="bottomdiv">
<div class="dropup" id="menu">
<i class="fas fa-bars"></i> Menu
<div class="dropup-content">
<a id="aboutBtn" class="About" href="javascript:void(0);"><i class="fas fa-question"></i> About</a>
<a id="toggleLog" class="toggleLog" href="javascript:void(0);"><i class="fas fa-clipboard"></i> Start Log</a>
<a id="downloadLog" href="javascript:void(0);"><i class="fas fa-download"></i> Download Log</a>
<a id="credentialsBtn" href="javascript:void(0);"><i class="fas fa-key"></i> Credentials</a>
</div>
</div>
<div id="footer"></div>
<div id="status"></div>
<div id="credentials"><a class="credentials" href="javascript:void(0);">CREDENTIALS</a></div>
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);">Download Log</a></div>
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);">Start Log</a></div>
</div>
</div>
<!-- The Modal -->
<div id="aboutModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<span class="aboutClose">&times;</span>
<h1>WebSSH2 0.2.0</h1>
</div>
<div class="modal-body">
<p>Copyright 2017 F5 Networks, Inc. See End User License Agreement (EULA) for license terms. Notwithstanding anything to the contrary in the EULA, Licensee may copy and modify this software product for its internal business purposes.</p>
</div>
<div class="modal-footer">
<p>This is pre-release content. This application may change in final release.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -9,12 +9,36 @@
<div id="header"></div>
<div id="terminal-container" class="terminal"></div>
<div id="bottomdiv">
<div class="dropup" id="menu">
<i class="fas fa-bars"></i> Menu
<div class="dropup-content">
<a id="aboutBtn" class="About" href="javascript:void(0);"><i class="fas fa-question"></i> About</a>
<a id="toggleLog" class="toggleLog" href="javascript:void(0);"><i class="fas fa-clipboard"></i> Start Log</a>
<a id="downloadLog" href="javascript:void(0);"><i class="fas fa-download"></i> Download Log</a>
<a id="credentialsBtn" href="javascript:void(0);"><i class="fas fa-key"></i> Credentials</a>
</div>
</div>
<div id="footer"></div>
<div id="status"></div>
<div id="credentials"><a class="credentials" href="javascript:void(0);">CREDENTIALS</a></div>
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);">Download Log</a></div>
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);">Start Log</a></div>
</div>
</div>
<!-- The Modal -->
<div id="aboutModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<span class="aboutClose">&times;</span>
<h1>WebSSH2 0.2.0</h1>
</div>
<div class="modal-body">
<p>Copyright 2017 F5 Networks, Inc. See End User License Agreement (EULA) for license terms. Notwithstanding anything to the contrary in the EULA, Licensee may copy and modify this software product for its internal business purposes.</p>
</div>
<div class="modal-footer">
<p>This is pre-release content. This application may change in final release.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -53,12 +53,27 @@ body {
flex: 0 1 30px;
}
#menu {
display: inline-block;
font-size:16px;
color: rgb(255, 255, 255);
padding-left: 10px;
z-index: 100;
}
#menu:hover .dropup-content {
display: block;
}
#footer {
display: inline-block;
color: rgb(240, 240, 240);
background-color: rgb(50, 50, 50);
padding-left: 10px;
padding-right: 10px;
padding-left: 5px;
padding-right: 5px;
border-color: white;
border-style: none none none solid;
border-width: 1px;
text-align: left;
}
@ -74,51 +89,110 @@ body {
text-align: left;
z-index: 100;
}
#credentials {
.dropup {
position: relative;
display: inline-block;
color: rgb(51, 51, 51);
background-color: rgb(255, 127, 0);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none none;
border-width: 1px;
text-align: left;
z-index: 100;
}
a.credentials {
color: rgb(51, 51, 51);
.dropup-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
bottom:16px;
z-index: 1;
}
.dropup-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
#downloadLog {
display: inline-block;
color: rgb(240, 240, 240);
background-color: rgb(255, 127, 0);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none none;
border-width: 1px;
text-align: left;
z-index: 100;
.dropup-content a:hover {background-color: #ccc}
.dropup:hover .dropup-content {
display: block;
}
a.downloadLog {
color: rgb(240, 240, 240);
.dropup:click .dropup-content {
display: block;
}
.dropup:hover .dropbtn {
background-color: #3e8e41;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 100; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(128,128,128); /* Fallback color */
background-color: rgba(128,128,128,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
position: relative;
background-color: #fefefe;
color: #000;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 80%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
/* Add Animation */
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
/* The Close Button */
.aboutClose {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
}
.aboutClose:hover,
.aboutClose:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
#toggleLog {
display: inline-block;
color: rgb(240, 240, 240);
background-color: rgb(0, 127, 0);
padding-left: 10px;
padding-right: 10px;
border-color: white;
border-style: none solid none none;
border-width: 1px;
text-align: left;
z-index: 100;
.modal-header {
padding: 2px 16px;
background-color: #5cb85c;
color: white;
}
a.toggleLog {
color: rgb(240, 240, 240);
text-decoration: none;
.modal-body {padding: 2px 16px;}
.modal-footer {
padding: 2px 16px;
background-color: #5cb85c;
color: white;
font-style: italic;
}

View file

@ -1,6 +1,24 @@
import * as io from '../../node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js'
'use strict'
import * as io from '../../node_modules/socket.io-client/dist/socket.io.js'
import * as Terminal from '../../node_modules/xterm/dist/xterm'
import * as fit from '../../node_modules/xterm/dist/addons/fit/fit'
// import 'font-awesome/css/font-awesome.css'
//
// import '@fortawesome/fontawesome/styles.css'
import fontawesome from '@fortawesome/fontawesome'
import faBars from '@fortawesome/fontawesome-free-solid/faBars'
import faQuestion from '@fortawesome/fontawesome-free-solid/faQuestion'
import faClipboard from '@fortawesome/fontawesome-free-solid/faClipboard'
import faDownload from '@fortawesome/fontawesome-free-solid/faDownload'
import faKey from '@fortawesome/fontawesome-free-solid/faKey'
import faCog from '@fortawesome/fontawesome-free-solid/faCog'
fontawesome.library.add(faBars, faQuestion, faClipboard, faDownload, faKey, faCog)
fontawesome.config.searchPseudoElements = true
fontawesome.dom.i2svg()
require('../../node_modules/xterm/dist/xterm.css')
require('../css/style.css')
@ -10,16 +28,17 @@ Terminal.applyAddon(fit)
/* global Blob */
var sessionLogEnable = false
var loggedData = false
var sessionLog, sessionFooter, logDate, currentDate, myFile, errorExists
var downloadLogButton = document.getElementById('downloadLog')
var credentialsButton = document.getElementById('credentials')
var credentialsBtn = document.getElementById('credentialsBtn')
var toggleLogButton = document.getElementById('toggleLog')
toggleLogButton.addEventListener('click', toggleLog)
downloadLogButton.style.display = 'none'
credentialsButton.style.display = 'none'
downloadLogButton.style.color = '#666'
credentialsBtn.style.color = '#666'
var terminalContainer = document.getElementById('terminal-container')
@ -29,7 +48,7 @@ var term = new Terminal({
var socket, termid // eslint-disable-line
term.open(terminalContainer)
term.focus()
// term.applyAddon(fit)
term.fit()
if (document.location.pathname) {
var parts = document.location.pathname.split('/')
@ -44,57 +63,75 @@ if (document.location.pathname) {
socket.on('connect', function () {
socket.emit('geometry', term.cols, term.rows)
term.on('data', function (data) {
socket.emit('data', data)
})
socket.on('title', function (data) {
document.title = data
}).on('status', function (data) {
document.getElementById('status').innerHTML = data
}).on('ssherror', function (data) {
document.getElementById('status').innerHTML = data
console.log('geometry cols: ' + term.cols + ' rows: ' + term.rows)
})
term.on('data', function (data) {
socket.emit('data', data)
})
socket.on('title', function (data) {
document.title = data
}).on('status', function (data) {
document.getElementById('status').innerHTML = data
}).on('ssherror', function (data) {
document.getElementById('status').innerHTML = data
document.getElementById('status').style.backgroundColor = 'red'
errorExists = true
}).on('headerBackground', function (data) {
document.getElementById('header').style.backgroundColor = data
}).on('header', function (data) {
document.getElementById('header').innerHTML = data
}).on('footer', function (data) {
sessionFooter = data
document.getElementById('footer').innerHTML = data
}).on('statusBackground', function (data) {
document.getElementById('status').style.backgroundColor = data
}).on('allowreplay', function (data) {
if (data === true) {
console.log('allowreplay: ' + data)
credentialsBtn.style.color = '#000'
credentialsBtn.addEventListener('click', replayCredentials)
} else {
console.log('allowreplay: ' + data)
credentialsBtn.style.color = '#666'
}
}).on('data', function (data) {
term.write(data)
if (sessionLogEnable) {
sessionLog = sessionLog + data
}
}).on('disconnect', function (err) {
if (!errorExists) {
document.getElementById('status').style.backgroundColor = 'red'
errorExists = true
}).on('headerBackground', function (data) {
document.getElementById('header').style.backgroundColor = data
}).on('header', function (data) {
document.getElementById('header').innerHTML = data
}).on('footer', function (data) {
sessionFooter = data
document.getElementById('footer').innerHTML = data
}).on('statusBackground', function (data) {
document.getElementById('status').style.backgroundColor = data
}).on('allowreplay', function (data) {
if (data === true) {
console.log('allowreplay: ' + data)
credentialsButton.style.display = 'inline'
credentialsButton.addEventListener('click', replayCredentials)
} else {
credentialsButton.style.display = 'none'
}
}).on('data', function (data) {
term.write(data)
if (sessionLogEnable) {
sessionLog = sessionLog + data
}
}).on('disconnect', function (err) {
if (!errorExists) {
document.getElementById('status').style.backgroundColor = 'red'
document.getElementById('status').innerHTML =
'WEBSOCKET SERVER DISCONNECTED: ' + err
}
socket.io.reconnection(false)
}).on('error', function (err) {
if (!errorExists) {
document.getElementById('status').style.backgroundColor = 'red'
document.getElementById('status').innerHTML = 'ERROR: ' + err
}
})
document.getElementById('status').innerHTML =
'WEBSOCKET SERVER DISCONNECTED: ' + err
}
socket.io.reconnection(false)
}).on('error', function (err) {
if (!errorExists) {
document.getElementById('status').style.backgroundColor = 'red'
document.getElementById('status').innerHTML = 'ERROR: ' + err
}
})
// replay password to server, requires
// bill test
// About Modal Stuff
var aboutModal = document.getElementById('aboutModal')
var aboutBtn = document.getElementById('aboutBtn')
var aboutClose = document.getElementsByClassName('aboutClose')[0]
aboutBtn.onclick = function () {
aboutModal.style.display = 'block'
}
aboutClose.onclick = function () {
aboutModal.style.display = 'none'
term.focus()
}
window.onclick = function (event) {
if (event.target == aboutModal) {
aboutModal.style.display = 'none'
term.focus()
}
}
// replay password to server, requires
function replayCredentials () { // eslint-disable-line
socket.emit('control', 'replayCredentials')
console.log('replaying credentials')
@ -107,8 +144,8 @@ function replayCredentials () { // eslint-disable-line
function toggleLog () { // eslint-disable-line
if (sessionLogEnable === true) {
sessionLogEnable = false
toggleLogButton.innerHTML =
'<a class="toggleLog" href="javascript:void(0);">Start Log</a>'
loggedData = true
toggleLogButton.innerHTML = '<i class="fas fa-clipboard"></i> Start Log'
console.log('stopping log, ' + sessionLogEnable)
currentDate = new Date()
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' +
@ -120,9 +157,9 @@ function toggleLog () { // eslint-disable-line
return false
} else {
sessionLogEnable = true
toggleLogButton.innerHTML =
'<a class="toggleLog" href="javascript:void(0)">Logging - STOP LOG</a>'
downloadLogButton.style.display = 'inline'
loggedData = true
toggleLogButton.innerHTML = '<i class="fas fa-cog fa-spin"></i> Stop Log'
downloadLogButton.style.color = '#000'
downloadLogButton.addEventListener('click', downloadLog)
console.log('starting log, ' + sessionLogEnable)
currentDate = new Date()
@ -139,22 +176,24 @@ function toggleLog () { // eslint-disable-line
// cross browser method to "download" an element to the local system
// used for our client-side logging feature
function downloadLog () { // eslint-disable-line
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) +
logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() +
logDate.getSeconds() + '.log'
// regex should eliminate escape sequences from being logged.
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
type: 'text/plain'
})
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, myFile)
} else {
var elem = window.document.createElement('a')
elem.href = window.URL.createObjectURL(blob)
elem.download = myFile
document.body.appendChild(elem)
elem.click()
document.body.removeChild(elem)
if (loggedData === true) {
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) +
logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() +
logDate.getSeconds() + '.log'
// regex should eliminate escape sequences from being logged.
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
type: 'text/plain'
})
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, myFile)
} else {
var elem = window.document.createElement('a')
elem.href = window.URL.createObjectURL(blob)
elem.download = myFile
document.body.appendChild(elem)
elem.click()
document.body.removeChild(elem)
}
}
term.focus()
}