Unverified Commit 1071415c authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Merge pull request #114 from jumpserver/dev

Dev
parents 669c22ad b5665827
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "WebTerminal"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"static",
"theme/default/",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"../node_modules/animate.css/animate.min.css",
"../node_modules/xterm/dist/xterm.css",
"sass/style.scss",
"styles.css",
"assets/ztree/awesomeStyle/awesome.css"
],
"scripts": [
"../node_modules/jquery/dist/jquery.min.js",
"../node_modules/metismenu/dist/metisMenu.js",
"../node_modules/jquery-sparkline/jquery.sparkline.js",
"../node_modules/tether/dist/js/tether.min.js",
"../node_modules/bootstrap/dist/js/bootstrap.min.js",
"assets/inspinia/inspinia.js",
"assets/slimscroll/jquery.slimscroll.min.js",
"../node_modules/xterm/dist/xterm.js",
"assets/ztree/jquery.ztree.all.min.js",
"assets/ztree/jquery.ztree.exhide.min.js"
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**"
},
{
"project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"component": {}
}
}
...@@ -8,6 +8,6 @@ RUN npm run-script build ...@@ -8,6 +8,6 @@ RUN npm run-script build
FROM nginx:alpine FROM nginx:alpine
COPY --from=stage-build /data/dist /opt/luna/ COPY --from=stage-build /data/luna /opt/luna/
COPY i18n /opt/luna/i18n COPY ./src/assets/i18n /opt/luna/i18n
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"WebTerminal": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
"src/static",
"src/theme/default",
"src/favicon.ico"
],
"styles": [
"node_modules/animate.css/animate.min.css",
"node_modules/xterm/dist/xterm.css",
"src/sass/style.scss",
"src/styles.css",
"src/assets/ztree/awesomeStyle/awesome.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/xterm/dist/xterm.js",
"src/assets/ztree/jquery.ztree.all.min.js",
"src/assets/ztree/jquery.ztree.exhide.min.js"
]
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "WebTerminal:build"
},
"configurations": {
"production": {
"browserTarget": "WebTerminal:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "WebTerminal:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/xterm/dist/xterm.js",
"src/assets/ztree/jquery.ztree.all.min.js",
"src/assets/ztree/jquery.ztree.exhide.min.js"
],
"styles": [
"node_modules/animate.css/animate.min.css",
"node_modules/xterm/dist/xterm.css",
"src/sass/style.scss",
"src/styles.css",
"src/assets/ztree/awesomeStyle/awesome.css"
],
"assets": [
"src/assets",
"src/static",
"src/theme/default",
"src/favicon.ico"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"WebTerminal-e2e": {
"root": "e2e",
"sourceRoot": "e2e",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "WebTerminal:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "WebTerminal",
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
"styleext": "scss"
},
"@schematics/angular:directive": {
"prefix": "app"
}
}
}
\ No newline at end of file
{
"reset": "重置",
"submit": "提交",
"email subject prefix": "邮件主题前缀",
"basic setting": "基本设置",
"email setting": "邮件设置",
"ldap setting": "LDAP设置",
"terminal setting": "终端设置",
"current site url": "当前站点URL",
"user guide url": "用户向导URL",
"user first login update profile done redirect to it": "用户第一次登录,修改profile后重定向到地址",
"server": "服务器",
"view": "视图",
"help": "帮助",
"hide left manager": "隐藏左边栏",
"show left manager": "显示左边栏",
"disconnect all": "断开所有链接",
"disconnect": "断开链接",
"website": "官网",
"search": "搜索",
"settings": "系统设置",
"job center": "作业中心",
"sessions": "会话管理",
"perms": "权限管理",
"assets": "资产管理",
"users": "用户管理",
"dashboard": "仪表盘",
"task": "任务",
"session online": "在线会话",
"session offline": "离线会话",
"commands": "命令记录",
"terminal": "终端管理",
"asset perminssion": "资产授权",
"asset": "资产",
"asset group": "资产组",
"cluster": "集群",
"admin user": "管理用户",
"system user": "系统用户",
"labels": "标签管理",
"user": "用户",
"user group": "用户组",
"login logs": "登陆日志",
"language": "语言选择",
"found": "发现",
"users ": "用户",
"choose a user": "选择一个用户",
"please choose a user": "请选择一个用户",
"cancel": "取消",
"confirm": "确认",
"document": "文档",
"support": "商业支持",
"speed": "速度",
"file manager": "文件管理",
"file": "文件管理",
"new connection": "连接",
"connect": "连接",
"rdp resolution": "RDP分辨率",
"set rdp solution": "设置分辨率",
"select a solution": "选择分辨率",
"set font": "设置字体",
"font": "字体",
"font size": "字体大小",
"full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
}
{
"reset": "重置",
"submit": "提交",
"email subject prefix": "邮件主题前缀",
"basic setting": "基本设置",
"email setting": "邮件设置",
"ldap setting": "LDAP设置",
"terminal setting": "终端设置",
"current site url": "当前站点URL",
"user guide url": "用户向导URL",
"user first login update profile done redirect to it": "用户第一次登录,修改profile后重定向到地址",
"server": "服务器",
"view": "视图",
"help": "帮助",
"hide left manager": "隐藏左边栏",
"show left manager": "显示左边栏",
"disconnect all": "断开所有链接",
"disconnect": "断开链接",
"website": "官网",
"search": "搜索",
"settings": "系统设置",
"job center": "作业中心",
"sessions": "会话管理",
"perms": "权限管理",
"assets": "资产管理",
"users": "用户管理",
"dashboard": "仪表盘",
"task": "任务",
"session online": "在线会话",
"session offline": "离线会话",
"commands": "命令记录",
"terminal": "终端管理",
"asset perminssion": "资产授权",
"asset": "资产",
"asset group": "资产组",
"cluster": "集群",
"admin user": "管理用户",
"system user": "系统用户",
"labels": "标签管理",
"user": "用户",
"user group": "用户组",
"login logs": "登陆日志",
"language": "语言选择",
"found": "发现",
"users ": "用户",
"choose a user": "选择一个用户",
"please choose a user": "请选择一个用户",
"cancel": "取消",
"confirm": "确认",
"document": "文档",
"support": "商业支持",
"speed": "速度",
"file manager": "文件管理",
"file": "文件管理",
"new connection": "连接",
"connect": "连接",
"rdp resolution": "RDP分辨率",
"set rdp solution": "设置分辨率",
"select a solution": "选择分辨率",
"set font": "设置字体",
"font": "字体",
"font size": "字体大小",
"full screen": "全屏显示",
"please input password": "请输入密码",
"username": "用户名",
"password": "密码"
}
...@@ -4,24 +4,22 @@ ...@@ -4,24 +4,22 @@
module.exports = function (config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['jasmine', '@angular/cli'], frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [ plugins: [
require('karma-jasmine'), require('karma-jasmine'),
require('karma-chrome-launcher'), require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'), require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma') require('@angular-devkit/build-angular/plugins/karma')
], ],
client:{ client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ], dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true fixWebpackSourcePaths: true
}, },
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'], reporters: ['progress', 'kjhtml'],
port: 9876, port: 9876,
colors: true, colors: true,
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -5,97 +5,93 @@ ...@@ -5,97 +5,93 @@
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json --host 0.0.0.0", "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/'", "build": "ng build --prod --base-href=/luna/ --output-path 'luna'",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e" "e2e": "ng e2e"
}, },
"private": true, "private": true,
"dependencies": { "abandon": {
"@angular/animations": "^5.2.10",
"@angular/cdk": "^5.2.5",
"@angular/common": "5.2.0",
"@angular/compiler": "5.2.0",
"@angular/core": "5.2.0",
"@angular/forms": "5.2.0",
"@angular/http": "5.2.0",
"@angular/material": "^5.2.5",
"@angular/platform-browser": "5.2.0",
"@angular/platform-browser-dynamic": "5.2.0",
"@angular/router": "5.2.0",
"@swimlane/ngx-datatable": "^11.3.2", "@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",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
"bootstrap": "^4.1.1",
"clipboard": "^1.7.1", "clipboard": "^1.7.1",
"compass-mixins": "^0.12.10", "compass-mixins": "^0.12.10",
"core-js": "2.5.3",
"directory-encoder": "^0.9.2", "directory-encoder": "^0.9.2",
"elfinder": "git+https://github.com/Studio-42/elFinder.git#2.1.33", "handlebars": "^4.1.2",
"filetree-css": "^1.0.0",
"font-awesome": "4.7.0",
"guacamole-common-js": "0.9.14-b",
"handlebars": "^4.0.11",
"intl": "1.2.5", "intl": "1.2.5",
"jquery": "3.2.1",
"jquery-slimscroll": "^1.3.8", "jquery-slimscroll": "^1.3.8",
"jquery-sparkline": "^2.4.0", "jquery-sparkline": "^2.4.0",
"jvectormap": "1.2.2", "jvectormap": "1.2.2",
"lodash": "^4.17.10", "lodash": "^4.17.15",
"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",
"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-layer": "0.0.4", "ngx-layer": "0.0.4",
"ngx-logger": "^2.2.4",
"ngx-perfect-scrollbar": "5.2.0", "ngx-perfect-scrollbar": "5.2.0",
"ngx-progressbar": "^2.1.1",
"npm-font-open-sans": "^1.1.0",
"peity": "^3.3.0", "peity": "^3.3.0",
"popper.js": "1.12.9",
"requirejs": "^2.3.5",
"roboto-fontface": "^0.8.0", "roboto-fontface": "^0.8.0",
"rxjs": "5.5.6",
"sass-math": "^1.0.0", "sass-math": "^1.0.0",
"socket.io": "^1.4.32",
"socket.io-client": "^1.4.32",
"ssh-keygen": "^0.4.1",
"tether": "^1.4.4", "tether": "^1.4.4",
"npm-font-open-sans": "^1.1.0",
"@swimlane/ngx-ui": "^25.11.0",
"utf-8-validate": "^5.0.2"
},
"dependencies": {
"@angular/animations": "~7.2.0",
"@angular/cdk": "^7.3.7",
"@angular/common": "~7.2.0",
"@angular/compiler": "~7.2.0",
"@angular/core": "~7.2.0",
"@angular/flex-layout": "^7.0.0-beta.19",
"@angular/forms": "~7.2.0",
"@angular/http": "^7.2.15",
"@angular/material": "^7.3.7",
"@angular/platform-browser": "~7.2.0",
"@angular/platform-browser-dynamic": "~7.2.0",
"@angular/router": "~7.2.0",
"@types/jquery": "^3.3.6",
"@types/neffos.js": "^0.1.1",
"ajv": "^6.5.0",
"animate.css": "^3.6.1",
"bootstrap": "^4.3.1",
"codemirror": "^5.42.0",
"core-js": "^2.5.4",
"font-awesome": "4.7.0",
"guacamole-common-js": "1.1.0",
"jquery": "^3.4.1",
"metismenu": "^2.7.9",
"neffos.js": "^0.1.19",
"ngx-cookie-service": "^1.0.10",
"ngx-logger": "4.0.4",
"popper.js": "^1.14.7",
"requirejs": "^2.3.5",
"rxjs": "~6.3.3",
"rxjs-compat": "^6.0.0-rc.0",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"utf-8-validate": "^5.0.2",
"uuid-js": "^0.7.5", "uuid-js": "^0.7.5",
"xterm": "3.3.0", "xterm": "3.3.0",
"zone.js": "0.8.20" "zone.js": "~0.8.26"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/core": "^0.4.9", "@angular-devkit/build-angular": "~0.13.0",
"@angular-devkit/schematics": "^0.4.9", "@angular/cli": "~7.3.9",
"@angular/cli": "^1.7.4", "@angular/compiler-cli": "~7.2.0",
"@angular/compiler-cli": "5.2.0", "@angular/language-service": "~7.2.0",
"@angular/language-service": "5.2.0", "@types/node": "~8.9.4",
"@types/jasmine": "2.8.4", "@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.2", "@types/jasminewd2": "~2.0.3",
"codelyzer": "4.0.2", "codelyzer": "~4.5.0",
"jasmine-core": "2.8.0", "jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "4.2.1", "jasmine-spec-reporter": "~4.2.1",
"karma": "2.0.0", "karma": "~4.0.0",
"karma-chrome-launcher": "2.2.0", "karma-chrome-launcher": "~2.2.0",
"karma-cli": "1.0.1", "karma-coverage-istanbul-reporter": "~2.0.1",
"karma-coverage-istanbul-reporter": "1.3.3", "karma-jasmine": "~1.1.2",
"karma-jasmine": "1.1.1", "karma-jasmine-html-reporter": "^0.2.2",
"karma-jasmine-html-reporter": "0.2.2", "protractor": "~5.4.0",
"node-sass": "^4.11.0", "ts-node": "~7.0.0",
"protractor": "^5.4.2", "tslint": "~5.11.0",
"ts-node": "3.3.0", "typescript": "~3.2.2"
"tslint": "5.9.1",
"typescript": "2.6.2"
} }
} }
{ {
"/api": { "/api": {
"target": "http://127.0.0.1:5001", "target": "http://127.0.0.1:8080",
"secure": false "secure": false
}, },
"/luna/i18n": { "/luna/i18n": {
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"secure": false "secure": false
}, },
"/socket.io/": { "/socket.io/": {
"target": "http://127.0.0.1:5001", "target": "http://127.0.0.1:5000",
"secure": false, "secure": false,
"ws": true "ws": true
}, },
...@@ -27,5 +27,17 @@ ...@@ -27,5 +27,17 @@
"^/rdp": "" "^/rdp": ""
}, },
"secure": false "secure": false
},
"/media/": {
"target": "http://127.0.0.1:8080",
"secure": false
},
"/guacamole/": {
"target": "http://127.0.0.1:8083",
"secure": false,
"ws": true,
"pathRewrite": {
"^/guacamole": ""
}
} }
} }
/**
* app 模块
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; // <-- NgModel lives here import {FormsModule, ReactiveFormsModule} from '@angular/forms'; // <-- NgModel lives here
import {NGXLogger} from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
import {HttpClientModule} from '@angular/common/http'; import {HttpClientModule} from '@angular/common/http';
import {AppRouterModule} from './router/router.module';
import {AppComponent} from './pages/app.component';
// service
import {AppService, HttpService, LocalStorageService, LogService, UUIDService} from './app.service';
import {CookieService} from 'ngx-cookie-service'; import {CookieService} from 'ngx-cookie-service';
import {MAT_LABEL_GLOBAL_OPTIONS} from '@angular/material'; import {MAT_LABEL_GLOBAL_OPTIONS} from '@angular/material';
// service
import {AllServices} from '@app/services';
import {AppRouterModule} from './router/router.module';
import {Pipes} from './pipes/pipes'; import {Pipes} from './pipes/pipes';
import {AppComponent} from './pages/app.component';
import {PagesComponents} from './pages/pages.component'; import {PagesComponents} from './pages/pages.component';
import {ElementComponents} from './elements/elements.component'; import {ElementComponents} from './elements/elements.component';
import {ChangLanWarningDialogComponent, RDPSolutionDialogComponent, FontDialogComponent} from './elements/nav/nav.component'; import {PageMainComponent} from '@app/pages/main/main.component';
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 {ChangLanWarningDialogComponent} from './elements/nav/nav.component';
import {AssetTreeDialogComponent, ManualPasswordDialogComponent} from './elements/asset-tree/asset-tree.component'; import {ElementSettingComponent} from '@app/elements/setting/setting.component';
import {SftpComponent} from './elements/sftp/sftp.component'; import {AssetTreeDialogComponent, ManualPasswordDialogComponent} from './elements/connect/connect.component';
@NgModule({ @NgModule({
...@@ -45,34 +32,25 @@ import {SftpComponent} from './elements/sftp/sftp.component'; ...@@ -45,34 +32,25 @@ import {SftpComponent} from './elements/sftp/sftp.component';
], ],
declarations: [ declarations: [
AppComponent, AppComponent,
TestPageComponent,
...Pipes, ...Pipes,
...ElementComponents, ...ElementComponents,
...PagesComponents, ...PagesComponents,
SftpComponent, ],
],
entryComponents: [ entryComponents: [
AssetTreeDialogComponent, AssetTreeDialogComponent,
ManualPasswordDialogComponent, ManualPasswordDialogComponent,
ElementDialogAlertComponent,
ChangLanWarningDialogComponent, ChangLanWarningDialogComponent,
RDPSolutionDialogComponent, PageMainComponent,
FontDialogComponent ElementSettingComponent,
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
providers: [ providers: [
// {provide: LoggerConfig, useValue: {level: LoggerLevel.WARN}}, // {provide: LoggerConfig, useValue: {level: LoggerLevel.WARN}},
// {provide: BrowserXhr, useClass: NgProgressBrowserXhr}, // {provide: BrowserXhr, useClass: NgProgressBrowserXhr},
AppService, ...AllServices,
HttpService,
LogService,
UUIDService,
LocalStorageService,
DialogService,
CookieService, CookieService,
NGXLogger, NGXLogger,
{provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: {float: 'always'}} {provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: {float: 'always'}}
] ]
}) })
export class AppModule { export class AppModule {
......
/**
* 后台控制
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Injectable, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {CookieService} from 'ngx-cookie-service';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import {DataStore, User, Browser, i18n} from './globals';
import {environment} from '../environments/environment';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {NGXLogger} from 'ngx-logger';
import * as UUID from 'uuid-js/lib/uuid.js';
declare function unescape(s: string): string;
class GuacObjAddResp {
code: number;
result: string;
}
@Injectable()
export class HttpService {
headers = new HttpHeaders();
constructor(private http: HttpClient) {
}
get(url: string, options?: any) {
return this.http.get(url, options);
}
post(url: string, options?: any) {
return this.http.post(url, options);
}
put(url: string, options?: any) {
return this.http.put(url, options);
}
delete(url: string, options?: any) {
return this.http.delete(url, options);
}
patch(url: string, options?: any) {
return this.http.patch(url, options);
}
head(url: string, options?: any) {
return this.http.head(url, options);
}
options(url: string, options?: any) {
return this.http.options(url, options);
}
report_browser() {
return this.http.post('/api/browser', JSON.stringify(Browser));
}
check_login(user: any) {
return this.http.post('/api/checklogin', user);
}
get_user_profile() {
return this.http.get('/api/users/v1/profile/');
}
get_my_granted_nodes() {
return this.http.get<Array<Node>>('/api/perms/v1/user/nodes-assets/tree/?cache_policy=1');
}
get_my_granted_remote_apps() {
return this.http.get<Array<Node>>('/api/perms/v1/user/remote-apps/tree/');
}
refresh_my_granted_nodes() {
return this.http.get<Array<Node>>('/api/perms/v1/user/nodes-assets/tree/?cache_policy=2');
}
get_guacamole_token(user_id: string, authToken: string) {
const body = new HttpParams()
.set('username', user_id)
.set('password', 'jumpserver')
.set('asset_token', authToken);
// {
// "authToken": "xxxxxxx",
// "username": "xxxxxx",
// "dataSource": "jumpserver",
// "availableDataSources":[
// "jumpserver"
// ]
// }
return this.http.post('/guacamole/api/tokens',
body.toString(),
{headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')});
}
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];
const height = solution.split('x')[1];
params = params.set('width', width).set('height', height);
}
return this.http.get<GuacObjAddResp>(
'/guacamole/api/session/ext/jumpserver/asset/add',
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
params: params
}
);
}
guacamole_add_remote_app(user_id: string, remote_app_id: string) {
let params = new HttpParams()
.set('user_id', user_id)
.set('remote_app_id', remote_app_id)
.set('token', DataStore.guacamole_token);
const solution = localStorage.getItem('rdpSolution') || 'Auto';
if (solution !== 'Auto') {
const width = solution.split('x')[0];
const height = solution.split('x')[1];
params = params.set('width', width).set('height', height);
}
return this.http.get<GuacObjAddResp>(
'/guacamole/api/session/ext/jumpserver/remote-app/add',
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
params: params
}
);
}
guacamole_token_add_asset(assetToken: string, token: string) {
let params = new HttpParams()
.set('asset_token', assetToken)
.set('token', token);
const solution = localStorage.getItem('rdpSolution') || 'Auto';
if (solution !== 'Auto') {
const width = solution.split('x')[0];
const height = solution.split('x')[1];
params = params.set('width', width).set('height', height);
}
return this.http.get(
'/guacamole/api/ext/jumpserver/asset/token/add',
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
params: params
}
);
}
search(q: string) {
const params = new HttpParams()
.set('q', q);
return this.http.get('/api/search', {params: params});
}
get_replay(token: string) {
return this.http.get('/api/terminal/v1/sessions/' + token + '/replay');
}
// get_replay_json(token: string) {
// return this.http.get('/api/terminal/v2/sessions/' + token + '/replay');
// }
get_replay_data(src: string) {
return this.http.get(src);
}
get_user_id_from_token(token: string) {
const params = new HttpParams()
.set('user-only', '1')
.set('token', token);
return this.http.get('/api/users/v1/connection-token/', {params: params});
}
}
@Injectable()
export class LogService {
level: number;
constructor(private _logger: NGXLogger) {
// 0.- Level.OFF
// 1.- Level.ERROR
// 2.- Level.WARN
// 3.- Level.INFO
// 4.- Level.DEBUG
// 5.- Level.LOG
this.level = 4;
}
log(message: any, ...additional: any[]) {
if (this.level > 4) {
this._logger.log(message, ...additional);
}
}
debug(message: any, ...additional: any[]) {
if (this.level > 3) {
this._logger.debug(message, ...additional);
}
}
info(message: any, ...additional: any[]) {
if (this.level > 2) {
this._logger.info(message, ...additional);
}
}
warn(message: any, ...additional: any[]) {
if (this.level > 1) {
this._logger.warn(message, ...additional);
}
}
error(message: any, ...additional: any[]) {
if (this.level > 0) {
this._logger.error(message, ...additional);
}
}
}
@Injectable()
export class LocalStorageService {
constructor() {
}
get(key: string): string {
return localStorage.getItem(key);
}
set(key: string, value: any) {
return localStorage.setItem(key, value);
}
delete(key: string) {
return localStorage.removeItem(key);
}
}
@Injectable()
export class AppService implements OnInit {
// user:User = user ;
lang: string;
constructor(private _http: HttpService,
private _router: Router,
private _logger: LogService,
private _cookie: CookieService,
private _localStorage: LocalStorageService) {
if (this._cookie.get('loglevel')) {
// 0.- Level.OFF
// 1.- Level.ERROR
// 2.- Level.WARN
// 3.- Level.INFO
// 4.- Level.DEBUG
// 5.- Level.LOG
this._logger.level = parseInt(this._cookie.get('loglevel'), 10);
// this._logger.debug('Your debug stuff');
// this._logger.info('An info');
// this._logger.warn('Take care ');
// this._logger.error('Too late !');
// this._logger.log('log !');
} else {
this._cookie.set('loglevel', '0', 99, '/', document.domain);
// this._logger.level = parseInt(Cookie.getCookie('loglevel'));
this._logger.level = 0;
}
if (environment.production) {
this._logger.level = 2;
this.checklogin();
}
if (this._cookie.get('lang')) {
this.lang = this._cookie.get('lang');
} else {
this.lang = window.navigator.languages ? window.navigator.languages[0] : 'cn';
this._cookie.set('lang', this.lang);
}
if (this.lang !== 'en') {
this._http.get('/luna/i18n/' + this.lang + '.json').subscribe(
data => {
this._localStorage.set('lang', JSON.stringify(data));
},
err => {
}
);
}
const l = this._localStorage.get('lang');
if (l) {
const data = JSON.parse(l);
Object.keys(data).forEach((k, _) => {
i18n.set(k, data[k]);
});
}
}
ngOnInit() {
}
checklogin() {
this._logger.log('service.ts:AppService,checklogin');
if (DataStore.Path) {
if (document.location.pathname === '/luna/connect') {
} else {
if (User.logined) {
if (document.location.pathname === '/login') {
this._router.navigate(['']);
} else {
this._router.navigate([document.location.pathname]);
}
// jQuery('angular2').show();
} else {
this._http.get_user_profile()
.subscribe(
data => {
User.id = data['id'];
User.name = data['name'];
User.username = data['username'];
User.email = data['email'];
User.is_active = data['is_active'];
User.is_superuser = data['is_superuser'];
User.role = data['role'];
// User.groups = data['groups'];
User.wechat = data['wechat'];
User.comment = data['comment'];
User.date_expired = data['date_expired'];
if (data['phone']) {
User.phone = data['phone'].toString();
}
User.logined = data['logined'];
this._logger.debug(User);
this._localStorage.set('user', data['id']);
},
err => {
// this._logger.error(err);
User.logined = false;
window.location.href = document.location.origin + '/users/login?next=' +
document.location.pathname + document.location.search;
// this._router.navigate(['login']);
},
// () => {
// if (User.logined) {
// if (document.location.pathname === '/login') {
// this._router.navigate(['']);
// } else {
// this._router.navigate([document.location.pathname]);
// }
// } else {
// this._router.navigate(['login']);
// }
// jQuery('angular2').show();
// }
);
}
}
} else {
this._router.navigate(['FOF']);
// jQuery('angular2').show();
}
}
browser() {
this._http.report_browser();
}
getQueryString(name) {
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
const r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
//
//
// HideLeft() {
// DataStore.leftbarhide = true;
//
// DataStore.Nav.map(function (value, i) {
// for (var ii in value['children']) {
// if (DataStore.Nav[i]['children'][ii]['id'] === 'HideLeftManager') {
// DataStore.Nav[i]['children'][ii] = {
// 'id': 'ShowLeftManager',
// 'click': 'ShowLeft',
// 'name': 'Show left manager'
// };
// }
// }
// });
//
// }
//
// ShowLeft() {
// DataStore.leftbarhide = false;
//
// DataStore.Nav.map(function (value, i) {
// for (var ii in value['children']) {
// if (DataStore.Nav[i]['children'][ii]['id'] === 'ShowLeftManager') {
// DataStore.Nav[i]['children'][ii] = {
// 'id': 'HideLeftManager',
// 'click': 'HideLeft',
// 'name': 'Hide left manager'
// };
// }
// }
// });
//
//
// }
//
// setMyinfo(user:User) {
// // Update data store
// this._dataStore.user = user;
// this._logger.log("service.ts:AppService,setMyinfo");
// this._logger.debug(user);
// // Push the new list of todos into the Observable stream
// // this._dataObserver.next(user);
// // this.myinfo$ = new Observable(observer => this._dataObserver = observer).share()
// }
//
// getMyinfo() {
// this._logger.log('service.ts:AppService,getMyinfo');
// return this.http.get('/api/userprofile')
// .map(res => res.json())
// .subscribe(response => {
// DataStore.user = response;
// // this._logger.warn(this._dataStore.user);
// // this._logger.warn(DataStore.user)
// });
// }
//
// getUser(id: string) {
// this._logger.log('service.ts:AppService,getUser');
// return this.http.get('/api/userprofile')
// .map(res => res.json());
// }
//
// gettest() {
// this._logger.log('service.ts:AppService,gettest');
// this.http.get('/api/userprofile')
// .map(res => res.json())
// .subscribe(res => {
// return res;
// });
// }
//
// getGrouplist() {
// this._logger.log('service.ts:AppService,getGrouplist');
// return this.http.get('/api/grouplist')
// .map(res => res.json());
// }
//
// getUserlist(id: string) {
// this._logger.log('service.ts:AppService,getUserlist');
// if (id)
// return this.http.get('/api/userlist/' + id)
// .map(res => res.json());
// else
// return this.http.get('/api/userlist')
// .map(res => res.json());
// }
//
// delGroup(id) {
//
// }
//
//
// copy() {
// var clipboard = new Clipboard('#Copy');
//
// clipboard.on('success', function (e) {
// console.info('Action:', e.action);
// console.info('Text:', e.text);
// console.info('Trigger:', e.trigger);
//
// e.clearSelection();
// });
// console.log('ffff');
// console.log(window.getSelection().toString());
//
// var copy = new Clipboard('#Copy', {
// text: function () {
// return window.getSelection().toString();
// }
// });
// copy.on('success', function (e) {
// layer.alert('Lucky Copyed!');
// });
//
// }
}
@Injectable()
export class UUIDService {
constructor() {
}
gen() {
return UUID.create()['hex'];
}
}
<ul id="ztree" class="ztree"></ul> <div>
<ul id="assetsTree" class="ztree">
{{ "Loading"|trans }} ...
</ul>
<ul id="remoteAppsTree" class="ztree">
{{ "Loading"|trans }} ...
</ul>
</div>
<div #rMenu *ngIf="isShowRMenu" class="basicContext" [style.top]="pos.top" [style.left]="pos.left"> <div #rMenu *ngIf="isShowRMenu" class="basicContext" [style.top]="pos.top" [style.left]="pos.left">
<table> <table>
<tbody> <tbody>
<tr class="basicContext__item "> <tr *ngFor="let menu of RMenuList; let i = index" class="basicContext__item ">
<td class="basicContext__data" data-num="0" (click)="connectTerminal()"> <span class="basicContext__icon fa fa-terminal new-connection"></span> {{ "New Connection"|trans }} </td> <td class="basicContext__data" [attr.data-num]="i" (click)="menu.click()" [ngStyle]="{'display': menu.hide ? 'none': ''}">
</tr> <span class="basicContext__icon fa" [ngClass]="menu.fa"></span> {{ menu.name|trans }}
<tr class="basicContext__item basicContext__item--separator"></tr> </td>
<tr class="basicContext__item ">
<td class="basicContext__data" data-num="2" (click)="connectFileManager()"><span class="basicContext__icon fa fa-file refresh"></span> {{ "File Manager"|trans }} </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
......
#ztree .fa { .tree-refresh .fa {
width: 24px; width: 24px;
height: 24px; height: 24px;
line-height: 24px; line-height: 24px;
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
user-select: none; user-select: none;
} }
.basicContext, .basicContext * { .basicContext, .basicContext * {
box-sizing: border-box; box-sizing: border-box;
} }
.basicContextContainer { .basicContextContainer {
...@@ -57,8 +57,9 @@ ...@@ -57,8 +57,9 @@
.basicContext td { .basicContext td {
display: block; display: block;
padding: 0 35px; padding: 0 15px;
text-decoration: none; text-decoration: none;
min-width: 150px;
width: auto; width: auto;
opacity: 1; opacity: 1;
white-space: nowrap; white-space: nowrap;
...@@ -69,6 +70,11 @@ ...@@ -69,6 +70,11 @@
border-radius: 0; border-radius: 0;
cursor: pointer; cursor: pointer;
} }
.basicContext__icon {
padding-right: 5px;
}
.basicContext__item.basicContext__item--separator { .basicContext__item.basicContext__item--separator {
background: #181414; background: #181414;
border: 0; border: 0;
...@@ -92,11 +98,11 @@ tr { ...@@ -92,11 +98,11 @@ tr {
} }
tr:hover { tr:hover {
background-color: #463e3e; background-color: #463e3e;
} }
.basicContext table { .basicContext table {
border-spacing: 0 !important; border-spacing: 0 !important;
} }
.basicContext__data { .basicContext__data {
......
import {Component, Input, Output, OnInit, Inject, SimpleChanges, OnChanges, ElementRef, ViewChild, EventEmitter} from '@angular/core'; import {Component, Input, OnInit, OnDestroy, ElementRef, ViewChild} from '@angular/core';
import {NavList, View} from '../../pages/control/control/control.component'; import {MatDialog} from '@angular/material';
import {AppService, HttpService, LogService} from '../../app.service'; import {BehaviorSubject, Subject} from 'rxjs';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material'; import {takeUntil} from 'rxjs/operators';
import {FormControl, Validators} from '@angular/forms';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {AppService, HttpService, LogService, NavService, SettingService, TreeFilterService} from '@app/services';
import {connectEvt, translate} from '@app/globals';
import {TreeNode, ConnectEvt} from '@app/model';
declare var $: any; declare var $: any;
@Component({ @Component({
selector: 'elements-asset-tree', selector: 'elements-asset-tree',
templateUrl: './asset-tree.component.html', templateUrl: './asset-tree.component.html',
styleUrls: ['./asset-tree.component.scss'] styleUrls: ['./asset-tree.component.scss'],
}) })
export class ElementAssetTreeComponent implements OnInit, OnChanges { export class ElementAssetTreeComponent implements OnInit, OnDestroy {
@Input() query: string; @Input() query: string;
@Input() searchEvt$: BehaviorSubject<string>; @Input() searchEvt$: BehaviorSubject<string>;
@ViewChild('rMenu') rMenu: ElementRef; @ViewChild('rMenu') rMenu: ElementRef;
...@@ -32,121 +34,138 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -32,121 +34,138 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
title: 'title' title: 'title'
} }
}, },
callback: {
onClick: this.onCzTreeOnClick.bind(this),
onRightClick: this.onRightClick.bind(this)
},
}; };
pos = {left: '100px', top: '200px'}; pos = {left: '100px', top: '200px'};
hiddenNodes: any; hiddenNodes: any;
expandNodes: any; expandNodes: any;
zTree: any; assetsTree: any;
remoteAppsTree: any;
isShowRMenu = false; isShowRMenu = false;
rightClickSelectNode: any; rightClickSelectNode: any;
hasLoginTo = false; hasLoginTo = false;
treeFilterSubscription: any;
isLoadTreeAsync: boolean;
filterAssetCancel$: Subject<boolean> = new Subject();
onCzTreeOnClick(event, treeId, treeNode, clickFlag) { constructor(private _appSvc: AppService,
if (treeNode.isParent) { private _treeFilterSvc: TreeFilterService,
const zTreeObj = $.fn.zTree.getZTreeObj('ztree');
zTreeObj.expandNode(treeNode);
} else {
this._http.get_user_profile().subscribe();
this.Connect(treeNode);
}
}
constructor(private _appService: AppService,
public _dialog: MatDialog, public _dialog: MatDialog,
public _logger: LogService, public _logger: LogService,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private _http: HttpService private _http: HttpService,
) { private settingSvc: SettingService
this.searchEvt$ = new BehaviorSubject<string>(this.query); ) {}
ngOnInit() {
this.isLoadTreeAsync = this.settingSvc.isLoadTreeAsync();
this.initTree();
document.addEventListener('click', this.hideRMenu.bind(this), false);
this.treeFilterSubscription = this._treeFilterSvc.onFilter.subscribe(
keyword => {
this._logger.debug('Filter tree: ', keyword);
this.filterAssets(keyword);
this.filterRemoteApps(keyword);
}
);
} }
getGrantedAssetsNodes() { ngOnDestroy(): void {
this._http.get_my_granted_nodes() this.treeFilterSubscription.unsubscribe();
.subscribe(response => {
this.Data = [...response, ...this.Data];
this.draw();
});
} }
refreshGrantedAssetsNodes() { onAssetsNodeClick(event, treeId, treeNode, clickFlag) {
this._http.refresh_my_granted_nodes() if (treeNode.isParent) {
.subscribe(response => { this.assetsTree.expandNode(treeNode);
this.Data = [...response, ...this.Data]; } else {
this.draw(); this._http.getUserProfile().subscribe();
}); this.connectAsset(treeNode);
}
} }
getGrantedRemoteApps() { refreshAssetsTree() {
this._http.get_my_granted_remote_apps() this.assetsTree.destroy();
.subscribe(response => { this.initAssetsTree(true);
if (response.length > 1) {
this.Data = [...this.Data, ...response];
this.draw();
}
});
} }
ngOnInit() { initAssetsTree(refresh?: boolean) {
this.getGrantedAssetsNodes(); const setting = Object.assign({}, this.setting);
this.getGrantedRemoteApps(); setting['callback'] = {
document.addEventListener('click', this.hideRMenu.bind(this), false); onClick: this.onAssetsNodeClick.bind(this),
this.searchEvt$.asObservable() onRightClick: this.onRightClick.bind(this)
.debounceTime(300) };
.distinctUntilChanged() if (this.isLoadTreeAsync) {
.subscribe((n) => { setting['async'] = {
this.filter(); enable: true,
url: '/api/v1/perms/users/nodes/children-with-assets/tree/?cache_policy=1',
autoParam: ['id=key', 'name=n', 'level=lv'],
type: 'get'
};
}
this._http.getMyGrantedNodes(this.isLoadTreeAsync, refresh).subscribe(resp => {
const assetsTree = $.fn.zTree.init($('#assetsTree'), setting, resp);
this.assetsTree = assetsTree;
this.rootNodeAddDom(assetsTree, () => {
this.refreshAssetsTree();
}); });
});
} }
ngOnChanges(changes: SimpleChanges) { refreshRemoteAppsTree() {
if (changes['Data'] && this.Data) { this.remoteAppsTree.destroy();
this.draw(); this.initRemoteAppsTree();
} }
if (changes['query'] && !changes['query'].firstChange) {
this.searchEvt$.next(this.query); onRemoteAppsNodeClick(event, treeId, treeNode, clickFlag) {
if (treeNode.isParent) {
this.remoteAppsTree.expandNode(treeNode);
} else {
this._http.getUserProfile().subscribe();
this.connectAsset(treeNode);
} }
} }
refreshNodes() { initRemoteAppsTree() {
this.zTree.destroy(); const setting = Object.assign({}, this.setting);
this.Data = []; setting['callback'] = {
this.refreshGrantedAssetsNodes(); onClick: this.onRemoteAppsNodeClick.bind(this),
this.getGrantedRemoteApps(); onRightClick: this.onRightClick.bind(this)
};
this._http.getMyGrantedRemoteApps().subscribe(
resp => {
if (!resp) {
return;
}
const tree = $.fn.zTree.init($('#remoteAppsTree'), setting, resp);
this.remoteAppsTree = tree;
this.rootNodeAddDom(tree, () => {
this.refreshRemoteAppsTree();
});
}
);
} }
draw() { initTree() {
$.fn.zTree.init($('#ztree'), this.setting, this.Data); this.initAssetsTree();
this.zTree = $.fn.zTree.getZTreeObj('ztree'); this.initRemoteAppsTree();
this.rootNodeAddDom(this.zTree, () => {
this.refreshNodes();
});
this.activatedRoute.queryParams.subscribe(params => {
const login_to = params['login_to'];
if (login_to && !this.hasLoginTo) {
this.Data.forEach(t => {
if (login_to === t.id && t.isParent === false) {
this.hasLoginTo = true;
this.Connect(t);
return;
}
});
}
});
} }
connectAsset(node: TreeNode) {
const evt = new ConnectEvt(node, 'asset');
connectEvt.next(evt);
}
rootNodeAddDom(ztree, callback) { rootNodeAddDom(ztree, callback) {
const refreshIcon = '<a id="tree-refresh"><i class="fa fa-refresh"></i></a>'; const tId = ztree.setting.treeId + '_tree_refresh';
const refreshIcon = '<a id=' + tId + ' class="tree-refresh">' +
'<i class="fa fa-refresh" style="font-family: FontAwesome !important;" ></i></a>';
const rootNode = ztree.getNodes()[0]; const rootNode = ztree.getNodes()[0];
const $rootNodeRef = $('#' + rootNode.tId + '_a'); const $rootNodeRef = $('#' + rootNode.tId + '_a');
$rootNodeRef.after(refreshIcon); $rootNodeRef.after(refreshIcon);
const refreshIconRef = $('#tree-refresh'); const refreshIconRef = $('#' + tId);
refreshIconRef.bind('click', function () { refreshIconRef.bind('click', function () {
callback(); callback();
}); });
} }
...@@ -164,174 +183,188 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -164,174 +183,188 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
this.isShowRMenu = false; this.isShowRMenu = false;
} }
onRightClick(event, treeId, treeNode) { nodeSupportSSH() {
if (!treeNode || treeNode.isParent ) { const host = this.rightClickSelectNode.meta.asset;
return null; if (!host) {
return false;
} }
const host = treeNode.meta.asset;
let findSSHProtocol = false; let findSSHProtocol = false;
const protocols = host.protocols || []; const protocols = host.protocols || [];
if (host.protocol) { if (host.protocol) {
protocols.push(host.protocol); protocols.push(host.protocol);
} }
for (let i = 0; i < protocols.length; i++) { for (let i = 0; i < protocols.length; i++) {
const protocol = protocols[i]; const protocol = protocols[i];
if (protocol && protocol.startsWith('ssh')) { if (protocol && protocol.startsWith('ssh')) {
findSSHProtocol = true; findSSHProtocol = true;
} }
}
return findSSHProtocol;
}
get RMenuList() {
const menuList = [{
'id': 'new-connection',
'name': 'Open in new window',
'fa': 'fa-terminal',
'hide': false,
'click': this.connectInNewWindow.bind(this)
}, {
'id': 'file-manager',
'name': 'File Manager',
'fa': 'fa-file',
'hide': !this.nodeSupportSSH(),
'click': this.connectFileManager.bind(this)
}];
if (!this.rightClickSelectNode) {
return [];
} }
if (!findSSHProtocol) { return menuList;
alert('Windows 请使用Ctrl+Shift+Alt呼出侧边栏上传下载'); }
onRightClick(event, treeId, treeNode) {
if (!treeNode || treeNode.isParent) {
return null;
} }
this.rightClickSelectNode = treeNode;
if (!treeNode && event.target.tagName.toLowerCase() !== 'button' && $(event.target).parents('a').length === 0) { if (!treeNode && event.target.tagName.toLowerCase() !== 'button' && $(event.target).parents('a').length === 0) {
this.zTree.cancelSelectedNode(); this.assetsTree.cancelSelectedNode();
this.showRMenu(event.clientX, event.clientY); this.showRMenu(event.clientX, event.clientY);
} else if (treeNode && !treeNode.noR) { } else if (treeNode && !treeNode.noR) {
this.zTree.selectNode(treeNode); this.assetsTree.selectNode(treeNode);
this.showRMenu(event.clientX, event.clientY); this.showRMenu(event.clientX, event.clientY);
this.rightClickSelectNode = treeNode; this.rightClickSelectNode = treeNode;
} }
} }
connectAsset(node) {
const system_users = node.meta.system_users;
const host = node.meta.asset;
let user: any;
if (system_users.length > 1) {
user = this.checkPriority(system_users);
if (user) {
this.login(host, user);
} else {
const dialogRef = this._dialog.open(AssetTreeDialogComponent, {
height: '200px',
width: '300px',
data: {users: system_users}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
for (const i of system_users) {
if (i.id.toString() === result.toString()) {
user = i;
break;
}
}
this.login(host, user);
}
});
}
} else if (system_users.length === 1) {
user = system_users[0];
this.login(host, user);
} else {
alert('该主机没有授权登录用户');
}
}
connectRemoteApp(node) { connectFileManager() {
const id = NavList.List.length - 1; const node = this.rightClickSelectNode;
if (node) { const evt = new ConnectEvt(node, 'sftp');
NavList.List[id].nick = node.name; connectEvt.next(evt);
NavList.List[id].connected = true;
NavList.List[id].edit = false;
NavList.List[id].closed = false;
NavList.List[id].remoteApp = node.id;
NavList.List[id].type = 'rdp';
NavList.List.push(new View());
NavList.Active = id;
}
this._logger.debug(NavList);
} }
Connect(node) { connectInNewWindow() {
const node = this.rightClickSelectNode;
let url = '/luna/connect?';
switch (node.meta.type) { switch (node.meta.type) {
case 'asset': case 'asset':
this.connectAsset(node); url += 'asset=' + node.meta.asset.id;
break; break;
case 'remote_app': case 'remote_app':
this.connectRemoteApp(node); url += 'remote_app=' + node.id;
break; break;
default: default:
alert('Unknown type: ' + node.meta.type); alert('Unknown type: ' + node.meta.type);
return;
} }
window.open(url, '_blank');
} }
connectFileManager() { filterAssets(keyword) {
const host = this.rightClickSelectNode.meta.asset; if (this.isLoadTreeAsync) {
const id = NavList.List.length - 1; this._logger.debug('Filter assets server');
if (host) { this.filterAssetsServer(keyword);
NavList.List[id].nick = '[FILE]' + host.hostname; } else {
NavList.List[id].connected = true; this._logger.debug('Filter assets local');
NavList.List[id].edit = false; this.filterAssetsLocal(keyword);
NavList.List[id].closed = false;
NavList.List[id].host = host;
NavList.List[id].type = 'sftp';
NavList.List.push(new View());
NavList.Active = id;
} }
this._logger.debug(NavList);
}
connectTerminal() {
const host = this.rightClickSelectNode;
this.Connect(host);
} }
manualSetUserAuthLogin(host, user) { filterTree(keyword, tree, filterCallback) {
user = Object.assign({}, user); const nodes = tree.transformToArray(tree.getNodes());
const dialogRef = this._dialog.open(ManualPasswordDialogComponent, { if (!keyword) {
height: '250px', if (tree.hiddenNodes) {
width: '400px', tree.showNodes(tree.hiddenNodes);
data: {username: user.username} tree.hiddenNodes = null;
}
if (tree.expandNodes) {
tree.expandNodes.forEach((node) => {
if (node.id !== nodes[0].id) {
tree.expandNode(node, false);
}
});
tree.expandNodes = null;
}
return null;
}
let shouldShow = [];
const matchedNodes = tree.getNodesByFilter(filterCallback);
matchedNodes.forEach((node) => {
const parents = this.recurseParent(node);
const children = this.recurseChildren(node);
shouldShow = [...shouldShow, ...parents, ...children, node];
}); });
dialogRef.afterClosed().subscribe(result => { tree.hiddenNodes = nodes;
if (!result) { tree.expandNodes = shouldShow;
return; tree.hideNodes(nodes);
tree.showNodes(shouldShow);
shouldShow.forEach((node) => {
if (node.isParent) {
tree.expandNode(node, true);
} }
user.username = btoa(result.username);
user.password = btoa(result.password);
return this.login(host, user);
}); });
} }
login(host, user) { filterRemoteApps(keyword) {
const id = NavList.List.length - 1; if (!this.remoteAppsTree) {
this._logger.debug(NavList); return null;
this._logger.debug(host); }
if (user.login_mode === 'manual' && !user.password && user.protocol === 'rdp') { function filterCallback(node: TreeNode) {
return this.manualSetUserAuthLogin(host, user); return node.name.toLowerCase().indexOf(keyword) !== -1;
}
return this.filterTree(keyword, this.remoteAppsTree, filterCallback);
}
filterAssetsServer(keyword) {
if (!this.assetsTree) {
return;
}
const searchNode = this.assetsTree.getNodesByFilter((node) => node.id === 'search');
if (searchNode) {
this.assetsTree.removeChildNodes(searchNode[0]);
this.assetsTree.removeNode(searchNode[0]);
} }
if (user) { if (!keyword) {
NavList.List[id].nick = host.hostname; const treeNodes = this.assetsTree.getNodes();
NavList.List[id].connected = true; if (treeNodes.length !== 0) {
NavList.List[id].edit = false; this.assetsTree.showNode(treeNodes[0]);
NavList.List[id].closed = false;
NavList.List[id].host = host;
NavList.List[id].user = user;
if (user.protocol === 'ssh' || user.protocol === 'telnet') {
NavList.List[id].type = 'ssh';
} else if (user.protocol === 'rdp' || user.protocol === 'vnc') {
NavList.List[id].type = 'rdp';
} }
NavList.List.push(new View()); return;
NavList.Active = id;
} }
this._logger.debug(NavList); this.filterAssetCancel$.next(true);
this._http.getMyGrantedAssets(keyword)
.pipe(takeUntil(this.filterAssetCancel$))
.subscribe(nodes => {
const treeNodes = this.assetsTree.getNodes();
if (treeNodes.length !== 0) {
this.assetsTree.hideNode(treeNodes[0]);
}
let name = translate('Search');
const assetsAmount = nodes.length;
name = `${name} (${assetsAmount})`;
const newNode = {id: 'search', name: name, isParent: true, open: true, zAsync: true};
const parentNode = this.assetsTree.addNodes(null, newNode)[0];
parentNode.zAsync = true;
this.assetsTree.addNodes(parentNode, nodes);
parentNode.open = true;
});
return;
} }
checkPriority(sysUsers) { filterAssetsLocal(keyword) {
let priority = -1; if (!this.assetsTree) {
let user: any; return null;
for (const u of sysUsers) { }
if (u.priority > priority) { function filterAssetsCallback(node) {
user = u; if (node.isParent) {
priority = u.priority; return false;
} else if (u.priority === priority) {
return null;
} }
const host = node.meta.asset;
return host.hostname.toLowerCase().indexOf(keyword) !== -1 || host.ip.indexOf(keyword) !== -1;
} }
return user; return this.filterTree(keyword, this.assetsTree, filterAssetsCallback);
// zTreeObj.expandAll(true);
} }
recurseParent(node) { recurseParent(node) {
...@@ -353,113 +386,11 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -353,113 +386,11 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
if (!children) { if (!children) {
return []; return [];
} }
let all_children = []; let allChildren = [];
children.forEach((n) => { children.forEach((n) => {
all_children = [...children, ...this.recurseChildren(n)]; allChildren = [...children, ...this.recurseChildren(n)];
});
return all_children;
}
filter() {
const zTreeObj = $.fn.zTree.getZTreeObj('ztree');
if (!zTreeObj) {
return null;
}
let _keywords = this.query;
const nodes = zTreeObj.transformToArray(zTreeObj.getNodes());
if (!_keywords) {
if (this.hiddenNodes) {
zTreeObj.showNodes(this.hiddenNodes);
this.hiddenNodes = null;
}
if (this.expandNodes) {
this.expandNodes.forEach((node) => {
if (node.id !== nodes[0].id) {
zTreeObj.expandNode(node, false);
}
});
this.expandNodes = null;
}
return null;
}
_keywords = _keywords.toLowerCase();
let shouldShow = [];
const matchedNodes = zTreeObj.getNodesByFilter(function(node) {
if (node.meta.type === 'asset') {
const host = node.meta.asset;
return host.hostname.toLowerCase().indexOf(_keywords) !== -1 || host.ip.indexOf(_keywords) !== -1;
} else {
return node.name.toLowerCase().indexOf(_keywords) !== -1;
}
}); });
matchedNodes.forEach((node) => { return allChildren;
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);
}
}
@Component({
selector: 'elements-asset-tree-dialog',
templateUrl: 'dialog.html',
})
export class AssetTreeDialogComponent implements OnInit {
UserSelectControl = new FormControl('', [Validators.required]);
selected: any;
constructor(public dialogRef: MatDialogRef<AssetTreeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private _logger: LogService) {
}
ngOnInit() {
this.selected = this.data.users[0].id;
this.UserSelectControl.setValue(this.selected);
// this._logger.debug(this.UserSelectControl);
}
onNoClick(): void {
this.dialogRef.close();
}
compareFn: ((f1: any, f2: any) => boolean) | null = this.compareByValue;
compareByValue(f1: any, f2: any) {
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 {
}
}
import {Component, OnInit, Output, Inject, OnDestroy, EventEmitter} from '@angular/core';
import 'rxjs/add/operator/toPromise';
import {connectEvt} from '@app/globals';
import {AppService, HttpService, LogService, NavService, SettingService} from '@app/services';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
import {FormControl, Validators} from '@angular/forms';
import {ActivatedRoute} from '@angular/router';
import {SystemUser, TreeNode, Asset} from '@app/model';
import {View} from '@app/model';
@Component({
selector: 'elements-connect',
templateUrl: './connect.component.html',
})
export class ElementConnectComponent implements OnInit, OnDestroy {
@Output() onNewView: EventEmitter<View> = new EventEmitter<View>();
hasLoginTo = false;
constructor(private _appSvc: AppService,
public _dialog: MatDialog,
public _logger: LogService,
private settingSvc: SettingService,
private activatedRoute: ActivatedRoute,
private _http: HttpService,
) {
}
ngOnInit(): void {
connectEvt.asObservable().subscribe(evt => {
switch (evt.action) {
case 'asset': {
this.Connect(evt.node);
break;
}
case 'sftp': {
this.connectFileManager(evt.node);
break;
}
}
});
const loginTo = this._appSvc.getQueryString('login_to');
const tp = this._appSvc.getQueryString('type') || 'asset';
if (this.hasLoginTo || !loginTo) {
return;
}
switch (tp) {
case 'asset':
this._http.filterMyGrantedAssetsById(loginTo).subscribe(
nodes => {
if (nodes.length === 1) {
this.hasLoginTo = true;
const node = nodes[0];
this.Connect(node);
}
}
);
break;
case 'remote_app':
this._http.getMyGrantedRemoteApps(loginTo).subscribe(
nodes => {
if (nodes.length === 1) {
this.hasLoginTo = true;
const node = nodes[0];
this.Connect(node);
}
}
);
break;
}
}
ngOnDestroy(): void {
connectEvt.unsubscribe();
}
Connect(node: TreeNode) {
switch (node.meta.type) {
case 'asset':
this.connectAsset(node);
break;
case 'remote_app':
this.connectRemoteApp(node);
break;
default:
alert('Unknown type: ' + node.meta.type);
}
}
async connectAsset(node: TreeNode) {
const host = node.meta.asset as Asset;
const systemUsers = await this._http.getMyAssetSystemUsers(host.id).toPromise();
let sysUser = await this.selectLoginSystemUsers(systemUsers);
sysUser = await this.manualSetUserAuthLoginIfNeed(sysUser);
if (sysUser && sysUser.id) {
this.loginAsset(host, sysUser);
} else {
alert('该主机没有授权系统用户');
}
}
selectLoginSystemUsers(systemUsers: Array<SystemUser>): Promise<SystemUser> {
const systemUserMaxPriority = this.filterMaxPrioritySystemUsers(systemUsers);
let user: SystemUser;
if (systemUsers.length > 1) {
return new Promise<SystemUser>(resolve => {
const dialogRef = this._dialog.open(AssetTreeDialogComponent, {
height: '200px',
width: '300px',
data: {users: systemUserMaxPriority}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
for (const i of systemUserMaxPriority) {
if (i.id.toString() === result.toString()) {
user = i;
resolve(user);
}
}
}
return null;
});
});
} else if (systemUserMaxPriority.length === 1) {
user = systemUserMaxPriority[0];
return Promise.resolve(user);
} else {
return Promise.resolve(null);
}
}
manualSetUserAuthLoginIfNeed(user: SystemUser): Promise<SystemUser> {
if (!user || user.login_mode !== 'manual' || user.protocol !== 'rdp' || this.settingSvc.isSkipAllManualPassword()) {
return Promise.resolve(user);
}
user = Object.assign({}, user);
return new Promise(resolve => {
const dialogRef = this._dialog.open(ManualPasswordDialogComponent, {
height: '250px',
width: '500px',
data: {username: user.username}
});
dialogRef.afterClosed().subscribe(result => {
if (!result) {
return resolve(null);
}
if (result.skip) {
return resolve(user);
}
user.username = result.username;
user.password = result.password;
return resolve(user);
});
});
}
async connectRemoteApp(node: TreeNode) {
this._logger.debug('Connect remote app: ', node.id);
const systemUsers = await this._http.getMyRemoteAppSystemUsers(node.id).toPromise();
let sysUser = await this.selectLoginSystemUsers(systemUsers);
sysUser = await this.manualSetUserAuthLoginIfNeed(sysUser);
if (sysUser && sysUser.id) {
return this.loginRemoteApp(node, sysUser);
} else {
alert('该应用没有授权系统用户');
}
}
loginRemoteApp(node: TreeNode, user: SystemUser) {
if (node) {
const view = new View();
view.nick = node.name;
view.connected = true;
view.editable = false;
view.closed = false;
view.remoteApp = node.id;
view.user = user;
view.type = 'rdp';
this.onNewView.emit(view);
}
}
connectFileManager(node: TreeNode) {
const host = node.meta.asset as Asset;
if (host) {
const view = new View();
view.nick = '[FILE] ' + host.hostname;
view.connected = true;
view.editable = false;
view.closed = false;
view.host = host;
view.type = 'sftp';
this.onNewView.emit(view);
// jQuery('.tabs').animate({'scrollLeft': 150 * id}, 400);
}
}
connectTerminal(node: TreeNode) {
this.Connect(node);
}
loginAsset(host: Asset, user: SystemUser) {
if (user) {
const view = new View();
view.nick = host.hostname;
view.connected = true;
view.editable = false;
view.closed = false;
view.host = host;
view.user = user;
if (user.protocol === 'ssh' || user.protocol === 'telnet') {
view.type = 'ssh';
} else if (user.protocol === 'rdp' || user.protocol === 'vnc') {
view.type = 'rdp';
}
this.onNewView.emit(view);
}
}
filterMaxPrioritySystemUsers(sysUsers: Array<SystemUser>): Array<SystemUser> {
const priorityAll: Array<number> = sysUsers.map(s => s.priority);
const maxPriority = Math.max(...priorityAll);
return sysUsers.filter(s => s.priority === maxPriority);
}
}
@Component({
selector: 'elements-asset-tree-dialog',
templateUrl: 'dialog.html',
})
export class AssetTreeDialogComponent implements OnInit {
UserSelectControl = new FormControl('', [Validators.required]);
selected: any;
constructor(public dialogRef: MatDialogRef<AssetTreeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private _logger: LogService) {
}
ngOnInit() {
this.selected = this.data.users[0].id;
this.UserSelectControl.setValue(this.selected);
// this._logger.debug(this.UserSelectControl);
}
onNoClick(): void {
this.dialogRef.close();
}
compareFn: ((f1: any, f2: any) => boolean) | null = this.compareByValue;
compareByValue(f1: any, f2: any) {
return f1 && f2 && f1.value === f2.value;
}
}
@Component({
selector: 'elements-manual-password-dialog',
templateUrl: 'manual-password-dialog.html',
})
export class ManualPasswordDialogComponent implements OnInit {
constructor(@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<ManualPasswordDialogComponent>) {
}
onSkip() {
this.data.skip = true;
this.dialogRef.close(this.data);
}
onSkipAll() {
this.data.skipAll = true;
this.dialogRef.close(this.data);
}
onNoClick() {
this.dialogRef.close();
}
onEnter() {
this.dialogRef.close(this.data);
}
ngOnInit(): void {
}
}
...@@ -5,12 +5,13 @@ ...@@ -5,12 +5,13 @@
</mat-form-field> </mat-form-field>
<mat-form-field style="width: 100%"> <mat-form-field style="width: 100%">
<input matInput [type]="'password'" [(ngModel)]="data.password" <input matInput [type]="'password'" [(ngModel)]="data.password" required
[formControl]="PasswordControl" placeholder="{{'Password'|trans}}" placeholder="{{'Password'|trans}}"
(keyup.enter)="onEnter()" [attr.cdkFocusInitial]="data.username? true : null"> (keyup.enter)="onEnter()" [attr.cdkFocusInitial]="data.username? true : null">
</mat-form-field> </mat-form-field>
<div style="float: right"> <div style="float: right">
<button mat-raised-button (click)="onSkip()" >{{"Skip"|trans}}</button>
<button mat-raised-button (click)="onNoClick()" >{{"Cancel"|trans}}</button> <button mat-raised-button (click)="onNoClick()" >{{"Cancel"|trans}}</button>
<button mat-raised-button color="primary" [type]="'submit'" [mat-dialog-close]="data" >{{"Confirm"|trans}}</button> <button mat-raised-button color="primary" [type]="'submit'" [mat-dialog-close]="data" >{{"Confirm"|trans}}</button>
</div> </div>
......
.tabs { .window {
height: 30px; display: none;
overflow-y: hidden; /*padding: 15px;*/
overflow-x: hidden;
position: relative;
} }
.tabs ul li.disconnected { li.disconnected {
background-color: darkgray; background-color: darkgray;
} }
.tabs ul li.hidden { li.hidden {
display: none; display: none;
} }
.tabs ul { li {
list-style-type: none;
height: 30px;
background-color: #3a3333;
display: block;
min-width: 100%;
padding-left: 0;
}
.tabs ul li {
display: inline-table; display: inline-table;
width: 150px; width: 150px;
height: 30px; height: 30px;
position: relative; position: relative;
box-sizing: content-box; box-sizing: content-box;
float: left;; float: left;;
background-color: #463a3a66;
border-right: 1px solid #6b6565c2;
border-radius: 2px;
} }
.tabs ul li.active { li.active {
box-sizing: border-box; box-sizing: border-box;
border-bottom: 3px solid #19aa8d !important; border-bottom: 3px solid #19aa8d;
} }
.tabs ul li span { li span {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
color: gray; color: #f1f0f0;
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 13px; font-size: 13px;
text-decoration: none; text-decoration: none;
...@@ -48,11 +40,12 @@ ...@@ -48,11 +40,12 @@
padding-right: 14px; padding-right: 14px;
line-height: 26px; line-height: 26px;
cursor: default; cursor: default;
width: 115px; /*width: 145px;*/
height: 21px; height: 26px;
display: block;
} }
.tabs ul li a.close { li a.close {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 13px; font-size: 13px;
position: absolute; position: absolute;
...@@ -64,49 +57,25 @@ ...@@ -64,49 +57,25 @@
display: inline-block; display: inline-block;
} }
.tabs ul li.active a { li.active a {
color: white; color: white;
} }
.tabs ul li.active span { li.active span {
padding-left: 12px; padding-left: 12px;
line-height: 26px; line-height: 26px;
color: white; color: white;
height: 18px; height: 26px;
} }
.tabs ul li input { li input {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 13px; font-size: 13px;
width: 115px; width: 120px;
border: none; border: none;
background-color: inherit; background-color: inherit;
color: white; color: white;
padding: 5px 20px 4px 15px; padding: 5px 20px 4px 15px;
height: 18px; height: 26px;
} }
/*
* scrollbar
*/
.tabs::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #676a6c;
}
.tabs::-webkit-scrollbar {
height: 2px;
}
.tabs::-webkit-scrollbar-thumb {
background-color: #F5F5F5;
}
.scroll-botton {
font-size: 20px;
float: left;
height: 30px;
overflow: hidden;
background-color: #3a3333;
color: white
}
<li
[ngClass]="{'active': view.active,'disconnected': !view.connected, 'hidden': view.closed != false}"
[id]="view.id" (click)="setActive()" (dblclick)="view.editable=true;setActive()"
>
<span *ngIf="view.nick && !view.editable">{{view.nick | truncatechars:25 }}</span>
<input *ngIf="view.editable" [(ngModel)]="view.nick" (blur)="view.editable=false" (keyup.enter)="view.editable=false" autofocus="autofocus"/>
<a class="close" (click)="close()">&times;</a>
</li>
import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
import {View, ViewAction} from '@app/model';
@Component({
selector: 'elements-content-tab',
templateUrl: './content-tab.component.html',
styleUrls: ['./content-tab.component.css'],
})
export class ElementContentTabComponent implements OnInit {
@Input() view: View;
@Output() onAction: EventEmitter<ViewAction> = new EventEmitter<ViewAction>();
ngOnInit(): void {
}
close() {
const action = new ViewAction(this.view, 'close');
this.onAction.emit(action);
}
setActive() {
const action = new ViewAction(this.view, 'active');
this.onAction.emit(action);
}
}
div, elements-term, elements-guacamole, elements-settings { div, elements-term, elements-guacamole {
height: 100%; height: 100%;
} }
elements-term, elements-guacamole, elements-settings { elements-term, elements-guacamole {
/*padding-bottom: 30px;*/ /*padding-bottom: 30px;*/
} }
.window { .window {
display: none; display: none;
height: 100%;
/*padding: 15px;*/ /*padding: 15px;*/
} }
.active { .active {
display: block; display: block;
} }
.right-side {
height: 100%;
width: 100%;
background-color: gray;
}
<div class="window" [ngClass]="{'active':view.active}" style="height: 100%">
<elements-ssh-term
[view]="view"
[host]="view.host"
[sysUser]="view.user"
*ngIf="view.type=='ssh'"
>
</elements-ssh-term>
<elements-guacamole
[view]="view"
[host]="view.host"
[sysUser]="view.user"
[remoteAppId]="view.remoteApp"
*ngIf="view.type=='rdp'"
>
</elements-guacamole>
<app-sftp *ngIf="view.type=='sftp'" [host]="view.host"></app-sftp>
</div>
import {Component, OnInit, Input} from '@angular/core';
import {View} from '@app/model';
@Component({
selector: 'elements-content-window',
templateUrl: './content-window.component.html',
styleUrls: ['./content-window.component.css']
})
export class ElementContentViewComponent implements OnInit {
@Input() view: View;
static active() {
// viewList.List.forEach((v, k) => {
// v.hide = id.toString() !== k;
// });
// viewList.Active = id;
}
static TerminalDisconnect(id) {
// if (viewList.List[id].connected) {
// viewList.List[id].connected = false;
// viewList.List[id].Term.write('\r\n\x1b[31mBye Bye!\x1b[m\r\n');
// TermWS.emit('logout', viewList.List[id].room);
// }
}
static RdpDisconnect(id) {
// viewList.List[id].connected = false;
}
static DisconnectAll() {
}
constructor() {
}
ngOnInit() {
}
// trackByFn(index: number, item: View) {
// return item.id;
// }
}
<div id="content">
<div>
<div class="scroll-button">
<a class="left" (click)="scrollLeft()"><i class="fa fa-caret-left"></i></a>
<a class="right" (click)="scrollRight()"><i class="fa fa-caret-right"></i></a>
</div>
<div class="tabs" #tabs>
<ul [ngStyle]="{'width':tabsWidth+'px'}">
<elements-content-tab
*ngFor="let view of viewList"
[view]="view" (onAction)="onViewAction($event)">
</elements-content-tab>
</ul>
</div>
</div>
<div id="winContainer">
<elements-content-window *ngFor="let view of viewList" [view]="view" ></elements-content-window>
</div>
</div>
<elements-connect [ngStyle]="{'display': 'none'}" (onNewView)="onNewView($event)" ></elements-connect>
.tabs {
height: 30px;
overflow-y: hidden;
overflow-x: hidden;
position: relative;
}
.tabs ul {
list-style-type: none;
height: 30px;
background-color: #3a3333;
//display: block;
display: inline-block;
min-width: 100%;
padding-left: 0;
margin: 0;
}
/*
* scrollbar
*/
.tabs::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #676a6c;
}
.tabs::-webkit-scrollbar {
height: 2px;
}
.tabs::-webkit-scrollbar-thumb {
background-color: #F5F5F5;
}
.scroll-button {
font-size: 20px;
float: left;
height: 30px;
overflow: hidden;
background-color: #3a3333;
color: white;
padding: 0 5px;
}
.scroll-button .right {
padding-left: 5px;
}
.scroll-button a.disabled {
color: #676A6D;
cursor: not-allowed;
}
#content {
height: 100%;
}
#winContainer {
height: calc(100% - 30px);
}
//
//.window {
// height: 100%;
//}
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {View, ViewAction} from '@app/model';
import {ViewService} from '@app/services';
@Component({
selector: 'elements-content',
templateUrl: './content.component.html',
styleUrls: ['./content.component.scss']
})
export class ElementContentComponent implements OnInit {
@ViewChild('tabs') tabsRef: ElementRef;
viewList: Array<View>;
static DisconnectAll() {
}
get tabsWidth() {
return (this.viewList.length + 1) * 151 + 10;
}
constructor(private viewSrv: ViewService) {
}
ngOnInit() {
this.viewList = this.viewSrv.viewList;
}
onNewView(view) {
this.scrollToEnd();
setTimeout(() => {
this.viewSrv.addView(view);
this.setViewActive(view);
}, 100);
}
onViewAction(action: ViewAction) {
switch (action.name) {
case 'active': {
this.setViewActive(action.view);
break;
}
case 'close': {
this.closeView(action.view);
break;
}
}
}
setViewActive(view) {
this.viewSrv.activeView(view);
}
closeView(view) {
let nextActiveView = null;
const index = this.viewList.indexOf(view);
if (view.active) {
// 如果关掉的是最后一个, 存在上一个
if (index === this.viewList.length - 1 && index !== 0) {
nextActiveView = this.viewList[index - 1];
} else if (index < this.viewList.length) {
nextActiveView = this.viewList[index + 1];
}
}
this.viewSrv.removeView(view);
if (nextActiveView) {
this.setViewActive(nextActiveView);
}
}
scrollLeft() {
this.tabsRef.nativeElement.scrollLeft -= 150 * 2;
}
scrollRight() {
this.tabsRef.nativeElement.scrollLeft += 150 * 2;
}
scrollToEnd() {
this.tabsRef.nativeElement.scrollLeft = this.tabsRef.nativeElement.scrollWidth;
}
}
import {Component, Inject, Injectable, OnInit} from '@angular/core'; import {Component, Inject, Injectable, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
import {LogService} from '../../app.service'; import {LogService} from '@app/services';
import {FormControl, Validators} from '@angular/forms'; import {FormControl, Validators} from '@angular/forms';
// import * as layer from 'layui-layer/src/layer.js'; // import * as layer from 'layui-layer/src/layer.js';
......
// Elements // Elements
import {ElementTableComponent} from './table/table.component'; import {ElementLeftBarComponent} from './left-bar/left-bar.component';
import {ElementLeftbarComponent} from './leftbar/leftbar.component'; import {ElementContentComponent} from './content/content.component';
import {ElementOfooterComponent} from './ofooter/ofooter.component'; import {ElementContentViewComponent} from './content-window/content-window.component';
import {ElementFooterComponent} from './footer/footer.component'; import {ElementContentTabComponent} from './content-tab/content-tab.component';
import {ElementAssetTreeComponent} from './asset-tree/asset-tree.component';
import {ElementTreeFilterComponent} from './tree-filter/tree-filter.component';
import {ElementTermComponent} from './term/term.component'; import {ElementTermComponent} from './term/term.component';
import {ElementInteractiveComponent} from './interactive/interactive.component';
import {ChangLanWarningDialogComponent, ElementNavComponent} from './nav/nav.component'; import {ChangLanWarningDialogComponent, ElementNavComponent} from './nav/nav.component';
import {ElementPopupComponent} from './popup/popup.component';
import {ElementRdpComponent} from './rdp/rdp.component'; import {ElementRdpComponent} from './rdp/rdp.component';
import {ElementServerMenuComponent} from './server-menu/server-menu.component';
import {ElementIframeComponent} from './iframe/iframe.component'; 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, ManualPasswordDialogComponent} from './asset-tree/asset-tree.component'; import {ElementConnectComponent, AssetTreeDialogComponent, ManualPasswordDialogComponent} from './connect/connect.component';
import {RDPSolutionDialogComponent, FontDialogComponent} from './nav/nav.component'; import {ElementSftpComponent} from '@app/elements/sftp/sftp.component';
import {ElementSettingComponent} from '@app/elements/setting/setting.component';
export const ElementComponents = [ export const ElementComponents = [
ElementLeftbarComponent, ElementLeftBarComponent,
ElementOfooterComponent, ElementContentComponent,
ElementTableComponent, ElementContentTabComponent,
ElementFooterComponent, ElementContentViewComponent,
ElementConnectComponent,
ElementTreeFilterComponent,
ElementTermComponent, ElementTermComponent,
ElementInteractiveComponent, ElementNavComponent,
ElementNavComponent, ChangLanWarningDialogComponent,
ElementPopupComponent,
ElementRdpComponent, ElementRdpComponent,
ElementServerMenuComponent,
ElementIframeComponent, ElementIframeComponent,
ElementDialogAlertComponent, ElementDialogAlertComponent,
ElementGuacamoleComponent, ElementGuacamoleComponent,
ElementAssetTreeComponent, ElementAssetTreeComponent,
ElementSshTermComponent, ElementSshTermComponent,
ElementConnectComponent,
ElementSftpComponent,
AssetTreeDialogComponent, AssetTreeDialogComponent,
ChangLanWarningDialogComponent,
ManualPasswordDialogComponent, ManualPasswordDialogComponent,
RDPSolutionDialogComponent, ElementSettingComponent
FontDialogComponent
]; ];
<div class="footer fixed">
<div class="pull-right">
Version <strong>{{version}}</strong> GPL.
</div>
<div>
<strong>Copyright</strong> Jumpserver.org Team &copy; 2014-2018
</div>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementFooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: ElementFooterComponent;
let fixture: ComponentFixture<ElementFooterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementFooterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementFooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
/**
* footer
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, OnInit} from '@angular/core';
import {AppService, LogService} from '../../app.service';
import {DataStore, User} from '../../globals';
import {version} from '../../../environments/environment';
@Component({
selector: 'elements-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.css']
})
export class ElementFooterComponent implements OnInit {
DataStore = DataStore;
User = User;
version = version;
constructor(private _appService: AppService,
private _logger: LogService) {
this._logger.log('nav.ts:NavComponent');
// this._appService.getnav()
}
ngOnInit() {
}
}
<!--<iframe #rdp [src]="trust('https://inews.gtimg.com/newsapp_bt/0/3460971429/1000')" width="100%" height="100%" (mouseenter)="active()"></iframe>--> <div class="rdpIframe">
<iframe *ngIf="target" #rdp [src]="trust(target)" width="100%" height="100%" (mouseenter)="active()"></iframe> <iframe #rdpRef *ngIf="target" [src]="trust(target)" width="100%" height="100%" (mouseenter)="active()"></iframe>
</div>
...@@ -2,3 +2,7 @@ iframe { ...@@ -2,3 +2,7 @@ iframe {
border: none; border: none;
background-color: white; background-color: white;
} }
.rdpIframe {
height: 100%;
}
import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core'; import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
import {CookieService} from 'ngx-cookie-service'; import {CookieService} from 'ngx-cookie-service';
import {HttpService, LogService} from '../../app.service'; import {HttpService, LogService} from '@app/services';
import {DataStore, User} from '../../globals'; import {DataStore, User} from '@app/globals';
import {DomSanitizer} from '@angular/platform-browser'; import {DomSanitizer} from '@angular/platform-browser';
import {environment} from '../../../environments/environment'; import {View} from '@app/model';
import {NavList} from '../../pages/control/control/control.component';
@Component({ @Component({
selector: 'elements-guacamole', selector: 'elements-guacamole',
...@@ -12,12 +11,13 @@ import {NavList} from '../../pages/control/control/control.component'; ...@@ -12,12 +11,13 @@ import {NavList} from '../../pages/control/control/control.component';
styleUrls: ['./guacamole.component.scss'] styleUrls: ['./guacamole.component.scss']
}) })
export class ElementGuacamoleComponent implements OnInit { export class ElementGuacamoleComponent implements OnInit {
@Input() view: View;
@Input() host: any; @Input() host: any;
@Input() sysUser: any; @Input() sysUser: any;
@Input() remoteAppId: string; @Input() remoteAppId: string;
@Input() target: string; @Input() target: string;
@Input() index: number; @Input() index: number;
@ViewChild('rdp') el: ElementRef; @ViewChild('rdpRef') el: ElementRef;
registered = false; registered = false;
constructor(private sanitizer: DomSanitizer, constructor(private sanitizer: DomSanitizer,
...@@ -28,20 +28,20 @@ export class ElementGuacamoleComponent implements OnInit { ...@@ -28,20 +28,20 @@ export class ElementGuacamoleComponent implements OnInit {
registerHost() { registerHost() {
let action: any; let action: any;
console.log(this.sysUser);
if (this.remoteAppId) { if (this.remoteAppId) {
action = this._http.guacamole_add_remote_app(User.id, this.remoteAppId); action = this._http.guacamoleAddRemoteApp(User.id, this.remoteAppId, this.sysUser.id, this.sysUser.username, this.sysUser.password);
} else { } else {
action = this._http.guacamole_add_asset(User.id, this.host.id, this.sysUser.id, this.sysUser.username, this.sysUser.password); action = this._http.guacamoleAddAsset(User.id, this.host.id, this.sysUser.id, this.sysUser.username, this.sysUser.password);
} }
action.subscribe( action.subscribe(
data => { data => {
const base = data.result; const base = data.result;
this.target = document.location.origin + '/guacamole/#/client/' + base + '?token=' + DataStore.guacamole_token; this.target = document.location.origin + '/guacamole/#/client/' + base + '?token=' + DataStore.guacamoleToken;
NavList.List[this.index].Rdp = this.el.nativeElement;
}, },
error => { error => {
if (!this.registered) { if (!this.registered) {
console.log('Register host error, register token then connect'); this._logger.debug('Register host error, register token then connect');
this.registerToken(); this.registerToken();
} }
} }
...@@ -52,11 +52,12 @@ export class ElementGuacamoleComponent implements OnInit { ...@@ -52,11 +52,12 @@ export class ElementGuacamoleComponent implements OnInit {
const now = new Date(); const now = new Date();
const nowTime = now.getTime() / 1000; const nowTime = now.getTime() / 1000;
this.registered = true; this.registered = true;
this._http.get_guacamole_token(User.id, '').subscribe( this._logger.debug('User id is', User.id);
this._http.getGuacamoleToken(User.id, '').subscribe(
data => { data => {
// /guacamole/client will redirect to http://guacamole/#/client // /guacamole/client will redirect to http://guacamole/#/client
DataStore.guacamole_token = data['authToken']; DataStore.guacamoleToken = data['authToken'];
DataStore.guacamole_token_time = nowTime; DataStore.guacamoleTokenTime = nowTime;
this.registerHost(); this.registerHost();
}, },
error => { error => {
...@@ -68,8 +69,8 @@ export class ElementGuacamoleComponent implements OnInit { ...@@ -68,8 +69,8 @@ export class ElementGuacamoleComponent implements OnInit {
ngOnInit() { ngOnInit() {
// /guacamole/api/tokens will redirect to http://guacamole/api/tokens // /guacamole/api/tokens will redirect to http://guacamole/api/tokens
this.view.type = 'rdp';
if (this.target) { if (this.target) {
NavList.List[this.index].Rdp = this.el.nativeElement;
return null; return null;
} }
...@@ -85,10 +86,6 @@ export class ElementGuacamoleComponent implements OnInit { ...@@ -85,10 +86,6 @@ export class ElementGuacamoleComponent implements OnInit {
return this.sanitizer.bypassSecurityTrustResourceUrl(url); return this.sanitizer.bypassSecurityTrustResourceUrl(url);
} }
Disconnect() {
NavList.List[this.index].connected = false;
}
active() { active() {
this.el.nativeElement.focus(); this.el.nativeElement.focus();
} }
......
import {Component, Input, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser'; import {DomSanitizer} from '@angular/platform-browser';
import {NavList} from '../../pages/control/control/control.component';
import {User, DataStore} from '../../globals';
import {HttpService, LogService} from '../../app.service';
import {environment} from '../../../environments/environment';
import {CookieService} from 'ngx-cookie-service';
@Component({ @Component({
selector: 'elements-iframe', selector: 'elements-iframe',
...@@ -13,15 +8,9 @@ import {CookieService} from 'ngx-cookie-service'; ...@@ -13,15 +8,9 @@ import {CookieService} from 'ngx-cookie-service';
styleUrls: ['./iframe.component.scss'] styleUrls: ['./iframe.component.scss']
}) })
export class ElementIframeComponent implements OnInit { export class ElementIframeComponent implements OnInit {
@Input() host: any;
@Input() userid: any;
@Input() index: number;
target: string; target: string;
constructor(private sanitizer: DomSanitizer, constructor(private sanitizer: DomSanitizer) {
private _http: HttpService,
private _cookie: CookieService,
private _logger: LogService) {
} }
ngOnInit() { ngOnInit() {
...@@ -30,8 +19,4 @@ export class ElementIframeComponent implements OnInit { ...@@ -30,8 +19,4 @@ export class ElementIframeComponent implements OnInit {
trust(url) { trust(url) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url); return this.sanitizer.bypassSecurityTrustResourceUrl(url);
} }
Disconnect() {
NavList.List[this.index].connected = false;
}
} }
<div class="menu">
<input type="checkbox" href="#" class="menu-open" name="menu-open" id="menu-open"/>
<label class="menu-open-button" for="menu-open">
<span class="hamburger hamburger-1"></span>
<span class="hamburger hamburger-2"></span>
<span class="hamburger hamburger-3"></span>
</label>
<a href="#" class="menu-item"> <i class="fa fa-bar-chart"></i> </a>
<a href="#" class="menu-item"> <i class="fa fa-plus"></i> </a>
<a href="#" class="menu-item"> <i class="fa fa-heart"></i> </a>
<a href="#" class="menu-item"> <i class="fa fa-envelope"></i> </a>
</div>
@import "~sass-math/math";
//vars
$fg: #ff4081;
$bg: #3f51b5;
$pi: pi();
//config
$menu-items: 5;
$open-distance: 115px;
$opening-angle: $pi - .2;
%goo {
filter: url('#shadowed-goo');
// debug
//background:rgba(255,0,0,0.2);
}
%ball {
background: $fg;
border-radius: 100%;
width: 80px;
height: 80px;
margin-left: -40px;
position: absolute;
top: 20px;
color: white;
text-align: center;
line-height: 80px;
transform: translate3d(0, 0, 0);
transition: transform ease-out 200ms;
}
.menu-open {
display: none;
}
.menu-item {
@extend %ball;
}
.hamburger {
$width: 25px;
$height: 3px;
width: $width;
height: $height;
background: white;
display: block;
position: absolute;
top: 50%;
left: 50%;
margin-left: -$width/2;
margin-top: -$height/2;
transition: transform 200ms;
}
$hamburger-spacing: 8px;
.hamburger-1 {
transform: translate3d(0, -$hamburger-spacing, 0);
}
.hamburger-2 {
transform: translate3d(0, 0, 0);
}
.hamburger-3 {
transform: translate3d(0, $hamburger-spacing, 0);
}
.menu-open:checked + .menu-open-button {
.hamburger-1 {
transform: translate3d(0, 0, 0) rotate(45deg);
}
.hamburger-2 {
transform: translate3d(0, 0, 0) scale(0.1, 1);
}
.hamburger-3 {
transform: translate3d(0, 0, 0) rotate(-45deg);
}
}
.menu {
@extend %goo;
$width: 380px;
$height: 250px;
position: absolute;
left: 50%;
margin-left: -$width/2;
padding-top: 20px;
padding-left: $width/2;
width: $width;
height: $height;
box-sizing: border-box;
font-size: 20px;
text-align: left;
}
.menu-item {
&:hover {
background: white;
color: $fg;
}
@for $i from 1 through $menu-items {
&:nth-child(#{$i+2}) {
transition-duration: 10ms+(60ms*($i));
}
}
}
.menu-open-button {
@extend %ball;
z-index: 2;
transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.275);
transition-duration: 400ms;
transform: scale(1.1, 1.1) translate3d(0, 0, 0);
cursor: pointer;
}
.menu-open-button:hover {
transform: scale(1.2, 1.2) translate3d(0, 0, 0);
}
.menu-open:checked + .menu-open-button {
transition-timing-function: linear;
transition-duration: 200ms;
transform: scale(0.8, 0.8) translate3d(0, 0, 0);
}
.menu-open:checked ~ .menu-item {
transition-timing-function: cubic-bezier(0.935, 0.000, 0.340, 1.330);
@for $i from 1 through $menu-items {
$angle: (($pi - $opening-angle)/2)+(($opening-angle/($menu-items - 1))*($i - 1)+($pi*0.43));
&:nth-child(#{$i+2}) {
transition-duration: 80ms+(80ms*$i);
transform: translate3d(sin($angle)*$open-distance, cos($angle)*$open-distance, 0);
}
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementInteractiveComponent } from './interactive.component';
describe('ElementInteractiveComponent', () => {
let component: ElementInteractiveComponent;
let fixture: ComponentFixture<ElementInteractiveComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementInteractiveComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementInteractiveComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'elements-interactive',
templateUrl: './interactive.component.html',
styleUrls: ['./interactive.component.scss']
})
export class ElementInteractiveComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
<div class="sidebar" fxLayout="column" >
<div fxflex="30px" class="tree-filter">
<elements-tree-filter ></elements-tree-filter>
</div>
<div fxflex="calc(100% - 60px)" class="overflow ngx-scroll-overlay">
<elements-asset-tree ></elements-asset-tree>
</div>
<div fxflex="30px" class="footer-version">
<p> Version <strong>{{version}}</strong></p>
</div>
</div>
.sidebar {
height: 100%;
width: 100%;
overflow: auto;
}
label {
margin-bottom: 0;
}
.fa.fa-undefined:before {
content: "\f26c";
}
.overflow {
display: flex;
width: 100%;
height: 100%;
float: left;
position: inherit;
background: #2f2a2a;
color: #d6cbcb;
}
.footer-version {
background: #2f2a2a;
font-size: 9pt;
left: 0;
width: 100%;
padding: 1px 20px 0 20px;
border-top: 1px solid #e7eaec;
bottom: 0;
height: 30px;
}
.footer-version > p {
height: 8px;
font-size: 13px;
padding-top: 2px;
padding-bottom: 2px;
color: #d6cbcb;
}
.ngx-scroll-overlay {
overflow: auto; // for FF
}
import {Component} from '@angular/core';
import {DataStore} from '@app/globals';
import {version} from '@src/environments/environment';
@Component({
selector: 'elements-left-bar',
templateUrl: './left-bar.component.html',
styleUrls: ['./left-bar.component.scss'],
})
export class ElementLeftBarComponent {
DataStore = DataStore;
version = version;
static Hide() {
DataStore.showLeftBar = false;
window.dispatchEvent(new Event('resize'));
}
static Show() {
DataStore.showLeftBar = true;
window.dispatchEvent(new Event('resize'));
}
}
<mat-sidenav-container>
<mat-sidenav #sidenav mode="side" opened="true" class="example-sidenav"
[fixedInViewport]="options.value.fixed" [fixedTopGap]="options.value.top"
[fixedBottomGap]="options.value.bottom">
<nav>
<div class="header">
<a href="http://www.jumpserver.org" target="_blank">
<img alt="image" height="55" src="/static/imgs/logo-text.png" style="margin-left: 10px">
</a>
</div>
<div class="body">
<ul class="nav metismenu nav-frist-level">
<li *ngFor="let bar of leftbar;let i = index" [ngClass]="{'active':i==active}">
<a (click)="gotoLink(bar.link,i,-1)">
<i class="{{bar.class}}"></i>
<span class="nav-label">{{bar.name|trans}}</span>
<span class="{{bar.label}}"></span>
</a>
<ul class="nav nav-second-level collapse">
<li *ngFor="let child of bar.child;let ii = index" [ngClass]="{'active':ii==active2}">
<a (click)="gotoLink(child.link,i,ii)">{{child.name|trans}}</a>
</li>
</ul>
</li>
</ul>
</div>
</nav>
</mat-sidenav>
<div class="navbar-header">
<button class="navbar-minimalize minimalize-styl-2 btn btn-primary " (click)="sidenav.toggle()"><i
class="fa fa-bars"></i></button>
</div>
</mat-sidenav-container>
li a {
background-color: rgba(0, 0, 0, 0);
box-sizing: border-box;
color: rgb(167, 177, 194);
cursor: pointer;
display: block;
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
font-weight: 600;
height: 46px;
line-height: 18.5714px;
list-style-image: none;
list-style-position: outside;
list-style-type: none;
padding-bottom: 14px;
padding-left: 25px;
padding-right: 20px;
padding-top: 14px;
position: relative;
text-align: left;
text-decoration-color: rgb(167, 177, 194);
text-decoration-line: none;
text-decoration-style: solid;
text-size-adjust: 100%;
width: 220px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
li.active > a {
width: 216px;
color: white;
}
li.active ul.nav.nav-second-level li {;
border-bottom-style: none;
border-bottom-width: 0px;
box-sizing: border-box;
color: rgb(103, 106, 108);
display: block;
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
height: 32px;
line-height: 18.5714px;
list-style-image: none;
list-style-position: outside;
list-style-type: none;
position: relative;
text-align: left;
text-size-adjust: 100%;
visibility: visible;
width: 216px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
li.active ul.nav.nav-second-level {
box-sizing: border-box;
color: rgb(103, 106, 108);
display: block;
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 18.5714px;
list-style-image: none;
list-style-position: outside;
list-style-type: none;
margin-bottom: 0px;
margin-top: 0px;
padding-left: 0px;
text-align: left;
text-size-adjust: 100%;
visibility: visible;
width: 216px;
-webkit-margin-after: 0px;
-webkit-margin-before: 0px;
-webkit-margin-end: 0px;
-webkit-margin-start: 0px;
-webkit-padding-start: 0px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
mat-sidenav {
background-color: #2f4050;
}
nav {
width: 220px;
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
}
div.header {
background-color: #202c37;
}
div.body {
background-color: #2f4050;
height: 100%;
}
li:hover {
color: white !important;
}
.nav-second-level li a {
padding: 7px 10px 7px 52px;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementLeftbarComponent } from './leftbar.component';
describe('ElementLeftbarComponent', () => {
let component: ElementLeftbarComponent;
let fixture: ComponentFixture<ElementLeftbarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementLeftbarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementLeftbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';
import {Router} from '@angular/router';
@Component({
selector: 'elements-leftbar',
templateUrl: './leftbar.component.html',
styleUrls: ['./leftbar.component.scss']
})
export class ElementLeftbarComponent implements OnInit {
leftbar = [
{
'name': 'Dashboard',
'class': 'fa fa-dashboard',
'label': '',
'link': '/'
},
{
'name': 'Users',
'class': 'fa fa-group',
'label': 'fa arrow',
'child': [
{
'name': 'User',
}, {
'name': 'User group',
}, {
'name': 'Login logs',
}
]
},
{
'name': 'Assets',
'class': 'fa fa-inbox',
'label': 'fa arrow',
'child': [
{
'name': 'Asset',
}, {
'name': 'Asset group',
}, {
'name': 'Cluster',
}, {
'name': 'Admin user',
}, {
'name': 'System user',
}, {
'name': 'Labels',
}
]
},
{
'name': 'Perms',
'class': 'fa fa-edit',
'label': 'fa arrow',
'child': [{'name': 'Asset permission'}]
},
{
'name': 'Sessions',
'class': 'fa fa-rocket',
'label': 'fa arrow',
'child': [
{
'name': 'Session online'
},
{
'name': 'Session offline'
},
{
'name': 'Commands'
},
{
'name': 'Terminal'
},
]
},
{
'name': 'Job Center',
'class': 'fa fa-coffee',
'label': 'fa arrow',
'child': [
{
'name': 'Task',
'link': '/task'
},
]
},
{
'name': 'Settings',
'class': 'fa fa-gears',
'label': '',
'link': '/luna/setting'
},
];
options: FormGroup;
active: number;
active2: number;
constructor(fb: FormBuilder,
private _router: Router) {
this.options = fb.group({
'fixed': true,
'top': 0,
'bottom': 0,
});
}
ngOnInit() {
this.active = 6;
this.active2 = 0;
}
gotoLink(link: string, index: number, index2: number) {
if (link) {
if (link === '/luna/setting') {
this._router.navigate(['setting']);
} else {
window.location.href = link;
}
}
this.active = index;
this.active2 = index2;
}
}
<h1 mat-dialog-title>{{"Set font"|trans}}</h1>
<mat-form-field>
<input matInput placeholder='{{"Font size"|trans}}' name="fontSize" type="number" min="5" max="60" [(ngModel)]="fontSize">
</mat-form-field>
<div style="float: right">
<button mat-raised-button (click)="onNoClick()">{{"Cancel"|trans}}</button>
<button mat-raised-button color="primary" [disabled]="!isValid()" (click)="onSubmit()">{{"Confirm"|trans}}</button>
</div>
.nav { .nav {
display: block; display: block;
margin-top: 2px; height: 30px;
height: 30px padding-top: 0;
background-color: #463e3e;
list-style: none;
} }
.nav ul { .nav ul {
list-style-type: none; list-style-type: none;
line-height: 24px; line-height: 24px;
margin: 0;
box-sizing: border-box;
font-weight: 400;
text-align: left;
} }
.nav li { .nav li {
display: inline-block; display: inline-block;
} }
.nav a { .nav .dropdown a {
color: #f0f0f1; color: #f0f0f1;
font-family: Roboto,sans-serif;
font-size: 13px;
font-weight: 300;
text-decoration: none; text-decoration: none;
padding: 6px 15px 6px 15px; padding: 6px 15px;
height: 30px;
} }
.nav a:hover { .nav a:hover {
...@@ -46,9 +56,9 @@ ...@@ -46,9 +56,9 @@
direction: ltr; direction: ltr;
width: auto; width: auto;
top: auto; top: auto;
left: 0px; left: 0;
margin-left: 0px; margin-left: 0;
margin-top: 0px; margin-top: 0;
min-width: 150px; min-width: 150px;
} }
...@@ -57,11 +67,11 @@ ...@@ -57,11 +67,11 @@
} }
.nav .dropdown-content li { .nav .dropdown-content li {
float: left; /*float: left;*/
display: flex; display: flex;
} }
.nav .dropdown-content li a { .nav .dropdown-content a {
padding: 6px 14px 6px 25px; padding: 6px 14px 6px 25px;
white-space: nowrap; white-space: nowrap;
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
...@@ -74,7 +84,7 @@ ...@@ -74,7 +84,7 @@
} }
.nav .dropdown-content li a span { .nav .dropdown-content li a span {
float: right; /*float: right;*/
} }
.dropdown-content li:hover { .dropdown-content li:hover {
...@@ -95,3 +105,19 @@ ...@@ -95,3 +105,19 @@
cursor: default; cursor: default;
color: #c5babc; color: #c5babc;
} }
.nav ul li.disconnected {
background-color: darkgray;
}
.flag {
display: none;
color: #19aa8d;
padding-right: 10px;
}
.active .flag {
display: inline;
}
<script src="../../trans.pipe.spec.ts"></script>
<div class="nav"> <div class="nav">
<ul> <ul class="nav-main">
<li><a href="/"><img src="static/imgs/logo.png" height="26px"/></a> <li style="padding-right: 10px">
<a href="/"><img src="static/imgs/logo.png" height="26px"/></a>
</li> </li>
<li *ngFor="let v of DataStore.Nav" [ngClass]="{'dropdown': v.children}"> <li *ngFor="let v of navs" [ngClass]="{'dropdown': v.children}" >
<a>{{v.name|trans}}</a> <a>{{v.name|trans}}</a>
<ul [ngClass]="{'dropdown-content': v.children}"> <ul [ngClass]="{'dropdown-content': v.children}" *ngIf="v.children">
<li *ngFor="let vv of v.children" [ngClass]="{'disabled': vv.disable}"> <li *ngFor="let vv of v.children" [ngClass]="{'disabled': vv.disable}">
<a *ngIf="vv.href" [routerLink]="[vv.href]">{{vv.name|trans}}</a> <a *ngIf="vv.href" [routerLink]="[vv.href]">{{vv.name|trans}}</a>
<a id="{{vv.id}}" *ngIf="vv.click" (click)="click(vv.click)">{{vv.name|trans}}</a> <a id="{{vv.id}}" *ngIf="vv.click && !vv.hide" (click)="click(vv.click)">{{vv.name|trans}}</a>
</li> </li>
</ul> </ul>
</li> </li>
<li [ngClass]="{'dropdown': true}">
<a>{{"Tab List"|trans}}</a>
<ul *ngIf="viewList.length > 0" [ngClass]="{'dropdown-content': true}">
<ng-container *ngFor="let v of viewList, let idx = index" >
<li *ngIf="v.nick!=null" [ngClass]="{'disconnected':!v.connected, 'hidden': v.closed != false}">
<span [class.active]="v.active">
<a id="{{ 'tab' + idx }}" (click)="_viewSrv.activeView(v)"><i class="fa fa-circle flag"></i> {{v.nick}} </a>
</span>
</li>
</ng-container>
</ul>
</li>
</ul> </ul>
</div> </div>
/**
* 主页导航条
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, Inject, OnInit} from '@angular/core'; import {Component, Inject, OnInit} from '@angular/core';
import {AppService, HttpService, LocalStorageService, LogService} from '../../app.service'; import {HttpService, LocalStorageService, NavService, LogService, ViewService} from '@app/services';
import {CleftbarComponent} from '../../pages/control/cleftbar/cleftbar.component'; import {DataStore, i18n} from '@app/globals';
import {ControlComponent, NavList, View} from '../../pages/control/control/control.component'; import {ElementLeftBarComponent} from '@app/elements/left-bar/left-bar.component';
import {DataStore, i18n} from '../../globals'; import {ElementSettingComponent} from '@app/elements/setting/setting.component';
import * as jQuery from 'jquery/dist/jquery.min.js';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
declare let layer: any; import {Nav, View} from '@app/model';
@Component({ @Component({
selector: 'elements-nav', selector: 'elements-nav',
...@@ -21,45 +13,49 @@ declare let layer: any; ...@@ -21,45 +13,49 @@ declare let layer: any;
}) })
export class ElementNavComponent implements OnInit { export class ElementNavComponent implements OnInit {
DataStore = DataStore; DataStore = DataStore;
ChangeLanWarningDialog: any; navs: Array<Nav>;
_asyncTree = false;
viewList: Array<View>;
static Hide() { constructor(private _http: HttpService,
jQuery('elements-nav').hide();
}
constructor(private _appService: AppService,
private _http: HttpService,
private _logger: LogService, private _logger: LogService,
public _dialog: MatDialog, public _dialog: MatDialog,
public _navSvc: NavService,
public _viewSrv: ViewService,
private _localStorage: LocalStorageService) { private _localStorage: LocalStorageService) {
this._logger.log('nav.ts:NavComponent');
this.getNav();
} }
ngOnInit() { ngOnInit() {
this.navs = this.getNav();
this.viewList = this._viewSrv.viewList;
}
get treeLoadAsync() {
return this._asyncTree;
}
set treeLoadAsync(value) {
this._asyncTree = value;
} }
click(event) { click(event) {
this._logger.debug('nav.ts:NavComponent,click', event);
switch (event) { switch (event) {
case 'ReloadLeftbar': {
CleftbarComponent.Reload();
break;
}
case 'ConnectSFTP': { case 'ConnectSFTP': {
window.open('/coco/elfinder/sftp/'); window.open('/coco/elfinder/sftp/');
break; break;
} }
case 'HideLeft': { case 'HideLeft': {
CleftbarComponent.Hide(); ElementLeftBarComponent.Hide();
this.refreshNav();
break; break;
} }
case 'Settings': { case 'ShowLeft': {
this.Settings(); ElementLeftBarComponent.Show();
this.refreshNav();
break; break;
} }
case 'ShowLeft': { case 'Setting': {
CleftbarComponent.Show(); this.Setting();
break; break;
} }
case 'Copy': { case 'Copy': {
...@@ -67,8 +63,10 @@ export class ElementNavComponent implements OnInit { ...@@ -67,8 +63,10 @@ export class ElementNavComponent implements OnInit {
break; break;
} }
case 'FullScreen': { case 'FullScreen': {
let ele:any = document.getElementsByClassName("window active ")[0]; const ele: any = document.getElementsByClassName('window active')[0];
if (!ele) {
return;
}
if (ele.requestFullscreen) { if (ele.requestFullscreen) {
ele.requestFullscreen(); ele.requestFullscreen();
} else if (ele.mozRequestFullScreen) { } else if (ele.mozRequestFullScreen) {
...@@ -80,35 +78,24 @@ export class ElementNavComponent implements OnInit { ...@@ -80,35 +78,24 @@ export class ElementNavComponent implements OnInit {
} else { } else {
throw new Error('不支持全屏api'); throw new Error('不支持全屏api');
} }
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
break; break;
} }
case'Disconnect': { case 'Reconnect': {
break;
}
case 'Disconnect': {
if (!confirm('断开当前连接? (RDP暂不支持)')) { if (!confirm('断开当前连接? (RDP暂不支持)')) {
break; break;
} }
switch (NavList.List[NavList.Active].type) { this._navSvc.disconnectConnection();
case 'ssh': {
ControlComponent.TerminalDisconnect(NavList.Active);
break;
}
case 'rdp': {
ControlComponent.RdpDisconnect(NavList.Active);
break;
}
default: {
// statements;
break;
}
}
break; break;
} }
case'DisconnectAll': { case'DisconnectAll': {
if (!confirm('断开所有连接? (RDP暂不支持)')) { if (!confirm('断开所有连接?')) {
break; break;
} }
ControlComponent.DisconnectAll(); this._navSvc.disconnectAllConnection();
break; break;
} }
case 'Website': { case 'Website': {
...@@ -123,38 +110,6 @@ export class ElementNavComponent implements OnInit { ...@@ -123,38 +110,6 @@ export class ElementNavComponent implements OnInit {
window.open('https://market.aliyun.com/products/53690006/cmgj026011.html?spm=5176.730005.0.0.cY2io1'); window.open('https://market.aliyun.com/products/53690006/cmgj026011.html?spm=5176.730005.0.0.cY2io1');
break; break;
} }
case 'SetResolution': {
const dialog = this._dialog.open(
RDPSolutionDialogComponent,
{
height: '200px',
width: '300px'
});
dialog.afterClosed().subscribe(result => {
if (result) {
console.log(result);
}
});
break;
}
case 'SetFont': {
const dialog = this._dialog.open(
FontDialogComponent,
{
height: '200px',
width: '300px'
});
dialog.afterClosed().subscribe(result => {
if (result) {
console.log(result);
}
});
break;
}
case 'EnterLicense': {
this.EnterLicense();
break;
}
case 'English': { case 'English': {
const dialog = this._dialog.open( const dialog = this._dialog.open(
ChangLanWarningDialogComponent, ChangLanWarningDialogComponent,
...@@ -202,146 +157,110 @@ export class ElementNavComponent implements OnInit { ...@@ -202,146 +157,110 @@ export class ElementNavComponent implements OnInit {
} }
EnterLicense() { refreshNav() {
layer.prompt({ this.navs = this.getNav();
formType: 2,
maxlength: 500,
title: 'Please Input Code',
scrollbar: false,
area: ['400px', '300px'],
moveOut: true,
moveType: 1
}, function (value, index) {
DataStore.socket.emit('key', value);
// layer.msg(value); //得到value
layer.close(index);
});
} }
getNav() { getNav() {
DataStore.Nav = [{ return [{
'id': 'File', id: 'FileManager',
'name': 'Server', name: 'File Manager',
'children': [ children: [
{
'id': 'Disconnect',
'click': 'Disconnect',
'name': 'Disconnect'
},
{ {
'id': 'DisconnectAll', id: 'Connect',
'click': 'DisconnectAll', click: 'ConnectSFTP',
'name': 'Disconnect all' name: 'Connect'
}, },
] ]
}, { }, {
'id': 'FileManager', id: 'View',
'name': 'File Manager', name: 'View',
'children': [ children: [
{
'id': 'Connect',
'click': 'ConnectSFTP',
'name': 'Connect'
},
]
},
{
'id': 'View',
'name': 'View',
'children': [
{ {
'id': 'HideLeftManager', id: 'HideLeftManager',
'click': 'HideLeft', click: 'HideLeft',
'name': 'Hide left manager' name: 'Hide left manager',
hide: !DataStore.showLeftBar
}, },
{ {
'id': 'RDPResolution', id: 'ShowLeftManager',
'click': 'SetResolution', click: 'ShowLeft',
'name': 'RDP Resolution' name: 'Show left manager',
hide: DataStore.showLeftBar
}, },
{ {
'id': 'Font', id: 'SplitVertical',
'click': 'SetFont', href: '',
'name': 'Font' name: 'Split vertical',
disable: true
}, },
{ {
'id': 'SplitVertical', id: 'CommandBar',
'href': '', href: '',
'name': 'Split vertical', name: 'Command bar',
'disable': true disable: true
}, },
{ {
'id': 'CommandBar', id: 'ShareSession',
'href': '', href: '',
'name': 'Command bar', name: 'Share session (read/write)',
'disable': true disable: true
}, },
{ {
'id': 'ShareSession', id: 'FullScreen',
'href': '', click: 'FullScreen',
'name': 'Share session (read/write)', name: 'Full Screen'
'disable': true
}, },
{ ]
'id': 'FullScreen',
'click': 'FullScreen',
'name': 'Full Screen'
}
]
}, { }, {
'id': 'Help', id: 'Help',
'name': 'Help', name: 'Help',
'children': [ children: [
{ {
'id': 'Website', id: 'Website',
'click': 'Website', click: 'Website',
'name': 'Website' name: 'Website'
}, },
{ {
'id': 'Document', id: 'Document',
'click': 'Document', click: 'Document',
'name': 'Document' name: 'Document'
}, },
{ {
'id': 'Support', id: 'Support',
'click': 'Support', click: 'Support',
'name': 'Support' name: 'Support'
}] }]
}, { }, {
'id': 'Language', id: 'Language',
'name': 'Language', name: 'Language',
'children': [ children: [
{ {
'id': 'English', id: 'English',
'click': 'English', click: 'English',
'name': 'English' name: 'English'
}, },
{ {
'id': 'Chinese', id: 'Chinese',
'click': 'Chinese', click: 'Chinese',
'name': '中文' name: '中文'
}
]
}, {
id: 'Setting',
name: 'Setting',
click: 'Setting',
children: [
{
id: 'Setting',
click: 'Setting',
name: 'Setting'
} }
] ]
} }
]; ];
} }
Connect() {
layer.prompt({
formType: 2,
maxlength: 500,
title: 'Please Input Code',
scrollbar: false,
area: ['400px', '300px'],
moveOut: true,
moveType: 1
}, function (value, index) {
DataStore.socket.emit('key', value);
layer.close(index);
});
}
English() { English() {
this._localStorage.delete('lang'); this._localStorage.delete('lang');
i18n.clear(); i18n.clear();
...@@ -364,15 +283,17 @@ export class ElementNavComponent implements OnInit { ...@@ -364,15 +283,17 @@ export class ElementNavComponent implements OnInit {
location.reload(); location.reload();
} }
Settings() { Setting() {
const id = NavList.List.length - 1; const dialog = this._dialog.open(
NavList.List[id].nick = 'Setting'; ElementSettingComponent,
NavList.List[id].connected = true; {
NavList.List[id].edit = false; height: '370px',
NavList.List[id].closed = false; width: '400px',
NavList.List[id].type = 'settings'; });
NavList.List.push(new View()); dialog.afterClosed().subscribe(result => {
NavList.Active = id; if (result) {
}
});
} }
} }
...@@ -396,71 +317,3 @@ export class ChangLanWarningDialogComponent implements OnInit { ...@@ -396,71 +317,3 @@ export class ChangLanWarningDialogComponent implements OnInit {
} }
} }
@Component({
selector: 'elements-rdp-solution-dialog',
templateUrl: 'rdpSolutionDialog.html',
styles: ['.mat-form-field { width: 100%; }']
})
export class RDPSolutionDialogComponent implements OnInit {
solutions = ['Auto', '1024x768', '1366x768', '1400x900'];
solution: string;
cacheKey = 'rdpSolution';
constructor(public dialogRef: MatDialogRef<RDPSolutionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
}
ngOnInit() {
this.solution = localStorage.getItem(this.cacheKey) || 'Auto';
}
setSolution(value: string) {
localStorage.setItem(this.cacheKey, value);
}
onSubmit() {
this.setSolution(this.solution);
this.dialogRef.close();
}
onNoClick(): void {
this.dialogRef.close();
}
}
@Component({
selector: 'elements-font-size-dialog',
templateUrl: 'fontDialog.html',
styles: ['.mat-form-field { width: 100%; }']
})
export class FontDialogComponent implements OnInit {
fontSize: string;
solution: string;
cacheKey = 'fontSize';
constructor(public dialogRef: MatDialogRef<FontDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
}
ngOnInit() {
this.fontSize = localStorage.getItem(this.cacheKey) || '14';
}
setFontSize(value: string) {
localStorage.setItem(this.cacheKey, value);
}
isValid() {
const size = parseInt(this.fontSize, 10);
return size > 5 && size < 60;
}
onSubmit() {
this.setFontSize(this.fontSize);
this.dialogRef.close();
}
onNoClick(): void {
this.dialogRef.close();
}
}
<h1 mat-dialog-title>{{"Set RDP solution"|trans}}</h1>
<mat-form-field>
<mat-select [(value)]="solution"
placeholder="{{'Select a solution'|trans}}" >
<mat-option *ngFor="let s of solutions" value="{{s}}">{{s}}</mat-option>
</mat-select>
</mat-form-field>
<div style="float: right">
<button mat-raised-button (click)="onNoClick()">{{"Cancel"|trans}}</button>
<button mat-raised-button color="primary" (click)="onSubmit()">{{"Confirm"|trans}}</button>
</div>
<div class="footer fixed">
<div class="pull-right">
Version <strong>{{version}}</strong> GPLv2.
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
</div>
<div>
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018
</div>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementOfooterComponent } from './ofooter.component';
describe('ElementOfooterComponent', () => {
let component: ElementOfooterComponent;
let fixture: ComponentFixture<ElementOfooterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementOfooterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementOfooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnInit} from '@angular/core';
import {version} from '../../../environments/environment';
import {DataStore} from '../../globals';
@Component({
selector: 'elements-ofooter',
templateUrl: './ofooter.component.html',
styleUrls: ['./ofooter.component.scss']
})
export class ElementOfooterComponent implements OnInit {
version = version;
constructor() {
DataStore.NavShow = false;
}
ngOnInit() {
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementPopupComponent } from './popup.component';
describe('ElementPopupComponent', () => {
let component: ElementPopupComponent;
let fixture: ComponentFixture<ElementPopupComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementPopupComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementPopupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'elements-popup',
templateUrl: './popup.component.html',
styleUrls: ['./popup.component.css']
})
export class ElementPopupComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
*/ */
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute, Params} from '@angular/router'; import {ActivatedRoute, Params} from '@angular/router';
import {DataStore} from '../../globals'; import {DataStore} from '@app/globals';
declare let Mstsc: any; declare let Mstsc: any;
......
<div class="ES-menu" [ngStyle]="{'top':top,'left':left}">
<ul class="dropdown-content">
<li [ngClass]="m.type" *ngFor="let m of MenuList">
<a (click)="m.action" *ngIf="m.type!='line'">{{m.name}}</a>
</li>
</ul>
</div>
.ES-menu {
display: block;
width: 120px;
background-color: #2f2a2a;
position: absolute;
}
.ES-menu ul {
list-style-type: none;
line-height: 24px;
}
.ES-menu li {
display: inline-block;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown:hover {
background-color: #2d2828;
}
.dropdown-content {
position: absolute;
background-color: black;
color: #c6bcbc;
padding: 4px 0;
z-index: 999;
float: none;
list-style: none;
line-height: normal;
direction: ltr;
width: auto;
top: auto;
left: 0px;
margin-left: 0px;
margin-top: 0px;
min-width: 150px;
}
.ES-menu:hover .dropdown-content {
display: block;
}
.ES-menu .dropdown-content li {
float: left;
display: flex;
}
.ES-menu .dropdown-content li a {
padding: 6px 14px 6px 14px;
white-space: nowrap;
font-family: 'Roboto', sans-serif;
font-size: 13px;
font-weight: 300;
position: relative;
text-decoration: none;
min-width: 150px;
line-height: normal;
}
.ES-menu .dropdown-content li a span {
float: right;
}
.dropdown-content li:hover {
background-color: #3a3333;
color: black;
width: 100%;
}
.dropdown-content li.disabled:hover {
background-color: black;
}
.dropdown-content li.disabled a {
color: #c5babc;
}
.dropdown-content li.disabled a:hover {
cursor: default;
color: #c5babc;
}
.ES-menu a {
color: #f0f0f1;
text-align: center;
text-decoration: none;
padding: 6px 15px 6px 15px;
}
.ES-menu a:hover {
color: #fff;
cursor: pointer
}
.ES-menu .line {
height: 1px;
width: 100%;
background-color: white;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementServerMenuComponent } from './server-menu.component';
describe('ElementServerMenuComponent', () => {
let component: ElementServerMenuComponent;
let fixture: ComponentFixture<ElementServerMenuComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementServerMenuComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementServerMenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnInit} from '@angular/core';
import {DialogService} from '../dialog/dialog.service';
export class Menu {
name: string;
type: string;
action: any;
}
@Component({
selector: 'elements-server-menu',
templateUrl: './server-menu.component.html',
styleUrls: ['./server-menu.component.scss'],
})
export class ElementServerMenuComponent implements OnInit {
MenuList: Array<any>;
top: number;
left: number;
constructor(private _dialog: DialogService) {
}
ngOnInit() {
const m = new Menu();
const line = new Menu();
m.action = '';
m.name = 'test';
m.type = 'lll';
line.type = 'line';
this.MenuList = [m, m, line, m, m];
this._dialog.alert('sss');
}
public contextmenu(top: number, left: number) {
this.top = top;
this.left = left;
}
}
<h1 mat-dialog-title>{{"Setting"|trans}}</h1>
<mat-form-field>
<mat-select [(value)]="setting.rdpSolution"
placeholder="{{'Select a solution'|trans}}" >
<mat-option *ngFor="let s of solutionsChoices" value="{{s}}">{{s}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input [(value)]="setting.fontSize" matInput placeholder='{{"Font size"|trans}}' name="fontSize" type="number" min="5" max="60" >
</mat-form-field>
<mat-form-field>
<mat-select [(value)]="setting.isLoadTreeAsync"
placeholder="{{'Load tree async'|trans }}" >
<mat-option *ngFor="let s of boolChoices" value="{{s.value}}">{{s.name|trans}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select [(value)]="setting.isSkipAllManualPassword"
placeholder="{{'Skip manual password'|trans }}" >
<mat-option *ngFor="let s of boolChoices" value="{{s.value}}">{{s.name|trans}}</mat-option>
</mat-select>
</mat-form-field>
<div style="float: right">
<button mat-raised-button (click)="onNoClick()">{{"Cancel"|trans}}</button>
<button mat-raised-button color="primary" (click)="onSubmit()">{{"Confirm"|trans}}</button>
</div>
import {Component, OnInit} from '@angular/core';
import { MatDialogRef} from '@angular/material';
import {SettingService} from '@app/services';
import {Setting} from '@app/model';
@Component({
selector: 'elements-setting',
templateUrl: './setting.component.html',
styles: ['.mat-form-field { width: 100%;}']
})
export class ElementSettingComponent implements OnInit {
solutionsChoices = ['Auto', '1024x768', '1366x768', '1600x900', '1920×1080'];
boolChoices = [{name: 'Yes', value: '1'}, {name: 'No', value: '0'}];
setting: Setting;
constructor(public dialogRef: MatDialogRef<ElementSettingComponent>,
private settingSrv: SettingService) {
}
ngOnInit() {
this.setting = this.settingSrv.setting;
}
onSubmit() {
this.settingSrv.save();
this.dialogRef.close();
}
onNoClick(): void {
this.dialogRef.close();
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SftpComponent } from './sftp.component'; import { ElementSftpComponent } from './sftp.component';
describe('SftpComponent', () => { describe('ElementSftpComponent', () => {
let component: SftpComponent; let component: ElementSftpComponent;
let fixture: ComponentFixture<SftpComponent>; let fixture: ComponentFixture<ElementSftpComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ SftpComponent ] declarations: [ ElementSftpComponent ]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SftpComponent); fixture = TestBed.createComponent(ElementSftpComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
......
import {Component, OnInit, Input, ElementRef, ViewChild} from '@angular/core'; import {Component, OnInit, Input, ElementRef, ViewChild} from '@angular/core';
import {DataStore} from '../../globals'; import {DataStore} from '@app/globals';
import {DomSanitizer} from '@angular/platform-browser'; import {DomSanitizer} from '@angular/platform-browser';
@Component({ @Component({
...@@ -7,7 +7,7 @@ import {DomSanitizer} from '@angular/platform-browser'; ...@@ -7,7 +7,7 @@ import {DomSanitizer} from '@angular/platform-browser';
templateUrl: './sftp.component.html', templateUrl: './sftp.component.html',
styleUrls: ['./sftp.component.scss'] styleUrls: ['./sftp.component.scss']
}) })
export class SftpComponent implements OnInit { export class ElementSftpComponent implements OnInit {
@Input() host: any; @Input() host: any;
target: any; target: any;
@ViewChild('sftp') el: ElementRef; @ViewChild('sftp') el: ElementRef;
......
import {AfterViewInit, Component, Input, OnInit, OnDestroy } from '@angular/core'; import {Component, Input, OnInit, OnDestroy } from '@angular/core';
import {Terminal} from 'xterm'; import {Terminal} from 'xterm';
import {NavList, View} from '../../pages/control/control/control.component'; import {View} from '@app/model';
import {UUIDService} from '../../app.service'; import {LogService, SettingService, UUIDService} from '@app/services';
import {CookieService} from 'ngx-cookie-service'; import {Socket} from '@app/utils/socket';
import {Socket} from '../../utils/socket'; import {getWsSocket, translate} from '@app/globals';
import {getWsSocket} from '../../globals';
@Component({ @Component({
...@@ -12,8 +11,9 @@ import {getWsSocket} from '../../globals'; ...@@ -12,8 +11,9 @@ import {getWsSocket} from '../../globals';
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, OnDestroy { export class ElementSshTermComponent implements OnInit, OnDestroy {
@Input() host: any; @Input() host: any;
@Input() view: View;
@Input() sysUser: any; @Input() sysUser: any;
@Input() index: number; @Input() index: number;
@Input() token: string; @Input() token: string;
...@@ -22,29 +22,25 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy ...@@ -22,29 +22,25 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy
secret: string; secret: string;
ws: Socket; ws: Socket;
roomID: string; roomID: string;
view: View;
constructor(private _uuid: UUIDService, private _cookie: CookieService) { constructor(private _uuid: UUIDService, private _logger: LogService, private settingSvc: SettingService) {
} }
ngOnInit() { ngOnInit() {
this.view = NavList.List[this.index];
this.secret = this._uuid.gen(); this.secret = this._uuid.gen();
this.newTerm(); this.newTerm();
getWsSocket().then(sock => { getWsSocket().then(sock => {
this.ws = sock; this.ws = sock;
this.connectHost(); this.connectHost();
}); });
} this.view.type = 'ssh';
ngAfterViewInit() {
} }
newTerm() { newTerm() {
const fontSize = localStorage.getItem('fontSize') || '14'; const fontSize = this.settingSvc.setting.fontSize;
this.term = new Terminal({ this.term = new Terminal({
fontFamily: 'monaco, Consolas, "Lucida Console", monospace', fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
fontSize: parseInt(fontSize, 10), fontSize: fontSize,
rightClickSelectsWord: true, rightClickSelectsWord: true,
theme: { theme: {
background: '#1f1b1b' background: '#1f1b1b'
...@@ -52,14 +48,14 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy ...@@ -52,14 +48,14 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy
}); });
this.view.Term = this.term; this.view.Term = this.term;
this.term.attachCustomKeyEventHandler(e => { this.term.attachCustomKeyEventHandler(e => {
if (e.ctrlKey && e.key == 'c' && term.hasSelection()) { if (e.ctrlKey && e.key === 'c' && this.term.hasSelection()) {
return false; return false;
} }
if (e.ctrlKey && e.key == 'v') { if (e.ctrlKey && e.key === 'v') {
return false; return false;
} }
return true; return true;
}) });
} }
changeWinSize(size: Array<number>) { changeWinSize(size: Array<number>) {
...@@ -68,7 +64,17 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy ...@@ -68,7 +64,17 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy
} }
} }
connectHost() { reconnect() {
if (this.view.connected !== true) {
if (!confirm(translate('Are you sure to reconnect it?(RDP not support)'))) {
return;
}
}
this.secret = this._uuid.gen();
this.emitHostAndTokenData();
}
emitHostAndTokenData() {
if (this.host) { if (this.host) {
const data = { const data = {
uuid: this.host.id, uuid: this.host.id,
...@@ -83,9 +89,13 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy ...@@ -83,9 +89,13 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy
'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._logger.debug('On token event trigger');
this.ws.emit('token', data); this.ws.emit('token', data);
} }
}
connectHost() {
this.emitHostAndTokenData();
this.term.on('data', data => { this.term.on('data', data => {
const d = {'data': data, 'room': this.roomID}; const d = {'data': data, 'room': this.roomID};
...@@ -100,39 +110,36 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy ...@@ -100,39 +110,36 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit, OnDestroy
// 服务器主动断开 // 服务器主动断开
this.ws.on('disconnect', () => { this.ws.on('disconnect', () => {
console.log('On disconnect event trigger'); this._logger.debug('On disconnect event trigger');
this.close(); this.view.connected = false;
}); });
this.ws.on('logout', data => { this.ws.on('logout', data => {
if (data.room === this.roomID) { if (data.room === this.roomID) {
console.log('On logout event trigger: ', data.room, this.roomID); this._logger.debug('On logout event trigger: ', data.room, this.roomID);
this.view.connected = false; this.view.connected = false;
} }
}); });
this.ws.on('room', data => { this.ws.on('room', data => {
if (data.secret === this.secret && data.room) { if (data.secret === this.secret && data.room) {
console.log('On room', data); this._logger.debug('On room', data);
this.roomID = data.room; this.roomID = data.room;
this.view.room = data.room; this.view.room = data.room;
this.view.connected = true;
} }
}); });
} }
// 客户端主动关闭
close() {
if (this.view && (this.view.room === this.roomID)) {
this.view.connected = false;
this.ws.emit('logout', this.roomID);
}
}
active() { active() {
this.term.focus(); this.term.focus();
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.close(); this._logger.debug('Close view');
if (this.view && (this.view.room === this.roomID)) {
this.view.connected = false;
this.ws.emit('logout', this.roomID);
}
} }
} }
<div>
<input
type='text'
placeholder='Type to filter the name column...'
(keyup)='updateFilter($event)'
*ngIf="config.search"/>
<ngx-datatable
#table
class="material"
[rows]="rows"
[columns]="columns"
[limit]="config.limit"
[columnMode]="config.columnMode"
[headerHeight]="config.headerHeight"
[footerHeight]="config.footerHeight"
[rowHeight]="config.rowHeight"
[scrollbarV]="config.scrollbarV"
[scrollbarH]="config.scrollbarH"
></ngx-datatable>
<mat-paginator #paginator
[pageSize]="config.pageSize"
[pageSizeOptions]="config.pageSizeOptions"
(click)="test()">
</mat-paginator>
</div>
<button (click)="test()">ssss</button>
input {
padding: 8px;
margin: 15px auto;
width: 30%;
}
.material {
box-shadow: 0 3px 5px -3px rgba(0, 0, 0, 0.2);
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementTableComponent } from './table.component';
describe('ElementTableComponent', () => {
let component: ElementTableComponent;
let fixture: ComponentFixture<ElementTableComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementTableComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {DatatableComponent} from '@swimlane/ngx-datatable';
import {MatPaginator} from '@angular/material';
import {LogService} from '../../app.service';
export let Config: {
search: boolean,
scrollbarV: boolean,
scrollbarH: boolean,
rowHeight: number,
footerHeight: number,
headerHeight: number,
limit: number,
columnMode: string,
pageSize: number,
pageSizeOptions: Array<number>,
} = {
search: false,
scrollbarV: false,
scrollbarH: false,
rowHeight: 50,
footerHeight: 50,
headerHeight: 50,
limit: 10,
columnMode: 'force',
pageSize: 10,
pageSizeOptions: [5, 10, 20],
};
@Component({
selector: 'elements-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class ElementTableComponent implements OnInit {
@Input() rows: Array<any>;
@Input() columns: Array<any>;
@Input() config: any;
temp = [];
@ViewChild(DatatableComponent) table: DatatableComponent;
@ViewChild(MatPaginator) paginator: MatPaginator;
constructor(private _logger: LogService) {
}
ngOnInit() {
Config = this.config;
this.paginator.length = this.rows.length;
}
updateFilter(event) {
const val = event.target.value.toLowerCase();
// filter our data
const temp = this.temp.filter(function (d) {
return d.name.toLowerCase().indexOf(val) !== -1 || !val;
});
// update the rows
this.rows = temp;
// Whenever the filter changes, always go back to the first page
this.table.offset = 0;
}
test() {
console.log(this.paginator._pageIndex);
console.log(this.paginator._pageIndex * this.paginator.pageSize + 1);
this.table.limit = this.paginator.pageSize;
}
}
...@@ -2,14 +2,11 @@ import {AfterViewInit, Component, Input, Output, OnInit, ViewChild, EventEmitter ...@@ -2,14 +2,11 @@ import {AfterViewInit, Component, Input, Output, OnInit, ViewChild, EventEmitter
import {ElementRef} from '@angular/core'; import {ElementRef} from '@angular/core';
import {Terminal} from 'xterm'; import {Terminal} from 'xterm';
import {fit} from 'xterm/lib/addons/fit/fit'; import {fit} from 'xterm/lib/addons/fit/fit';
import {Observable} from 'rxjs/Rx'; import {LogService} from '@app/services';
import {CookieService} from 'ngx-cookie-service'; import {Observable, fromEvent} from 'rxjs';
import {debounceTime, distinctUntilChanged } from 'rxjs/operators';
import * as $ from 'jquery/dist/jquery.min.js'; import * as $ from 'jquery/dist/jquery.min.js';
import 'rxjs/Observable'; import 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import {NavList} from '../../pages/control/control/control.component';
@Component({ @Component({
...@@ -24,19 +21,20 @@ export class ElementTermComponent implements OnInit, AfterViewInit { ...@@ -24,19 +21,20 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
@Output() winSizeChangeTrigger = new EventEmitter<Array<number>>(); @Output() winSizeChangeTrigger = new EventEmitter<Array<number>>();
winSizeChange$: Observable<any>; winSizeChange$: Observable<any>;
constructor(private _cookie: CookieService) { constructor(private _logger: LogService) {
} }
ngOnInit() { ngOnInit() {
this.winSizeChange$ = Observable.fromEvent(window, 'resize') this.winSizeChange$ = fromEvent(window, 'resize').pipe(
.debounceTime(500) debounceTime(500),
.distinctUntilChanged(); distinctUntilChanged(),
);
this.winSizeChange$ this.winSizeChange$
.subscribe(() => { .subscribe(() => {
if (NavList.List[NavList.Active].type !== 'rdp') { this._logger.debug('Get win size change event');
this.resizeTerm(); this.resizeTerm();
}
}); });
} }
...@@ -48,11 +46,11 @@ export class ElementTermComponent implements OnInit, AfterViewInit { ...@@ -48,11 +46,11 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
getWinSize() { getWinSize() {
let availableHeight = 0; let availableHeight = 0;
let availableWidth = 0; let availableWidth = 0;
if (document.fullscreenElement) { const activeEle = $('#winContainer');
if (document['fullscreenElement']) {
availableWidth = document.body.clientWidth - 10; availableWidth = document.body.clientWidth - 10;
availableHeight = document.body.clientHeight; availableHeight = document.body.clientHeight;
} else { } else if (activeEle) {
const activeEle = $('#winContainer');
const elementStyle = window.getComputedStyle(this.term.element); const elementStyle = window.getComputedStyle(this.term.element);
const elementPadding = { const elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top'), 10), top: parseInt(elementStyle.getPropertyValue('padding-top'), 10),
...@@ -65,11 +63,21 @@ export class ElementTermComponent implements OnInit, AfterViewInit { ...@@ -65,11 +63,21 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
availableHeight = activeEle.height() - elementPaddingVer; availableHeight = activeEle.height() - elementPaddingVer;
availableWidth = activeEle.width() - elementPaddingHor - (<any>this.term).viewport.scrollBarWidth; availableWidth = activeEle.width() - elementPaddingHor - (<any>this.term).viewport.scrollBarWidth;
} }
this._logger.debug('Winsize: ', availableWidth, availableHeight);
const dimensions = (<any>this.term).renderer.dimensions;
const geometry = [ const geometry = [
Math.floor(availableWidth / (<any>this.term).renderer.dimensions.actualCellWidth) - 1, Math.floor(availableWidth / dimensions.actualCellWidth) - 1,
Math.floor(availableHeight / (<any>this.term).renderer.dimensions.actualCellHeight) - 1 Math.floor(availableHeight / dimensions.actualCellHeight) - 1
]; ];
if (!isFinite(geometry[0])) {
geometry[0] = 80;
}
if (!isFinite(geometry[1])) {
geometry[1] = 24;
}
this._logger.debug('size: ', geometry);
return geometry; return geometry;
} }
......
/*.left-search {*/
/*padding-left: 14px;*/
/*width: 100%;*/
/*border: none;*/
/*}*/
.search {
border: none;
border-left-width: 0;
border-bottom: #19aa8d 1px inset;
width: 100%;
background: #2f2a2a;
color: #d6cbcb;
height: 30px;
padding-left: 10px;
/* padding-top: 30px;
//position: fixed;
//height: 28px;
*/
}
<input id="search" class="search"
[formControl]="searchControl"
placeholder=" {{'Search'| trans }} ..."
maxlength="2048"
name="keyword"
autocomplete="off"
title="Search"
type="text" tabindex="1" spellcheck="false"
>
import {Component, OnInit} from '@angular/core';
import {FormControl} from '@angular/forms';
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
import {LogService, TreeFilterService} from '@app/services';
@Component({
selector: 'elements-tree-filter',
templateUrl: './tree-filter.component.html',
styleUrls: ['./tree-filter.component.css'],
})
export class ElementTreeFilterComponent implements OnInit {
searchControl: FormControl;
private debounce = 400;
constructor(private _treeFilterService: TreeFilterService,
private _logger: LogService) {
}
ngOnInit(): void {
this.searchControl = new FormControl('');
this.searchControl.valueChanges
.pipe(debounceTime(this.debounce), distinctUntilChanged())
.subscribe(query => {
this._logger.debug('Tree filter: ', query);
this._treeFilterService.filter(query);
});
}
}
<div *ngFor="let node of TreeData">
<div *ngIf="node.leafs?.length>0">
{{node.id}}
<elements-tree [TreeData]="node.leafs"></elements-tree>
</div>
<div *ngIf="node.leafs?.length==0">
{{node.id}}
</div>
</div>
<!--<ul class="filetree ">-->
<!--<li *ngFor="let hostGroup of HostGroups | SearchFilter: q; let i = index ">-->
<!--<input type="checkbox" id="hostgroup-{{i}}">-->
<!--<label for="hostgroup-{{i}}" matTooltip="{{hostGroup.name}}" [matTooltipPosition]="TooltipPosition">{{hostGroup.name}}</label>-->
<!--</li>-->
<!--</ul>-->
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementTreeComponent } from './tree.component';
describe('R ', () => {
let component: ElementTreeComponent;
let fixture: ComponentFixture<ElementTreeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ElementTreeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ElementTreeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, Input, OnInit} from '@angular/core';
import {HttpService} from '../../app.service';
export interface Assets {
name: string;
id: string;
type: string;
}
export interface Groups {
id: string;
key: string;
name: string;
value: string;
parent: string;
assets_granted: Array<Assets>;
}
export class TreeStruct {
id: string;
leafs: Array<TreeStruct>;
static create(id, parent: string) {
const tmp = new TreeStruct();
tmp.id = id;
tmp.leafs = [];
return tmp;
}
}
@Component({
selector: 'elements-tree',
templateUrl: './tree.component.html',
styleUrls: ['./tree.component.scss']
})
export class ElementTreeComponent implements OnInit {
@Input() TreeData: Array<TreeStruct>;
constructor(private _http: HttpService) {
}
ngOnInit() {
}
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {ElementTreeComponent} from './tree.component';
@NgModule({
imports: [
CommonModule
],
declarations: [
ElementTreeComponent
]
})
export class TreeModule {
}
'use strict'; 'use strict';
import {EventEmitter} from 'events/events'; 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'; import {Socket} from './utils/socket';
import {BehaviorSubject} from 'rxjs';
import {ConnectEvt, User as _User } from './model';
import {DataStore as _DataStore, Browser as _Browser, Video as _Video, Monitor as _Monitor} from './model';
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'; const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws';
const port = document.location.port ? ':' + document.location.port : ''; const port = document.location.port ? ':' + document.location.port : '';
const wsURL = scheme + '://' + document.location.hostname + port + '/socket.io/'; const hostname = document.location.hostname;
const wsURL = `${scheme}://${hostname}${port}/socket.io/`;
export let TermWS = null; export let TermWS = null;
export const emitter = new(EventEmitter); export const emitter = new(EventEmitter);
export const sep = '/'; export const sep = '/';
export let Video: { export let Video = new _Video();
id: string, export let Monitor = new _Monitor();
src: string, export let User = new _User();
type: string, export const DataStore: _DataStore = {
height: number,
width: number,
json: object;
timelist: Array<number>;
totalTime: number;
} = {
id: '',
src: '',
type: '',
width: 0,
height: 0,
json: {},
timelist: [],
totalTime: 0,
};
export let Monitor: {
token: string,
room: string,
type: string,
} = {
token: '',
room: '',
type: 'term',
};
export class Group {
id: string;
name: string;
membercount: number;
comment: string;
}
export let User: {
id: string;
name: string;
username: string;
password: string;
phone: string;
avatar: string;
role: string;
email: string;
wechat: string;
comment: string;
is_active: boolean;
is_superuser: boolean;
date_joined: string;
last_login: string;
date_expired: string;
groups: Array<Group>;
logined: boolean;
} = {
id: '',
name: 'nobody',
username: '',
password: '',
phone: '',
avatar: '',
role: '',
email: '',
wechat: '',
comment: '',
is_active: false,
is_superuser: false,
date_joined: '',
last_login: '',
date_expired: '',
groups: [],
logined: false,
};
export let DataStore: {
socket: any;
Nav: Array<{}>;
NavShow: boolean;
Path: {};
error: {};
msg: {};
loglevel: number;
leftbarshow: boolean;
windowsize: Array<number>;
autologin: boolean;
guacamole_token: string;
guacamole_token_time: number;
} = {
socket: TermWS, socket: TermWS,
Nav: [{}], Nav: [{}],
NavShow: true, NavShow: true,
Path: {}, Path: {},
error: {}, error: {},
msg: {}, msg: {},
loglevel: 0, logLevel: 4,
leftbarshow: true, showLeftBar: true,
windowsize: [], windowSize: [],
autologin: false, autoLogin: false,
guacamole_token: '', guacamoleToken: '',
guacamole_token_time: 0 guacamoleTokenTime: 0
};
export let CSRF = '';
export let Browser: {
userAgent: string;
appCodeName: string;
appName: string;
appVersion: string;
language: string;
platform: string;
product: string;
productSub: string;
vendor: string;
} = {
userAgent: navigator.userAgent,
appCodeName: navigator.appCodeName,
appName: navigator.appName,
appVersion: navigator.appVersion,
language: navigator.language,
platform: navigator.platform,
product: navigator.product,
productSub: navigator.productSub,
vendor: navigator.vendor,
}; };
export let Browser = new _Browser();
export const i18n = new Map(); export const i18n = new Map();
export async function getWsSocket() { export async function getWsSocket() {
...@@ -149,10 +42,18 @@ export async function getWsSocket() { ...@@ -149,10 +42,18 @@ export async function getWsSocket() {
const nsConn = await TermWS.connect(); const nsConn = await TermWS.connect();
if (!nsConn) { if (!nsConn) {
console.log('Try to using socket.io protocol'); console.log('Try to using socket.io protocol');
TermWS = io.connect('/ssh', {reconnectionAttempts: 10});
} }
DataStore.socket = TermWS; DataStore.socket = TermWS;
return TermWS; return TermWS;
} }
export const connectEvt = new BehaviorSubject<ConnectEvt>(new ConnectEvt(null, null));
export function translate(value) {
if (i18n.has(value.toLowerCase())) {
return i18n.get(value.toLowerCase());
} else {
return value;
}
}
export class UserGroup {
id: string;
name: string;
comment: string;
}
export class User {
id: string;
name: string;
username: string;
password: string;
phone: string;
avatar: string;
role: string;
email: string;
wechat: string;
comment: string;
is_active: boolean;
is_superuser: boolean;
date_joined: string;
last_login: string;
date_expired: string;
groups: Array <UserGroup>;
logined: boolean;
}
export class SystemUser {
id: string;
name: string;
login_mode: string;
username: string;
priority: number;
protocol: string;
password: string;
actions: Array<string>;
}
export class TreeNode {
id: string;
name: string;
comment: string;
title: string;
isParent: boolean;
pId: string;
open: boolean;
iconSkin: string;
meta: any;
}
export class Node {
id: string;
key: string;
value: string;
}
export class Asset {
id: string;
hostname: string;
ip: string;
comment: string;
domain: string;
os: string;
platform: string;
protocols: Array<string>;
}
export class GuacObjAddResp {
code: number;
result: string;
}
export class ConnectEvt {
node: TreeNode;
action: string;
constructor(node: TreeNode, action: string) {
this.node = node;
this.action = action;
}
}
export class Nav {
id: string;
name: string;
children?: Array<Nav>;
hide?: boolean = false;
click?: string;
href?: string;
disable?: boolean = false;
}
export class NavEvt {
name: string;
value: any;
constructor(name: string, value: any) {
this.name = name;
this.value = value;
}
}
export class View {
id: string;
nick: string;
type: string;
editable: boolean;
active: boolean;
connected: boolean;
hide: boolean;
closed: boolean;
host: any;
user: any;
remoteApp: string;
room: string;
Rdp: any;
Term: any;
}
export class ViewAction {
view: View;
name: string;
constructor(view: View, name: string) {
this.view = view;
this.name = name;
}
}
export class DataStore {
socket: any;
Nav: Array<object>;
NavShow = true;
Path: {};
error: {};
msg: {};
logLevel: number;
showLeftBar = true;
windowSize: Array<number>;
autoLogin: boolean;
guacamoleToken: string;
guacamoleTokenTime: number;
}
export class Browser {
userAgent: string;
appCodeName: string;
appName: string;
appVersion: string;
language: string;
platform: string;
product: string;
productSub: string;
vendor: string;
constructor() {
this.userAgent = navigator.userAgent;
this.appCodeName = navigator.appCodeName;
this.appName = navigator.appName;
this.appVersion = navigator.appVersion;
this.language = navigator.language;
this.platform = navigator.platform;
this.product = navigator.product;
this.productSub = navigator.productSub;
this.vendor = navigator.vendor;
}
}
export class Video {
id: string;
src: string;
type: string;
height: number;
width: number;
json: object;
timeList: Array<number>;
totalTime: number;
}
export class Monitor {
token: string;
room: string;
type: string;
}
export class Setting {
rdpSolution: string = 'Auto';
fontSize: number = 14;
isLoadTreeAsync: string = '1';
isSkipAllManualPassword: string = '0';
}
<ng-progress></ng-progress>
<elements-nav *ngIf="DataStore.NavShow"></elements-nav>
<router-outlet></router-outlet> <router-outlet></router-outlet>
<!--<elements-interactive></elements-interactive>-->
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});
/** import {Component} from '@angular/core';
* 控制主页 import {AppService} from '@app/services';
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, HostListener} from '@angular/core';
import {DataStore} from '../globals';
import { environment } from '../../environments/environment';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
...@@ -16,18 +8,8 @@ import { environment } from '../../environments/environment'; ...@@ -16,18 +8,8 @@ import { environment } from '../../environments/environment';
}) })
export class AppComponent { export class AppComponent {
DataStore = DataStore;
constructor() {} constructor(private appSrv: AppService) {
@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
const notInIframe = window.self === window.top;
const notInReplay = location.pathname.indexOf('/luna/replay') === -1;
if (environment.production && notInIframe && notInReplay) {
return false;
}
return true;
} }
} }
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {DataStore} from '../../globals'; import {DataStore} from '@app/globals';
@Component({ @Component({
selector: 'pages-blank', selector: 'pages-blank',
......
<elements-ssh-term <div class="windows" id="winContainer">
[token]="token" <elements-content-window [view]="view" *ngIf="view"></elements-content-window>
[index]="0" <elements-connect [ngStyle]="{'display': 'none'}" (onNewView)="onNewView($event)"></elements-connect>
*ngIf="system =='linux'"> </div>
</elements-ssh-term>
<elements-guacamole
[target]="target"
[index]="0"
*ngIf="system=='windows' && target">
</elements-guacamole>
.windows {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
background-color: #1f1b1b;
}
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {AppService, HttpService, LocalStorageService} from '../../app.service'; import {AppService, HttpService, LocalStorageService} from '@app/services';
import {DataStore} from '../../globals'; import {connectEvt} from '@app/globals';
import * as jQuery from 'jquery/dist/jquery.min.js'; import {ConnectEvt} from '@app/model';
// import {DataStore} from '@app/globals';
// import * as jQuery from 'jquery/dist/jquery.min.js';
import {View, ViewAction} from '@app/model';
@Component({ @Component({
selector: 'pages-connect', selector: 'pages-connect',
...@@ -11,80 +14,44 @@ import * as jQuery from 'jquery/dist/jquery.min.js'; ...@@ -11,80 +14,44 @@ import * as jQuery from 'jquery/dist/jquery.min.js';
export class PagesConnectComponent implements OnInit { export class PagesConnectComponent implements OnInit {
token: string; token: string;
system: string; system: string;
authToken: string; view: View;
userid: string;
target: string;
base: string;
constructor(private _appService: AppService, constructor(private _appService: AppService,
private _http: HttpService, private _http: HttpService,
private _localStorage: LocalStorageService) { private _localStorage: LocalStorageService) {
DataStore.NavShow = false; }
onNewView(view) {
view.active = true;
this.view = view;
} }
ngOnInit() { ngOnInit() {
this.system = this._appService.getQueryString('system'); this.system = this._appService.getQueryString('system');
this.token = this._appService.getQueryString('token'); this.token = this._appService.getQueryString('token');
const assetId = this._appService.getQueryString('asset');
this.userid = this._localStorage.get('user-' + this.token); const remoteAppId = this._appService.getQueryString('remote_app');
this.authToken = this._localStorage.get('authToken-' + this.token); if (assetId) {
this.base = this._localStorage.get('base-' + this.token); this._http.filterMyGrantedAssetsById(assetId).subscribe(
nodes => {
jQuery('body').css('background-color', '#1f1b1b'); if (!nodes) {
if (this.system === 'windows') { return;
if (!this.userid) {
this._http.get_user_id_from_token(this.token)
.subscribe(
data => {
this._localStorage.set('user-' + this.token, data['user']);
this.userid = data['user'];
this.getAuthToken();
}
);
} else {
this.getAuthToken();
}
}
}
getAuthToken() {
if (!this.authToken) {
this._http.get_guacamole_token(this.userid, this.token).subscribe(
data => {
if (data['authToken']) {
this._localStorage.set('authToken-' + this.token, data['authToken']);
this.authToken = data['authToken'];
this.getBase();
} }
const evt = new ConnectEvt(nodes[0], 'asset');
connectEvt.next(evt);
} }
); );
} else {
this.getBase();
} }
} if (remoteAppId) {
this._http.getMyGrantedRemoteApps(remoteAppId).subscribe(
getBase() { nodes => {
if (!this.base) { if (!nodes) {
this._http.guacamole_token_add_asset(this.token, this.authToken).subscribe( return;
data => {
if (data['result']) {
this._localStorage.set('base-' + this.token, data['result']);
this.base = data['result'];
this.setWinTarget();
} }
}); const evt = new ConnectEvt(nodes[0], 'asset');
} else { connectEvt.next(evt);
this.setWinTarget(); }
} );
}
setWinTarget() {
if (this.base && this.authToken) {
this.target = document.location.origin + '/guacamole/#/client/' + this.base +
'?asset_token=jumpserver&token=' + this.authToken;
} else {
window.location.reload();
} }
} }
} }
<div class="sidebar" fxLayout="column" ngxSplit="column">
<div fxflex="0 0 30px" class="search">
<input #keyword id="keyword" class="left-search"
placeholder=" {{'Search'| trans }} ..."
maxlength="2048"
name="q"
autocomplete="off"
title="Search"
type="text" tabindex="1" spellcheck="false" [(ngModel)]="q">
</div>
<div class="overflow ngx-scroll-overlay" fxflex="1 1 90%">
<elements-asset-tree [query]="q" ></elements-asset-tree>
</div>
<div class="footer-version" fxflex="0 0 26px">
<p> Version <strong>{{version}}</strong></p>
</div>
</div>
<!--<elements-server-menu></elements-server-menu>-->
.sidebar {
height: 100%;
width: 100%;
overflow: auto;
}
:root {
font-family: "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
}
label {
margin-bottom: 0;
}
.filetree {
padding-left: 20px;
height: inherit;
}
.filetree input[type="checkbox"] {
display: none;
}
.filetree input[type=checkbox] + label:before {
font-family: FontAwesome;
display: inline-block;
}
.filetree input[type=checkbox] + label:before {
content: "\f114";
letter-spacing: 10px;
width: 30px;
}
.filetree input[type=checkbox]:checked + label:before {
content: "\f115";
}
.filetree ul {
height: 0;
overflow: hidden;
}
.filetree > li input:checked ~ ul, .filetree > li ul.insearch {
height: auto;
}
.filetree li {
list-style: none;
cursor: pointer;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.filetree label {
line-height: 33px;
display: inline-block;
}
.fa.fa-undefined:before {
content: "\f26c";
}
.left-search {
padding-left: 14px;
width: 100%;
border: none;
background: #2f2a2a;
color: #ffffff;
}
.search {
border-left-width: 0;
border-bottom: #19aa8d 2px inset;
//padding-top: 30px;
width: 100%;
//position: fixed;
//height: 28px;
}
.search > input {
height: 30px;
}
.overflow {
height: 100%;
width: 100%;
float: left;
position: inherit;
background: #2f2a2a;
color: #d6cbcb;
}
.footer-version {
background: #2f2a2a;
font-size: 9pt;
left: 0;
width: 100%;
padding: 1px 20px 0 20px;
border-top: 1px solid #e7eaec;
bottom: 0;
//height: 30px;
//position: fixed;
}
.footer-version > p {
height: 8px;
padding-top: 2px;
padding-bottom: 2px;
}
//@import "~@swimlane/ngx-ui/release/styles/components/scrollbars";
.ngx-scroll-overlay {
overflow: auto; // for FF
//-ms-overflow-style: -ms-autohiding-scrollbar;
//
//&::-webkit-scrollbar {
// display: none;
//}
//
//&:hover::-webkit-scrollbar {
// display: initial;
//}
}
//.sidebar::-webkit-scrollbar-track {
// -webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3);
// background-color: #676a6c;
//}
//
//.sidebar::-webkit-scrollbar {
// width: 8px;
//}
//
//.sidebar::-webkit-scrollbar-thumb {
// background-color: #F5F5F5;
// border-radius: 6px;
// border: 2px solid transparent;
//}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CleftbarComponent } from './cleftbar.component';
describe('CleftbarComponent', () => {
let component: CleftbarComponent;
let fixture: ComponentFixture<CleftbarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CleftbarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CleftbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
/**
* 控制页面的左边栏主机树状页
*
* 以树状方式列出所有主机
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
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';
import {version} from '../../../../environments/environment';
import {NavList, View} from '../control/control.component';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
import {ElementServerMenuComponent} from '../../../elements/server-menu/server-menu.component';
import {DialogService} from '../../../elements/dialog/dialog.service';
export interface Node {
id: string;
name: string;
comment: string;
title: string;
isParent: boolean;
pId: string;
open: boolean;
iconSkin: string;
meta: object;
}
export class Host {
name: string;
id: string;
type: string;
}
@Component({
selector: 'pages-control-cleftbar',
templateUrl: './cleftbar.component.html',
styleUrls: ['./cleftbar.component.scss'],
providers: [SearchComponent, ElementServerMenuComponent]
})
export class CleftbarComponent {
DataStore = DataStore;
version = version;
q: string;
event: MouseEvent;
clientX = 0;
clientY = 0;
TooltipPosition = 'above';
static Reload() {
}
static Hide() {
DataStore.leftbarshow = false;
DataStore.Nav.map(function (value, i) {
value['children'].forEach((v, key) => {
if (DataStore.Nav[i]['children'][key]['id'] === 'HideLeftManager') {
DataStore.Nav[i]['children'][key] = {
'id': 'ShowLeftManager',
'click': 'ShowLeft',
'name': 'Show left manager'
};
}
});
});
window.dispatchEvent(new Event('resize'));
}
static Show() {
DataStore.leftbarshow = true;
DataStore.Nav.map(function (value, i) {
value['children'].forEach((v, key) => {
if (DataStore.Nav[i]['children'][key]['id'] === 'ShowLeftManager') {
DataStore.Nav[i]['children'][key] = {
'id': 'HideLeftManager',
'click': 'HideLeft',
'name': 'Hide left manager'
};
}
});
});
window.dispatchEvent(new Event('resize'));
}
constructor(private _appService: AppService,
private _http: HttpService,
private _search: SearchComponent,
private _logger: LogService,
private _menu: ElementServerMenuComponent,
public _dialog: MatDialog,
private _layer: DialogService) {
this._logger.log('nav.ts:NavComponent');
}
Search(q) {
this._search.Search(q);
}
onRightClick(event: MouseEvent): void {
this.clientX = event.clientX;
this.clientY = event.clientY;
}
}
div {
height: 100%;
width: 100%;
padding: 0;
background-color: #1f1b1b;
margin: 0;
position: initial;
}
pages-control-cleftbar, pages-control-control {
background: #2f2a2a;
color: #d6cbcb;
}
.container-fluid {
padding-top: 30px;
}
<div class="container-fluid row" fxLayout="row" ngxSplit="row">
<div fxFlex="1 1 20%" minBasis="100px" maxBasis="800px" fxFlexFill ngxSplitArea *ngIf="DataStore.leftbarshow">
<pages-control-cleftbar></pages-control-cleftbar>
</div>
<div fxFlex="0" ngxSplitHandle [style.display]="activeViewIsRdp() ? 'none' : 'block'" (mouseup)="dragSplitBtn($event)"></div>
<div [fxFlex]="DataStore.leftbarshow ? '1 1 80%' : '1 1 100%'" ngxSplitArea class="content">
<pages-control-control></pages-control-control>
</div>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PagesControlComponent } from './control.component';
describe('ControlPageComponent', () => {
let component: PagesControlComponent;
let fixture: ComponentFixture<PagesControlComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PagesControlComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PagesControlComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
/**
* 控制页面
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, OnInit} from '@angular/core';
import {DataStore, User} from '../../globals';
import {NavList} from './control/control.component';
@Component({
selector: 'pages-control',
templateUrl: './control.component.html',
styleUrls: ['./control.component.css'],
})
export class PagesControlComponent implements OnInit {
DataStore = DataStore;
User = User;
constructor() {
}
activeViewIsRdp() {
return NavList.List[NavList.Active].type === 'rdp';
}
dragSplitBtn(evt) {
window.dispatchEvent(new Event('resize'));
}
ngOnInit() {
}
}
<div fxLayout="column" ngxSplit="column" style="width: 100%;height: 100%;">
<div fxFlex="0 0 30px" class="search">
<pages-control-nav></pages-control-nav>
</div>
<div fxFlex="0 0 calc(100%-35px)" id="winContainer">
<div class="window" *ngFor="let m of NavList.List;let i=index"
[ngClass]="{'active':i==NavList.Active}" style="height: 100%">
<elements-ssh-term [index]="i"
[host]="m.host"
[sysUser]="m.user"
*ngIf="m.type=='ssh'">
</elements-ssh-term>
<elements-guacamole [index]="i"
[host]="m.host"
[sysUser]="m.user"
[remoteAppId]="m.remoteApp"
*ngIf="m.type=='rdp'">
</elements-guacamole>
<app-sftp *ngIf="m.type=='sftp'" [host]="m.host">
</app-sftp>
<!--<elements-settings [index]="i"-->
<!--*ngIf="m.type=='settings'">-->
<!--</elements-settings>-->
</div>
</div>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ControlComponent } from './control.component';
describe('ControlComponent', () => {
let component: ControlComponent;
let fixture: ComponentFixture<ControlComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ControlComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ControlComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
/**
* 控制页面
*
* 主管已连接的主机标签卡,各连接方式的web展现(WebTerminal、RDP、VNC等)
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, OnInit} from '@angular/core';
import {TermWS} from '../../../globals';
export class View {
nick: string;
type: string;
edit: boolean;
connected: boolean;
hide: boolean;
closed: boolean;
host: any;
user: any;
remoteApp: string;
room: string;
Rdp: any;
Term: any;
}
export let NavList: {
List: Array<View>;
Active: number;
} = {
List: [new View()],
Active: 0,
};
@Component({
selector: 'pages-control-control',
templateUrl: './control.component.html',
styleUrls: ['./control.component.css']
})
export class ControlComponent implements OnInit {
NavList = NavList;
static active(id) {
NavList.List.forEach((v, k) => {
v.hide = id.toString() !== k;
});
NavList.Active = id;
}
static TerminalDisconnect(id) {
if (NavList.List[id].connected) {
NavList.List[id].connected = false;
NavList.List[id].Term.write('\r\n\x1b[31mBye Bye!\x1b[m\r\n');
TermWS.emit('logout', NavList.List[id].room);
}
}
static RdpDisconnect(id) {
NavList.List[id].connected = false;
}
static DisconnectAll() {
for (let i = 0; i < NavList.List.length; i++) {
ControlComponent.TerminalDisconnect(i);
}
}
constructor() {
}
ngOnInit() {
}
// trackByFn(index: number, item: View) {
// return item.id;
// }
}
<div class="scroll-botton" style="padding: 0 5px">
<a class="left" (click)="scrollleft()"><i class="fa fa-caret-left"></i></a>
<a class="right" (click)="scrollright()"><i class="fa fa-caret-right"></i></a>
</div>
<div class="tabs">
<ul [ngStyle]="{'width':150*NavList.List.length+'px'}">
<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 | 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>
</ul>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PagesControlNavComponent } from './nav.component';
describe('ControlPagesControlNavComponent', () => {
let component: PagesControlNavComponent;
let fixture: ComponentFixture<PagesControlNavComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PagesControlNavComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PagesControlNavComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
/**
* 控制页面的已连接主机选项卡
*
* 展示所有已连接的主机
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {ControlComponent, NavList} from '../control.component';
import * as jQuery from 'jquery/dist/jquery.min.js';
@Component({
selector: 'pages-control-nav',
templateUrl: './nav.component.html',
styleUrls: ['./nav.component.css'],
})
export class PagesControlNavComponent implements OnInit {
setActive = PagesControlNavComponent.setActive;
NavList = NavList;
static checkActive(index) {
const len = NavList.List.length;
if (len === 1) {
// 唯一一个
NavList.Active = 0;
} else if (len - 1 === index) {
// 删了最后一个
NavList.Active = len - 2;
} else {
NavList.Active = index;
}
PagesControlNavComponent.setActive(NavList.Active);
}
static setActive(index) {
NavList.List.forEach((value, key) => {
NavList.List[key].hide = true;
});
NavList.List[index].hide = false;
NavList.Active = index;
if (!NavList.List[index].edit) {
if (NavList.List[index].type === 'ssh') {
NavList.List[index].Term.focus();
} else if (NavList.List[index].type === 'rdp') {
// NavList.List[index].Rdp.focus();
}
} else {
}
}
constructor() {
}
ngOnInit() {
}
close(host, index) {
if (host.type === 'rdp') {
ControlComponent.RdpDisconnect(index);
} else if (host.type === 'ssh') {
ControlComponent.TerminalDisconnect(index);
}
NavList.List.splice(index, 1);
PagesControlNavComponent.checkActive(index);
}
scrollleft() {
jQuery('.tabs').scrollLeft(jQuery('.tabs').scrollLeft() - 100);
}
scrollright() {
jQuery('.tabs').scrollLeft(jQuery('.tabs').scrollLeft() + 100);
}
}
.left-search {
padding-left: 14px;
width: 100%;
border: none;
}
<input class="left-search" placeholder=" Search ..." maxlength="2048" name="q" autocomplete="off"
title="Search"
type="text" tabindex="1" spellcheck="false" autofocus [(ngModel)]="q" (keyup.enter)="Search(q)"
(ngModelChange)="modelChange($event)">
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchComponent } from './search.component';
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SearchComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
/**
* 控制页面的搜索框
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, OnChanges, Input, Pipe, PipeTransform} from '@angular/core';
import {AppService, HttpService, LogService} from '../../../app.service';
export let Q = '';
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnChanges {
q: string;
@Input() input;
searchrequest: any;
constructor(private _appService: AppService,
private _http: HttpService,
private _logger: LogService) {
this._logger.log('LeftbarComponent.ts:SearchBar');
}
ngOnChanges(changes) {
Q = changes.input.currentValue;
}
modelChange($event) {
this.Search(Q);
}
public Search(q) {
if (this.searchrequest) {
this.searchrequest.unsubscribe();
}
this.searchrequest = this._http.search(q)
.subscribe(
data => {
this._logger.log(data);
},
err => {
this._logger.error(err);
},
() => {
}
);
this._logger.log(q);
}
}
@Pipe({name: 'SearchFilter'})
export class SearchFilter implements PipeTransform {
transform(value: any, input: string) {
if (input) {
input = input.toLowerCase();
return value.filter(function (el: any) {
// ToDo: search with a simple SQL like language, and a bug search a group's hosts
return JSON.stringify(el).toLowerCase().indexOf(input) > -1;
});
}
return value;
}
}
<div class="container" *ngIf="User.logined">
<div class="row row-offcanvas row-offcanvas-right">
<div class="col-12 col-md-9">
<p class="float-right hidden-md-up">
<button type="button" class="btn btn-primary btn-sm" data-toggle="offcanvas">Toggle nav</button>
</p>
<div class="jumbotron">
<h1>Hello, world! {{User.username}}</h1>
<p>This is an example to show the potential of an offcanvas layout pattern in Bootstrap. Try some
responsive-range viewport sizes to see it in action.</p>
</div>
<div class="row">
<div class="col-6 col-lg-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui. </p>
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
</div><!--/span-->
<div class="col-6 col-lg-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui. </p>
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
</div><!--/span-->
<div class="col-6 col-lg-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui. </p>
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
</div><!--/span-->
<div class="col-6 col-lg-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui. </p>
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
</div><!--/span-->
<div class="col-6 col-lg-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui. </p>
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
</div><!--/span-->
<div class="col-6 col-lg-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui. </p>
<p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>
</div><!--/span-->
</div><!--/row-->
</div><!--/span-->
<div class="col-6 col-md-3 sidebar-offcanvas" id="sidebar">
<div class="list-group">
<a href="#" class="list-group-item active">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
<a href="#" class="list-group-item">Link</a>
</div>
</div><!--/span-->
</div><!--/row-->
<hr>
</div>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PagesIndexComponent } from './index.component';
describe('PagesIndexComponent', () => {
let component: PagesIndexComponent;
let fixture: ComponentFixture<PagesIndexComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PagesIndexComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PagesIndexComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
/**
* 主页
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {Component, OnInit} from '@angular/core';
import {AppService} from '../../app.service';
import {User} from '../../globals';
@Component({
selector: 'pages-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.css'],
})
export class PagesIndexComponent implements OnInit {
User = User;
constructor() {
}
ngOnInit() {
}
}
...@@ -6,10 +6,10 @@ ...@@ -6,10 +6,10 @@
* @author liuzheng <liuzheng712@gmail.com> * @author liuzheng <liuzheng712@gmail.com>
*/ */
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {AppService, HttpService, LogService} from '../../app.service'; import {AppService, HttpService, LogService} from '@app/services';
import {NgForm} from '@angular/forms'; import {NgForm} from '@angular/forms';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {DataStore, User} from '../../globals'; import {DataStore, User} from '@app/globals';
import * as jQuery from 'jquery/dist/jquery.min.js'; import * as jQuery from 'jquery/dist/jquery.min.js';
@Component({ @Component({
...@@ -44,7 +44,7 @@ export class PagesLoginComponent implements OnInit { ...@@ -44,7 +44,7 @@ export class PagesLoginComponent implements OnInit {
DataStore.error['login'] = ''; DataStore.error['login'] = '';
this._logger.log(User); this._logger.log(User);
if (User.username.length > 0 && User.password.length > 6 && User.password.length < 100) { if (User.username.length > 0 && User.password.length > 6 && User.password.length < 100) {
this._http.check_login(JSON.stringify(User)) this._http.checkLogin(JSON.stringify(User))
.subscribe( .subscribe(
data => { data => {
User.logined = data['logined']; User.logined = data['logined'];
......
#container {
height: calc(100% - 30px);
width: 100%;
padding: 0;
margin: 0;
background-color: #1f1b1b;
}
.content {
height: 100%;
padding: 0;
background-color: #1f1b1b;
/*background-color: red;*/
margin: 0;
position: initial;
min-width: 100px;
}
/*.content {*/
/*overflow: hidden !important;*/
/*}*/
.left-side {
}
.handle {
outline: none;
-webkit-user-select: none;
user-select: none;
z-index: 99;
height: 5px;
width: 0;
display: block;
padding: 0;
margin: 0;
position: relative;
line-height: 0;
}
.handle-row {
top: 50%;
left: -8px;
transform: translateX(-50%) rotate(270deg);
cursor: col-resize;;
}
elements-nav {
height: 30px;
}
/*.container-fluid {*/
/*padding-top: 30px;*/
/*}*/
<elements-nav></elements-nav>
<div fxLayout="row" id="container" ngxSplit="row">
<div fxFlex="20%" fxFlexFill ngxSplitArea
[ngStyle]="{'display': store.showLeftBar ? '' : 'none', 'min-width': store.showLeftBar ? '100px': ''}"
class="left-side"
>
<elements-left-bar></elements-left-bar>
</div>
<div ngxSplitHandle (mouseup)="dragSplitBtn($event)" class="handle handle-row"
[ngStyle]="{'display': showSplitter ? '' : 'none'}" >
<i class="fa fa-window-minimize" style="color: white"></i>
</div>
<div [fxFlex]="store.showLeftBar ? '80%' : '100%'" fxFlexFill ngxSplitArea class="content">
<elements-content></elements-content>
</div>
</div>
import {Component, HostListener, OnInit} from '@angular/core';
import {DataStore, User} from '@app/globals';
import {environment} from '@src/environments/environment';
import {ViewService} from '@app/services';
@Component({
selector: 'pages-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.css'],
})
export class PageMainComponent implements OnInit {
User = User;
store = DataStore;
constructor(public viewSrv: ViewService) {}
get currentView() {
return this.viewSrv.currentView;
}
get showSplitter() {
if (this.currentView && this.currentView.type !== 'ssh') {
return false;
}
return this.store.showLeftBar;
}
ngOnInit(): void {
}
dragSplitBtn(evt) {
window.dispatchEvent(new Event('resize'));
}
@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
const notInIframe = window.self === window.top;
const notInReplay = location.pathname.indexOf('/luna/replay') === -1;
return !(environment.production && notInIframe && notInReplay);
}
}
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {Monitor} from '../../../globals'; import {Monitor} from '@app/globals';
@Component({ @Component({
selector: 'pages-monitor-linux', selector: 'pages-monitor-linux',
......
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params} from '@angular/router'; import {ActivatedRoute, Params} from '@angular/router';
import {DataStore, Monitor} from '../../globals'; import {DataStore, Monitor} from '@app/globals';
@Component({ @Component({
selector: 'pages-monitor', selector: 'pages-monitor',
......
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {DataStore} from '../../globals'; import {DataStore} from '@app/globals';
@Component({ @Component({
selector: 'pages-not-found', selector: 'pages-not-found',
......
import {PageMainComponent} from './main/main.component';
import {PagesBlankComponent} from './blank/blank.component'; import {PagesBlankComponent} from './blank/blank.component';
import {PagesConnectComponent} from './connect/connect.component'; import {PagesConnectComponent} from './connect/connect.component';
import {PagesControlComponent} from './control/control.component';
import {PagesIndexComponent} from './index/index.component';
import {PagesMonitorComponent} from './monitor/monitor.component'; import {PagesMonitorComponent} from './monitor/monitor.component';
import {PagesReplayComponent} from './replay/replay.component'; import {PagesReplayComponent} from './replay/replay.component';
// import {PagesSettingComponent} from './setting/setting.component';
import {PagesNotFoundComponent} from './not-found/not-found.component'; import {PagesNotFoundComponent} from './not-found/not-found.component';
import {PagesLoginComponent} from './login/login.component'; import {PagesLoginComponent} from './login/login.component';
import {CleftbarComponent} from './control/cleftbar/cleftbar.component';
import {JsonComponent} from './replay/json/json.component'; import {JsonComponent} from './replay/json/json.component';
import {ControlComponent} from './control/control/control.component';
import {PagesControlNavComponent} from './control/control/controlnav/nav.component';
import {SearchComponent, SearchFilter} from './control/search/search.component';
import {PagesMonitorLinuxComponent} from './monitor/linux/linux.component'; import {PagesMonitorLinuxComponent} from './monitor/linux/linux.component';
import {PagesMonitorWindowsComponent} from './monitor/windows/windows.component'; import {PagesMonitorWindowsComponent} from './monitor/windows/windows.component';
import {ReplayGuacamoleComponent} from './replay/guacamole/guacamole.component'; import {ReplayGuacamoleComponent} from './replay/guacamole/guacamole.component';
export const PagesComponents = [ export const PagesComponents = [
PageMainComponent,
PagesBlankComponent, PagesBlankComponent,
PagesConnectComponent, PagesConnectComponent,
PagesControlComponent, ControlComponent, PagesControlNavComponent,
CleftbarComponent,
PagesIndexComponent,
PagesMonitorComponent, PagesMonitorComponent,
PagesReplayComponent, JsonComponent, PagesReplayComponent, JsonComponent,
// PagesSettingComponent,
PagesNotFoundComponent, PagesNotFoundComponent,
PagesLoginComponent, PagesLoginComponent,
SearchComponent,
SearchFilter,
PagesMonitorLinuxComponent, PagesMonitorLinuxComponent,
PagesMonitorWindowsComponent, PagesMonitorWindowsComponent,
ReplayGuacamoleComponent ReplayGuacamoleComponent
......
<div id="player"> <div id="player">
<div class="controls"> <div class="controls">
<button id="play-pause" class="btn" (click)="toggle()"> <button id="play-pause" class="btn" (click)="toggle()">
<i class="fa" [ngClass]="{'fa-play':!isPlaying,'fa-pause': isPlaying}"></i> <i class="fa" [ngClass]="{'fa-play':!isPlaying,'fa-pause': isPlaying}"></i>
</button> </button>
<button type="button" class="btn" (click)="restart()"> <button type="button" class="btn" (click)="restart()">
<i class="fa fa-repeat" aria-hidden="true"></i> <i class="fa fa-repeat" aria-hidden="true"></i>
</button> </button>
<input id="position-slider" type="range" [(ngModel)]="percent" [attr.max]="max" (mouseup)="runFrom()"> <input id="position-slider" type="range" [(ngModel)]="percent" [attr.max]="max" (mouseup)="runFrom()">
<span id="position">{{ position }}</span> <span id="position">{{ position }}</span>
<span>/</span> <span>/</span>
<span id="duration">{{ duration }}</span> <span id="duration">{{ duration }}</span>
</div> </div>
<div id="display" (click)="toggle()"> <div id="display" (click)="toggle()">
<div class="notification-container"> <div class="notification-container">
<div class="seek-notification"> <div class="seek-notification">
<p> <p>
Seek in progress... Seek in progress...
<button id="cancel-seek" class="btn" (click)="cancelSeek($event)">Cancel</button> <button id="cancel-seek" class="btn" (click)="cancelSeek($event)">Cancel</button>
</p> </p>
</div>
</div> </div>
</div>
<div id="screen"></div>
</div> </div>
</div> </div>
#player { #player {
width: 800px; width: 100%;
height: 100%;
padding: 5px;
} }
#display { #display {
position: relative; position: relative;
width: calc(100vw - 10px);
height: calc(100vh - 40px);
}
#screen * {
} }
#player .notification-container { #player .notification-container {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
top: 0; top: 0;
right: 0; right: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
} }
#player .seek-notification { #player .seek-notification {
color: white;
background: rgba(0, 0, 0, 0.75);
color: white; display: none; /* Initially hidden */
background: rgba(0, 0, 0, 0.75); width: 100%;
height: 100%;
display: none; /* Initially hidden */
width: 100%;
height: 100%;
} }
#player.seeking .seek-notification { #player.seeking .seek-notification {
display: table; display: table;
} }
#player .seek-notification p { #player .seek-notification p {
display: table-cell; display: table-cell;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
font-family: sans-serif; font-family: sans-serif;
} }
#player .controls { #player .controls {
width: 100%; width: 100%;
height: 30px;
/* IE10 */
display: -ms-flexbox;
-ms-flex-align: center;
-ms-flex-direction: row;
/* IE10 */ /* Ancient Mozilla */
display: -ms-flexbox; display: -moz-box;
-ms-flex-align: center; -moz-box-align: center;
-ms-flex-direction: row; -moz-box-orient: horizontal;
/* Ancient Mozilla */ /* Ancient WebKit */
display: -moz-box; display: -webkit-box;
-moz-box-align: center; -webkit-box-align: center;
-moz-box-orient: horizontal; -webkit-box-orient: horizontal;
/* Ancient WebKit */ /* Old WebKit */
display: -webkit-box; display: -webkit-flex;
-webkit-box-align: center; -webkit-align-items: center;
-webkit-box-orient: horizontal; -webkit-flex-direction: row;
/* Old WebKit */ /* W3C */
display: -webkit-flex; display: flex;
-webkit-align-items: center; align-items: center;
-webkit-flex-direction: row; flex-direction: row;
/* W3C */ padding-right: 10px;
display: flex;
align-items: center;
flex-direction: row;
} }
#player .controls > * { #player .controls > * {
margin: 0.25em; margin: 0.25em;
} }
#player .controls #position-slider { #player .controls #position-slider {
-ms-flex: 1 1 auto; -ms-flex: 1 1 auto;
-moz-box-flex: 1; -moz-box-flex: 1;
-webkit-box-flex: 1; -webkit-box-flex: 1;
-webkit-flex: 1 1 auto; -webkit-flex: 1 1 auto;
flex: 1 1 auto; flex: 1 1 auto;
} }
#player .controls #play-pause { #player .controls #play-pause {
margin-left: 0; margin-left: 0;
//min-width: 5em; //min-width: 5em;
} }
#player .controls #position, #player .controls #position,
#player .controls #duration { #player .controls #duration {
font-family: monospace; font-family: monospace;
} }
#player .controls #duration { #player .controls #duration {
margin-right: 0; margin-right: 0;
} }
...@@ -55,6 +55,8 @@ export class ReplayGuacamoleComponent implements OnInit { ...@@ -55,6 +55,8 @@ export class ReplayGuacamoleComponent implements OnInit {
recording: any; recording: any;
playerRef: any; playerRef: any;
displayRef: any; displayRef: any;
screenRef: any;
recordingDisplay: any;
max = 100; max = 100;
percent = 0; percent = 0;
duration = '00:00'; duration = '00:00';
...@@ -70,46 +72,51 @@ export class ReplayGuacamoleComponent implements OnInit { ...@@ -70,46 +72,51 @@ export class ReplayGuacamoleComponent implements OnInit {
} }
this.playerRef = document.getElementById('player'); this.playerRef = document.getElementById('player');
this.displayRef = document.getElementById('display'); this.displayRef = document.getElementById('display');
this.screenRef = document.getElementById('screen');
const tunnel = new Guacamole.StaticHTTPTunnel(this.replay.src); const tunnel = new Guacamole.StaticHTTPTunnel(this.replay.src);
this.recording = new Guacamole.SessionRecording(tunnel); this.recording = new Guacamole.SessionRecording(tunnel);
const recordingDisplay = this.recording.getDisplay(); this.recordingDisplay = this.recording.getDisplay();
const recordingElement = this.recordingDisplay.getElement();
this.displayRef.appendChild(recordingDisplay.getElement()); recordingElement.style.margin = '0 auto';
this.screenRef.appendChild(recordingElement);
this.initRecording(); this.initRecording();
const that = this;
recordingDisplay.onresize = function displayResized(width, height) {
// Do not scale if displayRef has no width
if (!width) {
return;
}
// Scale displayRef to fit width of container
recordingDisplay.scale(that.displayRef.offsetWidth / width);
};
// this.toggle(); // this.toggle();
} }
initRecording() { initRecording() {
const that = this;
this.recording.connect(''); this.recording.connect('');
this.recording.onplay = function() { this.recording.onplay = () => {
that.isPlaying = true; this.isPlaying = true;
}; };
this.recording.onseek = function (millis) { this.recording.onseek = (millis) => {
that.position = formatTime(millis); this.position = formatTime(millis);
that.percent = millis; this.percent = millis;
}; };
this.recording.onprogress = function (millis) { this.recording.onprogress = (millis) => {
that.duration = formatTime(millis); this.duration = formatTime(millis);
that.max = millis; this.max = millis;
that.toggle(); this.toggle();
}; };
// If paused, the play/pause button should read "Play" // If paused, the play/pause button should read "Play"
this.recording.onpause = function() { this.recording.onpause = () => {
that.isPlaying = false; this.isPlaying = false;
};
this.recordingDisplay.onresize = (width, height) => {
// Do not scale if displayRef has no width
if (!height) {
return;
}
// Scale displayRef to fit width of container
const widthScale = this.displayRef.offsetWidth / width;
const heightScale = this.displayRef.offsetHeight / height;
const minScale = widthScale < heightScale ? widthScale : heightScale;
this.recordingDisplay.scale(minScale);
}; };
} }
......
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core';
import {Terminal} from 'xterm'; import {Terminal} from 'xterm';
import {HttpService, LogService} from '../../../app.service'; import {HttpService, LogService} from '@app/services';
import {Replay} from '../replay.model'; import {Replay} from '../replay.model';
function zeroPad(num, minLength) { function zeroPad(num, minLength) {
...@@ -86,7 +86,7 @@ export class JsonComponent implements OnInit { ...@@ -86,7 +86,7 @@ export class JsonComponent implements OnInit {
} }
}); });
if (this.replay.src !== 'READY') { if (this.replay.src !== 'READY') {
this._http.get_replay_data(this.replay.src) this._http.getReplayData(this.replay.src)
.subscribe( .subscribe(
data => { data => {
this.replayData = data; this.replayData = data;
......
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {HttpService, LogService} from '../../app.service'; import {HttpService, LogService} from '@app/services';
import {DataStore} from '../../globals';
import {Replay} from './replay.model'; import {Replay} from './replay.model';
@Component({ @Component({
...@@ -15,25 +14,22 @@ export class PagesReplayComponent implements OnInit { ...@@ -15,25 +14,22 @@ export class PagesReplayComponent implements OnInit {
constructor(private route: ActivatedRoute, constructor(private route: ActivatedRoute,
private _http: HttpService, private _http: HttpService,
private _logger: LogService) { private _logger: LogService) {
DataStore.NavShow = false;
} }
ngOnInit() { ngOnInit() {
let token = ''; let sid = '';
this.route.params this.route.params.subscribe(params => {
.subscribe(params => { sid = params['sid'];
token = params['token']; });
}); this._http.getReplay(sid).subscribe(
this._http.get_replay(token) data => {
.subscribe( this.replay.type = data['type'];
data => { this.replay.src = data['src'];
this.replay.type = data['type']; this.replay.id = data['id'];
this.replay.src = data['src']; },
this.replay.id = data['id']; err => {
}, alert('没找到录像文件');
err => { }
alert('没找到录像文件'); );
}
);
} }
} }
import {TransPipe} from './trans.pipe'; import {TransPipe} from './trans.pipe';
import {UtcDatePipe} from './date.pipe'; import {UtcDatePipe} from './date.pipe';
import {TruncatecharsPipe} from './truncatechars.pipe'; import {TruncatecharsPipe} from './truncatechars.pipe';
import {SearchFilter} from './search.pipe';
export const Pipes = [ export const Pipes = [
UtcDatePipe, UtcDatePipe,
TransPipe, TransPipe,
TruncatecharsPipe TruncatecharsPipe,
SearchFilter
]; ];
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({name: 'SearchFilter'})
export class SearchFilter implements PipeTransform {
transform(value: any, input: string) {
if (input) {
input = input.toLowerCase();
return value.filter(function (el: any) {
// ToDo: search with a simple SQL like language, and a bug search a group's hosts
return JSON.stringify(el).toLowerCase().indexOf(input) > -1;
});
}
return value;
}
}
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {i18n} from '../globals'; import {translate} from '../globals';
@Pipe({ @Pipe({
name: 'trans' name: 'trans'
...@@ -7,10 +7,6 @@ import {i18n} from '../globals'; ...@@ -7,10 +7,6 @@ import {i18n} from '../globals';
export class TransPipe implements PipeTransform { export class TransPipe implements PipeTransform {
transform(value: any, args?: any): any { transform(value: any, args?: any): any {
if (i18n.has(value.toLowerCase())) { return translate(value);
return i18n.get(value.toLowerCase());
} else {
return value;
}
} }
} }
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import { import {
MatAutocompleteModule, // MatAutocompleteModule,
MatButtonModule, MatButtonModule,
MatButtonToggleModule, // MatButtonToggleModule,
MatCardModule, MatCardModule,
MatCheckboxModule, // MatCheckboxModule,
MatChipsModule, MatChipsModule,
MatDatepickerModule, // MatDatepickerModule,
MatDialogModule, MatDialogModule,
MatExpansionModule, // MatExpansionModule,
MatGridListModule, // MatGridListModule,
MatIconModule, // MatIconModule,
MatInputModule, MatInputModule,
MatListModule, // MatListModule,
MatMenuModule, // MatMenuModule,
MatNativeDateModule, // MatNativeDateModule,
MatPaginatorModule, // MatPaginatorModule,
MatProgressBarModule, // MatProgressBarModule,
MatProgressSpinnerModule, // MatProgressSpinnerModule,
MatRadioModule, MatRadioModule,
MatRippleModule, // MatRippleModule,
MatSelectModule, MatSelectModule,
MatSidenavModule, // MatSidenavModule,
MatSliderModule, // MatSliderModule,
MatSlideToggleModule, // MatSlideToggleModule,
MatSnackBarModule, // MatSnackBarModule,
MatSortModule, // MatSortModule,
MatTableModule, // MatTableModule,
MatTabsModule, // MatTabsModule,
MatToolbarModule, // MatToolbarModule,
MatTooltipModule, // MatTooltipModule,
MatStepperModule, // MatStepperModule,
} from '@angular/material'; } from '@angular/material';
import {CdkTableModule} from '@angular/cdk/table'; import {CdkTableModule} from '@angular/cdk/table';
@NgModule({ @NgModule({
exports: [ exports: [
CdkTableModule, CdkTableModule,
MatAutocompleteModule, // MatAutocompleteModule,
MatButtonModule, MatButtonModule,
MatButtonToggleModule, // MatButtonToggleModule,
MatCardModule, MatCardModule,
MatCheckboxModule, // MatCheckboxModule,
MatChipsModule, MatChipsModule,
MatStepperModule, // MatStepperModule,
MatDatepickerModule, // MatDatepickerModule,
MatDialogModule, MatDialogModule,
MatExpansionModule, // MatExpansionModule,
MatGridListModule, // MatGridListModule,
MatIconModule, // MatIconModule,
MatInputModule, MatInputModule,
MatListModule, // MatListModule,
MatMenuModule, // MatMenuModule,
MatNativeDateModule, // MatNativeDateModule,
MatPaginatorModule, // MatPaginatorModule,
MatProgressBarModule, // MatProgressBarModule,
MatProgressSpinnerModule, // MatProgressSpinnerModule,
MatRadioModule, MatRadioModule,
MatRippleModule, // MatRippleModule,
MatSelectModule, MatSelectModule,
MatSidenavModule, // MatSidenavModule,
MatSliderModule, // MatSliderModule,
MatSlideToggleModule, // MatSlideToggleModule,
MatSnackBarModule, // MatSnackBarModule,
MatSortModule, // MatSortModule,
MatTableModule, // MatTableModule,
MatTabsModule, // MatTabsModule,
MatToolbarModule, // MatToolbarModule,
MatTooltipModule, // MatTooltipModule,
] ]
}) })
export class MaterialModule { export class MaterialModule {
......
import {MaterialModule} from './MaterialModule.component'; import {MaterialModule} from './MaterialModule.component';
import {NgxUIModule, SplitModule} from '@swimlane/ngx-ui'; // import {SplitModule, NgxUIModule} from '@swimlane/ngx-ui';
import {SplitModule} from '@app/plugins/split/split.module';
import {FlexLayoutModule} from '@angular/flex-layout';
import {LoggerModule, NgxLoggerLevel} from 'ngx-logger'; import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
import {NgxDatatableModule} from '@swimlane/ngx-datatable';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {NgProgressModule} from 'ngx-progressbar';
export const PluginModules = [ export const PluginModules = [
BrowserAnimationsModule, BrowserAnimationsModule,
NgProgressModule,
MaterialModule, MaterialModule,
LoggerModule.forRoot({serverLoggingUrl: '/api/logs', level: NgxLoggerLevel.DEBUG, serverLogLevel: NgxLoggerLevel.ERROR}), LoggerModule.forRoot({level: NgxLoggerLevel.DEBUG, serverLogLevel: NgxLoggerLevel.ERROR}),
NgxDatatableModule, FlexLayoutModule,
NgxUIModule, SplitModule,
SplitModule // NgxUIModule,
]; ];
import {Directive, Optional, Self} from '@angular/core';
import {DefaultFlexDirective} from '@angular/flex-layout';
@Directive({
selector: '[ngxSplitArea]',
host: {
style: 'overflow: auto;'
}
})
export class SplitAreaDirective {
constructor(@Optional() @Self() public flex: DefaultFlexDirective) {}
}
import {Directive, ElementRef, Inject, Output} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {fromEvent, Observable} from 'rxjs';
import {map, switchMap, takeUntil, throttleTime} from 'rxjs/operators';
@Directive({
selector: '[ngxSplitHandle]',
host: {
class: 'ngx-split-handle',
title: 'Drag to resize'
}
})
export class SplitHandleDirective {
@Output() drag: Observable<{ x: number, y: number }>;
constructor(ref: ElementRef, @Inject(DOCUMENT) _document: any) {
let last = {x: 0, y: 0};
const scanPositionDelta = (event: MouseEvent) => {
const current = {x: event.screenX, y: event.screenY};
const delta = {x: current.x - last.x, y: current.y - last.y};
last = current;
return delta;
};
const mousedown$ = fromEvent(ref.nativeElement, 'mousedown').pipe(map(scanPositionDelta));
const mousemove$ = fromEvent(window, 'mousemove').pipe(
throttleTime(50),
map(scanPositionDelta)
);
const mouseup$ = fromEvent(window, 'mouseup');
this.drag = mousedown$.pipe(switchMap(() => mousemove$.pipe(takeUntil(mouseup$))));
}
}
import {
AfterContentInit,
ContentChild,
ContentChildren,
Directive,
ElementRef,
Input,
OnDestroy,
QueryList,
} from '@angular/core';
import {FlexDirective} from '@angular/flex-layout';
import {Subscription} from 'rxjs';
import {SplitHandleDirective} from './split-handle.directive';
import {SplitAreaDirective} from './split-area.directive';
@Directive({
selector: '[ngxSplit]',
host: {
class: 'ngx-split'
}
})
export class SplitDirective implements AfterContentInit, OnDestroy {
@Input('ngxSplit') direction = 'row';
@ContentChild(SplitHandleDirective) handle: SplitHandleDirective;
@ContentChildren(SplitAreaDirective) areas: QueryList<SplitAreaDirective>;
private watcher: Subscription;
constructor(private elementRef: ElementRef) {}
ngAfterContentInit(): void {
this.watcher = this.handle.drag.subscribe(this.onDrag.bind(this));
}
ngOnDestroy() {
this.watcher.unsubscribe();
}
/**
* While dragging, continually update the `flex.activatedValue` for each area
* managed by the splitter.
*/
onDrag({x, y}: {x: number, y: number}): void {
const dragAmount = (this.direction === 'row') ? x : y;
this.areas.forEach((area, i) => {
// get the cur flex and the % in px
const flex = (area.flex as FlexDirective);
const delta = (i === 0) ? dragAmount : -dragAmount;
const currentValue = flex.activatedValue;
// Update Flex-Layout value to build/inject new flexbox CSS
flex.activatedValue = `${this.calculateSize(currentValue, delta)}`;
});
}
/**
* Use the pixel delta change to recalculate the area size (%)
* Note: flex value may be '', %, px, or '<grow> <shrink> <basis>'
*/
calculateSize(value: string, delta: number): number {
const containerSizePx = this.elementRef.nativeElement.clientWidth;
const elementSizePx = Math.round(valueToPixel(value, containerSizePx));
const elementSize = ((elementSizePx + delta) / containerSizePx) * 100;
return Math.round(elementSize * 100) / 100;
}
}
/** Convert the pixel or percentage value to a raw pixel float value */
function valueToPixel(value: string, parentWidth: number): number {
const size = parseFloat(value);
return !value.includes('px') ? parentWidth * (size / 100) : size;
}
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FlexLayoutModule} from '@angular/flex-layout';
import {SplitDirective} from './split.directive';
import {SplitAreaDirective} from './split-area.directive';
import {SplitHandleDirective} from './split-handle.directive';
@NgModule({
imports: [
CommonModule,
FlexLayoutModule,
],
declarations: [SplitDirective, SplitAreaDirective, SplitHandleDirective],
exports: [SplitDirective, SplitAreaDirective, SplitHandleDirective]
})
export class SplitModule {}
/**
* app路由
*
*
* @date 2017-11-07
* @author liuzheng <liuzheng712@gmail.com>
*/
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router'; import {RouterModule, Routes} from '@angular/router';
import {PagesBlankComponent} from '../pages/blank/blank.component'; import {PagesBlankComponent} from '../pages/blank/blank.component';
import {TestPageComponent} from '../test-page/test-page.component';
import {PagesConnectComponent} from '../pages/connect/connect.component'; import {PagesConnectComponent} from '../pages/connect/connect.component';
import {PagesReplayComponent} from '../pages/replay/replay.component'; import {PagesReplayComponent} from '../pages/replay/replay.component';
import {PagesControlComponent} from '../pages/control/control.component'; import {PageMainComponent} from '../pages/main/main.component';
import {PagesNotFoundComponent} from '../pages/not-found/not-found.component';
import {PagesMonitorComponent} from '../pages/monitor/monitor.component'; import {PagesMonitorComponent} from '../pages/monitor/monitor.component';
import {SftpComponent} from '../elements/sftp/sftp.component'; import {ElementSftpComponent} from '../elements/sftp/sftp.component';
const appRoutes: Routes = [ const appRoutes: Routes = [
{path: 'replay/:token', component: PagesReplayComponent}, {path: 'replay/:sid', component: PagesReplayComponent},
{path: 'monitor/:token', component: PagesMonitorComponent}, {path: 'monitor/:token', component: PagesMonitorComponent},
{path: 'test', component: TestPageComponent},
{path: 'connect', component: PagesConnectComponent}, {path: 'connect', component: PagesConnectComponent},
{path: 'sftp', component: SftpComponent}, {path: 'sftp', component: ElementSftpComponent},
{path: 'undefined', component: PagesBlankComponent}, {path: 'undefined', component: PagesBlankComponent},
{path: '', component: PagesControlComponent}, {path: '', component: PageMainComponent},
{path: '**', component: PagesNotFoundComponent} // {path: '**', component: PagesNotFoundComponent}
]; ];
@NgModule({ @NgModule({
...@@ -34,7 +24,6 @@ const appRoutes: Routes = [ ...@@ -34,7 +24,6 @@ const appRoutes: Routes = [
RouterModule.forRoot( RouterModule.forRoot(
appRoutes, appRoutes,
{enableTracing: false} // <-- debugging purposes only {enableTracing: false} // <-- debugging purposes only
// {enableTracing: !environment.production} // <-- debugging purposes only
) )
], ],
exports: [ exports: [
......
import {Injectable, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {CookieService} from 'ngx-cookie-service';
import {environment} from '@src/environments/environment';
import {DataStore, i18n, User} from '@app/globals';
import {HttpService} from './http';
import {LocalStorageService, LogService} from './share';
declare function unescape(s: string): string;
@Injectable()
export class AppService implements OnInit {
// user:User = user ;
lang: string;
constructor(private _http: HttpService,
private _router: Router,
private _cookie: CookieService,
private _logger: LogService,
private _localStorage: LocalStorageService) {
this.setLogLevel();
this.setLang();
this.checklogin();
}
ngOnInit() {
}
setLogLevel() {
// 设置logger level
let logLevel = this._cookie.get('logLevel');
if (!logLevel) {
logLevel = environment.production ? '1' : '5';
}
this._logger.level = parseInt(logLevel, 10);
}
setLang() {
let lang = this._cookie.get('lang');
if (!lang) {
lang = navigator.language;
}
lang = lang.substr(0, 2);
this.lang = lang;
if (lang !== 'en') {
let url = `/luna/i18n/zh.json`;
if (!environment.production) {
url = `/assets/i18n/zh.json`;
}
this._http.get(url).subscribe(
data => {
this._localStorage.set('lang', JSON.stringify(data));
},
err => {
this._logger.error('Load i18n file error: ', err.error);
}
);
}
const l = this._localStorage.get('lang');
if (l) {
try {
const data = JSON.parse(l);
Object.keys(data).forEach((k, _) => {
i18n.set(k, data[k]);
});
} catch (e) {
this._logger.error('Parse lang json failed');
}
}
}
checklogin() {
this._logger.debug('Check user auth');
if (!DataStore.Path) {
this._router.navigate(['FOF']);
}
if (User.logined) {
if (document.location.pathname === '/login') {
this._router.navigate(['']);
} else {
this._router.navigate([document.location.pathname]);
}
return;
}
this._http.getUserProfile().subscribe(
user => {
Object.assign(User, user);
User.logined = true;
this._localStorage.set('user', user.id);
},
err => {
// this._logger.error(err);
User.logined = false;
if (document.location.pathname !== '/luna/connect') {
window.location.href = document.location.origin + '/users/login?next=' +
document.location.pathname + document.location.search;
}
// this._router.navigate(['login']);
},
);
}
browser() {
this._http.reportBrowser();
}
getQueryString(name) {
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
const r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
}
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {Browser, DataStore} from '@app/globals';
import {GuacObjAddResp, SystemUser, TreeNode, User as _User} from '@app/model';
import {SettingService} from './setting';
@Injectable()
export class HttpService {
headers = new HttpHeaders();
constructor(private http: HttpClient, private settingSrv: SettingService) {
}
get(url: string, options?: any) {
return this.http.get(url, options);
}
post(url: string, options?: any) {
return this.http.post(url, options);
}
put(url: string, options?: any) {
return this.http.put(url, options);
}
delete(url: string, options?: any) {
return this.http.delete(url, options);
}
patch(url: string, options?: any) {
return this.http.patch(url, options);
}
head(url: string, options?: any) {
return this.http.head(url, options);
}
options(url: string, options?: any) {
return this.http.options(url, options);
}
reportBrowser() {
return this.http.post('/api/browser', JSON.stringify(Browser));
}
checkLogin(user: any) {
return this.http.post('/api/checklogin', user);
}
getUserProfile() {
return this.http.get<_User>('/api/v1/users/profile/');
}
getMyGrantedAssets(keyword) {
const url = `/api/v1/perms/users/assets/tree/?search=${keyword}`;
return this.http.get<Array<TreeNode>>(url);
}
filterMyGrantedAssetsById(id: string) {
const url = `/api/v1/perms/users/assets/tree/?id=${id}`;
return this.http.get<Array<TreeNode>>(url);
}
getMyGrantedNodes(async: boolean, refresh?: boolean) {
const cachePolicy = refresh ? '2' : '1';
const syncUrl = `/api/v1/perms/users/nodes-with-assets/tree/?cache_policy=${cachePolicy}`;
const asyncUrl = `/api/v1/perms/users/nodes/children-with-assets/tree/?cache_policy=${cachePolicy}`;
const url = async ? asyncUrl : syncUrl;
return this.http.get<Array<TreeNode>>(url);
}
getMyGrantedRemoteApps(id?: string) {
let url = '/api/v1/perms/user/remote-apps/tree/';
if (id) {
url += `?id=${id}&only=1`;
}
return this.http.get<Array<TreeNode>>(url);
}
getMyRemoteAppSystemUsers(remoteAppId: string) {
const url = `/api/v1/perms/users/remote-apps/${remoteAppId}/system-users/`;
return this.http.get<Array<SystemUser>>(url);
}
getMyAssetSystemUsers(assetId: string) {
const url = `/api/v1/perms/users/assets/${assetId}/system-users/`;
return this.http.get<Array<SystemUser>>(url);
}
getGuacamoleToken(user_id: string, authToken: string) {
const body = new HttpParams()
.set('username', user_id)
.set('password', 'jumpserver')
.set('asset_token', authToken);
// {
// "authToken": "xxxxxxx",
// "username": "xxxxxx",
// "dataSource": "jumpserver",
// "availableDataSources":[
// "jumpserver"
// ]
// }
return this.http.post('/guacamole/api/tokens',
body.toString(),
{headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')});
}
guacamoleAddAsset(userId: string, assetId: string, systemUserId: string, systemUserUsername?: string, systemUserPassword?: string) {
let params = new HttpParams()
.set('user_id', userId)
.set('asset_id', assetId)
.set('system_user_id', systemUserId)
.set('token', DataStore.guacamoleToken);
let body = new HttpParams();
if (systemUserUsername && systemUserPassword) {
systemUserUsername = btoa(systemUserUsername);
systemUserPassword = btoa(systemUserPassword);
body = body.set('username', systemUserUsername).set('password', systemUserPassword);
}
const solution = this.settingSrv.setting.rdpSolution || 'Auto';
if (solution !== 'Auto') {
const width = solution.split('x')[0];
const height = solution.split('x')[1];
params = params.set('width', width).set('height', height);
}
return this.http.post<GuacObjAddResp>(
'/guacamole/api/session/ext/jumpserver/asset/add',
body.toString(),
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
params: params
}
);
}
guacamoleAddRemoteApp(userId: string, remoteAppId: string, sysUserId: string, systemUserUsername?: string, systemUserPassword?: string) {
let params = new HttpParams()
.set('user_id', userId)
.set('remote_app_id', remoteAppId)
.set('system_user_id', sysUserId)
.set('token', DataStore.guacamoleToken);
let body = new HttpParams();
if (systemUserUsername && systemUserPassword) {
systemUserUsername = btoa(systemUserUsername);
systemUserPassword = btoa(systemUserPassword);
body = body.set('username', systemUserUsername).set('password', systemUserPassword);
}
const solution = this.settingSrv.setting.rdpSolution || 'Auto';
if (solution !== 'Auto') {
const width = solution.split('x')[0];
const height = solution.split('x')[1];
params = params.set('width', width).set('height', height);
}
return this.http.post<GuacObjAddResp>(
'/guacamole/api/session/ext/jumpserver/remote-app/add',
body.toString(),
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
params: params
}
);
}
guacamoleTokenAddAsset(assetToken: string, token: string) {
let params = new HttpParams()
.set('asset_token', assetToken)
.set('token', token);
const solution = this.settingSrv.setting.rdpSolution || 'Auto';
if (solution !== 'Auto') {
const width = solution.split('x')[0];
const height = solution.split('x')[1];
params = params.set('width', width).set('height', height);
}
return this.http.get(
'/guacamole/api/ext/jumpserver/asset/token/add',
{
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
params: params
}
);
}
search(q: string) {
const params = new HttpParams().set('q', q);
return this.http.get('/api/search', {params: params});
}
getReplay(token: string) {
return this.http.get('/api/v1/terminal/sessions/' + token + '/replay');
}
// get_replay_json(token: string) {
// return this.http.get('/api/terminal/v2/sessions/' + token + '/replay');
// }
getReplayData(src: string) {
return this.http.get(src);
}
getUserIdFromToken(token: string) {
const params = new HttpParams()
.set('user-only', '1')
.set('token', token);
return this.http.get('/api/v1/users/connection-token/', {params: params});
}
}
import {LogService, LocalStorageService, UUIDService} from './share';
export {LogService, LocalStorageService, UUIDService} from './share';
import {AppService} from './app';
export {AppService} from './app';
import {HttpService} from './http';
export {HttpService} from './http';
import {NavService} from './nav';
export {NavService} from './nav';
import {TreeFilterService} from './treeFilter';
export {TreeFilterService} from './treeFilter';
import {SettingService} from './setting';
export {SettingService} from './setting';
import {ViewService} from './view';
export {ViewService} from './view';
export const AllServices = [
LogService, LocalStorageService, UUIDService,
AppService,
HttpService,
NavService,
TreeFilterService,
SettingService,
ViewService,
];
import {EventEmitter, Injectable} from '@angular/core';
import {NavEvt} from '@app/model';
import {LocalStorageService} from './share';
@Injectable()
export class NavService {
onNavClick: EventEmitter<NavEvt> = new EventEmitter<NavEvt>();
constructor(private store: LocalStorageService) {}
disconnectAllConnection() {
const evt = new NavEvt('disconnectAll', '');
this.onNavClick.emit(evt);
}
disconnectConnection() {
const evt = new NavEvt('disconnect', '');
this.onNavClick.emit(evt);
}
changeLang(value) {
const evt = new NavEvt('changeLang', value);
this.onNavClick.emit(evt);
}
get treeLoadAsync() {
const value = this.store.get('LoadTreeAsync');
return value === '1';
}
set treeLoadAsync(v: boolean) {
const value = v ? '1' : '0';
this.store.set('LoadTreeAsync', value);
}
get skipAllManualPassword() {
const value = this.store.get('SkipAllManualPassword');
return value === '1';
}
set skipAllManualPassword(v) {
const value = v ? '1' : '0';
this.store.set('SkipAllManualPassword', value);
}
}
import {Injectable} from '@angular/core';
import {Setting} from '@app/model';
import {LocalStorageService} from './share';
@Injectable()
export class SettingService {
setting: Setting;
settingKey: 'LunaSetting';
constructor(private store: LocalStorageService) {
const settingData = this.store.get(this.settingKey);
if (settingData) {
try {
this.setting = JSON.parse(settingData) as Setting;
} catch (e) {
this.setting = new Setting();
}
} else {
this.setting = new Setting();
}
}
save() {
const settingData = JSON.stringify(this.setting);
this.store.set(this.settingKey, settingData);
}
isLoadTreeAsync(): boolean {
return this.setting.isLoadTreeAsync === '1';
}
isSkipAllManualPassword(): boolean {
return this.setting.isSkipAllManualPassword === '1';
}
}
import {Injectable} from '@angular/core';
import {NGXLogger} from 'ngx-logger';
import * as UUID from 'uuid-js/lib/uuid';
@Injectable()
export class LogService {
level: number;
constructor(private _logger: NGXLogger) {
// 0.- Level.OFF
// 1.- Level.ERROR
// 2.- Level.WARN
// 3.- Level.INFO
// 4.- Level.DEBUG
// 5.- Level.LOG
this.level = 4;
}
log(message: any, ...additional: any[]) {
if (this.level > 4) {
this._logger.log(message, ...additional);
}
}
debug(message: any, ...additional: any[]) {
if (this.level > 3) {
this._logger.debug(message, ...additional);
}
}
info(message: any, ...additional: any[]) {
if (this.level > 2) {
this._logger.info(message, ...additional);
}
}
warn(message: any, ...additional: any[]) {
if (this.level > 1) {
this._logger.warn(message, ...additional);
}
}
error(message: any, ...additional: any[]) {
if (this.level > 0) {
this._logger.error(message, ...additional);
}
}
}
@Injectable()
export class LocalStorageService {
constructor() {
}
get(key: string): string {
return localStorage.getItem(key);
}
set(key: string, value: any) {
return localStorage.setItem(key, value);
}
delete(key: string) {
return localStorage.removeItem(key);
}
}
@Injectable()
export class UUIDService {
constructor() {
}
gen() {
return UUID.create()['hex'];
}
}
import {EventEmitter, Injectable} from '@angular/core';
@Injectable()
export class TreeFilterService {
onFilter: EventEmitter<string> = new EventEmitter<string>();
filter(q: string) {
this.onFilter.emit(q);
}
}
import {Injectable} from '@angular/core';
import {View} from '@app/model';
@Injectable()
export class ViewService {
viewList: Array<View> = [];
currentView: View;
num = 0;
addView(view: View) {
this.num += 1;
view.id = 'View_' + this.num;
this.viewList.push(view);
}
activeView(view: View) {
this.viewList.forEach((v, k) => {
v.active = v === view;
});
setTimeout(() => {
const viewEl = document.getElementById(view.id);
if (viewEl) {
viewEl.scrollIntoView();
}
}, 100);
this.currentView = view;
}
removeView(view: View) {
const index = this.viewList.indexOf(view);
this.viewList.splice(index, 1);
}
}
<!--<tree-root [nodes]="nodes" [options]="options"></tree-root>-->
<!--<app-replay-mp4></app-replay-mp4>-->
<!--<elements-tree [TreeData]="nodes1"></elements-tree>-->
<elements-asset-tree></elements-asset-tree>
:host {
display: block;
width: 100%;
margin: 50px 0;
}
.btns {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TestPageComponent } from './test-page.component';
describe('TestPageComponent', () => {
let component: TestPageComponent;
let fixture: ComponentFixture<TestPageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestPageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnInit} from '@angular/core';
import {DataStore, Video} from '../globals';
@Component({
selector: 'app-test-page',
templateUrl: './test-page.component.html',
styleUrls: ['./test-page.component.scss']
})
export class TestPageComponent implements OnInit {
zNodes = [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'name': '新节点 12',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_granted': [
{
'id': '1600ed6d-e3b6-434c-a960-c5bb818806b6',
'hostname': 'windows1',
'ip': '10.1.10.178',
'port': 3389,
'system_users_granted': [
{
'id': '8763b81a-bb5e-484a-abca-10514c7bb185',
'name': '组织1-部门1-Administrator',
'username': 'Administrator',
'priority': 10,
'protocol': 'rdp',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'Administrator',
'os': null,
'domain': null,
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Windows',
'comment': ''
},
{
'id': 'b952a481-a624-467e-b97f-9435155f0d53',
'hostname': 'testserver',
'ip': '10.1.10.192',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': 'CentOS',
'domain': null,
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
},
{
'id': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'key': '0',
'value': 'Fit2cloud',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
},
{
'id': 'ad594b10-9f64-4913-b7b1-135fe63561d1',
'hostname': 'ali-windows',
'ip': '47.104.206.228',
'port': 3389,
'system_users_granted': [
{
'id': '8763b81a-bb5e-484a-abca-10514c7bb185',
'name': '组织1-部门1-Administrator',
'username': 'Administrator',
'priority': 10,
'protocol': 'rdp',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'Administrator',
'os': null,
'domain': null,
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'key': '0',
'value': 'Fit2cloud',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Windows',
'comment': ''
},
{
'id': 'b6f16269-d02a-4055-9cd8-460fa10b1540',
'hostname': 'test-vm3',
'ip': '172.19.185.8',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': null,
'domain': '8789580f-b5ca-4478-b6d3-d0dafc4c48e8',
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
},
{
'id': '27e50edc-52d9-41ef-8c9e-1bff9d1628b2',
'hostname': 'test-vm2',
'ip': '172.19.185.7',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': null,
'domain': '8789580f-b5ca-4478-b6d3-d0dafc4c48e8',
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
},
{
'id': '9ef36bb3-1bed-455f-be09-3770d3f4bf97',
'hostname': 'test-vm1',
'ip': '172.19.185.6',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': null,
'domain': '8789580f-b5ca-4478-b6d3-d0dafc4c48e8',
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
}
],
'assets_amount': 6
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'name': '网域测试',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_granted': [
{
'id': '1600ed6d-e3b6-434c-a960-c5bb818806b6',
'hostname': 'windows1',
'ip': '10.1.10.178',
'port': 3389,
'system_users_granted': [
{
'id': '8763b81a-bb5e-484a-abca-10514c7bb185',
'name': '组织1-部门1-Administrator',
'username': 'Administrator',
'priority': 10,
'protocol': 'rdp',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'Administrator',
'os': null,
'domain': null,
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Windows',
'comment': ''
},
{
'id': 'b952a481-a624-467e-b97f-9435155f0d53',
'hostname': 'testserver',
'ip': '10.1.10.192',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': 'CentOS',
'domain': null,
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
},
{
'id': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'key': '0',
'value': 'Fit2cloud',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
},
{
'id': 'b6f16269-d02a-4055-9cd8-460fa10b1540',
'hostname': 'test-vm3',
'ip': '172.19.185.8',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': null,
'domain': '8789580f-b5ca-4478-b6d3-d0dafc4c48e8',
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
},
{
'id': '27e50edc-52d9-41ef-8c9e-1bff9d1628b2',
'hostname': 'test-vm2',
'ip': '172.19.185.7',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': null,
'domain': '8789580f-b5ca-4478-b6d3-d0dafc4c48e8',
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
},
{
'id': '9ef36bb3-1bed-455f-be09-3770d3f4bf97',
'hostname': 'test-vm1',
'ip': '172.19.185.6',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': null,
'domain': '8789580f-b5ca-4478-b6d3-d0dafc4c48e8',
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
}
],
'assets_amount': 5
},
{
'id': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'key': '0',
'name': 'Fit2cloud',
'value': 'Fit2cloud',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_granted': [
{
'id': 'b952a481-a624-467e-b97f-9435155f0d53',
'hostname': 'testserver',
'ip': '10.1.10.192',
'port': 22,
'system_users_granted': [
{
'id': '7e326f71-aee5-4688-8cc1-717919470a09',
'name': 'root',
'username': 'root',
'priority': 10,
'protocol': 'ssh',
'comment': ''
},
{
'id': '17f384f4-683d-4944-a38d-db73608b92a9',
'name': 'zbh-test',
'username': 'zbh',
'priority': 10,
'protocol': 'ssh',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'root, zbh',
'os': 'CentOS',
'domain': null,
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'key': '0:11',
'value': '网域测试',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
},
{
'id': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'key': '0',
'value': 'Fit2cloud',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Linux',
'comment': ''
},
{
'id': 'ad594b10-9f64-4913-b7b1-135fe63561d1',
'hostname': 'ali-windows',
'ip': '47.104.206.228',
'port': 3389,
'system_users_granted': [
{
'id': '8763b81a-bb5e-484a-abca-10514c7bb185',
'name': '组织1-部门1-Administrator',
'username': 'Administrator',
'priority': 10,
'protocol': 'rdp',
'comment': ''
}
],
'is_active': true,
'system_users_join': 'Administrator',
'os': null,
'domain': null,
'nodes': [
{
'id': '67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6',
'key': '0:11:77',
'value': '新节点 12',
'parent': '9c83d432-a353-4a4e-9fd9-be27a5851c2d',
'assets_amount': 6,
'is_asset': false
},
{
'id': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'key': '0',
'value': 'Fit2cloud',
'parent': 'be9d9c3a-68d0-40ec-887c-5815d68e2f2c',
'assets_amount': 6,
'is_asset': false
}
],
'platform': 'Windows',
'comment': ''
}
],
'assets_amount': 2
}
];
constructor() {
DataStore.NavShow = false;
}
ngOnInit() {
Video.id = 'asfafdasd';
Video.src = 'https://www.w3schools.com/tags/movie.mp4';
}
}
...@@ -17,6 +17,7 @@ export class Socket { ...@@ -17,6 +17,7 @@ export class Socket {
async connect() { async connect() {
const emitter = new EventEmitter(); const emitter = new EventEmitter();
emitter.setMaxListeners(20);
this.emitter = emitter; this.emitter = emitter;
const events = { const events = {
}; };
......
...@@ -63,5 +63,16 @@ ...@@ -63,5 +63,16 @@
"full screen": "全屏显示", "full screen": "全屏显示",
"please input password": "请输入密码", "please input password": "请输入密码",
"username": "用户名", "username": "用户名",
"password": "密码" "password": "密码",
"skip": "跳过",
"loading": "加载中",
"load tree async": "异步加载树",
"load tree sync": "同步加载树",
"show manual password": "显示手动密码窗",
"skip manual password": "跳过手动密码窗",
"tab list": "窗口列表",
"open in new window": "新窗口打开",
"setting": "设置",
"yes": "是",
"no": "否"
} }
...@@ -2,5 +2,5 @@ export const environment = { ...@@ -2,5 +2,5 @@ export const environment = {
production: true production: true
}; };
// export const version = '1.3.0-{{BUILD_NUMBER}} GPLv2.'; // export const version = '1.3.0-{{BUILD_NUMBER}} GPLv2.';
export const version = '1.5.2-101 GPLv2.'; export const version = '1.5.3-101 GPLv2.';
// export const version = '1.4.1-{{BUILD_NUMBER}} GPLv2.'; // export const version = '1.4.1-{{BUILD_NUMBER}} GPLv2.';
...@@ -9,9 +9,8 @@ ...@@ -9,9 +9,8 @@
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
</head> </head>
<body> <body>
<app-root></app-root> <app-root>加载中...</app-root>
<span id="marker" style="display: none;font-size: 14px">marker</span> <span id="marker" style="display: none;font-size: 14px">marker</span>
</body> </body>
<script> <script>
window.onload = function (ev) { window.onload = function (ev) {
...@@ -21,15 +20,15 @@ ...@@ -21,15 +20,15 @@
} }
if (navigator.clipboard && navigator.clipboard.readText) { if (navigator.clipboard && navigator.clipboard.readText) {
navigator.clipboard.readText().then(function textRead(text) { navigator.clipboard.readText().then(function textRead(text) {
clipboardData = text; clipboardData = text;
}); });
} }
if (navigator.clipboard && navigator.clipboard.writeText) { if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(clipboardData) navigator.clipboard.writeText(clipboardData)
} }
} }
</script> </script>
</html> </html>
...@@ -3,12 +3,14 @@ $fa-font-path: '~font-awesome/fonts'; ...@@ -3,12 +3,14 @@ $fa-font-path: '~font-awesome/fonts';
@import '~font-awesome/scss/font-awesome'; @import '~font-awesome/scss/font-awesome';
// bootstrap // bootstrap
// Todo: 去掉依赖
@import '~bootstrap/scss/bootstrap'; @import '~bootstrap/scss/bootstrap';
$FontPathOpenSans: '~npm-font-open-sans/fonts'; @import "~@angular/material/prebuilt-themes/indigo-pink.css";
@import '~npm-font-open-sans/open-sans'; //$FontPathOpenSans: '~npm-font-open-sans/fonts';
$roboto-font-path: '~roboto-fontface/fonts'; //@import '~npm-font-open-sans/open-sans';
@import '~roboto-fontface/css/mixins.scss'; //$roboto-font-path: '~roboto-fontface/fonts';
//@import '~roboto-fontface/css/mixins.scss';
/* /*
* *
...@@ -17,51 +19,52 @@ $roboto-font-path: '~roboto-fontface/fonts'; ...@@ -17,51 +19,52 @@ $roboto-font-path: '~roboto-fontface/fonts';
* *
*/ */
$asset-path: '../static/imgs/inspinia'; $asset-path: '../static/imgs/inspinia';
// Todo: 去掉依赖
@import '../assets/inspinia/style'; @import '../assets/inspinia/style';
@import '~@swimlane/ngx-datatable/release/index.css'; //@import '~@swimlane/ngx-datatable/release/index.css';
@import '~@swimlane/ngx-datatable/release/themes/material.css'; //@import '~@swimlane/ngx-datatable/release/themes/material.css';
@import '~@swimlane/ngx-datatable/release/assets/icons.css'; //@import '~@swimlane/ngx-datatable/release/assets/icons.css';
@import './material.css'; @import './material.css';
$material-design-icons-font-path: '~/material-design-icons/iconfont/'; //$material-design-icons-font-path: '~/material-design-icons/iconfont/';
//@import '~material-design-icons/iconfont/material-icons'; //@import '~material-design-icons/iconfont/material-icons';
@font-face { //@font-face {
font-family: 'Material Icons'; // font-family: 'Material Icons';
font-style: normal; // font-style: normal;
font-weight: 400; // font-weight: 400;
src: url('#{$material-design-icons-font-path}/MaterialIcons-Regular.eot'); /* For IE6-8 */ // src: url('#{$material-design-icons-font-path}/MaterialIcons-Regular.eot'); /* For IE6-8 */
src: local('Material Icons'), // src: local('Material Icons'),
local('MaterialIcons-Regular'), // local('MaterialIcons-Regular'),
url('#{$material-design-icons-font-path}/MaterialIcons-Regular.woff2') format('woff2'), // url('#{$material-design-icons-font-path}/MaterialIcons-Regular.woff2') format('woff2'),
url('#{$material-design-icons-font-path}/MaterialIcons-Regular.woff') format('woff'), // url('#{$material-design-icons-font-path}/MaterialIcons-Regular.woff') format('woff'),
url('#{$material-design-icons-font-path}/MaterialIcons-Regular.ttf') format('truetype'); // url('#{$material-design-icons-font-path}/MaterialIcons-Regular.ttf') format('truetype');
} //}
//
.material-icons { //.material-icons {
font-family: 'Material Icons'; // font-family: 'Material Icons';
font-weight: normal; // font-weight: normal;
font-style: normal; // font-style: normal;
font-size: 24px; /* Preferred icon size */ // font-size: 24px; /* Preferred icon size */
display: inline-block; // display: inline-block;
line-height: 1; // line-height: 1;
text-transform: none; // text-transform: none;
letter-spacing: normal; // letter-spacing: normal;
word-wrap: normal; // word-wrap: normal;
white-space: nowrap; // white-space: nowrap;
direction: ltr; // direction: ltr;
//
/* Support for all WebKit browsers. */ // /* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased; // -webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */ // /* Support for Safari and Chrome. */
text-rendering: optimizeLegibility; // text-rendering: optimizeLegibility;
//
/* Support for Firefox. */ // /* Support for Firefox. */
-moz-osx-font-smoothing: grayscale; // -moz-osx-font-smoothing: grayscale;
//
/* Support for IE. */ // /* Support for IE. */
font-feature-settings: 'liga'; // font-feature-settings: 'liga';
} //}
//ul { //ul {
// padding-left: 0; // padding-left: 0;
...@@ -97,3 +100,7 @@ button.icon-split-handle.ngx-split-button { ...@@ -97,3 +100,7 @@ button.icon-split-handle.ngx-split-button {
//border: 1px solid #3a3333; //border: 1px solid #3a3333;
opacity: 0.0; opacity: 0.0;
} }
button.mat-raised-button {
margin-left: 5px;
}
...@@ -10,6 +10,7 @@ body { ...@@ -10,6 +10,7 @@ body {
width: 100%; width: 100%;
background-color: rgb(243, 243, 244); background-color: rgb(243, 243, 244);
overflow-y: hidden; overflow-y: hidden;
margin: 0;
} }
app-root { app-root {
...@@ -90,7 +91,7 @@ body ::-webkit-scrollbar-thumb { ...@@ -90,7 +91,7 @@ body ::-webkit-scrollbar-thumb {
border-radius: 6px; border-radius: 6px;
} }
.ztree * { .ztree *:not(.fa) {
font-family: 'Monaco', 'Consolas', 'monospace' !important; font-family: 'Monaco', 'Consolas', 'monospace' !important;
font-size: 13px !important; font-size: 13px !important;
} }
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
] ]
}, },
"files": [ "files": [
"test.ts" "test.ts",
"polyfills.ts"
], ],
"include": [ "include": [
"**/*.spec.ts", "**/*.spec.ts",
......
...@@ -2,10 +2,9 @@ ...@@ -2,10 +2,9 @@
set -ex set -ex
rm -fr luna
npm run-script build npm run-script build
rm -fr luna* cp -R src/assets/i18n luna/
mv dist luna
cp -R i18n luna/
tar czf luna.tar.gz luna tar czf luna.tar.gz luna
md5 luna.tar.gz md5 luna.tar.gz
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"baseUrl": "src",
"paths": {
"@src/*": [
"*"
],
"@app/*": [
"app/*"
]
},
"allowJs": true, "allowJs": true,
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"sourceMap": true, "sourceMap": true,
...@@ -16,6 +25,8 @@ ...@@ -16,6 +25,8 @@
"lib": [ "lib": [
"es2017", "es2017",
"dom" "dom"
] ],
"module": "es2015",
"allowSyntheticDefaultImports": true
} }
} }
...@@ -14,8 +14,7 @@ ...@@ -14,8 +14,7 @@
"eofline": true, "eofline": true,
"forin": true, "forin": true,
"import-blacklist": [ "import-blacklist": [
true, true
"rxjs"
], ],
"import-spacing": true, "import-spacing": true,
"indent": [ "indent": [
......
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