Unverified Commit c6b443be authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #91 from jumpserver/dev

Dev
parents a44c0b99 95b667ce
...@@ -60,5 +60,8 @@ ...@@ -60,5 +60,8 @@
"set font": "设置字体", "set font": "设置字体",
"font": "字体", "font": "字体",
"font size": "字体大小", "font size": "字体大小",
"full screen": "全屏显示" "full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
} }
...@@ -60,5 +60,8 @@ ...@@ -60,5 +60,8 @@
"set font": "设置字体", "set font": "设置字体",
"font": "字体", "font": "字体",
"font size": "字体大小", "font size": "字体大小",
"full screen": "全屏显示" "full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
} }
...@@ -60,5 +60,8 @@ ...@@ -60,5 +60,8 @@
"set font": "设置字体", "set font": "设置字体",
"font": "字体", "font": "字体",
"font size": "字体大小", "font size": "字体大小",
"full screen": "全屏显示" "full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
} }
This diff is collapsed.
{ {
"name": "luna", "name": "luna",
"version": "1.4.7", "version": "1.5.2",
"license": "GPLv3", "license": "GPLv3",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
"@swimlane/ngx-datatable": "^11.3.2", "@swimlane/ngx-datatable": "^11.3.2",
"@swimlane/ngx-ui": "^20.2.1", "@swimlane/ngx-ui": "^20.2.1",
"@types/jquery": "^3.3.6", "@types/jquery": "^3.3.6",
"@types/neffos.js": "^0.1.1",
"@types/socket.io-client": "^1.4.32", "@types/socket.io-client": "^1.4.32",
"ajv": "^6.5.0", "ajv": "^6.5.0",
"animate.css": "^3.6.1", "animate.css": "^3.6.1",
...@@ -49,6 +50,7 @@ ...@@ -49,6 +50,7 @@
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"materialize-css": "^0.100.2", "materialize-css": "^0.100.2",
"metismenu": "^2.7.9", "metismenu": "^2.7.9",
"neffos.js": "^0.1.19",
"ng2-charts": "^1.5.0", "ng2-charts": "^1.5.0",
"ngx-bootstrap": "^1.6.6", "ngx-bootstrap": "^1.6.6",
"ngx-cookie-service": "^1.0.10", "ngx-cookie-service": "^1.0.10",
...@@ -63,8 +65,8 @@ ...@@ -63,8 +65,8 @@
"roboto-fontface": "^0.8.0", "roboto-fontface": "^0.8.0",
"rxjs": "5.5.6", "rxjs": "5.5.6",
"sass-math": "^1.0.0", "sass-math": "^1.0.0",
"socket.io": "^2.1.0", "socket.io": "^1.4.32",
"socket.io-client": "^2.1.0", "socket.io-client": "^1.4.32",
"ssh-keygen": "^0.4.1", "ssh-keygen": "^0.4.1",
"tether": "^1.4.4", "tether": "^1.4.4",
"tslib": "^1.9.0", "tslib": "^1.9.0",
......
...@@ -30,7 +30,7 @@ import {ChangLanWarningDialogComponent, RDPSolutionDialogComponent, FontDialogCo ...@@ -30,7 +30,7 @@ import {ChangLanWarningDialogComponent, RDPSolutionDialogComponent, FontDialogCo
import {DialogService, ElementDialogAlertComponent} from './elements/dialog/dialog.service'; import {DialogService, ElementDialogAlertComponent} from './elements/dialog/dialog.service';
import {PluginModules} from './plugins/plugins'; import {PluginModules} from './plugins/plugins';
import {TestPageComponent} from './test-page/test-page.component'; import {TestPageComponent} from './test-page/test-page.component';
import {AssetTreeDialogComponent} from './elements/asset-tree/asset-tree.component'; import {AssetTreeDialogComponent, ManualPasswordDialogComponent} from './elements/asset-tree/asset-tree.component';
import {SftpComponent} from './elements/sftp/sftp.component'; import {SftpComponent} from './elements/sftp/sftp.component';
...@@ -53,6 +53,7 @@ import {SftpComponent} from './elements/sftp/sftp.component'; ...@@ -53,6 +53,7 @@ import {SftpComponent} from './elements/sftp/sftp.component';
], ],
entryComponents: [ entryComponents: [
AssetTreeDialogComponent, AssetTreeDialogComponent,
ManualPasswordDialogComponent,
ElementDialogAlertComponent, ElementDialogAlertComponent,
ChangLanWarningDialogComponent, ChangLanWarningDialogComponent,
RDPSolutionDialogComponent, RDPSolutionDialogComponent,
......
...@@ -100,12 +100,18 @@ export class HttpService { ...@@ -100,12 +100,18 @@ export class HttpService {
{headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')}); {headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')});
} }
guacamole_add_asset(user_id: string, asset_id: string, system_user_id: string) { guacamole_add_asset(user_id: string, asset_id: string, system_user_id: string, system_user_username?: string, system_user_password?: string) {
let params = new HttpParams() let params = new HttpParams()
.set('user_id', user_id) .set('user_id', user_id)
.set('asset_id', asset_id) .set('asset_id', asset_id)
.set('system_user_id', system_user_id) .set('system_user_id', system_user_id)
.set('token', DataStore.guacamole_token); .set('token', DataStore.guacamole_token);
if (system_user_username) {
params = params.set('username', system_user_username);
}
if (system_user_password) {
params = params.set('password', system_user_password);
}
const solution = localStorage.getItem('rdpSolution') || 'Auto'; const solution = localStorage.getItem('rdpSolution') || 'Auto';
if (solution !== 'Auto') { if (solution !== 'Auto') {
const width = solution.split('x')[0]; const width = solution.split('x')[0];
......
...@@ -151,6 +151,10 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -151,6 +151,10 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
} }
showRMenu(left, top) { showRMenu(left, top) {
const clientHeight = document.body.clientHeight;
if (top + 60 > clientHeight) {
top -= 60;
}
this.pos.left = left + 'px'; this.pos.left = left + 'px';
this.pos.top = top + 'px'; this.pos.top = top + 'px';
this.isShowRMenu = true; this.isShowRMenu = true;
...@@ -274,10 +278,30 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -274,10 +278,30 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
this.Connect(host); this.Connect(host);
} }
manualSetUserAuthLogin(host, user) {
user = Object.assign({}, user);
const dialogRef = this._dialog.open(ManualPasswordDialogComponent, {
height: '250px',
width: '400px',
data: {username: user.username}
});
dialogRef.afterClosed().subscribe(result => {
if (!result) {
return;
}
user.username = btoa(result.username);
user.password = btoa(result.password);
return this.login(host, user);
});
}
login(host, user) { login(host, user) {
const id = NavList.List.length - 1; const id = NavList.List.length - 1;
this._logger.debug(NavList); this._logger.debug(NavList);
this._logger.debug(host); this._logger.debug(host);
if (user.login_mode === 'manual' && !user.password && user.protocol === 'rdp') {
return this.manualSetUserAuthLogin(host, user);
}
if (user) { if (user) {
NavList.List[id].nick = host.hostname; NavList.List[id].nick = host.hostname;
NavList.List[id].connected = true; NavList.List[id].connected = true;
...@@ -416,3 +440,26 @@ export class AssetTreeDialogComponent implements OnInit { ...@@ -416,3 +440,26 @@ export class AssetTreeDialogComponent implements OnInit {
return f1 && f2 && f1.value === f2.value; return f1 && f2 && f1.value === f2.value;
} }
} }
@Component({
selector: 'elements-manual-password-dialog',
templateUrl: 'manual-password-dialog.html',
})
export class ManualPasswordDialogComponent implements OnInit {
PasswordControl = new FormControl('', [Validators.required]);
constructor(@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<ManualPasswordDialogComponent>) {
}
onNoClick() {
this.dialogRef.close();
}
onEnter() {
this.dialogRef.close(this.data);
}
ngOnInit(): void {
}
}
<h1 mat-dialog-title>{{"Found"|trans}} {{data.users.length}} {{"Users "|trans}}</h1> <h1 mat-dialog-title>{{"Found"|trans}} {{data.users.length}} {{"Users "|trans}}</h1>
<mat-form-field> <mat-form-field style="width: 100%">
<mat-select [(value)]="selected" <mat-select [(value)]="selected"
[compareWith]="compareFn" [compareWith]="compareFn"
[formControl]="UserSelectControl" [formControl]="UserSelectControl"
......
<h1 mat-dialog-title>{{"Please input password"|trans}}</h1>
<mat-form-field style="width: 100%">
<input matInput placeholder="{{'Username'|trans}}" [(ngModel)]="data.username">
</mat-form-field>
<mat-form-field style="width: 100%">
<input matInput [type]="'password'" [(ngModel)]="data.password"
[formControl]="PasswordControl" placeholder="{{'Password'|trans}}"
(keyup.enter)="onEnter()" [attr.cdkFocusInitial]="data.username? true : null">
</mat-form-field>
<div style="float: right">
<button mat-raised-button (click)="onNoClick()" >{{"Cancel"|trans}}</button>
<button mat-raised-button color="primary" [type]="'submit'" [mat-dialog-close]="data" >{{"Confirm"|trans}}</button>
</div>
...@@ -13,7 +13,7 @@ import {ElementIframeComponent} from './iframe/iframe.component'; ...@@ -13,7 +13,7 @@ import {ElementIframeComponent} from './iframe/iframe.component';
import {ElementDialogAlertComponent} from './dialog/dialog.service'; import {ElementDialogAlertComponent} from './dialog/dialog.service';
import {ElementGuacamoleComponent} from './guacamole/guacamole.component'; import {ElementGuacamoleComponent} from './guacamole/guacamole.component';
import {ElementSshTermComponent} from './ssh-term/ssh-term.component'; import {ElementSshTermComponent} from './ssh-term/ssh-term.component';
import {AssetTreeDialogComponent, ElementAssetTreeComponent} from './asset-tree/asset-tree.component'; import {AssetTreeDialogComponent, ElementAssetTreeComponent, ManualPasswordDialogComponent} from './asset-tree/asset-tree.component';
import {RDPSolutionDialogComponent, FontDialogComponent} from './nav/nav.component'; import {RDPSolutionDialogComponent, FontDialogComponent} from './nav/nav.component';
export const ElementComponents = [ export const ElementComponents = [
...@@ -33,6 +33,7 @@ export const ElementComponents = [ ...@@ -33,6 +33,7 @@ export const ElementComponents = [
ElementAssetTreeComponent, ElementAssetTreeComponent,
ElementSshTermComponent, ElementSshTermComponent,
AssetTreeDialogComponent, AssetTreeDialogComponent,
ManualPasswordDialogComponent,
RDPSolutionDialogComponent, RDPSolutionDialogComponent,
FontDialogComponent FontDialogComponent
]; ];
...@@ -13,7 +13,7 @@ import {NavList} from '../../pages/control/control/control.component'; ...@@ -13,7 +13,7 @@ import {NavList} from '../../pages/control/control/control.component';
}) })
export class ElementGuacamoleComponent implements OnInit { export class ElementGuacamoleComponent implements OnInit {
@Input() host: any; @Input() host: any;
@Input() userid: any; @Input() sysUser: any;
@Input() remoteAppId: string; @Input() remoteAppId: string;
@Input() target: string; @Input() target: string;
@Input() index: number; @Input() index: number;
...@@ -31,7 +31,7 @@ export class ElementGuacamoleComponent implements OnInit { ...@@ -31,7 +31,7 @@ export class ElementGuacamoleComponent implements OnInit {
if (this.remoteAppId) { if (this.remoteAppId) {
action = this._http.guacamole_add_remote_app(User.id, this.remoteAppId); action = this._http.guacamole_add_remote_app(User.id, this.remoteAppId);
} else { } else {
action = this._http.guacamole_add_asset(User.id, this.host.id, this.userid); action = this._http.guacamole_add_asset(User.id, this.host.id, this.sysUser.id, this.sysUser.username, this.sysUser.password);
} }
action.subscribe( action.subscribe(
data => { data => {
...@@ -73,11 +73,11 @@ export class ElementGuacamoleComponent implements OnInit { ...@@ -73,11 +73,11 @@ export class ElementGuacamoleComponent implements OnInit {
return null; return null;
} }
if (!environment.production) { // if (!environment.production) {
this.target = this._cookie.get('guacamole'); // this.target = this._cookie.get('guacamole');
NavList.List[this.index].Rdp = this.el.nativeElement; // NavList.List[this.index].Rdp = this.el.nativeElement;
return null; // return null;
} // }
this.registerHost(); this.registerHost();
} }
......
import {AfterViewInit, Component, Input, OnInit } from '@angular/core'; import {AfterViewInit, Component, Input, OnInit, OnDestroy } from '@angular/core';
import {Terminal} from 'xterm'; import {Terminal} from 'xterm';
import {NavList} from '../../pages/control/control/control.component'; import {NavList, View} from '../../pages/control/control/control.component';
import {UUIDService} from '../../app.service'; import {UUIDService} from '../../app.service';
import {CookieService} from 'ngx-cookie-service'; import {CookieService} from 'ngx-cookie-service';
import {TermWS} from '../../globals'; import {Socket} from '../../utils/socket';
import {getWsSocket} from '../../globals';
const ws = TermWS;
@Component({ @Component({
selector: 'elements-ssh-term', selector: 'elements-ssh-term',
templateUrl: './ssh-term.component.html', templateUrl: './ssh-term.component.html',
styleUrls: ['./ssh-term.component.scss'] styleUrls: ['./ssh-term.component.scss']
}) })
export class ElementSshTermComponent implements OnInit, AfterViewInit { export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() host: any; @Input() host: any;
@Input() userid: any; @Input() sysUser: any;
@Input() index: number; @Input() index: number;
@Input() token: string; @Input() token: string;
term: Terminal; term: Terminal;
secret: string; secret: string;
ws: Socket;
roomID: string;
view: View;
constructor(private _uuid: UUIDService, private _cookie: CookieService) { constructor(private _uuid: UUIDService, private _cookie: CookieService) {
} }
ngOnInit() { ngOnInit() {
this.view = NavList.List[this.index];
this.secret = this._uuid.gen(); this.secret = this._uuid.gen();
this.newTerm();
getWsSocket().then(sock => {
this.ws = sock;
this.connectHost();
});
}
ngAfterViewInit() {
}
newTerm() {
const fontSize = localStorage.getItem('fontSize') || '14'; const fontSize = localStorage.getItem('fontSize') || '14';
this.term = new Terminal({ this.term = new Terminal({
fontFamily: 'monaco, Consolas, "Lucida Console", monospace', fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
...@@ -35,73 +50,80 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit { ...@@ -35,73 +50,80 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit {
background: '#1f1b1b' background: '#1f1b1b'
} }
}); });
} this.view.Term = this.term;
ngAfterViewInit() {
this.joinRoom();
} }
changeWinSize(size: Array<number>) { changeWinSize(size: Array<number>) {
ws.emit('resize', {'cols': size[0], 'rows': size[1]}); if (this.ws) {
this.ws.emit('resize', {'cols': size[0], 'rows': size[1]});
}
} }
joinRoom() { connectHost() {
NavList.List[this.index].Term = this.term;
console.log(this.term);
console.log('Col: ', this.term.cols, 'rows', this.term.rows);
if (this.host) { if (this.host) {
ws.emit('host', { const data = {
'uuid': this.host.id, uuid: this.host.id,
'userid': this.userid, userid: this.sysUser.id,
'secret': this.secret, secret: this.secret,
'size': [this.term.cols, this.term.rows] size: [this.term.cols, this.term.rows]
}); };
this.ws.emit('host', data);
} }
if (this.token) { if (this.token) {
ws.emit('token', { const data = {
'token': this.token, 'secret': this.secret, 'token': this.token, 'secret': this.secret,
'size': [this.term.cols, this.term.rows] 'size': [this.term.cols, this.term.rows]
}); };
console.log('On token event trigger');
this.ws.emit('token', data);
} }
const that = this;
this.term.on('data', function (data) { this.term.on('data', data => {
ws.emit('data', {'data': data, 'room': NavList.List[that.index].room}); const d = {'data': data, 'room': this.roomID};
this.ws.emit('data', d);
}); });
ws.on('data', data => { this.ws.on('data', data => {
const view = NavList.List[that.index]; if (data.room === this.roomID) {
if (view && data['room'] === view.room) { this.term.write(data['data']);
that.term.write(data['data']);
} }
}); });
ws.on('disconnect', () => { // 服务器主动断开
that.close(); this.ws.on('disconnect', () => {
console.log('On disconnect event trigger');
this.close();
}); });
ws.on('logout', (data) => { this.ws.on('logout', data => {
if (data['room'] === NavList.List[that.index].room) { if (data.room === this.roomID) {
NavList.List[that.index].connected = false; console.log('On logout event trigger: ', data.room, this.roomID);
this.view.connected = false;
} }
}); });
ws.on('room', data => { this.ws.on('room', data => {
if (data['secret'] === this.secret) { if (data.secret === this.secret && data.room) {
NavList.List[that.index].room = data['room']; console.log('On room', data);
this.roomID = data.room;
this.view.room = data.room;
} }
}); });
} }
// 客户端主动关闭
close() { close() {
const view = NavList.List[this.index]; if (this.view && (this.view.room === this.roomID)) {
if (view) { this.view.connected = false;
NavList.List[this.index].connected = false; this.ws.emit('logout', this.roomID);
ws.emit('logout', NavList.List[this.index].room);
} }
} }
active() { active() {
this.term.focus(); this.term.focus();
} }
ngOnDestroy(): void {
this.close();
}
} }
'use strict'; 'use strict';
import {EventEmitter} from 'events/events';
import * as io from 'socket.io-client'; import * as io from 'socket.io-client';
import * as neffos from 'neffos.js';
import {Terminal} from 'xterm'; import {Terminal} from 'xterm';
// const abc = io.connect('/ssh');
import {Socket} from './utils/socket';
export const TermWS = io.connect('/ssh'); const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws';
const port = document.location.port ? ':' + document.location.port : '';
const wsURL = scheme + '://' + document.location.hostname + port + '/socket.io/';
export let TermWS = null;
export const emitter = new(EventEmitter);
export const sep = '/'; export const sep = '/';
export let Video: { export let Video: {
id: string, id: string,
...@@ -93,7 +102,7 @@ export let DataStore: { ...@@ -93,7 +102,7 @@ export let DataStore: {
guacamole_token: string; guacamole_token: string;
guacamole_token_time: number; guacamole_token_time: number;
} = { } = {
socket: io.connect(), socket: TermWS,
Nav: [{}], Nav: [{}],
NavShow: true, NavShow: true,
Path: {}, Path: {},
...@@ -130,9 +139,20 @@ export let Browser: { ...@@ -130,9 +139,20 @@ export let Browser: {
vendor: navigator.vendor, vendor: navigator.vendor,
}; };
export let wsEvent: {
event: string;
data: any;
};
export const i18n = new Map(); export const i18n = new Map();
export async function getWsSocket() {
if (TermWS) {
return TermWS;
}
TermWS = new Socket(wsURL, 'ssh');
const nsConn = await TermWS.connect();
if (!nsConn) {
console.log('Try to using socket.io protocol');
TermWS = io.connect('/ssh', {reconnectionAttempts: 10});
}
DataStore.socket = TermWS;
return TermWS;
}
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
[ngClass]="{'active':i==NavList.Active}" style="height: 100%"> [ngClass]="{'active':i==NavList.Active}" style="height: 100%">
<elements-ssh-term [index]="i" <elements-ssh-term [index]="i"
[host]="m.host" [host]="m.host"
[userid]="m.user.id" [sysUser]="m.user"
*ngIf="m.type=='ssh'"> *ngIf="m.type=='ssh'">
</elements-ssh-term> </elements-ssh-term>
<elements-guacamole [index]="i" <elements-guacamole [index]="i"
[host]="m.host" [host]="m.host"
[userid]="m.user?.id" [sysUser]="m.user"
[remoteAppId]="m.remoteApp" [remoteAppId]="m.remoteApp"
*ngIf="m.type=='rdp'"> *ngIf="m.type=='rdp'">
</elements-guacamole> </elements-guacamole>
......
import {EventEmitter} from 'events/events';
import {Conn, NSConn, marshal} from 'neffos.js';
import * as neffos from 'neffos.js';
export class Socket {
conn: Conn;
nsConn: NSConn;
emitter: EventEmitter;
url: string;
namespace: string;
constructor(url: string, namespace: string) {
this.url = url;
this.namespace = namespace;
}
async connect() {
const emitter = new EventEmitter();
this.emitter = emitter;
const events = {
};
let interval = null;
events[this.namespace] = {
_OnNamespaceConnected: (ns, msg) => {
emitter.emit('connect', ns);
if (ns.conn.wasReconnected()) {
this.conn = ns.conn;
this.nsConn = ns;
console.log('Ws was reconnected');
}
interval = setInterval(() => ns.emit('ping', ''), 10000);
},
_OnNamespaceDisconnect: function (ns, msg) {
emitter.emit('disconnect', ns);
if (interval) {
clearInterval(interval);
}
},
_OnAnyEvent: function (ns, msg) {
let data = '';
if (msg.Body) {
data = msg.unmarshal();
}
emitter.emit(msg.Event, data);
},
};
const options = {
reconnect: 5000,
headers: {
'X-Namespace': 'ssh'
},
};
this.conn = <Conn>await neffos.dial(this.url, events, options)
.catch(err => {
console.log('connect to neffos ws error: ', err);
return null;
});
if (!this.conn) {
return null;
}
this.nsConn = <NSConn> await this.conn.connect(this.namespace)
.catch(err => {
console.log('connect to namespace error: ', err);
return null;
});
return this.nsConn;
}
emit(type: string, obj: any) {
const msg = marshal(obj);
this.nsConn.emit(type, msg);
}
on(type: string, fn: Function, opt_scope?: any, opt_oneshot?: boolean) {
this.emitter.on(type, fn, opt_scope, opt_oneshot);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment