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 @@
"set font": "设置字体",
"font": "字体",
"font size": "字体大小",
"full screen": "全屏显示"
"full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
}
......@@ -60,5 +60,8 @@
"set font": "设置字体",
"font": "字体",
"font size": "字体大小",
"full screen": "全屏显示"
"full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
}
......@@ -60,5 +60,8 @@
"set font": "设置字体",
"font": "字体",
"font size": "字体大小",
"full screen": "全屏显示"
"full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
}
This diff is collapsed.
{
"name": "luna",
"version": "1.4.7",
"version": "1.5.2",
"license": "GPLv3",
"scripts": {
"ng": "ng",
......@@ -26,6 +26,7 @@
"@swimlane/ngx-datatable": "^11.3.2",
"@swimlane/ngx-ui": "^20.2.1",
"@types/jquery": "^3.3.6",
"@types/neffos.js": "^0.1.1",
"@types/socket.io-client": "^1.4.32",
"ajv": "^6.5.0",
"animate.css": "^3.6.1",
......@@ -49,6 +50,7 @@
"material-design-icons": "^3.0.1",
"materialize-css": "^0.100.2",
"metismenu": "^2.7.9",
"neffos.js": "^0.1.19",
"ng2-charts": "^1.5.0",
"ngx-bootstrap": "^1.6.6",
"ngx-cookie-service": "^1.0.10",
......@@ -63,8 +65,8 @@
"roboto-fontface": "^0.8.0",
"rxjs": "5.5.6",
"sass-math": "^1.0.0",
"socket.io": "^2.1.0",
"socket.io-client": "^2.1.0",
"socket.io": "^1.4.32",
"socket.io-client": "^1.4.32",
"ssh-keygen": "^0.4.1",
"tether": "^1.4.4",
"tslib": "^1.9.0",
......
......@@ -30,7 +30,7 @@ import {ChangLanWarningDialogComponent, RDPSolutionDialogComponent, FontDialogCo
import {DialogService, ElementDialogAlertComponent} from './elements/dialog/dialog.service';
import {PluginModules} from './plugins/plugins';
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';
......@@ -53,6 +53,7 @@ import {SftpComponent} from './elements/sftp/sftp.component';
],
entryComponents: [
AssetTreeDialogComponent,
ManualPasswordDialogComponent,
ElementDialogAlertComponent,
ChangLanWarningDialogComponent,
RDPSolutionDialogComponent,
......
......@@ -100,12 +100,18 @@ export class HttpService {
{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()
.set('user_id', user_id)
.set('asset_id', asset_id)
.set('system_user_id', system_user_id)
.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';
if (solution !== 'Auto') {
const width = solution.split('x')[0];
......
......@@ -151,6 +151,10 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
}
showRMenu(left, top) {
const clientHeight = document.body.clientHeight;
if (top + 60 > clientHeight) {
top -= 60;
}
this.pos.left = left + 'px';
this.pos.top = top + 'px';
this.isShowRMenu = true;
......@@ -274,10 +278,30 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
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) {
const id = NavList.List.length - 1;
this._logger.debug(NavList);
this._logger.debug(host);
if (user.login_mode === 'manual' && !user.password && user.protocol === 'rdp') {
return this.manualSetUserAuthLogin(host, user);
}
if (user) {
NavList.List[id].nick = host.hostname;
NavList.List[id].connected = true;
......@@ -416,3 +440,26 @@ export class AssetTreeDialogComponent implements OnInit {
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>
<mat-form-field>
<mat-form-field style="width: 100%">
<mat-select [(value)]="selected"
[compareWith]="compareFn"
[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';
import {ElementDialogAlertComponent} from './dialog/dialog.service';
import {ElementGuacamoleComponent} from './guacamole/guacamole.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';
export const ElementComponents = [
......@@ -33,6 +33,7 @@ export const ElementComponents = [
ElementAssetTreeComponent,
ElementSshTermComponent,
AssetTreeDialogComponent,
ManualPasswordDialogComponent,
RDPSolutionDialogComponent,
FontDialogComponent
];
......@@ -13,7 +13,7 @@ import {NavList} from '../../pages/control/control/control.component';
})
export class ElementGuacamoleComponent implements OnInit {
@Input() host: any;
@Input() userid: any;
@Input() sysUser: any;
@Input() remoteAppId: string;
@Input() target: string;
@Input() index: number;
......@@ -31,7 +31,7 @@ export class ElementGuacamoleComponent implements OnInit {
if (this.remoteAppId) {
action = this._http.guacamole_add_remote_app(User.id, this.remoteAppId);
} 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(
data => {
......@@ -73,11 +73,11 @@ export class ElementGuacamoleComponent implements OnInit {
return null;
}
if (!environment.production) {
this.target = this._cookie.get('guacamole');
NavList.List[this.index].Rdp = this.el.nativeElement;
return null;
}
// if (!environment.production) {
// this.target = this._cookie.get('guacamole');
// NavList.List[this.index].Rdp = this.el.nativeElement;
// return null;
// }
this.registerHost();
}
......
import {AfterViewInit, Component, Input, OnInit } from '@angular/core';
import {AfterViewInit, Component, Input, OnInit, OnDestroy } from '@angular/core';
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 {CookieService} from 'ngx-cookie-service';
import {TermWS} from '../../globals';
import {Socket} from '../../utils/socket';
import {getWsSocket} from '../../globals';
const ws = TermWS;
@Component({
selector: 'elements-ssh-term',
templateUrl: './ssh-term.component.html',
styleUrls: ['./ssh-term.component.scss']
})
export class ElementSshTermComponent implements OnInit, AfterViewInit {
export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() host: any;
@Input() userid: any;
@Input() sysUser: any;
@Input() index: number;
@Input() token: string;
term: Terminal;
secret: string;
ws: Socket;
roomID: string;
view: View;
constructor(private _uuid: UUIDService, private _cookie: CookieService) {
}
ngOnInit() {
this.view = NavList.List[this.index];
this.secret = this._uuid.gen();
this.newTerm();
getWsSocket().then(sock => {
this.ws = sock;
this.connectHost();
});
}
ngAfterViewInit() {
}
newTerm() {
const fontSize = localStorage.getItem('fontSize') || '14';
this.term = new Terminal({
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
......@@ -35,73 +50,80 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit {
background: '#1f1b1b'
}
});
}
ngAfterViewInit() {
this.joinRoom();
this.view.Term = this.term;
}
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() {
NavList.List[this.index].Term = this.term;
console.log(this.term);
console.log('Col: ', this.term.cols, 'rows', this.term.rows);
connectHost() {
if (this.host) {
ws.emit('host', {
'uuid': this.host.id,
'userid': this.userid,
'secret': this.secret,
'size': [this.term.cols, this.term.rows]
});
const data = {
uuid: this.host.id,
userid: this.sysUser.id,
secret: this.secret,
size: [this.term.cols, this.term.rows]
};
this.ws.emit('host', data);
}
if (this.token) {
ws.emit('token', {
const data = {
'token': this.token, 'secret': this.secret,
'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) {
ws.emit('data', {'data': data, 'room': NavList.List[that.index].room});
this.term.on('data', data => {
const d = {'data': data, 'room': this.roomID};
this.ws.emit('data', d);
});
ws.on('data', data => {
const view = NavList.List[that.index];
if (view && data['room'] === view.room) {
that.term.write(data['data']);
this.ws.on('data', data => {
if (data.room === this.roomID) {
this.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) => {
if (data['room'] === NavList.List[that.index].room) {
NavList.List[that.index].connected = false;
this.ws.on('logout', data => {
if (data.room === this.roomID) {
console.log('On logout event trigger: ', data.room, this.roomID);
this.view.connected = false;
}
});
ws.on('room', data => {
if (data['secret'] === this.secret) {
NavList.List[that.index].room = data['room'];
this.ws.on('room', data => {
if (data.secret === this.secret && data.room) {
console.log('On room', data);
this.roomID = data.room;
this.view.room = data.room;
}
});
}
// 客户端主动关闭
close() {
const view = NavList.List[this.index];
if (view) {
NavList.List[this.index].connected = false;
ws.emit('logout', NavList.List[this.index].room);
if (this.view && (this.view.room === this.roomID)) {
this.view.connected = false;
this.ws.emit('logout', this.roomID);
}
}
active() {
this.term.focus();
}
ngOnDestroy(): void {
this.close();
}
}
'use strict';
import {EventEmitter} from 'events/events';
import * as io from 'socket.io-client';
import * as neffos from 'neffos.js';
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 let Video: {
id: string,
......@@ -93,7 +102,7 @@ export let DataStore: {
guacamole_token: string;
guacamole_token_time: number;
} = {
socket: io.connect(),
socket: TermWS,
Nav: [{}],
NavShow: true,
Path: {},
......@@ -130,9 +139,20 @@ export let Browser: {
vendor: navigator.vendor,
};
export let wsEvent: {
event: string;
data: any;
};
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 @@
[ngClass]="{'active':i==NavList.Active}" style="height: 100%">
<elements-ssh-term [index]="i"
[host]="m.host"
[userid]="m.user.id"
[sysUser]="m.user"
*ngIf="m.type=='ssh'">
</elements-ssh-term>
<elements-guacamole [index]="i"
[host]="m.host"
[userid]="m.user?.id"
[sysUser]="m.user"
[remoteAppId]="m.remoteApp"
*ngIf="m.type=='rdp'">
</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