diff --git a/src/app/elements/connect/acl-dialog/acl-dialog.component.html b/src/app/elements/connect/acl-dialog/acl-dialog.component.html index aeb306a3..269fe4af 100644 --- a/src/app/elements/connect/acl-dialog/acl-dialog.component.html +++ b/src/app/elements/connect/acl-dialog/acl-dialog.component.html @@ -1,13 +1,13 @@

{{ 'Login reminder' | translate }}

-
+
{{ 'ACL reject login asset' | translate }}
- +
@@ -16,8 +16,8 @@

{{ 'Login reminder' | translate }}

{{ 'Need review for login asset' | translate }}
- - + +
@@ -34,8 +34,10 @@

{{ 'Login reminder' | translate }}

- - + +
@@ -44,7 +46,7 @@

{{ 'Login reminder' | translate }}

{{ 'Ticket review rejected for login asset' | translate }}
- + @@ -53,7 +55,7 @@

{{ 'Login reminder' | translate }}

{{ 'Ticket review closed for login asset' | translate }}
- + @@ -62,18 +64,27 @@

{{ 'Login reminder' | translate }}

{{ 'Account not found' | translate }}
- +
-
{{ otherError }}
> +
{{ otherError }}
+ >
- +
- +
+ +
{{ data.error.error | json }}
+
+ + + +
+ diff --git a/src/app/elements/connect/acl-dialog/acl-dialog.component.ts b/src/app/elements/connect/acl-dialog/acl-dialog.component.ts index d899f126..ad586074 100644 --- a/src/app/elements/connect/acl-dialog/acl-dialog.component.ts +++ b/src/app/elements/connect/acl-dialog/acl-dialog.component.ts @@ -3,6 +3,7 @@ import {Asset, ConnectData, ConnectionToken} from '@app/model'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; import {HttpService, I18nService} from '@app/services'; import {ToastrService} from 'ngx-toastr'; +import {HttpErrorResponse} from '@angular/common/http'; @Component({ selector: 'elements-acl-dialog', @@ -14,6 +15,7 @@ export class ElementACLDialogComponent implements OnInit { public connectInfo: ConnectData; public code: string; public connectionToken: ConnectionToken = null; + public error: HttpErrorResponse; public otherError: string; public ticketAssignees: string = '-'; // Token 的行为,创建或者兑换 Token, create, exchange @@ -21,13 +23,17 @@ export class ElementACLDialogComponent implements OnInit { public tokenID: string; private timerCheckTicket: number; + constructor(public dialogRef: MatDialogRef, + private _i18n: I18nService, + private _toastr: ToastrService, + private _http: HttpService, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + } - constructor( public dialogRef: MatDialogRef, - private _i18n: I18nService, - private _toastr: ToastrService, - private _http: HttpService, - @Inject(MAT_DIALOG_DATA) public data: any - ) {} + get ticketDetailPageURL(): string { + return this.connectionToken.from_ticket_info.ticket_detail_page_url; + } ngOnInit() { // 创建 Token 的时候,需要传入 Asset 和 ConnectInfo @@ -35,13 +41,9 @@ export class ElementACLDialogComponent implements OnInit { this.connectInfo = this.data.connectInfo; this.code = this.data.code; // 兑换 Token 的时候,需要传入 Token ID - this.tokenID = this.data.tokenID + this.tokenID = this.data.tokenID; // 控制 token 的行为, 创建还是兑换 - this.tokenAction = this.data.tokenAction - } - - get ticketDetailPageURL(): string { - return this.connectionToken.from_ticket_info.ticket_detail_page_url; + this.tokenAction = this.data.tokenAction; } async onCopySuccess(evt) { @@ -60,7 +62,7 @@ export class ElementACLDialogComponent implements OnInit { this.code = 'ticket_review_pending'; this.checkTicket(); } - } + }; const errorCallback = (error) => { if (error.error.code.startsWith('acl_')) { this.code = error.error.code; @@ -68,11 +70,11 @@ export class ElementACLDialogComponent implements OnInit { this.code = 'other'; this.otherError = error.error.detail; } - } + }; if (this.tokenAction === 'exchange') { - this._http.exchangeConnectToken(this.tokenID, true).subscribe(successCallback, errorCallback) + this._http.exchangeConnectToken(this.tokenID, true).subscribe(successCallback, errorCallback); } else { - this._http.createConnectToken(this.asset, this.connectInfo, true).subscribe(successCallback, errorCallback) + this._http.createConnectToken(this.asset, this.connectInfo, true).subscribe(successCallback, errorCallback); } } diff --git a/src/app/elements/content/content-window/magnus/magnus.component.html b/src/app/elements/content/content-window/magnus/magnus.component.html index 603f97b0..9e3d9f6e 100644 --- a/src/app/elements/content/content-window/magnus/magnus.component.html +++ b/src/app/elements/content/content-window/magnus/magnus.component.html @@ -1,26 +1,36 @@ -
+

{{ 'Database connect info' | translate }}

-
{{ item.label | async }} - + {{ passwordShow }} - + {{ this.info[item.name] }} + + + {{ "Re-use for a long time after opening" | translate }} + + @@ -34,19 +44,21 @@

$ {{ cliSafe }}
- - diff --git a/src/app/elements/content/content-window/magnus/magnus.component.scss b/src/app/elements/content/content-window/magnus/magnus.component.scss index 7b7b78b4..614d19a1 100644 --- a/src/app/elements/content/content-window/magnus/magnus.component.scss +++ b/src/app/elements/content/content-window/magnus/magnus.component.scss @@ -98,3 +98,15 @@ iframe { width: 15px; height: 15px; } + +.reusable-button ::ng-deep { + .mat-slide-toggle-bar { + background-color: gray; + } + .mat-slide-toggle.mat-checked .mat-slide-toggle-bar { + background-color: var(--primary-color) !important; + } + .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: white; + } +} diff --git a/src/app/elements/content/content-window/magnus/magnus.component.ts b/src/app/elements/content/content-window/magnus/magnus.component.ts index 3998edf3..7609529e 100644 --- a/src/app/elements/content/content-window/magnus/magnus.component.ts +++ b/src/app/elements/content/content-window/magnus/magnus.component.ts @@ -1,8 +1,9 @@ -import {Component, Input, OnInit} from '@angular/core'; -import {View, Account, Endpoint, Asset, ConnectionToken} from '@app/model'; +import {Component, Input, OnInit, ViewChild} from '@angular/core'; +import {Account, Asset, ConnectionToken, Endpoint, View} from '@app/model'; import {ConnectTokenService, HttpService, I18nService, SettingService} from '@app/services'; import {User} from '@app/globals'; import {ToastrService} from 'ngx-toastr'; +import {MatTooltip} from '@angular/material/tooltip'; interface InfoItem { name: string; @@ -18,6 +19,7 @@ interface InfoItem { export class ElementConnectorMagnusComponent implements OnInit { @Input() view: View; + @ViewChild(MatTooltip, {static: false}) tooltip: MatTooltip; asset: Asset; account: Account; @@ -34,6 +36,8 @@ export class ElementConnectorMagnusComponent implements OnInit { passwordMask = '******'; passwordShow = '******'; token: ConnectionToken; + showSetReusable: boolean; + hoverClipTip: string = this._i18n.instant('Click to copy'); constructor(private _http: HttpService, private _i18n: I18nService, @@ -42,10 +46,11 @@ export class ElementConnectorMagnusComponent implements OnInit { private _settingSvc: SettingService ) { this.globalSetting = this._settingSvc.globalSetting; + this.showSetReusable = this.globalSetting.CONNECTION_TOKEN_REUSABLE; } async ngOnInit() { - const {asset, account, protocol, smartEndpoint, connectToken } = this.view; + const {asset, account, protocol, smartEndpoint, connectToken} = this.view; this.token = connectToken; this.asset = asset; this.account = account; @@ -57,7 +62,7 @@ export class ElementConnectorMagnusComponent implements OnInit { this.setDBInfo(); this.generateConnCli(); this.loading = false; - this.view.termComp = this + this.view.termComp = this; } setDBInfo() { @@ -69,11 +74,14 @@ export class ElementConnectorMagnusComponent implements OnInit { {name: 'host', value: host, label: this._i18n.t('Host')}, {name: 'port', value: port, label: this._i18n.t('Port')}, {name: 'username', value: this.token.id, label: this._i18n.t('Username')}, - {name: 'password', value: this.token.value, label: this._i18n.t('Password')}, + {name: 'password', value: this.token.value, label: this._i18n.t('Password')}, {name: 'database', value: database, label: this._i18n.t('Database')}, {name: 'protocol', value: this.protocol, label: this._i18n.t('Protocol')}, - {name: 'expire_time', value: `${this.token.expire_time} s` , label: this._i18n.t('Expire time')}, + {name: 'date_expired', value: `${this.token.date_expired}`, label: this._i18n.t('Expire time')}, ]; + if (this.showSetReusable) { + this.infoItems.push({name: 'set_reusable', value: '', label: this._i18n.t('Set reusable')}); + } this.info = this.infoItems.reduce((pre, current) => { pre[current.name] = current.value; return pre; @@ -126,6 +134,19 @@ export class ElementConnectorMagnusComponent implements OnInit { this.cli = cli.replace(passwordHolder, password); } + setReusable(event) { + this._connectTokenSvc.setReusable(this.token, event.checked).subscribe( + res => { + this.token = Object.assign(this.token, res); + this.info['date_expired'] = `${this.token.date_expired}`; + }, + error => { + this.token.is_reusable = false; + this._toastr.error(error.error.msg || error.error.is_reusable || error.message); + } + ); + } + startClient() { const {protocol} = this.info; const data = { @@ -149,19 +170,22 @@ export class ElementConnectorMagnusComponent implements OnInit { } async onCopySuccess(evt) { - const msg = await this._i18n.t('Copied'); - this._toastr.success(msg); + this.hoverClipTip = this._i18n.instant('Copied'); + } + + onHoverClipRef(evt) { + this.hoverClipTip = this._i18n.instant('Click to copy'); } async reconnect() { - const oldConnectToken = this.view.connectToken - const newConnectToken = await this._connectTokenSvc.exchange(oldConnectToken) + const oldConnectToken = this.view.connectToken; + const newConnectToken = await this._connectTokenSvc.exchange(oldConnectToken); if (!newConnectToken) { - return + return; } // 更新当前 view 的 connectToken - this.view.connectToken = newConnectToken - await this.ngOnInit() + this.view.connectToken = newConnectToken; + await this.ngOnInit(); // 刷新完成隐藏密码 this.passwordShow = this.passwordMask; } diff --git a/src/app/model.ts b/src/app/model.ts index 695b3ee0..022eea48 100644 --- a/src/app/model.ts +++ b/src/app/model.ts @@ -267,6 +267,7 @@ export class GlobalSetting { INTERFACE: any; TERMINAL_OMNIDB_ENABLED: boolean; TERMINAL_GRAPHICAL_RESOLUTION: string; + CONNECTION_TOKEN_REUSABLE: boolean; } export class Setting { @@ -389,6 +390,8 @@ export class ConnectionToken { account: string; expire_time: number; is_active: boolean; + date_expired: Date; + is_reusable: boolean; from_ticket: { id: string; }; diff --git a/src/app/services/connect-token.ts b/src/app/services/connect-token.ts index 53ab5224..128cf189 100644 --- a/src/app/services/connect-token.ts +++ b/src/app/services/connect-token.ts @@ -25,9 +25,10 @@ export class ConnectTokenService { create(asset: Asset, connectInfo: ConnectData): Promise { return new Promise((resolve, reject) => { this._http.createConnectToken(asset, connectInfo).subscribe( - (token: ConnectionToken) => {resolve(token);}, + (token: ConnectionToken) => { resolve(token); }, (error) => { - this.handleError({asset, connectInfo, code: error.error.code, tokenAction: 'create'}, resolve) + console.log('Error: ', error); + this.handleError({asset, connectInfo, code: error.error.code, tokenAction: 'create', error: error}, resolve ); } ); }); @@ -38,9 +39,15 @@ export class ConnectTokenService { this._http.exchangeConnectToken(connectToken.id).subscribe( (token: ConnectionToken) => { resolve(token); }, (error) => { - this.handleError({tokenID: connectToken.id, code: error.error.code, tokenAction: 'exchange'}, resolve) + this.handleError({tokenID: connectToken.id, code: error.error.code, tokenAction: 'exchange'}, resolve); } ); }); } + + setReusable(connectToken: ConnectionToken, reusable: Boolean) { + const url = `/api/v1/authentication/connection-token/${connectToken.id}/`; + const data = {is_reusable: reusable}; + return this._http.patch(url, data); + } } diff --git a/src/app/services/http.ts b/src/app/services/http.ts index 6538bf6c..97a520e8 100644 --- a/src/app/services/http.ts +++ b/src/app/services/http.ts @@ -84,9 +84,9 @@ export class HttpService { ); } - patch(url: string, options?: any): Observable { + patch(url: string, body?: any, options?: any): Observable { options = this.setOptionsCSRFToken(options); - return this.http.patch(url, options).pipe( + return this.http.patch(url, body, options).pipe( catchError(this.handleError.bind(this)) ); } @@ -282,11 +282,13 @@ export class HttpService { } async handleConnectMethodExpiredError(error) { - if (error.status === 400 && error.error && error.error.error && error.error.error.startsWith('Connect method')) { - const errMsg = await this._i18n.t('The connection method is invalid, please refresh the page') - alert(errMsg) + if (error.status === 400 ) { + if (error.error && error.error.error && error.error.error.startsWith('Connect method')) { + const errMsg = await this._i18n.t('The connection method is invalid, please refresh the page'); + alert(errMsg); + } } - throw error + throw error; } getSmartEndpoint({ assetId, sessionId, token }, protocol ): Promise { diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index bd20f267..1b45d30f 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -143,5 +143,7 @@ "Copy link": "Copy link", "Login review approved": "Login review has been approved, connecting assets...", "No account available": "No available accounts", + "Set reusable": "Set reusable", + "Re-use for a long time after opening": "Re-use for a long time after opening", "The connection method is invalid, please refresh the page": "The connection method is invalid, please refresh the page" } diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json index 22268052..c887a98d 100644 --- a/src/assets/i18n/ja.json +++ b/src/assets/i18n/ja.json @@ -146,5 +146,7 @@ "Copy link": "リンクをコピーする", "Login review approved": "ログイン監査に合格し、アセットを接続しています...", "No account available": "アカウントがありません", + "Set reusable": "再利用可能な", + "Re-use for a long time after opening": "開いた後、長い間再利用する", "The connection method is invalid, please refresh the page": "接続方法が無効です。ページを更新してください" } diff --git a/src/assets/i18n/zh.json b/src/assets/i18n/zh.json index 34f8b889..91579797 100644 --- a/src/assets/i18n/zh.json +++ b/src/assets/i18n/zh.json @@ -124,7 +124,7 @@ "General": "基本配置", "Applet connect method": "远程应用连接方式", "Client": "客户端", - + "Keyboard layout": "键盘布局", "UK English keyboard layout": "UK English (Qwerty)", "US English keyboard layout": "US English (Qwerty)", @@ -146,6 +146,8 @@ "Copy link": "复制链接", "Login review approved": "登录审核已通过, 正在连接资产...", "No account available": "没有可用账号", + "Set reusable": "开启复用", + "Re-use for a long time after opening": "开启后该连接信息可长时间多次使用", "The connection method is invalid, please refresh the page": "该连接方式已失效,请刷新页面" }