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..c969cc54 100644 --- a/src/app/elements/connect/acl-dialog/acl-dialog.component.html +++ b/src/app/elements/connect/acl-dialog/acl-dialog.component.html @@ -74,6 +74,14 @@

{{ 'Login reminder' | translate }}

- +
+ +
{{ JSON.stringify(data.error.error) }}
+
+ + + +
+ 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..e4e0eec7 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 @@ -121,4 +123,6 @@ export class ElementACLDialogComponent implements OnInit { ); }, 3000); } + + protected readonly JSON = JSON; } 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..ec64fa53 100644 --- a/src/app/elements/content/content-window/magnus/magnus.component.html +++ b/src/app/elements/content/content-window/magnus/magnus.component.html @@ -6,10 +6,11 @@

{{ 'Database connect info' | translate }}

{{ item.label | async }} @@ -18,9 +19,17 @@

{{ 'Database connect info' | translate }}

- + {{ this.info[item.name] }} + + + {{ "Re-use for a long time after opening" | translate }} + + 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..584de850 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 {Component, Input, OnInit, ViewChild} from '@angular/core'; import {View, Account, Endpoint, Asset, ConnectionToken} 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; + hoverTip: string = this._i18n.instant('Click to copy'); constructor(private _http: HttpService, private _i18n: I18nService, @@ -42,6 +46,7 @@ export class ElementConnectorMagnusComponent implements OnInit { private _settingSvc: SettingService ) { this.globalSetting = this._settingSvc.globalSetting; + this.showSetReusable = this.globalSetting.CONNECTION_TOKEN_REUSABLE; } async ngOnInit() { @@ -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() { @@ -72,8 +77,11 @@ export class ElementConnectorMagnusComponent implements OnInit { {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.hoverTip = this._i18n.instant('Copied'); + } + + onHover(evt) { + this.hoverTip = 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": "该连接方式已失效,请刷新页面" }