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 @@
"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",
"node_modules/ngx-toastr/toastr.css",
"src/sass/style.scss",
"src/styles.css",
"src/theme.scss",
"src/assets/ztree/awesomeStyle/awesome.css"
],
"scripts": [
......
......@@ -8906,6 +8906,14 @@
"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": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
......
......@@ -38,7 +38,7 @@
"utf-8-validate": "^5.0.2"
},
"dependencies": {
"@angular/animations": "~7.2.0",
"@angular/animations": "^7.2.0",
"@angular/cdk": "^7.3.7",
"@angular/common": "~7.2.0",
"@angular/compiler": "~7.2.0",
......@@ -64,6 +64,7 @@
"neffos.js": "^0.1.19",
"ngx-cookie-service": "^1.0.10",
"ngx-logger": "4.0.4",
"ngx-toastr": "^10.2.0",
"popper.js": "^1.14.7",
"requirejs": "^2.3.5",
"rxjs": "~6.3.3",
......
......@@ -4,6 +4,8 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms'; // <-- NgModel
import {NGXLogger} from 'ngx-logger';
import {HttpClientModule} from '@angular/common/http';
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';
// service
......@@ -26,8 +28,10 @@ import {AssetTreeDialogComponent, ManualPasswordDialogComponent} from './element
BrowserModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
ReactiveFormsModule,
AppRouterModule,
ToastrModule.forRoot(),
...PluginModules
],
declarations: [
......
......@@ -3,6 +3,7 @@ import {MatDialog} from '@angular/material';
import {BehaviorSubject, Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {ActivatedRoute} from '@angular/router';
import {ToastrService} from 'ngx-toastr';
import {groupBy} from '@app/utils/common';
import {AppService, HttpService, LogService, NavService, SettingService, TreeFilterService} from '@app/services';
......@@ -48,6 +49,7 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
isLoadTreeAsync: boolean;
filterAssetCancel$: Subject<boolean> = new Subject();
loading = true;
favoriteAssets = [];
constructor(private _appSvc: AppService,
private _treeFilterSvc: TreeFilterService,
......@@ -55,7 +57,8 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
public _logger: LogService,
private activatedRoute: ActivatedRoute,
private _http: HttpService,
private settingSvc: SettingService
private settingSvc: SettingService,
private toastr: ToastrService
) {}
ngOnInit() {
......@@ -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._http.getMyGrantedNodes(this.isLoadTreeAsync, refresh).subscribe(resp => {
this.loading = false;
......@@ -206,6 +213,15 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
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() {
const menuList = [{
'id': 'new-connection',
......@@ -219,6 +235,18 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
'fa': 'fa-file',
'hide': !this.nodeSupportSSH(),
'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) {
return [];
......@@ -265,6 +293,26 @@ export class ElementAssetTreeComponent implements OnInit, OnDestroy {
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) {
if (this.isLoadTreeAsync) {
this._logger.debug('Filter assets server');
......
......@@ -107,8 +107,8 @@ export class ElementConnectComponent implements OnInit, OnDestroy {
if (systemUsers.length > 1) {
return new Promise<SystemUser>(resolve => {
const dialogRef = this._dialog.open(AssetTreeDialogComponent, {
height: '200px',
width: '300px',
height: '250px',
width: '500px',
data: {users: systemUserMaxPriority}
});
......
<h1 mat-dialog-title>{{"Found"|trans}} {{data.users.length}} {{"Users "|trans}}</h1>
<mat-form-field style="width: 100%">
<mat-select [(value)]="selected"
[compareWith]="compareFn"
[formControl]="UserSelectControl"
placeholder="{{'Choose a User'|trans}}" required>
<mat-option *ngFor="let u of data.users" value="{{u.id}}">{{u.name}}</mat-option>
</mat-select>
<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"
[compareWith]="compareFn"
[formControl]="UserSelectControl"
placeholder="{{'Choose a User'|trans}}" required>
<mat-option *ngFor="let u of data.users" value="{{u.id}}">{{u.name}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<mat-error *ngIf="UserSelectControl.hasError('required')">{{"Please choose a User"|trans}}</mat-error>
</mat-form-field>
</div>
<div style="float: right">
<button mat-raised-button (click)="onNoClick()">{{"Cancel"|trans}}</button>
<button mat-raised-button color="primary" [mat-dialog-close]="selected" cdkFocusInitial>{{"Confirm"|trans}}</button>
......
import {NgModule} from '@angular/core';
import {
// MatAutocompleteModule,
MatAutocompleteModule,
MatButtonModule,
// MatButtonToggleModule,
MatButtonToggleModule,
MatCardModule,
// MatCheckboxModule,
MatCheckboxModule,
MatChipsModule,
// MatDatepickerModule,
MatDatepickerModule,
MatDialogModule,
// MatExpansionModule,
// MatGridListModule,
// MatIconModule,
MatExpansionModule,
MatGridListModule,
MatIconModule,
MatInputModule,
// MatListModule,
// MatMenuModule,
// MatNativeDateModule,
// MatPaginatorModule,
// MatProgressBarModule,
// MatProgressSpinnerModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
// MatRippleModule,
MatRippleModule,
MatSelectModule,
// MatSidenavModule,
// MatSliderModule,
// MatSlideToggleModule,
// MatSnackBarModule,
// MatSortModule,
// MatTableModule,
// MatTabsModule,
// MatToolbarModule,
// MatTooltipModule,
// MatStepperModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatStepperModule,
} from '@angular/material';
import {CdkTableModule} from '@angular/cdk/table';
@NgModule({
exports: [
CdkTableModule,
// MatAutocompleteModule,
MatAutocompleteModule,
MatButtonModule,
// MatButtonToggleModule,
MatButtonToggleModule,
MatCardModule,
// MatCheckboxModule,
MatCheckboxModule,
MatChipsModule,
// MatStepperModule,
// MatDatepickerModule,
MatStepperModule,
MatDatepickerModule,
MatDialogModule,
// MatExpansionModule,
// MatGridListModule,
// MatIconModule,
MatExpansionModule,
MatGridListModule,
MatIconModule,
MatInputModule,
// MatListModule,
// MatMenuModule,
// MatNativeDateModule,
// MatPaginatorModule,
// MatProgressBarModule,
// MatProgressSpinnerModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
// MatRippleModule,
MatRippleModule,
MatSelectModule,
// MatSidenavModule,
// MatSliderModule,
// MatSlideToggleModule,
// MatSnackBarModule,
// MatSortModule,
// MatTableModule,
// MatTabsModule,
// MatToolbarModule,
// MatTooltipModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
]
})
export class MaterialModule {
......
......@@ -3,6 +3,7 @@ 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';
import {getCookie} from '@app/utils/common';
@Injectable()
......@@ -12,23 +13,43 @@ export class HttpService {
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) {
return this.http.get(url, options);
}
post(url: string, options?: any) {
return this.http.post(url, options);
post(url: string, body: any, options?: any) {
options = this.setOptionsCSRFToken(options);
return this.http.post(url, body, options);
}
put(url: string, options?: any) {
options = this.setOptionsCSRFToken(options);
return this.http.put(url, options);
}
delete(url: string, options?: any) {
options = this.setOptionsCSRFToken(options);
return this.http.delete(url, options);
}
patch(url: string, options?: any) {
options = this.setOptionsCSRFToken(options);
return this.http.patch(url, options);
}
......@@ -88,6 +109,25 @@ export class HttpService {
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) {
const body = new HttpParams()
.set('username', user_id)
......
......@@ -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 @@
"yes": "是",
"no": "否",
"cols": "列数",
"rows": "行数"
"rows": "行数",
"favorite": "收藏",
"disfavor": "取消收藏",
"success": "成功"
}
......@@ -124,3 +124,17 @@ body ::-webkit-scrollbar-thumb {
display: inline-block;
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