Unverified Commit 33d73edb authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #129 from jumpserver/dev

Dev
parents 9388e27a 99ab8e18
...@@ -19,14 +19,15 @@ ...@@ -19,14 +19,15 @@
"assets": [ "assets": [
"src/assets", "src/assets",
"src/static", "src/static",
"src/theme/default",
"src/favicon.ico" "src/favicon.ico"
], ],
"styles": [ "styles": [
"node_modules/animate.css/animate.min.css", "node_modules/animate.css/animate.min.css",
"node_modules/xterm/dist/xterm.css", "node_modules/xterm/dist/xterm.css",
"node_modules/ngx-toastr/toastr.css",
"src/sass/style.scss", "src/sass/style.scss",
"src/styles.css", "src/styles.css",
"src/theme.scss",
"src/assets/ztree/awesomeStyle/awesome.css" "src/assets/ztree/awesomeStyle/awesome.css"
], ],
"scripts": [ "scripts": [
......
...@@ -8906,6 +8906,14 @@ ...@@ -8906,6 +8906,14 @@
"vlq": "^1.0.0" "vlq": "^1.0.0"
} }
}, },
"ngx-toastr": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-10.2.0.tgz",
"integrity": "sha512-6ASr5bcvQmtNKb4D2VEsQjCXyROq6GwberBWO0bVt+xcBYPUea4aRTgX8in9apX9buaTafzG+h3HlnIraspoPg==",
"requires": {
"tslib": "^1.9.0"
}
},
"nice-try": { "nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
"utf-8-validate": "^5.0.2" "utf-8-validate": "^5.0.2"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "~7.2.0", "@angular/animations": "^7.2.0",
"@angular/cdk": "^7.3.7", "@angular/cdk": "^7.3.7",
"@angular/common": "~7.2.0", "@angular/common": "~7.2.0",
"@angular/compiler": "~7.2.0", "@angular/compiler": "~7.2.0",
...@@ -64,6 +64,7 @@ ...@@ -64,6 +64,7 @@
"neffos.js": "^0.1.19", "neffos.js": "^0.1.19",
"ngx-cookie-service": "^1.0.10", "ngx-cookie-service": "^1.0.10",
"ngx-logger": "4.0.4", "ngx-logger": "4.0.4",
"ngx-toastr": "^10.2.0",
"popper.js": "^1.14.7", "popper.js": "^1.14.7",
"requirejs": "^2.3.5", "requirejs": "^2.3.5",
"rxjs": "~6.3.3", "rxjs": "~6.3.3",
......
...@@ -4,6 +4,8 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms'; // <-- NgModel ...@@ -4,6 +4,8 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms'; // <-- NgModel
import {NGXLogger} from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
import {HttpClientModule} from '@angular/common/http'; import {HttpClientModule} from '@angular/common/http';
import {CookieService} from 'ngx-cookie-service'; import {CookieService} from 'ngx-cookie-service';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ToastrModule} from 'ngx-toastr';
import {MAT_LABEL_GLOBAL_OPTIONS} from '@angular/material'; import {MAT_LABEL_GLOBAL_OPTIONS} from '@angular/material';
// service // service
...@@ -26,8 +28,10 @@ import {AssetTreeDialogComponent, ManualPasswordDialogComponent} from './element ...@@ -26,8 +28,10 @@ import {AssetTreeDialogComponent, ManualPasswordDialogComponent} from './element
BrowserModule, BrowserModule,
FormsModule, FormsModule,
HttpClientModule, HttpClientModule,
BrowserAnimationsModule,
ReactiveFormsModule, ReactiveFormsModule,
AppRouterModule, AppRouterModule,
ToastrModule.forRoot(),
...PluginModules ...PluginModules
], ],
declarations: [ declarations: [
......
...@@ -3,6 +3,7 @@ import {MatDialog} from '@angular/material'; ...@@ -3,6 +3,7 @@ import {MatDialog} from '@angular/material';
import {BehaviorSubject, Subject} from 'rxjs'; import {BehaviorSubject, Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators'; import {takeUntil} from 'rxjs/operators';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {ToastrService} from 'ngx-toastr';
import {groupBy} from '@app/utils/common'; import {groupBy} from '@app/utils/common';
import {AppService, HttpService, LogService, NavService, SettingService, TreeFilterService} from '@app/services'; import {AppService, HttpService, LogService, NavService, SettingService, TreeFilterService} from '@app/services';
...@@ -48,6 +49,7 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy { ...@@ -48,6 +49,7 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
isLoadTreeAsync: boolean; isLoadTreeAsync: boolean;
filterAssetCancel$: Subject<boolean> = new Subject(); filterAssetCancel$: Subject<boolean> = new Subject();
loading = true; loading = true;
favoriteAssets = [];
constructor(private _appSvc: AppService, constructor(private _appSvc: AppService,
private _treeFilterSvc: TreeFilterService, private _treeFilterSvc: TreeFilterService,
...@@ -55,7 +57,8 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy { ...@@ -55,7 +57,8 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
public _logger: LogService, public _logger: LogService,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private _http: HttpService, private _http: HttpService,
private settingSvc: SettingService private settingSvc: SettingService,
private toastr: ToastrService
) {} ) {}
ngOnInit() { ngOnInit() {
...@@ -105,6 +108,10 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy { ...@@ -105,6 +108,10 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
}; };
} }
this._http.getFavoriteAssets().subscribe(resp => {
this.favoriteAssets = resp.map(i => i.asset);
});
this.loading = true; this.loading = true;
this._http.getMyGrantedNodes(this.isLoadTreeAsync, refresh).subscribe(resp => { this._http.getMyGrantedNodes(this.isLoadTreeAsync, refresh).subscribe(resp => {
this.loading = false; this.loading = false;
...@@ -206,6 +213,15 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy { ...@@ -206,6 +213,15 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
return findSSHProtocol; return findSSHProtocol;
} }
isAssetFavorite() {
const host = this.rightClickSelectNode.meta.asset;
if (!host) {
return false;
}
const assetId = host.id;
return this.favoriteAssets.indexOf(assetId) !== -1;
}
get RMenuList() { get RMenuList() {
const menuList = [{ const menuList = [{
'id': 'new-connection', 'id': 'new-connection',
...@@ -219,6 +235,18 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy { ...@@ -219,6 +235,18 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
'fa': 'fa-file', 'fa': 'fa-file',
'hide': !this.nodeSupportSSH(), 'hide': !this.nodeSupportSSH(),
'click': this.connectFileManager.bind(this) 'click': this.connectFileManager.bind(this)
}, {
'id': 'favorite',
'name': 'Favorite',
'fa': 'fa-star-o',
'hide': this.isAssetFavorite(),
'click': this.favoriteAsset.bind(this)
}, {
'id': 'disfavor',
'name': 'Disfavor',
'fa': 'fa-star',
'hide': !this.isAssetFavorite(),
'click': this.favoriteAsset.bind(this)
}]; }];
if (!this.rightClickSelectNode) { if (!this.rightClickSelectNode) {
return []; return [];
...@@ -265,6 +293,26 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy { ...@@ -265,6 +293,26 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
window.open(url, '_blank'); window.open(url, '_blank');
} }
favoriteAsset() {
const host = this.rightClickSelectNode.meta.asset;
if (!host) {
return false;
}
const assetId = host.id;
if (this.isAssetFavorite()) {
this._http.favoriteAsset(assetId, false).subscribe(() => {
const i = this.favoriteAssets.indexOf(assetId);
this.favoriteAssets.splice(i, 1);
this.toastr.success(translate('Disfavor') + ' ' + translate('success'), '', {timeOut: 2000});
});
} else {
this._http.favoriteAsset(assetId, true).subscribe(() => {
this.favoriteAssets.push(assetId);
this.toastr.success(translate('Favorite') + ' ' + translate('success'), '', {timeOut: 2000});
});
}
}
filterAssets(keyword) { filterAssets(keyword) {
if (this.isLoadTreeAsync) { if (this.isLoadTreeAsync) {
this._logger.debug('Filter assets server'); this._logger.debug('Filter assets server');
......
...@@ -107,8 +107,8 @@ export class ElementConnectComponent implements OnInit, OnDestroy { ...@@ -107,8 +107,8 @@ export class ElementConnectComponent implements OnInit, OnDestroy {
if (systemUsers.length > 1) { if (systemUsers.length > 1) {
return new Promise<SystemUser>(resolve => { return new Promise<SystemUser>(resolve => {
const dialogRef = this._dialog.open(AssetTreeDialogComponent, { const dialogRef = this._dialog.open(AssetTreeDialogComponent, {
height: '200px', height: '250px',
width: '300px', width: '500px',
data: {users: systemUserMaxPriority} data: {users: systemUserMaxPriority}
}); });
......
<h1 mat-dialog-title>{{"Found"|trans}} {{data.users.length}} {{"Users "|trans}}</h1> <h1 mat-dialog-title>{{"Found"|trans}} {{data.users.length}} {{"Users "|trans}}</h1>
<mat-form-field style="width: 100%"> <div style="height: 110px">
<div *ngIf="data.users.length < 5">
<div>
<label>{{'Choose a User'|trans}}: </label>
</div>
<mat-radio-group required [formControl]="UserSelectControl" [(ngModel)]="selected">
<mat-radio-button *ngFor="let u of data.users" value="{{u.id}}"
style="padding-right: 10px">{{u.name}}</mat-radio-button>
</mat-radio-group>
</div>
<div *ngIf="data.users.length >= 5">
<mat-form-field style="width: 100%" >
<mat-select [(value)]="selected" <mat-select [(value)]="selected"
[compareWith]="compareFn" [compareWith]="compareFn"
[formControl]="UserSelectControl" [formControl]="UserSelectControl"
placeholder="{{'Choose a User'|trans}}" required> placeholder="{{'Choose a User'|trans}}" required>
<mat-option *ngFor="let u of data.users" value="{{u.id}}">{{u.name}}</mat-option> <mat-option *ngFor="let u of data.users" value="{{u.id}}">{{u.name}}</mat-option>
</mat-select> </mat-select>
</mat-form-field>
</div>
<mat-error *ngIf="UserSelectControl.hasError('required')">{{"Please choose a User"|trans}}</mat-error> <mat-error *ngIf="UserSelectControl.hasError('required')">{{"Please choose a User"|trans}}</mat-error>
</mat-form-field> </div>
<div style="float: right"> <div style="float: right">
<button mat-raised-button (click)="onNoClick()">{{"Cancel"|trans}}</button> <button mat-raised-button (click)="onNoClick()">{{"Cancel"|trans}}</button>
<button mat-raised-button color="primary" [mat-dialog-close]="selected" cdkFocusInitial>{{"Confirm"|trans}}</button> <button mat-raised-button color="primary" [mat-dialog-close]="selected" cdkFocusInitial>{{"Confirm"|trans}}</button>
......
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 {
......
...@@ -3,6 +3,7 @@ import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; ...@@ -3,6 +3,7 @@ import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {Browser, DataStore} from '@app/globals'; import {Browser, DataStore} from '@app/globals';
import {GuacObjAddResp, SystemUser, TreeNode, User as _User} from '@app/model'; import {GuacObjAddResp, SystemUser, TreeNode, User as _User} from '@app/model';
import {SettingService} from './setting'; import {SettingService} from './setting';
import {getCookie} from '@app/utils/common';
@Injectable() @Injectable()
...@@ -12,23 +13,43 @@ export class HttpService { ...@@ -12,23 +13,43 @@ export class HttpService {
constructor(private http: HttpClient, private settingSrv: SettingService) { constructor(private http: HttpClient, private settingSrv: SettingService) {
} }
setOptionsCSRFToken(options) {
const csrfToken = getCookie('csrftoken');
let headers;
if (!options) {
options = {};
}
if (!options.headers) {
headers = new HttpHeaders();
} else {
headers = options.headers;
}
headers = headers.set('X-CSRFToken', csrfToken);
options.headers = headers;
return options;
}
get(url: string, options?: any) { get(url: string, options?: any) {
return this.http.get(url, options); return this.http.get(url, options);
} }
post(url: string, options?: any) { post(url: string, body: any, options?: any) {
return this.http.post(url, options); options = this.setOptionsCSRFToken(options);
return this.http.post(url, body, options);
} }
put(url: string, options?: any) { put(url: string, options?: any) {
options = this.setOptionsCSRFToken(options);
return this.http.put(url, options); return this.http.put(url, options);
} }
delete(url: string, options?: any) { delete(url: string, options?: any) {
options = this.setOptionsCSRFToken(options);
return this.http.delete(url, options); return this.http.delete(url, options);
} }
patch(url: string, options?: any) { patch(url: string, options?: any) {
options = this.setOptionsCSRFToken(options);
return this.http.patch(url, options); return this.http.patch(url, options);
} }
...@@ -88,6 +109,25 @@ export class HttpService { ...@@ -88,6 +109,25 @@ export class HttpService {
return this.http.get<Array<SystemUser>>(url); return this.http.get<Array<SystemUser>>(url);
} }
favoriteAsset(assetId: string, favorite: boolean) {
let url: string;
if (favorite) {
url = `/api/v1/assets/favorite-assets/`;
const data = {
asset: assetId
};
return this.post(url, data);
} else {
url = `/api/v1/assets/favorite-assets/?asset=${assetId}`;
return this.delete(url);
}
}
getFavoriteAssets() {
const url = '/api/v1/assets/favorite-assets/';
return this.http.get<Array<any>>(url);
}
getGuacamoleToken(user_id: string, authToken: string) { getGuacamoleToken(user_id: string, authToken: string) {
const body = new HttpParams() const body = new HttpParams()
.set('username', user_id) .set('username', user_id)
......
...@@ -31,3 +31,19 @@ export function newTerminal(fontSize?: number) { ...@@ -31,3 +31,19 @@ export function newTerminal(fontSize?: number) {
} }
}); });
} }
export function getCookie(name: string): string {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
...@@ -76,5 +76,8 @@ ...@@ -76,5 +76,8 @@
"yes": "是", "yes": "是",
"no": "否", "no": "否",
"cols": "列数", "cols": "列数",
"rows": "行数" "rows": "行数",
"favorite": "收藏",
"disfavor": "取消收藏",
"success": "成功"
} }
...@@ -124,3 +124,17 @@ body ::-webkit-scrollbar-thumb { ...@@ -124,3 +124,17 @@ body ::-webkit-scrollbar-thumb {
display: inline-block; display: inline-block;
font: normal normal normal 14px/1 FontAwesome !important; font: normal normal normal 14px/1 FontAwesome !important;
} }
#toast-container > .toast-warning:before {
}
#toast-container > .toast-error:before {
}
#toast-container > .toast-info:before {
}
#toast-container > .toast-success:before {
content: "";
}
@import '~@angular/material/theming';
@include mat-core();
$my-teal: (
50: #e0f2f1,
100: #b2dfdb,
200: #80cbc4,
300: #4db6ac,
400: #26a69a,
500: #1ab394,
600: #00897b,
700: #00796b,
800: #00695c,
900: #004d40,
A100: #a7ffeb,
A200: #64ffda,
A400: #1de9b6,
A700: #00bfa5,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
400: $dark-primary-text,
500: $light-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $dark-primary-text,
A400: $dark-primary-text,
A700: $dark-primary-text,
)
);
$candy-app-primary: mat-palette($my-teal, 500);
$candy-app-accent: mat-palette($my-teal, 500);
// The warn palette is optional (defaults to red).
$candy-app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($candy-app-theme);
This diff is collapsed.
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