mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-21 02:48:00 +00:00
first working version of serial port integration
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
"pug-static-loader": "2.0.0",
|
"pug-static-loader": "2.0.0",
|
||||||
"raw-loader": "4.0.0",
|
"raw-loader": "4.0.0",
|
||||||
"sass-loader": "^8.0.0",
|
"sass-loader": "^8.0.0",
|
||||||
|
"serialport": "^8.0.0",
|
||||||
"shelljs": "0.8.3",
|
"shelljs": "0.8.3",
|
||||||
"source-code-pro": "^2.30.2",
|
"source-code-pro": "^2.30.2",
|
||||||
"source-sans-pro": "3.6.0",
|
"source-sans-pro": "3.6.0",
|
||||||
@@ -58,8 +59,8 @@
|
|||||||
"*/node-abi": "^2.14.0"
|
"*/node-abi": "^2.14.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js",
|
"build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js && webpack --color --config terminus-serial/webpack.config.js",
|
||||||
"build:typings": "tsc --project terminus-core/tsconfig.typings.json && tsc --project terminus-settings/tsconfig.typings.json && tsc --project terminus-terminal/tsconfig.typings.json && tsc --project terminus-plugin-manager/tsconfig.typings.json && tsc --project terminus-ssh/tsconfig.typings.json",
|
"build:typings": "tsc --project terminus-core/tsconfig.typings.json && tsc --project terminus-settings/tsconfig.typings.json && tsc --project terminus-terminal/tsconfig.typings.json && tsc --project terminus-plugin-manager/tsconfig.typings.json && tsc --project terminus-ssh/tsconfig.typings.json && tsc --project terminus-serial/tsconfig.typings.json",
|
||||||
"watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
|
"watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
|
||||||
"start": "cross-env TERMINUS_DEV=1 electron app --debug",
|
"start": "cross-env TERMINUS_DEV=1 electron app --debug",
|
||||||
"prod": "cross-env TERMINUS_DEV=1 electron app",
|
"prod": "cross-env TERMINUS_DEV=1 electron app",
|
||||||
|
38
terminus-serial/package.json
Normal file
38
terminus-serial/package.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "terminus-serial",
|
||||||
|
"version": "1.0.99-nightly.0",
|
||||||
|
"description": "Serial connection manager for Terminus",
|
||||||
|
"keywords": [
|
||||||
|
"terminus-builtin-plugin"
|
||||||
|
],
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"typings": "typings/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --progress --color",
|
||||||
|
"watch": "webpack --progress --color --watch"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"author": "Eugene Pankov",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "12.7.3",
|
||||||
|
"@types/ssh2": "^0.5.35",
|
||||||
|
"ansi-colors": "^4.1.1",
|
||||||
|
"cli-spinner": "^0.2.10",
|
||||||
|
"electron-rebuild": "^1.10.0",
|
||||||
|
"serialport": "^8.0.0",
|
||||||
|
"terminus-terminal": "^1.0.98-nightly.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^7",
|
||||||
|
"@angular/core": "^7",
|
||||||
|
"@angular/forms": "^7",
|
||||||
|
"@ng-bootstrap/ng-bootstrap": "^1",
|
||||||
|
"rxjs": "^5",
|
||||||
|
"terminus-core": "*",
|
||||||
|
"terminus-settings": "*",
|
||||||
|
"terminus-terminal": "*"
|
||||||
|
}
|
||||||
|
}
|
154
terminus-serial/src/api.ts
Normal file
154
terminus-serial/src/api.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import { BaseSession } from 'terminus-terminal'
|
||||||
|
import { SerialPort } from 'serialport'
|
||||||
|
import { Logger } from 'terminus-core'
|
||||||
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
|
||||||
|
export interface LoginScript {
|
||||||
|
expect: string
|
||||||
|
send: string
|
||||||
|
isRegex?: boolean
|
||||||
|
optional?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerialConnection {
|
||||||
|
name: string
|
||||||
|
port: string
|
||||||
|
baudrate: number
|
||||||
|
databits: number
|
||||||
|
stopbits: number
|
||||||
|
parity: string
|
||||||
|
rtscts: boolean
|
||||||
|
xon: boolean
|
||||||
|
xoff: boolean
|
||||||
|
xany: boolean
|
||||||
|
group: string | null
|
||||||
|
scripts?: LoginScript[]
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SerialSession extends BaseSession {
|
||||||
|
scripts?: LoginScript[]
|
||||||
|
serial: SerialPort
|
||||||
|
logger: Logger
|
||||||
|
|
||||||
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
|
private serviceMessage = new Subject<string>()
|
||||||
|
|
||||||
|
constructor (public connection: SerialConnection) {
|
||||||
|
super()
|
||||||
|
this.scripts = connection.scripts || []
|
||||||
|
}
|
||||||
|
|
||||||
|
async start () {
|
||||||
|
this.open = true
|
||||||
|
|
||||||
|
this.serial.on('data', data => {
|
||||||
|
const dataString = data.toString()
|
||||||
|
this.emitOutput(data)
|
||||||
|
|
||||||
|
if (this.scripts) {
|
||||||
|
let found = false
|
||||||
|
for (const script of this.scripts) {
|
||||||
|
let match = false
|
||||||
|
let cmd = ''
|
||||||
|
if (script.isRegex) {
|
||||||
|
const re = new RegExp(script.expect, 'g')
|
||||||
|
if (dataString.match(re)) {
|
||||||
|
cmd = dataString.replace(re, script.send)
|
||||||
|
match = true
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dataString.includes(script.expect)) {
|
||||||
|
cmd = script.send
|
||||||
|
match = true
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
this.logger.info('Executing script: "' + cmd + '"')
|
||||||
|
this.serial.write(cmd + '\n')
|
||||||
|
this.scripts = this.scripts.filter(x => x !== script)
|
||||||
|
} else {
|
||||||
|
if (script.optional) {
|
||||||
|
this.logger.debug('Skip optional script: ' + script.expect)
|
||||||
|
found = true
|
||||||
|
this.scripts = this.scripts.filter(x => x !== script)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
this.executeUnconditionalScripts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.serial.on('end', () => {
|
||||||
|
this.logger.info('Shell session ended')
|
||||||
|
if (this.open) {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.executeUnconditionalScripts()
|
||||||
|
}
|
||||||
|
|
||||||
|
emitServiceMessage (msg: string) {
|
||||||
|
this.serviceMessage.next(msg)
|
||||||
|
this.logger.info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
write (data) {
|
||||||
|
if (this.serial) {
|
||||||
|
this.serial.write(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy (): Promise<void> {
|
||||||
|
this.serviceMessage.complete()
|
||||||
|
await super.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
resize (columns, rows) {
|
||||||
|
console.log('resize')
|
||||||
|
}
|
||||||
|
|
||||||
|
kill (signal?: string) {
|
||||||
|
console.log('valar morghulis')
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChildProcesses (): Promise<any[]> {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
async gracefullyKillProcess (): Promise<void> {
|
||||||
|
this.kill('TERM')
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkingDirectory (): Promise<string|null> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private executeUnconditionalScripts () {
|
||||||
|
if (this.scripts) {
|
||||||
|
for (const script of this.scripts) {
|
||||||
|
if (!script.expect) {
|
||||||
|
console.log('Executing script:', script.send)
|
||||||
|
this.serial.write(script.send + '\n')
|
||||||
|
this.scripts = this.scripts.filter(x => x !== script)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerialConnectionGroup {
|
||||||
|
name: string
|
||||||
|
connections: SerialConnection[]
|
||||||
|
}
|
36
terminus-serial/src/buttonProvider.ts
Normal file
36
terminus-serial/src/buttonProvider.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { HotkeysService, ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
|
||||||
|
import { SerialModalComponent } from './components/serialModal.component'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
|
constructor (
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
hotkeys: HotkeysService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
hotkeys.matchedHotkey.subscribe(async (hotkey: string) => {
|
||||||
|
if (hotkey === 'serial') {
|
||||||
|
this.activate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
activate () {
|
||||||
|
this.ngbModal.open(SerialModalComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
provide (): ToolbarButton[] {
|
||||||
|
return [{
|
||||||
|
icon: require('./icons/serial.svg'),
|
||||||
|
weight: 5,
|
||||||
|
title: 'Serial connections',
|
||||||
|
touchBarNSImage: 'NSTouchBarOpenInBrowserTemplate',
|
||||||
|
click: async () => {
|
||||||
|
this.activate()
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
140
terminus-serial/src/components/editConnectionModal.component.pug
Normal file
140
terminus-serial/src/components/editConnectionModal.component.pug
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
.modal-body
|
||||||
|
ngb-tabset([activeId]='basic')
|
||||||
|
ngb-tab(id='basic')
|
||||||
|
ng-template(ngbTabTitle) General
|
||||||
|
ng-template(ngbTabContent)
|
||||||
|
.form-group
|
||||||
|
label Name
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='connection.name',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Group
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
placeholder='Ungrouped',
|
||||||
|
[(ngModel)]='connection.group',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Path
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='connection.port',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Baud Rate
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='connection.baudrate',
|
||||||
|
)
|
||||||
|
|
||||||
|
ngb-tab(id='advanced')
|
||||||
|
ng-template(ngbTabTitle) Advanced
|
||||||
|
ng-template(ngbTabContent)
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Tab color
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='connection.color',
|
||||||
|
placeholder='#000000'
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title DataBits
|
||||||
|
input.form-control(
|
||||||
|
type='number',
|
||||||
|
placeholder='8',
|
||||||
|
[(ngModel)]='connection.databits',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title StopBits
|
||||||
|
input.form-control(
|
||||||
|
type='number',
|
||||||
|
placeholder='1',
|
||||||
|
[(ngModel)]='connection.stopbits',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Parity
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='connection.parity',
|
||||||
|
placeholder='none'
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title RTSCTS
|
||||||
|
toggle([(ngModel)]='connection.rtscts')
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Xon
|
||||||
|
toggle([(ngModel)]='connection.xon')
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Xoff
|
||||||
|
toggle([(ngModel)]='connection.xoff')
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Xany
|
||||||
|
toggle([(ngModel)]='connection.xany')
|
||||||
|
|
||||||
|
ngb-tab(id='scripts')
|
||||||
|
ng-template(ngbTabTitle) Login scripts
|
||||||
|
ng-template(ngbTabContent)
|
||||||
|
table(*ngIf='connection.scripts.length > 0')
|
||||||
|
tr
|
||||||
|
th String to expect
|
||||||
|
th String to be sent
|
||||||
|
th.pl-2 Regex
|
||||||
|
th.pl-2 Optional
|
||||||
|
th.pl-2 Actions
|
||||||
|
tr(*ngFor='let script of connection.scripts')
|
||||||
|
td.pr-2
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='script.expect'
|
||||||
|
)
|
||||||
|
td
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='script.send'
|
||||||
|
)
|
||||||
|
td.pl-2
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='script.isRegex',
|
||||||
|
)
|
||||||
|
td.pl-2
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='script.optional',
|
||||||
|
)
|
||||||
|
td.pl-2
|
||||||
|
.input-group.flex-nowrap
|
||||||
|
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
||||||
|
i.fas.fa-arrow-up
|
||||||
|
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
||||||
|
i.fas.fa-arrow-down
|
||||||
|
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
||||||
|
i.fas.fa-trash
|
||||||
|
|
||||||
|
button.btn.btn-outline-info.mt-2((click)='addScript()')
|
||||||
|
i.fas.fa-plus
|
||||||
|
span New item
|
||||||
|
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-outline-primary((click)='save()') Save
|
||||||
|
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
@@ -0,0 +1,77 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { ElectronService, HostAppService } from 'terminus-core'
|
||||||
|
import { SerialConnection, LoginScript } from '../api'
|
||||||
|
// import { PromptModalComponent } from './promptModal.component'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./editConnectionModal.component.pug'),
|
||||||
|
})
|
||||||
|
export class EditConnectionModalComponent {
|
||||||
|
connection: SerialConnection
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalInstance: NgbActiveModal,
|
||||||
|
private electron: ElectronService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
// private ngbModal: NgbModal,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit () {
|
||||||
|
this.connection.scripts = this.connection.scripts || []
|
||||||
|
}
|
||||||
|
|
||||||
|
save () {
|
||||||
|
this.modalInstance.close(this.connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this.modalInstance.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
moveScriptUp (script: LoginScript) {
|
||||||
|
if (!this.connection.scripts) {
|
||||||
|
this.connection.scripts = []
|
||||||
|
}
|
||||||
|
const index = this.connection.scripts.indexOf(script)
|
||||||
|
if (index > 0) {
|
||||||
|
this.connection.scripts.splice(index, 1)
|
||||||
|
this.connection.scripts.splice(index - 1, 0, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveScriptDown (script: LoginScript) {
|
||||||
|
if (!this.connection.scripts) {
|
||||||
|
this.connection.scripts = []
|
||||||
|
}
|
||||||
|
const index = this.connection.scripts.indexOf(script)
|
||||||
|
if (index >= 0 && index < this.connection.scripts.length - 1) {
|
||||||
|
this.connection.scripts.splice(index, 1)
|
||||||
|
this.connection.scripts.splice(index + 1, 0, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteScript (script: LoginScript) {
|
||||||
|
if (this.connection.scripts && (await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Delete this script?',
|
||||||
|
detail: script.expect,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
|
this.connection.scripts = this.connection.scripts.filter(x => x !== script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addScript () {
|
||||||
|
if (!this.connection.scripts) {
|
||||||
|
this.connection.scripts = []
|
||||||
|
}
|
||||||
|
this.connection.scripts.push({ expect: '', send: '' })
|
||||||
|
}
|
||||||
|
}
|
14
terminus-serial/src/components/promptModal.component.pug
Normal file
14
terminus-serial/src/components/promptModal.component.pug
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.modal-body
|
||||||
|
input.form-control(
|
||||||
|
[type]='"text"',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='value',
|
||||||
|
#input,
|
||||||
|
[placeholder]='prompt',
|
||||||
|
(keyup.enter)='ok()',
|
||||||
|
(keyup.esc)='cancel()',
|
||||||
|
)
|
||||||
|
.d-flex.align-items-start.mt-2
|
||||||
|
button.btn.btn-primary.ml-auto(
|
||||||
|
(click)='ok()',
|
||||||
|
) Enter
|
31
terminus-serial/src/components/promptModal.component.ts
Normal file
31
terminus-serial/src/components/promptModal.component.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component, Input, ViewChild, ElementRef } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./promptModal.component.pug'),
|
||||||
|
})
|
||||||
|
export class PromptModalComponent {
|
||||||
|
@Input() value: string
|
||||||
|
@ViewChild('input') input: ElementRef
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalInstance: NgbActiveModal,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.input.nativeElement.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ok () {
|
||||||
|
this.modalInstance.close({
|
||||||
|
value: this.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this.modalInstance.close(null)
|
||||||
|
}
|
||||||
|
}
|
32
terminus-serial/src/components/serialModal.component.pug
Normal file
32
terminus-serial/src/components/serialModal.component.pug
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.modal-body
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='quickTarget',
|
||||||
|
autofocus,
|
||||||
|
placeholder='Quick connect: path@baudrate',
|
||||||
|
(ngModelChange)='refresh()',
|
||||||
|
(keyup.enter)='quickConnect()'
|
||||||
|
)
|
||||||
|
|
||||||
|
.list-group.mt-3(*ngIf='lastConnection')
|
||||||
|
a.list-group-item.list-group-item-action.d-flex.align-items-center((click)='connect(lastConnection)')
|
||||||
|
i.fas.fa-fw.fa-history
|
||||||
|
.mr-auto {{lastConnection.name}}
|
||||||
|
button.btn.btn-outline-danger.btn-sm((click)='clearLastConnection(); $event.stopPropagation()')
|
||||||
|
i.fas.fa-trash
|
||||||
|
|
||||||
|
.list-group.mt-3.connections-list(*ngIf='childGroups.length')
|
||||||
|
ng-container(*ngFor='let group of childGroups')
|
||||||
|
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
|
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
|
||||||
|
)
|
||||||
|
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
||||||
|
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
||||||
|
.ml-2 {{group.name || "Ungrouped"}}
|
||||||
|
ng-container(*ngIf='!groupCollapsed[group.name]')
|
||||||
|
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
||||||
|
*ngFor='let connection of group.connections',
|
||||||
|
(click)='connect(connection)'
|
||||||
|
)
|
||||||
|
.mr-2 {{connection.name}}
|
||||||
|
.text-muted {{connection.port}}
|
@@ -0,0 +1,5 @@
|
|||||||
|
.list-group.connections-list {
|
||||||
|
display: block;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
108
terminus-serial/src/components/serialModal.component.ts
Normal file
108
terminus-serial/src/components/serialModal.component.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { ToastrService } from 'ngx-toastr'
|
||||||
|
import { ConfigService, AppService } from 'terminus-core'
|
||||||
|
import { SettingsTabComponent } from 'terminus-settings'
|
||||||
|
import { SerialService } from '../services/serial.service'
|
||||||
|
import { SerialConnection, SerialConnectionGroup } from '../api'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./serialModal.component.pug'),
|
||||||
|
styles: [require('./serialModal.component.scss')],
|
||||||
|
})
|
||||||
|
export class SerialModalComponent {
|
||||||
|
connections: SerialConnection[]
|
||||||
|
childFolders: SerialConnectionGroup[]
|
||||||
|
quickTarget: string
|
||||||
|
lastConnection: SerialConnection|null = null
|
||||||
|
childGroups: SerialConnectionGroup[]
|
||||||
|
groupCollapsed: {[id: string]: boolean} = {}
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public modalInstance: NgbActiveModal,
|
||||||
|
private config: ConfigService,
|
||||||
|
private serial: SerialService,
|
||||||
|
private app: AppService,
|
||||||
|
private toastr: ToastrService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.connections = this.config.store.serial.connections
|
||||||
|
if (window.localStorage.lastSerialConnection) {
|
||||||
|
this.lastConnection = JSON.parse(window.localStorage.lastSerialConnection)
|
||||||
|
}
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
quickConnect () {
|
||||||
|
let path = this.quickTarget
|
||||||
|
let baudrate = 115200
|
||||||
|
if (this.quickTarget.includes('@')) {
|
||||||
|
baudrate = parseInt(path.split('@')[1])
|
||||||
|
path = path.split('@')[0]
|
||||||
|
}
|
||||||
|
const connection: SerialConnection = {
|
||||||
|
name: this.quickTarget,
|
||||||
|
group: null,
|
||||||
|
port: path,
|
||||||
|
baudrate: baudrate,
|
||||||
|
databits: 8,
|
||||||
|
parity: "none",
|
||||||
|
rtscts: false,
|
||||||
|
stopbits: 1,
|
||||||
|
xany: false,
|
||||||
|
xoff: false,
|
||||||
|
xon: false,
|
||||||
|
}
|
||||||
|
window.localStorage.lastSerialConnection = JSON.stringify(connection)
|
||||||
|
this.connect(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLastConnection () {
|
||||||
|
window.localStorage.lastSerialConnection = null
|
||||||
|
this.lastConnection = null
|
||||||
|
}
|
||||||
|
|
||||||
|
connect (connection: SerialConnection) {
|
||||||
|
this.close()
|
||||||
|
this.serial.openTab(connection).catch(error => {
|
||||||
|
this.toastr.error(`Could not connect: ${error}`)
|
||||||
|
}).then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.app.activeTab.emitFocused()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manageConnections () {
|
||||||
|
this.close()
|
||||||
|
this.app.openNewTab(SettingsTabComponent, { activeTab: 'serial' })
|
||||||
|
}
|
||||||
|
|
||||||
|
close () {
|
||||||
|
this.modalInstance.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh () {
|
||||||
|
this.childGroups = []
|
||||||
|
|
||||||
|
let connections = this.connections
|
||||||
|
if (this.quickTarget) {
|
||||||
|
connections = connections.filter((connection: SerialConnection) => (connection.name + connection.group!).toLowerCase().includes(this.quickTarget))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const connection of connections) {
|
||||||
|
connection.group = connection.group || null
|
||||||
|
let group = this.childGroups.find(x => x.name === connection.group)
|
||||||
|
if (!group) {
|
||||||
|
group = {
|
||||||
|
name: connection.group!,
|
||||||
|
connections: [],
|
||||||
|
}
|
||||||
|
this.childGroups.push(group!)
|
||||||
|
}
|
||||||
|
group.connections.push(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
h3 Connections
|
||||||
|
|
||||||
|
.list-group.list-group-flush.mt-3.mb-3
|
||||||
|
ng-container(*ngFor='let group of childGroups')
|
||||||
|
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
|
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
|
||||||
|
)
|
||||||
|
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
||||||
|
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
||||||
|
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
|
||||||
|
button.btn.btn-outline-info.ml-2((click)='editGroup(group)')
|
||||||
|
i.fas.fa-edit
|
||||||
|
button.btn.btn-outline-danger.ml-1((click)='deleteGroup(group)')
|
||||||
|
i.fas.fa-trash
|
||||||
|
ng-container(*ngIf='!groupCollapsed[group.name]')
|
||||||
|
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
||||||
|
*ngFor='let connection of group.connections',
|
||||||
|
(click)='editConnection(connection)'
|
||||||
|
)
|
||||||
|
.mr-auto
|
||||||
|
div {{connection.name}}
|
||||||
|
.text-muted {{connection.port}}
|
||||||
|
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteConnection(connection)')
|
||||||
|
i.fas.fa-trash
|
||||||
|
|
||||||
|
button.btn.btn-primary((click)='createConnection()')
|
||||||
|
i.fas.fa-fw.fa-plus
|
||||||
|
span.ml-2 Add connection
|
131
terminus-serial/src/components/serialSettingsTab.component.ts
Normal file
131
terminus-serial/src/components/serialSettingsTab.component.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { ConfigService, ElectronService, HostAppService } from 'terminus-core'
|
||||||
|
import { SerialConnection, SerialConnectionGroup } from '../api'
|
||||||
|
import { EditConnectionModalComponent } from './editConnectionModal.component'
|
||||||
|
import { PromptModalComponent } from './promptModal.component'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./serialSettingsTab.component.pug'),
|
||||||
|
})
|
||||||
|
export class SerialSettingsTabComponent {
|
||||||
|
connections: SerialConnection[]
|
||||||
|
childGroups: SerialConnectionGroup[]
|
||||||
|
groupCollapsed: {[id: string]: boolean} = {}
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public config: ConfigService,
|
||||||
|
private electron: ElectronService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
) {
|
||||||
|
this.connections = this.config.store.serial.connections
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
createConnection () {
|
||||||
|
const connection: SerialConnection = {
|
||||||
|
name: '',
|
||||||
|
group: null,
|
||||||
|
port: '',
|
||||||
|
baudrate: 115200,
|
||||||
|
databits: 8,
|
||||||
|
parity: "none",
|
||||||
|
rtscts: false,
|
||||||
|
stopbits: 1,
|
||||||
|
xany: false,
|
||||||
|
xoff: false,
|
||||||
|
xon: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = this.ngbModal.open(EditConnectionModalComponent)
|
||||||
|
modal.componentInstance.connection = connection
|
||||||
|
modal.result.then(result => {
|
||||||
|
this.connections.push(result)
|
||||||
|
this.config.store.serial.connections = this.connections
|
||||||
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editConnection (connection: SerialConnection) {
|
||||||
|
const modal = this.ngbModal.open(EditConnectionModalComponent, { size: 'lg' })
|
||||||
|
modal.componentInstance.connection = Object.assign({}, connection)
|
||||||
|
modal.result.then(result => {
|
||||||
|
Object.assign(connection, result)
|
||||||
|
this.config.store.serial.connections = this.connections
|
||||||
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteConnection (connection: SerialConnection) {
|
||||||
|
if ((await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Delete "${connection.name}"?`,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
|
this.connections = this.connections.filter(x => x !== connection)
|
||||||
|
this.config.store.serial.connections = this.connections
|
||||||
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editGroup (group: SerialConnectionGroup) {
|
||||||
|
const modal = this.ngbModal.open(PromptModalComponent)
|
||||||
|
modal.componentInstance.prompt = 'New group name'
|
||||||
|
modal.componentInstance.value = group.name
|
||||||
|
modal.result.then(result => {
|
||||||
|
if (result) {
|
||||||
|
for (const connection of this.connections.filter(x => x.group === group.name)) {
|
||||||
|
connection.group = result.value
|
||||||
|
}
|
||||||
|
this.config.store.serial.connections = this.connections
|
||||||
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteGroup (group: SerialConnectionGroup) {
|
||||||
|
if ((await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Delete "${group}"?`,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
|
for (const connection of this.connections.filter(x => x.group === group.name)) {
|
||||||
|
connection.group = null
|
||||||
|
}
|
||||||
|
this.config.save()
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh () {
|
||||||
|
this.connections = this.config.store.serial.connections
|
||||||
|
this.childGroups = []
|
||||||
|
|
||||||
|
for (const connection of this.connections) {
|
||||||
|
connection.group = connection.group || null
|
||||||
|
let group = this.childGroups.find(x => x.name === connection.group)
|
||||||
|
if (!group) {
|
||||||
|
group = {
|
||||||
|
name: connection.group!,
|
||||||
|
connections: [],
|
||||||
|
}
|
||||||
|
this.childGroups.push(group!)
|
||||||
|
}
|
||||||
|
group.connections.push(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
terminus-serial/src/components/serialTab.component.pug
Normal file
11
terminus-serial/src/components/serialTab.component.pug
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.serial-tab-toolbar
|
||||||
|
.btn.btn-outline-secondary.reveal-button
|
||||||
|
i.fas.fa-ellipsis-h
|
||||||
|
.toolbar(*ngIf='session', [class.show]='!session.open')
|
||||||
|
i.fas.fa-circle.text-success.mr-2(*ngIf='session.open')
|
||||||
|
i.fas.fa-circle.text-danger.mr-2(*ngIf='!session.open')
|
||||||
|
strong.mr-auto(*ngIf='session') {{session.connection.port}} ({{session.connection.baudrate}})
|
||||||
|
|
||||||
|
button.btn.btn-info((click)='reconnect()', *ngIf='!session.open')
|
||||||
|
i.fas.fa-reload
|
||||||
|
span Reconnect
|
71
terminus-serial/src/components/serialTab.component.scss
Normal file
71
terminus-serial/src/components/serialTab.component.scss
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
:host {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&> .content {
|
||||||
|
flex: auto;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-tab-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 4;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.reveal-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 35px;
|
||||||
|
padding: 0;
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
transition: 0.125s opacity;
|
||||||
|
opacity: .5;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .reveal-button {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
opacity: 0;
|
||||||
|
background: rgba(0, 0, 0, .75);
|
||||||
|
padding: 10px 20px;
|
||||||
|
transition: 0.25s opacity;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
|
&>* {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
.reveal-button {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
terminus-serial/src/components/serialTab.component.ts
Normal file
111
terminus-serial/src/components/serialTab.component.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import colors from 'ansi-colors'
|
||||||
|
import { Spinner } from 'cli-spinner'
|
||||||
|
import { Component } from '@angular/core'
|
||||||
|
// import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { first } from 'rxjs/operators'
|
||||||
|
import { BaseTerminalTabComponent } from 'terminus-terminal'
|
||||||
|
import { SerialService } from '../services/serial.service'
|
||||||
|
import { SerialConnection, SerialSession } from '../api'
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
selector: 'serial-tab',
|
||||||
|
template: BaseTerminalTabComponent.template + require<string>('./serialTab.component.pug'),
|
||||||
|
styles: [require('./serialTab.component.scss'), ...BaseTerminalTabComponent.styles],
|
||||||
|
animations: BaseTerminalTabComponent.animations,
|
||||||
|
})
|
||||||
|
export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||||
|
connection: SerialConnection
|
||||||
|
serial: SerialService
|
||||||
|
session: SerialSession
|
||||||
|
// private ngbModal: NgbModal
|
||||||
|
private homeEndSubscription: Subscription
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
// this.ngbModal = this.injector.get<NgbModal>(NgbModal)
|
||||||
|
|
||||||
|
this.logger = this.log.create('terminalTab')
|
||||||
|
this.serial = this.injector.get(SerialService)
|
||||||
|
|
||||||
|
this.homeEndSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
|
if (!this.hasFocus) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (hotkey) {
|
||||||
|
case 'home':
|
||||||
|
this.sendInput('\x1b[H' )
|
||||||
|
break
|
||||||
|
case 'end':
|
||||||
|
this.sendInput('\x1b[F' )
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.frontendReady$.pipe(first()).subscribe(() => {
|
||||||
|
this.initializeSession()
|
||||||
|
})
|
||||||
|
|
||||||
|
super.ngOnInit()
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
this.setTitle(this.connection.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async initializeSession () {
|
||||||
|
if (!this.connection) {
|
||||||
|
this.logger.error('No Serial connection info supplied')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.session = this.serial.createSession(this.connection)
|
||||||
|
this.session.serviceMessage$.subscribe(msg => {
|
||||||
|
this.write('\r\n' + colors.black.bgWhite(' serial ') + ' ' + msg + '\r\n')
|
||||||
|
this.session.resize(this.size.columns, this.size.rows)
|
||||||
|
})
|
||||||
|
this.attachSessionHandlers()
|
||||||
|
this.write(`Connecting to `)
|
||||||
|
|
||||||
|
const spinner = new Spinner({
|
||||||
|
text: 'Connecting',
|
||||||
|
stream: {
|
||||||
|
write: x => this.write(x),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
spinner.setSpinnerString(6)
|
||||||
|
spinner.start()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.serial.connectSession(this.session, (message: string) => {
|
||||||
|
spinner.stop(true)
|
||||||
|
this.write(message + '\r\n')
|
||||||
|
spinner.start()
|
||||||
|
})
|
||||||
|
spinner.stop(true)
|
||||||
|
} catch (e) {
|
||||||
|
spinner.stop(true)
|
||||||
|
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.session.start()
|
||||||
|
this.session.resize(this.size.columns, this.size.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecoveryToken (): Promise<any> {
|
||||||
|
return {
|
||||||
|
type: 'app:serial-tab',
|
||||||
|
connection: this.connection,
|
||||||
|
savedState: this.frontend?.saveState(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnect () {
|
||||||
|
this.initializeSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.homeEndSubscription.unsubscribe()
|
||||||
|
super.ngOnDestroy()
|
||||||
|
}
|
||||||
|
}
|
19
terminus-serial/src/config.ts
Normal file
19
terminus-serial/src/config.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ConfigProvider } from 'terminus-core'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
export class SerialConfigProvider extends ConfigProvider {
|
||||||
|
defaults = {
|
||||||
|
serial: {
|
||||||
|
connections: [],
|
||||||
|
options: {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hotkeys: {
|
||||||
|
serial: [
|
||||||
|
'Alt-K',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
platformDefaults = { }
|
||||||
|
}
|
17
terminus-serial/src/hotkeys.ts
Normal file
17
terminus-serial/src/hotkeys.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HotkeyDescription, HotkeyProvider } from 'terminus-core'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class SerialHotkeyProvider extends HotkeyProvider {
|
||||||
|
hotkeys: HotkeyDescription[] = [
|
||||||
|
{
|
||||||
|
id: 'serial',
|
||||||
|
name: 'Show Serial connections',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async provide (): Promise<HotkeyDescription[]> {
|
||||||
|
return this.hotkeys
|
||||||
|
}
|
||||||
|
}
|
27
terminus-serial/src/icons/serial.svg
Normal file
27
terminus-serial/src/icons/serial.svg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="32"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 32 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg3749">
|
||||||
|
<defs
|
||||||
|
id="defs3753" />
|
||||||
|
<g
|
||||||
|
id="g3747"
|
||||||
|
transform="matrix(0.48599086,0,0,0.48599086,0.50191451,-0.299629)"
|
||||||
|
style="fill:none;fill-rule:evenodd">
|
||||||
|
<g
|
||||||
|
id="g3741"
|
||||||
|
transform="translate(-292.02353,-314.25882)"
|
||||||
|
style="fill:#ffffff">
|
||||||
|
<path
|
||||||
|
style="fill-rule:nonzero;stroke-width:0.10270619"
|
||||||
|
d="M 16.007812,0 3.2929688,0.03515625 2.2324219,0.40234375 C 0.91449728,1.1071083 0,2.575555 0,3.9863281 c 0,1.2651363 1.3074352,7.8137089 1.7402344,8.7167969 0.553077,1.153703 1.988134,2.456836 3.234375,2.9375 0.8530743,0.328933 1.4753185,0.348528 10.9804686,0.357422 9.789951,0.0091 10.106534,-0.002 11.087891,-0.369141 1.221173,-0.456851 2.835858,-1.955656 3.333984,-3.09375 0.336145,-0.767943 1.638672,-7.5615083 1.638672,-8.5488279 0,-1.4107731 -0.849384,-3.02258715 -2.234375,-3.58398435 L 28.791016,0 Z m -4.27539,4.890625 c 0.427942,0 0.812664,0.071135 1.152344,0.2128906 0.342354,0.1390812 0.631097,0.3382162 0.86914,0.5976563 0.243393,0.2674641 0.430211,0.5965523 0.558594,0.984375 0.131057,0.3878229 0.197266,0.8262256 0.197266,1.3183593 0,0.4921338 -0.06744,0.9337216 -0.201172,1.3242188 -0.131058,0.387823 -0.316645,0.711263 -0.554688,0.970703 -0.246067,0.270139 -0.536042,0.474922 -0.873047,0.611328 -0.33433,0.136407 -0.71782,0.203125 -1.148437,0.203125 -0.419919,0 -0.801456,-0.0699 -1.146484,-0.208984 C 10.243584,10.765216 9.9516549,10.563618 9.7109375,10.298828 9.4702197,10.034039 9.2834009,9.7093672 9.1523438,9.3242188 9.0239608,8.9390707 8.9609375,8.4987146 8.9609375,8.0039062 c 0,-0.4867846 0.063023,-0.9214925 0.1914063,-1.3066406 C 9.2807263,6.3094427 9.4687769,5.9766597 9.7148438,5.7011719 9.950212,5.4390572 10.242141,5.2386906 10.589844,5.0996094 10.940222,4.9605279 11.320527,4.890625 11.732422,4.890625 Z m 9.933594,0 c 0.427942,0 0.812664,0.071135 1.152343,0.2128906 0.342354,0.1390812 0.631098,0.3382162 0.869141,0.5976563 0.243392,0.2674641 0.430211,0.5965523 0.558594,0.984375 0.131057,0.3878229 0.197265,0.8262256 0.197265,1.3183593 0,0.4921338 -0.06744,0.9337216 -0.201171,1.3242188 -0.131058,0.387823 -0.316645,0.711263 -0.554688,0.970703 -0.246067,0.270139 -0.536042,0.474922 -0.873047,0.611328 -0.33433,0.136407 -0.71782,0.203125 -1.148437,0.203125 -0.419919,0 -0.801456,-0.0699 -1.146485,-0.208984 -0.342353,-0.139081 -0.634282,-0.340679 -0.875,-0.605469 C 19.403813,10.034039 19.216995,9.7093672 19.085938,9.3242188 18.957555,8.9390707 18.894531,8.4987146 18.894531,8.0039062 c 0,-0.4867846 0.06302,-0.9214925 0.191407,-1.3066406 0.128382,-0.3878229 0.316433,-0.7206059 0.5625,-0.9960937 0.235368,-0.2621147 0.527296,-0.4624813 0.875,-0.6015625 0.350377,-0.1390815 0.730683,-0.2089844 1.142578,-0.2089844 z m -16.0839848,0.125 h 2.359375 V 5.625 H 7.1582031 v 4.753906 h 0.7832031 v 0.611328 H 5.5820312 V 10.378906 H 6.3652344 V 5.625 H 5.5820312 Z m 9.9335938,0 H 17.875 V 5.625 h -0.783203 v 4.753906 H 17.875 v 0.611328 h -2.359375 v -0.611328 h 0.783203 V 5.625 h -0.783203 z m 9.933594,0 h 2.359375 V 5.625 h -0.783203 v 4.753906 h 0.783203 v 0.611328 h -2.359375 v -0.611328 h 0.783203 V 5.625 h -0.783203 z m -13.712891,0.5625 c -0.607143,0 -1.083937,0.2102191 -1.43164,0.6328125 -0.3450291,0.4199185 -0.5175786,1.0173231 -0.5175786,1.7929687 0,0.7836694 0.1762443,1.3854905 0.5292966,1.8027344 0.353053,0.4145694 0.826152,0.6210934 1.419922,0.6210934 0.59377,0 1.065638,-0.206524 1.416016,-0.6210934 0.353052,-0.4172439 0.529297,-1.019065 0.529297,-1.8027344 0,-0.7756456 -0.173782,-1.3730502 -0.521485,-1.7929687 C 12.812453,5.7883441 12.338122,5.578125 11.736328,5.578125 Z m 9.933594,0 c -0.607144,0 -1.083938,0.2102191 -1.431641,0.6328125 -0.345028,0.4199185 -0.517578,1.0173231 -0.517578,1.7929687 0,0.7836694 0.176244,1.3854905 0.529297,1.8027344 0.353052,0.4145694 0.826152,0.6210934 1.419922,0.6210934 0.59377,0 1.065638,-0.206524 1.416016,-0.6210934 0.353052,-0.4172439 0.529296,-1.019065 0.529296,-1.8027344 0,-0.7756456 -0.173781,-1.3730502 -0.521484,-1.7929687 C 22.746047,5.7883441 22.271716,5.578125 21.669922,5.578125 Z"
|
||||||
|
transform="matrix(2.0576519,0,0,2.0576519,290.99076,314.87535)"
|
||||||
|
id="path3739" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
51
terminus-serial/src/index.ts
Normal file
51
terminus-serial/src/index.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { ToastrModule } from 'ngx-toastr'
|
||||||
|
import TerminusCoreModule, { ToolbarButtonProvider, ConfigProvider, TabRecoveryProvider, HotkeyProvider } from 'terminus-core'
|
||||||
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
import TerminusTerminalModule from 'terminus-terminal'
|
||||||
|
|
||||||
|
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||||
|
import { SerialModalComponent } from './components/serialModal.component'
|
||||||
|
import { SerialSettingsTabComponent } from './components/serialSettingsTab.component'
|
||||||
|
import { SerialTabComponent } from './components/serialTab.component'
|
||||||
|
|
||||||
|
import { ButtonProvider } from './buttonProvider'
|
||||||
|
import { SerialConfigProvider } from './config'
|
||||||
|
import { SerialSettingsTabProvider } from './settings'
|
||||||
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
|
import { SerialHotkeyProvider } from './hotkeys'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
NgbModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ToastrModule,
|
||||||
|
TerminusCoreModule,
|
||||||
|
TerminusTerminalModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
|
{ provide: ConfigProvider, useClass: SerialConfigProvider, multi: true },
|
||||||
|
{ provide: SettingsTabProvider, useClass: SerialSettingsTabProvider, multi: true },
|
||||||
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
|
{ provide: HotkeyProvider, useClass: SerialHotkeyProvider, multi: true },
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
EditConnectionModalComponent,
|
||||||
|
SerialModalComponent,
|
||||||
|
SerialSettingsTabComponent,
|
||||||
|
SerialTabComponent,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
EditConnectionModalComponent,
|
||||||
|
SerialModalComponent,
|
||||||
|
SerialSettingsTabComponent,
|
||||||
|
SerialTabComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export default class SerialModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
21
terminus-serial/src/recoveryProvider.ts
Normal file
21
terminus-serial/src/recoveryProvider.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { TabRecoveryProvider, RecoveredTab } from 'terminus-core'
|
||||||
|
|
||||||
|
import { SerialTabComponent } from './components/serialTab.component'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
|
async recover (recoveryToken: any): Promise<RecoveredTab|null> {
|
||||||
|
if (recoveryToken && recoveryToken.type === 'app:serial-tab') {
|
||||||
|
return {
|
||||||
|
type: SerialTabComponent,
|
||||||
|
options: {
|
||||||
|
connection: recoveryToken.connection,
|
||||||
|
savedState: recoveryToken.savedState,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
68
terminus-serial/src/services/serial.service.ts
Normal file
68
terminus-serial/src/services/serial.service.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
|
const SerialPort = require('serialport')
|
||||||
|
import { ToastrService } from 'ngx-toastr'
|
||||||
|
import { AppService, LogService } from 'terminus-core'
|
||||||
|
import { SerialConnection, SerialSession } from '../api'
|
||||||
|
import { SerialTabComponent } from '../components/serialTab.component'
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class SerialService {
|
||||||
|
|
||||||
|
private constructor (
|
||||||
|
private log: LogService,
|
||||||
|
private app: AppService,
|
||||||
|
private zone: NgZone,
|
||||||
|
private toastr: ToastrService,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async openTab (connection: SerialConnection): Promise<SerialTabComponent> {
|
||||||
|
const tab = this.zone.run(() => this.app.openNewTab(
|
||||||
|
SerialTabComponent,
|
||||||
|
{ connection }
|
||||||
|
) as SerialTabComponent)
|
||||||
|
if (connection.color) {
|
||||||
|
(this.app.getParentTab(tab) || tab).color = connection.color
|
||||||
|
}
|
||||||
|
return tab
|
||||||
|
}
|
||||||
|
|
||||||
|
createSession (connection: SerialConnection): SerialSession {
|
||||||
|
const session = new SerialSession(connection)
|
||||||
|
session.logger = this.log.create(`serial-${connection.port}`)
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectSession (session: SerialSession, logCallback?: (s: any) => void): Promise<void> {
|
||||||
|
const serial = new SerialPort(session.connection.port, { autoOpen: false, baudRate: session.connection.baudrate,
|
||||||
|
dataBits: session.connection.databits, stopBits: session.connection.stopbits, parity: session.connection.parity,
|
||||||
|
rtscts: session.connection.rtscts, xon: session.connection.xon, xoff: session.connection.xoff,
|
||||||
|
xany: session.connection.xany })
|
||||||
|
session.serial = serial
|
||||||
|
let connected = false
|
||||||
|
await new Promise(async (resolve, reject) => {
|
||||||
|
serial.on('open', () => {
|
||||||
|
connected = true
|
||||||
|
this.zone.run(resolve)
|
||||||
|
})
|
||||||
|
serial.on('error', error => {
|
||||||
|
this.zone.run(() => {
|
||||||
|
if (connected) {
|
||||||
|
this.toastr.error(error.toString())
|
||||||
|
} else {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
serial.open()
|
||||||
|
} catch (e) {
|
||||||
|
this.toastr.error(e.message)
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
16
terminus-serial/src/settings.ts
Normal file
16
terminus-serial/src/settings.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
|
import { SerialSettingsTabComponent } from './components/serialSettingsTab.component'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class SerialSettingsTabProvider extends SettingsTabProvider {
|
||||||
|
id = 'serial'
|
||||||
|
icon = 'keyboard'
|
||||||
|
title = 'Serial'
|
||||||
|
|
||||||
|
getComponentType (): any {
|
||||||
|
return SerialSettingsTabComponent
|
||||||
|
}
|
||||||
|
}
|
7
terminus-serial/tsconfig.json
Normal file
7
terminus-serial/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "dist", "typings"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src"
|
||||||
|
}
|
||||||
|
}
|
15
terminus-serial/tsconfig.typings.json
Normal file
15
terminus-serial/tsconfig.typings.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "dist", "typings"],
|
||||||
|
"include": ["src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src",
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "./typings",
|
||||||
|
"paths": {
|
||||||
|
"terminus-*": ["../../terminus-*"],
|
||||||
|
"*": ["../../app/node_modules/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
terminus-serial/webpack.config.js
Normal file
59
terminus-serial/webpack.config.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
target: 'node',
|
||||||
|
entry: 'src/index.ts',
|
||||||
|
context: __dirname,
|
||||||
|
devtool: 'source-map',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'index.js',
|
||||||
|
pathinfo: true,
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
devtoolModuleFilenameTemplate: 'webpack-terminus-serial:///[resource-path]',
|
||||||
|
},
|
||||||
|
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
|
||||||
|
optimization:{
|
||||||
|
minimize: false,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
|
extensions: ['.ts', '.js', '.node'],
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
|
loader: 'awesome-typescript-loader',
|
||||||
|
options: {
|
||||||
|
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||||
|
typeRoots: [
|
||||||
|
path.resolve(__dirname, 'node_modules/@types'),
|
||||||
|
path.resolve(__dirname, '../node_modules/@types'),
|
||||||
|
],
|
||||||
|
paths: {
|
||||||
|
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
||||||
|
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
|
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
|
{ test: /\.svg/, use: ['svg-inline-loader'] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
externals: [
|
||||||
|
'fs',
|
||||||
|
'keytar',
|
||||||
|
'path',
|
||||||
|
'serialport',
|
||||||
|
'ngx-toastr',
|
||||||
|
'windows-process-tree/build/Release/windows_process_tree.node',
|
||||||
|
/^rxjs/,
|
||||||
|
/^@angular/,
|
||||||
|
/^@ng-bootstrap/,
|
||||||
|
/^terminus-/,
|
||||||
|
],
|
||||||
|
}
|
1375
terminus-serial/yarn.lock
Normal file
1375
terminus-serial/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ import { BaseTerminalTabComponent } from 'terminus-terminal'
|
|||||||
import { SSHService } from '../services/ssh.service'
|
import { SSHService } from '../services/ssh.service'
|
||||||
import { SSHConnection, SSHSession } from '../api'
|
import { SSHConnection, SSHSession } from '../api'
|
||||||
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
||||||
import {Subscription} from "rxjs";
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
|
@@ -7,4 +7,5 @@ module.exports = [
|
|||||||
require('./terminus-community-color-schemes/webpack.config.js'),
|
require('./terminus-community-color-schemes/webpack.config.js'),
|
||||||
require('./terminus-plugin-manager/webpack.config.js'),
|
require('./terminus-plugin-manager/webpack.config.js'),
|
||||||
require('./terminus-ssh/webpack.config.js'),
|
require('./terminus-ssh/webpack.config.js'),
|
||||||
|
require('./terminus-serial/webpack.config.js'),
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user