mirror of
https://github.com/Eugeny/tabby
synced 2024-11-14 08:57:21 +00:00
.
This commit is contained in:
parent
98fea7b102
commit
8a02fd1708
14 changed files with 197 additions and 211 deletions
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.9 KiB |
|
@ -1,107 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
xml:space="preserve"
|
||||
width="536.82501"
|
||||
height="126.525"
|
||||
viewBox="0 0 536.82501 126.525"
|
||||
sodipodi:docname="elements_wortmarke+bildmarke_gelb+weiÃ_rz.svg"
|
||||
inkscape:export-filename="/home/eugene/Downloads/logo.png"
|
||||
inkscape:export-xdpi="42.677204"
|
||||
inkscape:export-ydpi="42.677204"><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1845"
|
||||
inkscape:window-height="1025"
|
||||
id="namedview4"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1586643"
|
||||
inkscape:cx="143.54613"
|
||||
inkscape:cy="-3.8338411"
|
||||
inkscape:window-x="75"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g10" /><g
|
||||
id="g10"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="ink_ext_XXXXXX"
|
||||
transform="matrix(1.25,0,0,-1.25,0,126.525)"><g
|
||||
id="g12"
|
||||
transform="scale(0.1,0.1)"><path
|
||||
d="m 202.457,809.793 404.891,0 0,202.457 -404.891,0 0,-202.457 z"
|
||||
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path14"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 0,607.375 202.445,0 0,202.457 -202.445,0 0,-202.457 z"
|
||||
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path16"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 202.457,404.918 404.891,0 0,202.457 -404.891,0 0,-202.457 z"
|
||||
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path18"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 0,202.461 202.445,0 0,202.457 -202.445,0 0,-202.457 z"
|
||||
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path20"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 202.457,0 404.891,0 0,202.461 -404.891,0 0,-202.461 z"
|
||||
style="fill:#fff200;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path22"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 1072.96,482.414 -148.108,0 0,48.219 148.108,0 0,-48.219 z m 39.27,-234.23 -302.441,0 0,516.679 302.441,0 0,-48.218 -247.32,0 0,-420.243 247.32,0 0,-48.218"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path24"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 1518.04,248.184 -187.4,0 0,48.218 187.4,0 0,-48.218 z m -247.34,0 -55.1,0 0,516.679 55.1,0 0,-516.679"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path26"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 1875.61,482.414 -148.12,0 0,48.219 148.12,0 0,-48.219 z m 39.27,-234.23 -302.43,0 0,516.679 302.43,0 0,-48.218 -247.33,0 0,-420.243 247.33,0 0,-48.218"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path28"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 2515.65,248.184 -55.12,0 0,333.425 55.12,0 0,-333.425 z m -432.66,0 -55.1,0 0,333.425 55.1,0 0,-333.425"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path30"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 2899.41,482.414 -148.12,0 0,48.219 148.12,0 0,-48.219 z m 39.28,-234.23 -302.44,0 0,516.679 302.44,0 0,-48.218 -247.34,0 0,-420.243 247.34,0 0,-48.218"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path32"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 3438.17,419.605 -55.11,0 0,345.258 55.11,0 0,-345.258 z m -334.12,-171.421 -55.12,0 0,344.457 55.12,0 0,-344.457 z m 336.1,19.675 -44.09,-28.949 -352.46,505.524 44.79,28.933 351.76,-505.508"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path34"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 3732.38,248.184 -55.1,0 0,408.523 55.1,0 0,-408.523 z m 160.52,468.461 -376.14,0 0,48.218 376.14,0 0,-48.218"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path36"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 4243.6,638.805 c -18.61,62.683 -73.02,92.308 -126.76,92.308 -56.49,0 -108.85,-33.754 -108.85,-95.761 0,-37.891 23.43,-75.786 73.02,-86.801 l 16.54,-4.141 -13.1,-48.91 -15.16,3.453 c -75.77,17.902 -115.04,76.461 -115.04,135.02 0,95.757 80.6,144.683 162.59,144.683 73.71,0 148.81,-39.273 171.54,-122.64 L 4243.6,638.805 Z M 4122.34,234.406 c -75.78,0 -153.63,38.571 -181.18,124.696 l 46.84,19.285 c 19.99,-66.137 75.79,-95.762 132.27,-95.762 63.39,0 121.26,37.203 121.26,104.027 0,46.856 -28.24,81.297 -83.37,92.313 l -13.77,2.758 13.08,48.91 19.99,-4.129 c 76.46,-15.848 117.12,-69.586 117.12,-137.777 0,-85.438 -68.9,-154.321 -172.24,-154.321"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path38"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 2528.61,742.93 -44.08,35.597 -211.89,-224.117 -212.58,224.731 -44.08,-35.598 249.15,-263.246 7.51,-7.883 7.51,7.883 248.46,262.633"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path40"
|
||||
inkscape:connector-curvature="0" /></g></g></svg>
|
Before Width: | Height: | Size: 6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.1 KiB |
|
@ -4,13 +4,6 @@ import { HttpModule } from '@angular/http'
|
|||
import { FormsModule } from '@angular/forms'
|
||||
import { ToasterModule } from 'angular2-toaster'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PerfectScrollbarModule } from 'angular2-perfect-scrollbar'
|
||||
import { PerfectScrollbarConfigInterface } from 'angular2-perfect-scrollbar'
|
||||
|
||||
const PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {
|
||||
suppressScrollX: true
|
||||
}
|
||||
|
||||
|
||||
import { ConfigService } from 'services/config'
|
||||
import { ElectronService } from 'services/electron'
|
||||
|
@ -19,11 +12,13 @@ import { LogService } from 'services/log'
|
|||
import { ModalService } from 'services/modal'
|
||||
import { NotifyService } from 'services/notify'
|
||||
import { QuitterService } from 'services/quitter'
|
||||
import { SessionsService } from 'services/sessions'
|
||||
import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
|
||||
|
||||
import { AppComponent } from 'components/app'
|
||||
import { CheckboxComponent } from 'components/checkbox'
|
||||
import { SettingsModalComponent } from 'components/settingsModal'
|
||||
import { TerminalComponent } from 'components/terminal'
|
||||
|
||||
|
||||
@NgModule({
|
||||
|
@ -33,7 +28,6 @@ import { SettingsModalComponent } from 'components/settingsModal'
|
|||
FormsModule,
|
||||
ToasterModule,
|
||||
NgbModule.forRoot(),
|
||||
PerfectScrollbarModule.forRoot(PERFECT_SCROLLBAR_CONFIG),
|
||||
],
|
||||
providers: [
|
||||
ConfigService,
|
||||
|
@ -43,6 +37,7 @@ import { SettingsModalComponent } from 'components/settingsModal'
|
|||
ModalService,
|
||||
NotifyService,
|
||||
QuitterService,
|
||||
SessionsService,
|
||||
LocalStorageService,
|
||||
],
|
||||
entryComponents: [
|
||||
|
@ -52,6 +47,7 @@ import { SettingsModalComponent } from 'components/settingsModal'
|
|||
AppComponent,
|
||||
CheckboxComponent,
|
||||
SettingsModalComponent,
|
||||
TerminalComponent,
|
||||
],
|
||||
bootstrap: [
|
||||
AppComponent
|
||||
|
|
|
@ -3,14 +3,16 @@ div.navbar.navbar-default.draggable
|
|||
| ×
|
||||
button.btn.btn-default.navbar-btn.navbar-btn-big.pull-right((click)='showSettings()', title='Settings')
|
||||
i.fa.fa-cog
|
||||
div.navbar-brand
|
||||
img.logo(src=require("img/logo.svg"))
|
||||
|
||||
perfect-scrollbar
|
||||
div.container
|
||||
div#term(style='width: 300px; height: 300px;')
|
||||
|
||||
ngb-tabset
|
||||
ngb-tab(*ngFor='let tab of tabs; trackBy: tab?.name')
|
||||
template(ngbTabTitle)
|
||||
span {{tab.name}}
|
||||
button.btn.btn-default((click)='closeTab(tab)') ×
|
||||
template(ngbTabContent)
|
||||
terminal([session]='tab', style='width: 300px; height: 300px;')
|
||||
|
||||
button.btn.btn-default((click)='newTab()') New tab
|
||||
footer
|
||||
|
||||
toaster-container([toasterconfig]="toasterconfig")
|
||||
|
|
|
@ -5,15 +5,13 @@ import { HostAppService } from 'services/hostApp'
|
|||
import { LogService } from 'services/log'
|
||||
import { QuitterService } from 'services/quitter'
|
||||
import { ToasterConfig } from 'angular2-toaster'
|
||||
import { Session, SessionsService } from 'services/sessions'
|
||||
|
||||
import { SettingsModalComponent } from 'components/settingsModal'
|
||||
|
||||
import 'angular2-toaster/lib/toaster.css'
|
||||
import 'global.less'
|
||||
|
||||
const hterm = require('hterm-commonjs')
|
||||
var pty = require('pty.js');
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
|
@ -25,6 +23,7 @@ export class AppComponent {
|
|||
private hostApp: HostAppService,
|
||||
private modal: ModalService,
|
||||
private electron: ElectronService,
|
||||
private sessions: SessionsService,
|
||||
element: ElementRef,
|
||||
log: LogService,
|
||||
_quitter: QuitterService,
|
||||
|
@ -42,40 +41,17 @@ export class AppComponent {
|
|||
}
|
||||
|
||||
toasterConfig: ToasterConfig
|
||||
tabs: Session[] = []
|
||||
|
||||
newTab () {
|
||||
this.tabs.push(this.sessions.createSession({command: 'zsh'}))
|
||||
}
|
||||
|
||||
closeTab (session) {
|
||||
session.destroy()
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
let io
|
||||
hterm.hterm.defaultStorage = new hterm.lib.Storage.Memory()
|
||||
let t = new hterm.hterm.Terminal()
|
||||
t.onTerminalReady = function() {
|
||||
t.installKeyboard()
|
||||
io = t.io.push();
|
||||
//#t.decorate(element.nativeElement);
|
||||
|
||||
var cmd = pty.spawn('bash', [], {
|
||||
name: 'xterm-color',
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
cwd: process.env.HOME,
|
||||
env: process.env
|
||||
});
|
||||
cmd.on('data', function(data) {
|
||||
io.writeUTF8(data);
|
||||
});
|
||||
|
||||
|
||||
io.onVTKeystroke = function(str) {
|
||||
cmd.write(str)
|
||||
};
|
||||
io.sendString = function(str) {
|
||||
cmd.write(str)
|
||||
};
|
||||
io.onTerminalResize = function(columns, rows) {
|
||||
cmd.resize(columns, rows)
|
||||
};
|
||||
};
|
||||
console.log(document.querySelector('#term'))
|
||||
t.decorate(document.querySelector('#term'));
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
|
|
|
@ -19,21 +19,6 @@ div.modal-body
|
|||
.title Server
|
||||
.value Not connected
|
||||
|
||||
.status-line(*ngIf='!userInfo?.user')
|
||||
.icon
|
||||
img(src=require("img/user.png"))
|
||||
.main
|
||||
.title Login
|
||||
.value Not logged in
|
||||
|
||||
.status-line(*ngIf='userInfo?.user')
|
||||
.icon
|
||||
img([src]='userInfo.user.avatar || "../../assets/img/user.png"')
|
||||
.main
|
||||
.title Login
|
||||
.value {{ userInfo.user.full_name || userInfo.user.username }}
|
||||
|
||||
br
|
||||
|
||||
div.form-group
|
||||
checkbox(text='Remember connected workspaces', '[(model)]'='config.store.rememberWorkspaces')
|
||||
|
|
5
app/src/components/terminal.less
Normal file
5
app/src/components/terminal.less
Normal file
|
@ -0,0 +1,5 @@
|
|||
:host {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
55
app/src/components/terminal.ts
Normal file
55
app/src/components/terminal.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Component, Input, ElementRef } from '@angular/core'
|
||||
import { ElectronService } from 'services/electron'
|
||||
import { ConfigService } from 'services/config'
|
||||
|
||||
import { Session } from 'services/sessions'
|
||||
|
||||
const hterm = require('hterm-commonjs')
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'terminal',
|
||||
template: '',
|
||||
styles: [require('./terminal.less')],
|
||||
})
|
||||
export class TerminalComponent {
|
||||
@Input() session: Session
|
||||
private terminal: any
|
||||
|
||||
constructor(
|
||||
private electron: ElectronService,
|
||||
private elementRef: ElementRef,
|
||||
public config: ConfigService,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
let io
|
||||
hterm.hterm.defaultStorage = new hterm.lib.Storage.Memory()
|
||||
this.terminal = new hterm.hterm.Terminal()
|
||||
this.terminal.onTerminalReady = () => {
|
||||
this.terminal.installKeyboard()
|
||||
io = this.terminal.io.push()
|
||||
const dataSubscription = this.session.dataAvailable.subscribe((data) => {
|
||||
io.writeUTF8(data)
|
||||
})
|
||||
const closedSubscription = this.session.closed.subscribe(() => {
|
||||
dataSubscription.unsubscribe()
|
||||
closedSubscription.unsubscribe()
|
||||
})
|
||||
io.onVTKeystroke = (str) => {
|
||||
this.session.write(str)
|
||||
}
|
||||
io.sendString = (str) => {
|
||||
this.session.write(str)
|
||||
}
|
||||
io.onTerminalResize = (columns, rows) => {
|
||||
this.session.resize(columns, rows)
|
||||
}
|
||||
}
|
||||
this.terminal.decorate(this.elementRef.nativeElement)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
}
|
||||
}
|
|
@ -67,41 +67,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin: 20px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fa-live {
|
||||
color: #7aff00;
|
||||
|
||||
.list-group-item &.fa-2x {
|
||||
top: 10px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.otp-input {
|
||||
height: 50px;
|
||||
|
||||
input {
|
||||
height: 50px;
|
||||
font-size: 41px;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngb-modal-backdrop {
|
||||
|
@ -169,8 +134,3 @@ ngb-tabset {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ps-container.ps-in-scrolling>.ps-scrollbar-y-rail,
|
||||
.ps-container:hover>.ps-scrollbar-y-rail:hover {
|
||||
background: rgba(0,0,0,.5) !important;
|
||||
}
|
||||
|
|
111
app/src/services/sessions.ts
Normal file
111
app/src/services/sessions.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||
import { Logger, LogService } from 'services/log'
|
||||
import * as ptyjs from 'pty.js'
|
||||
|
||||
|
||||
export interface SessionOptions {
|
||||
name?: string,
|
||||
command: string,
|
||||
cwd?: string,
|
||||
env?: string,
|
||||
}
|
||||
|
||||
export class Session {
|
||||
open: boolean
|
||||
name: string
|
||||
pty: any
|
||||
dataAvailable = new EventEmitter()
|
||||
closed = new EventEmitter()
|
||||
destroyed = new EventEmitter()
|
||||
|
||||
constructor (options: SessionOptions) {
|
||||
this.name = options.name
|
||||
this.pty = ptyjs.spawn('sh', ['-c', options.command], {
|
||||
name: 'xterm-color',
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
cwd: options.cwd || process.env.HOME,
|
||||
env: options.env || process.env,
|
||||
})
|
||||
|
||||
this.open = true
|
||||
|
||||
this.pty.on('data', (data) => {
|
||||
this.dataAvailable.emit(data)
|
||||
})
|
||||
|
||||
this.pty.on('close', () => {
|
||||
this.open = false
|
||||
this.closed.emit()
|
||||
})
|
||||
}
|
||||
|
||||
resize (columns, rows) {
|
||||
this.pty.resize(columns, rows)
|
||||
}
|
||||
|
||||
write (data) {
|
||||
this.pty.write(data)
|
||||
}
|
||||
|
||||
sendSignal (signal) {
|
||||
this.pty.kill(signal)
|
||||
}
|
||||
|
||||
close () {
|
||||
this.open = false
|
||||
this.closed.emit()
|
||||
this.pty.end()
|
||||
}
|
||||
|
||||
gracefullyDestroy () {
|
||||
return new Promise((resolve) => {
|
||||
this.sendSignal('SIGTERM')
|
||||
if (!open) {
|
||||
resolve()
|
||||
this.destroy()
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (this.open) {
|
||||
this.sendSignal('SIGKILL')
|
||||
this.destroy()
|
||||
}
|
||||
resolve()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
destroy () {
|
||||
if (open) {
|
||||
this.close()
|
||||
}
|
||||
this.destroyed.emit()
|
||||
this.pty.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SessionsService {
|
||||
sessions: {[id: string]: Session} = {}
|
||||
logger: Logger
|
||||
private lastID = 0
|
||||
|
||||
constructor(
|
||||
log: LogService,
|
||||
) {
|
||||
this.logger = log.create('sessions')
|
||||
}
|
||||
|
||||
createSession (options: SessionOptions) : Session {
|
||||
this.lastID++
|
||||
options.name = `session-${this.lastID}`
|
||||
let session = new Session(options)
|
||||
const destroySubscription = session.destroyed.subscribe(() => {
|
||||
delete this.sessions[session.name]
|
||||
destroySubscription.unsubscribe()
|
||||
})
|
||||
this.sessions[session.name] = session
|
||||
return session
|
||||
}
|
||||
}
|
|
@ -4,5 +4,8 @@
|
|||
"electron": "registry:dt/electron#1.3.3+20161012142539",
|
||||
"jquery": "registry:dt/jquery#1.10.0+20160929162922",
|
||||
"node": "registry:dt/node#6.0.0+20161014191813"
|
||||
},
|
||||
"dependencies": {
|
||||
"pty.js": "registry:dt/pty.js#0.2.7-1+20161128184045"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue