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

Merge pull request #35 from jumpserver/dev

Dev
parents ebda194f f844a768
......@@ -22,7 +22,6 @@
"styles": [
"../node_modules/animate.css/animate.min.css",
"../node_modules/xterm/dist/xterm.css",
"../node_modules/elfinder/css/elfinder.min.css",
"sass/style.scss",
"styles.css",
"assets/ztree/awesomeStyle/awesome.css"
......@@ -35,7 +34,6 @@
"../node_modules/bootstrap/dist/js/bootstrap.min.js",
"assets/inspinia/inspinia.js",
"assets/slimscroll/jquery.slimscroll.min.js",
"../node_modules/elfinder/js/elfinder.min.js",
"../node_modules/xterm/dist/xterm.js",
"assets/ztree/jquery.ztree.all.min.js",
"assets/ztree/jquery.ztree.exhide.min.js"
......
This diff is collapsed.
......@@ -4,7 +4,7 @@
"license": "GPLv3",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"start": "ng serve --proxy-config proxy.conf.json --host 0.0.0.0",
"build": "ng build --environment prod --aot --prod --base-href=/luna/ --deploy '/luna/'",
"test": "ng test",
"lint": "ng lint",
......@@ -25,6 +25,7 @@
"@angular/router": "5.2.0",
"@swimlane/ngx-datatable": "^11.3.2",
"@swimlane/ngx-ui": "^20.2.1",
"@types/socket.io-client": "^1.4.32",
"ajv": "^6.5.0",
"animate.css": "^3.6.1",
"body-parser": "^1.18.2",
......
{
"/api": {
"target": "http://127.0.0.1:5000",
"target": "http://127.0.0.1:5001",
"secure": false
},
"/luna/i18n": {
"target": "http://127.0.0.1:8088",
"target": "http://127.0.0.1:5001",
"secure": false
},
"/socket.io/": {
"target": "http://127.0.0.1:5000",
"secure": false
"target": "http://127.0.0.1:5001",
"secure": false,
"ws": true
},
"/rdp/socket.io/": {
"target": "http://localhost:9250",
......
import {Component, Input, OnInit, Inject, SimpleChanges, OnChanges} from '@angular/core';
import {Component, Input, OnInit, Inject, SimpleChanges, OnChanges, EventEmitter} from '@angular/core';
import {NavList, View} from '../../pages/control/control/control.component';
import {AppService, LogService} from '../../app.service';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
import {FormControl, Validators} from '@angular/forms';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
declare var $: any;
......@@ -14,6 +15,7 @@ declare var $: any;
export class ElementAssetTreeComponent implements OnInit, OnChanges {
@Input() Data: any;
@Input() query: string;
@Input() searchEvt$: BehaviorSubject<string>;
nodes = [];
setting = {
view: {
......@@ -30,6 +32,7 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
},
};
hiddenNodes: any;
expandNodes: any;
onCzTreeOnClick(event, treeId, treeNode, clickFlag) {
if (treeNode.isParent) {
......@@ -43,12 +46,19 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
constructor(private _appService: AppService,
public _dialog: MatDialog,
public _logger: LogService) {
this.searchEvt$ = new BehaviorSubject<string>(this.query);
}
ngOnInit() {
if (this.Data) {
this.draw();
}
this.searchEvt$.asObservable()
.debounceTime(300)
.distinctUntilChanged()
.subscribe((n) => {
this.filter();
});
}
ngOnChanges(changes: SimpleChanges) {
......@@ -56,7 +66,8 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
this.draw();
}
if (changes['query'] && !changes['query'].firstChange) {
this.filter();
this.searchEvt$.next(this.query);
// this.filter();
}
}
......@@ -98,8 +109,11 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
this.nodes.sort(function(node1, node2) {
if (node1.isParent && !node2.isParent) {
return -1;
}
} else if (!node1.isParent && node2.isParent) {
return 1;
} else {
return node1.name < node2.name ? -1 : 1;
}
});
$.fn.zTree.init($('#ztree'), this.setting, this.nodes);
}
......@@ -174,25 +188,72 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
return user;
}
recurseParent(node) {
const parentNode = node.getParentNode();
if (parentNode && parentNode.pId) {
return [parentNode, ...this.recurseParent(parentNode)];
} else if (parentNode) {
return [parentNode];
} else {
return [];
}
}
recurseChildren(node) {
if (!node.isParent) {
return [];
}
const children = node.children;
if (!children) {
return [];
}
let all_children = [];
children.forEach((n) => {
all_children = [...children, ...this.recurseChildren(n)];
});
return all_children;
}
filter() {
const zTreeObj = $.fn.zTree.getZTreeObj('ztree');
const _keywords = $('#keyword').val();
if (!zTreeObj) {
return null;
}
const _keywords = this.query;
const nodes = zTreeObj.transformToArray(zTreeObj.getNodes());
if (!_keywords) {
if (this.hiddenNodes) {
zTreeObj.showNodes(this.hiddenNodes);
function filterFunc(node) {
if (node.isParent || node.name.indexOf(_keywords) !== -1) {
return false;
this.hiddenNodes = null;
}
return true;
if (this.expandNodes) {
this.expandNodes.forEach((node) => {
if (node.id !== nodes[0].id) {
zTreeObj.expandNode(node, false);
}
this.hiddenNodes = zTreeObj.getNodesByFilter(filterFunc);
zTreeObj.hideNodes(this.hiddenNodes);
if (_keywords) {
zTreeObj.expandAll(true);
} else {
zTreeObj.expandAll(false);
});
this.expandNodes = null;
}
return null;
}
let shouldShow = [];
nodes.forEach((node) => {
if (shouldShow.indexOf(node) === -1 && node.name.indexOf(_keywords) !== -1) {
const parents = this.recurseParent(node);
const children = this.recurseChildren(node);
shouldShow = [...shouldShow, ...parents, ...children, node];
}
});
this.hiddenNodes = nodes;
this.expandNodes = shouldShow;
zTreeObj.hideNodes(nodes);
zTreeObj.showNodes(shouldShow);
shouldShow.forEach((node) => {
if (node.isParent) {
zTreeObj.expandNode(node, true);
}
});
// zTreeObj.expandAll(true);
}
}
......
......@@ -34,9 +34,6 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit {
background: '#1f1b1b'
}
});
const rowInit = parseInt(this._cookie.get('rows') || '24', 10);
const colsInit = parseInt(this._cookie.get('cols') || '80', 10);
this.term.resize(colsInit, rowInit);
}
ngAfterViewInit() {
......@@ -49,11 +46,21 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit {
joinRoom() {
NavList.List[this.index].Term = this.term;
console.log(this.term);
console.log('Col: ', this.term.cols, 'rows', this.term.rows);
if (this.host) {
ws.emit('host', {'uuid': this.host.id, 'userid': this.userid, 'secret': this.secret});
ws.emit('host', {
'uuid': this.host.id,
'userid': this.userid,
'secret': this.secret,
'size': [this.term.cols, this.term.rows]
});
}
if (this.token) {
ws.emit('token', {'token': this.token, 'secret': this.secret});
ws.emit('token', {
'token': this.token, 'secret': this.secret,
'size': [this.term.cols, this.term.rows]
});
}
const that = this;
......
......@@ -20,6 +20,7 @@ import {NavList} from '../../pages/control/control/control.component';
export class ElementTermComponent implements OnInit, AfterViewInit {
@ViewChild('term') el: ElementRef;
@Input() term: Terminal;
@Input() offset: Array<number>;
@Output() winSizeChangeTrigger = new EventEmitter<Array<number>>();
winSizeChange$: Observable<any>;
......@@ -33,7 +34,7 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
this.winSizeChange$
.subscribe(() => {
if (NavList.List[NavList.Active].type === 'ssh') {
if (NavList.List[NavList.Active].type !== 'rdp') {
this.resizeTerm();
}
});
......@@ -62,15 +63,10 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
Math.floor(availableHeight / (<any>this.term).renderer.dimensions.actualCellHeight) - 1
];
return geometry;
// const cols = Math.floor((activeEle.width() - 15) / markerEle.width() * 6) - 1;
// const rows = Math.floor(activeEle.height() / markerEle.height()) - 1;
// return [cols, rows];
}
resizeTerm() {
const size = this.getWinSize();
console.log('get SIze', size);
if (isNaN(size[0]) || isNaN(size[1])) {
fit(this.term);
} else {
......@@ -78,8 +74,6 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
this.term.resize(size[0], size[1]);
}
this.winSizeChangeTrigger.emit([this.term.cols, this.term.rows]);
this._cookie.set('cols', this.term.cols.toString(), 0, '/', document.domain);
this._cookie.set('rows', this.term.rows.toString(), 0, '/', document.domain);
}
active() {
......
<div class="sidebar" fxLayout="column" ngxSplit="column">
<div fxflex="0 0 30px" class="search">
<input id="keyword" class="left-search" placeholder=" {{'Search'| trans }} ..." maxlength="2048" name="q"
<input #keyword id="keyword" class="left-search" placeholder=" {{'Search'| trans }} ..." maxlength="2048" name="q"
autocomplete="off"
title="Search"
type="text" tabindex="1" spellcheck="false" autofocus [(ngModel)]="q" (keyup.enter)="Search(q)">
type="text" tabindex="1" spellcheck="false" [(ngModel)]="q">
</div>
<div class="overflow ngx-scroll-overlay" fxflex="1 1 90%">
<elements-asset-tree [Data]="zNodes" [query]="q"></elements-asset-tree>
......
......@@ -7,7 +7,7 @@
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, Inject, OnInit} from '@angular/core';
import {Component, Inject, OnInit, ViewChild, ElementRef} from '@angular/core';
import {AppService, HttpService, LogService} from '../../../app.service';
import {SearchComponent} from '../search/search.component';
import {DataStore} from '../../../globals';
......@@ -17,6 +17,7 @@ import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
import {FormControl, Validators} from '@angular/forms';
import {ElementServerMenuComponent} from '../../../elements/server-menu/server-menu.component';
import {DialogService} from '../../../elements/dialog/dialog.service';
import {Observable} from '../../../../../node_modules/rxjs';
export interface HostGroup {
name: string;
......@@ -102,7 +103,6 @@ export class CleftbarComponent implements OnInit {
});
}
Search(q) {
this._search.Search(q);
}
......
......@@ -44,7 +44,8 @@
font-family: 'Roboto', sans-serif;
font-size: 13px;
text-decoration: none;
padding-left: 24px;
padding-left: 12px;
padding-right: 14px;
line-height: 26px;
cursor: default;
width: 115px;
......@@ -68,7 +69,7 @@
}
.tabs ul li.active span {
padding-left: 24px;
padding-left: 12px;
line-height: 26px;
color: white;
height: 18px;
......
......@@ -7,7 +7,7 @@
<li *ngFor="let m of NavList.List;let i = index"
[ngClass]="{'active':i==NavList.Active,'disconnected':!m.connected, 'hidden': m.closed != false}"
id="termnav-{{i}}" (click)="setActive(i)" (dblclick)="m.edit=true;setActive(i)">
<span *ngIf="!m.edit">{{m.nick}}</span>
<span *ngIf="!m.edit">{{m.nick | truncatechars:25 }}</span>
<input *ngIf="m.edit" [(ngModel)]="m.nick" (blur)="m.edit=false" (keyup.enter)="m.edit=false" autofocus="true"/>
<a class="close" (click)="close(m,i)">&times;</a>
</li>
......
......@@ -11,13 +11,40 @@ function zeroPad(num, minLength) {
return str;
}
function formatTimeWithSeconds(seconds) {
let hour = 0, minute = 0, second = 0;
const ref = [3600, 60, 1];
for (let i = 0; i < ref.length; i++) {
const val = ref[i];
while (val <= seconds) {
seconds -= val;
switch (i) {
case 0:
hour++;
break;
case 1:
minute++;
break;
case 2:
second++;
break;
}
}
}
return [hour, minute, second];
}
function formatTime(millis: number) {
const totalSeconds = Math.floor(millis / 1000);
const seconds = totalSeconds % 60;
const minutes = Math.floor(totalSeconds / 60);
return zeroPad(minutes, 2) + ':' + zeroPad(seconds, 2);
const totalSeconds = millis / 1000;
const [hour, minute, second] = formatTimeWithSeconds(totalSeconds);
let time = zeroPad(minute, 2) + ':' + zeroPad(second, 2);
if (hour > 0) {
time = zeroPad(hour, 2) + ':' + time;
}
return time;
}
@Component({
selector: 'app-replay-guacamole',
templateUrl: './guacamole.component.html',
......@@ -115,6 +142,4 @@ export class ReplayGuacamoleComponent implements OnInit {
this.isPlaying = false;
}
}
}
<div>
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-stop" aria-hidden="true"></i>-->
<!--</button>-->
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-step-backward" aria-hidden="true"></i>-->
<!--</button>-->
<button type="button" class="btn">
<i class="fa fa-backward" aria-hidden="true" (click)="speedDown()"></i>
</button>
<button type="button" class="btn" (click)="toggle()">
<i class="fa" aria-hidden="true" [ngClass]="{'fa-play':!play,'fa-pause': play}"></i>
<i class="fa" aria-hidden="true" [ngClass]="{'fa-play':!isPlaying, 'fa-pause': isPlaying}"></i>
</button>
<button type="button" class="btn" (click)="speedUp()">
<i class="fa fa-forward" aria-hidden="true"></i>
</button>
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-step-forward" aria-hidden="true"></i>-->
<!--</button>-->
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-expand" aria-hidden="true"></i>-->
<!--</button>-->
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-compress" aria-hidden="true"></i>-->
<!--</button>-->
<button type="button" class="btn" (click)="restart()">
<i class="fa fa-repeat" aria-hidden="true"></i>
</button>
<input id="scrubber" type="range" [(ngModel)]="percent" min=0 max=100 (mousedown)="stop()" (mouseup)="runFrom()"/>
<input id="scrubber" type="range" [(ngModel)]="time" min=0 [attr.max]="max" (mouseup)="runFrom()"/>
{{time | utcDate | date:"HH:mm:ss"}}
<span id="position">{{ position }}</span>
<span>/</span>
<span id="duration">{{ duration }}</span>
{{"Speed"|trans}}: {{speed}}
</div>
<elements-term [term]="term"></elements-term>
<div id="winContainer" style="height: 90%;width: 100%">
<elements-term [term]="term"></elements-term>
</div>
<!--<asciinema-player></asciinema-player>-->
......@@ -3,23 +3,75 @@ import {Terminal} from 'xterm';
import {HttpService, LogService} from '../../../app.service';
import {Replay} from '../replay.model';
function zeroPad(num, minLength) {
let str = num.toString();
// Add leading zeroes until string is long enough
while (str.length < minLength) {
str = '0' + str;
}
return str;
}
function formatTimeWithSeconds(seconds) {
let hour = 0, minute = 0, second = 0;
const ref = [3600, 60, 1];
for (let i = 0; i < ref.length; i++) {
const val = ref[i];
while (val <= seconds) {
seconds -= val;
switch (i) {
case 0:
hour++;
break;
case 1:
minute++;
break;
case 2:
second++;
break;
}
}
}
return [hour, minute, second];
}
function formatTime(millis: number) {
const totalSeconds = millis / 1000;
const [hour, minute, second] = formatTimeWithSeconds(totalSeconds);
let time = zeroPad(minute, 2) + ':' + zeroPad(second, 2);
if (hour > 0) {
time = zeroPad(hour, 2) + ':' + time;
}
return time;
}
@Component({
selector: 'app-replay-json',
templateUrl: './json.component.html',
styleUrls: ['./json.component.css']
})
export class JsonComponent implements OnInit {
isPlaying = false;
recording: any;
playerRef: any;
displayRef: any;
max = 0;
time = 0;
duration = '00:00';
timeList = [];
replayData = {};
speed = 2;
percent = 0;
play = false;
tick = 33;
timeStep = 33;
time = 1;
tick = 33; // 每33s滴答一次
timeStep = 33; // 步长
timer: any; // 多长时间播放下一个
pos = 0; // 播放点
scrubber: number;
term: Terminal;
get position() {
return formatTime(this.time);
}
set position(data) {
}
@Input() replay: Replay;
constructor(private _http: HttpService) {}
......@@ -37,12 +89,13 @@ export class JsonComponent implements OnInit {
this._http.get_replay_data(this.replay.src)
.subscribe(
data => {
this.replay.json = data;
this.replay.timelist = Object.keys(this.replay.json).map(Number);
this.replay.timelist = this.replay.timelist.sort((a, b) => {
this.replayData = data;
this.timeList = Object.keys(this.replayData).map(Number);
this.timeList = this.timeList.sort((a, b) => {
return a - b;
});
this.replay.totalTime = this.replay.timelist[this.replay.timelist.length - 1] * 1000;
this.max = this.timeList[this.timeList.length - 1] * 1000;
this.duration = formatTime(this.max);
this.toggle();
},
err => {
......@@ -56,55 +109,48 @@ export class JsonComponent implements OnInit {
restart() {
clearInterval(this.timer);
this.term.reset();
this.time = 1;
this.pos = 0;
this.play = true;
this.isPlaying = true;
this.timer = setInterval(() => {
this.advance();
}, this.tick);
}
toggle() {
if (this.play) {
if (this.isPlaying) {
clearInterval(this.timer);
this.play = !this.play;
this.isPlaying = !this.isPlaying;
} else {
this.timer = setInterval(() => {
this.advance();
}, this.tick);
this.play = !this.play;
this.isPlaying = !this.isPlaying;
}
}
advance() {
// 每个time间隔执行一次
// this.scrubber = Math.ceil((this.time / this.replay.totalTime) * 100);
for (; this.pos < this.replay.timelist.length; this.pos++) {
if (this.replay.timelist[this.pos] * 1000 <= this.time) {
this.term.write(this.replay.json[this.replay.timelist[this.pos].toString()]);
// for (let i in this.timeList) {
// }
for (; this.pos < this.timeList.length; this.pos++) {
if (this.timeList[this.pos] * 1000 <= this.time) {
this.term.write(this.replayData[this.timeList[this.pos].toString()]);
} else {
break;
}
}
// 超过了总的时间点, 停止播放
if (this.pos >= this.replay.timelist.length) {
this.play = !this.play;
if (this.pos >= this.timeList.length) {
this.isPlaying = !this.isPlaying;
clearInterval(this.timer);
}
// 如果两次时间间隔超过了5s
if (this.replay.timelist[this.pos] - this.replay.timelist[this.pos - 1] > 5) {
this.time += 5000;
}
this.time += this.timeStep * this.speed;
this.percent = this.time / this.replay.totalTime * 100;
}
stop() {
clearInterval(this.timer);
this.play = false;
this.isPlaying = false;
}
speedUp() {
......@@ -116,31 +162,15 @@ export class JsonComponent implements OnInit {
}
runFrom() {
clearInterval(this.timer);
const time = this.replay.totalTime * this.percent / 100;
this.replay.timelist.forEach((v, i) => {
const preTime = this.replay.timelist[i - 1];
if (time <= v * 1000 && time >= preTime * 1000) {
for (let i = 0; i < this.timeList.length; i++) {
const v = this.timeList[i];
const preTime = this.timeList[i - 1];
if (this.time <= v * 1000 && this.time >= preTime * 1000) {
this.time = v * 1000;
this.pos = i;
return;
break;
}
}
});
this.timer = setInterval(() => {
this.advance();
}, this.tick);
this.play = !this.play;
}
// this.pos = 0;
// this.term.reset();
// this.play = false;
// for (; this.pos < this.replay.timelist.length; this.pos++) {
// if (this.replay.timelist[this.pos] * 1000 <= this.percent / 100 * this.replay.totalTime) {
// this.term.term.write(this.replay.json[this.replay.timelist[this.pos].toString()]);
// } else {
// break;
// }
// }
// this.time = this.replay.totalTime * this.percent / 100;
// }
}
}
import {TransPipe} from './trans.pipe';
import {UtcDatePipe} from './date.pipe';
import {TruncatecharsPipe} from './truncatechars.pipe';
export const Pipes = [
UtcDatePipe,
TransPipe
TransPipe,
TruncatecharsPipe
];
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'truncatechars'})
export class TruncatecharsPipe implements PipeTransform {
transform(value: string, length: number): string {
if (!value) {
return value;
}
if (value.length < length) {
return value;
}
return value.slice(0, length) + '..';
}
}
......@@ -9,6 +9,7 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"importHelpers": true,
"typeRoots": [
"node_modules/@types"
],
......
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