diff --git a/Dockerfile b/Dockerfile index c5d0e08a..a188123e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,6 @@ RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=lun && yarn build \ && cp -R src/assets/i18n luna/ -FROM nginx:alpine +FROM nginx:1.24 COPY --from=stage-build /data/luna /opt/luna COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/package.json b/package.json index 09ee51d7..b38d0eda 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "GPLv3", "scripts": { "ng": "ng", - "start": "ng serve --proxy-config proxy.conf.json --host 0.0.0.0 --base-href=/luna/ --disable-host-check", + "start": "ng serve --hmr --proxy-config proxy.conf.json --host 0.0.0.0 --base-href=/luna/ --disable-host-check", "build": "ng build --prod --base-href=/luna/ --output-path 'luna'", "extract": "ngx-translate-extract --input ./src --output ./src/assets/i18n/*.json --sort --format namespaced-json", "test": "ng test", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e7e0b4aa..00229f78 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,6 @@ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; -import { InfiniteScrollModule } from 'ngx-infinite-scroll'; +import {InfiniteScrollModule} from 'ngx-infinite-scroll'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; // <-- NgModel lives here import {NGXLogger} from 'ngx-logger'; import {CookieService} from 'ngx-cookie-service'; @@ -8,7 +8,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {ToastrModule} from 'ngx-toastr'; import {MAT_LABEL_GLOBAL_OPTIONS} from '@angular/material'; import {HttpClient, HttpClientModule} from '@angular/common/http'; -import {TranslateModule, TranslateLoader} from '@ngx-translate/core'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {TranslateHttpLoader} from '@ngx-translate/http-loader'; // service @@ -29,10 +29,10 @@ import {ChangLanWarningDialogComponent} from './elements/nav/nav.component'; import {ElementSettingComponent} from '@app/elements/setting/setting.component'; import {ElementConnectDialogComponent} from './elements/connect/connect-dialog/connect-dialog.component'; import {ElementDownloadDialogComponent} from './elements/connect/download-dialog/download-dialog.component'; -import {ElementACLDialogComponent} from './elements/connect/acl-dialog/acl-dialog.component'; -import {ElementDialogAlertComponent} from './elements/dialog/dialog.service'; +import {ElementACLDialogComponent} from '@app/services/connect-token/acl-dialog/acl-dialog.component'; +import {ElementDialogAlertComponent} from '@app/services/dialog/dialog.service'; import {ClipboardService} from 'ngx-clipboard'; -import { ElementsReplayMp4Component } from './elements/replay/mp4/mp4.component'; +import {ElementsReplayMp4Component} from './elements/replay/mp4/mp4.component'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, '/luna/assets/i18n/'); diff --git a/src/app/elements/asset-tree/asset-tree.component.scss b/src/app/elements/asset-tree/asset-tree.component.scss index 44f164cf..be5576b0 100644 --- a/src/app/elements/asset-tree/asset-tree.component.scss +++ b/src/app/elements/asset-tree/asset-tree.component.scss @@ -200,7 +200,7 @@ tr:hover { } .expand-tree { - height: 100%; + height: calc(100% - 31px); } .tree-type .tree-search { diff --git a/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.html b/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.html index 18396f06..14fcbf34 100644 --- a/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.html +++ b/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.html @@ -1,48 +1,45 @@ -
+
- + {{ 'Advanced option' | translate }} - - - - {{ item.label | translate }} - -
- - - + +
+ - {{i}} - - -
-
- + {{ item.label | translate }} + +
+ + + {{ item.label | translate }} + + + {{ option.label | translate }} + + + + + +
+ + {{ item.label | translate }} + + {{ option.label | translate }} + + +
+
+
+
diff --git a/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.scss b/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.scss index 7414ae92..fa91cac4 100644 --- a/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.scss +++ b/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.scss @@ -1,40 +1,83 @@ .advanced-option { - margin-top: 1.25em; + margin-top: 1.1em; } + .mat-expansion-panel-header { - height: 32px!important; - padding: 0; - padding-right: 4px; + height: 32px !important; + padding: 0 4px 0 0; + + &:hover { + background-color: rgb(255, 255, 255); + } } + +.mat-expansion-panel:not(.mat-expanded) .mat-expansion-panel-header:not([aria-disabled=true]):hover { + background: rgba(0, 0, 0, 0); +} + .mat-expansion-panel-header-title { color: rgba(0, 0, 0, 0.54); font-size: 13px; } + .mat-expansion-panel:not([class*=mat-elevation-z]) { box-shadow: none; } + .mat-expansion-panel { - border-bottom: 1px solid rgba(0, 0, 0, 0.42); + border-bottom: 1px solid rgb(0, 0, 0, 12%); + &::ng-deep .mat-expansion-panel-body { - padding: 0 0 14px!important; + padding: 0 0 0 !important; } } + +.mat-expansion-panel.panel-show.mat-expanded { + border-bottom: none; + + &::ng-deep .mat-expansion-panel-content { + padding: 10px 5px 0 5px; + border: solid 1px rgb(0, 0, 0, 12%); + } +} + .mat-accordion .mat-expansion-panel:last-of-type { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } + .example-radio-group { display: flex; flex-direction: column; margin-top: 4px; } + .example-radio-button { margin: 2px; } -.ng-star-inserted { - margin-top: 5px; + + +.option-item.mat-form-field { + margin-top: 10px; } - .mat-checkbox-layout { - margin-left: 12px!important; + +.option-item { + flex: 0 0 49%; + box-sizing: border-box; + padding: 0 5px; +} + +.mat-checkbox-layout { + margin-left: 12px !important; +} + +.options-container { + display: flex; + flex-wrap: wrap; + align-items: baseline; /* 添加这一行 */ +} + +.options-container > ng-container { + } diff --git a/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.ts b/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.ts index cb42c241..bd56d48e 100644 --- a/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.ts +++ b/src/app/elements/connect/connect-dialog/advanced-option/advanced-option.component.ts @@ -1,5 +1,7 @@ -import {Component, Input, OnChanges, Output, EventEmitter} from '@angular/core'; -import {ConnectMethod, ConnectOption, Protocol} from '@app/model'; +import {Component, EventEmitter, Input, OnChanges, Output} from '@angular/core'; +import {ConnectMethod, ConnectOption, Protocol, Setting} from '@app/model'; +import {resolutionsChoices} from '@app/globals'; +import {SettingService} from '@app/services'; @Component({ selector: 'elements-advanced-option', @@ -12,24 +14,69 @@ export class ElementAdvancedOptionComponent implements OnChanges { @Output() onOptionsChange = new EventEmitter(); public advancedOptions: ConnectOption[] = []; public isShowAdvancedOption = false; - public needShowAutoCompletionProtocols: Array = ['mysql', 'mariadb']; + public setting: Setting; + private boolChoices = [ + {label: 'Yes', value: true}, + {label: 'No', value: false}, + ]; - constructor() {} + constructor(_settingSvc: SettingService) { + this.setting = _settingSvc.setting; + } ngOnChanges() { this.advancedOptions = [ { - type: 'checkbox', + type: 'select', + field: 'charset', + label: 'Charset', + hidden: () => { + const protocolsCanCharset: Array = ['ssh', 'telnet']; + return this.connectMethod && this.connectMethod.component !== 'koko' || !protocolsCanCharset.includes(this.protocol.name); + }, + value: 'default', + options: [ + {label: 'Default', value: 'default'}, + {label: 'UTF-8', value: 'utf8'}, + {label: 'GBK', value: 'gbk'}, + ] + }, + { + type: 'select', field: 'disableautohash', hidden: () => { - return this.connectMethod.value === 'web_cli' - && this.needShowAutoCompletionProtocols.includes(this.protocol.name); + const protocolsCanAutoHash: Array = ['mysql', 'mariadb']; + return this.connectMethod && this.connectMethod.component !== 'koko' || !protocolsCanAutoHash.includes(this.protocol.name); }, label: 'Disable auto completion', - value: false + value: false, + options: this.boolChoices + }, + { + type: 'select', + field: 'resolution', + hidden: () => { + const protocolsCanResolution: Array = ['rdp']; + return !protocolsCanResolution.includes(this.protocol.name); + }, + options: resolutionsChoices.map(i => ({label: i, value: i})), + label: 'Resolution', + value: this.setting.rdpResolution + }, + { + type: 'select', + field: 'backspaceAsCtrlH', + hidden: () => { + return this.connectMethod && this.connectMethod.component !== 'koko'; + }, + options: this.boolChoices, + label: 'Backspace as Ctrl+H', + value: !!this.setting.backspaceAsCtrlH } ]; - this.isShowAdvancedOption = this.advancedOptions.some(i => i.hidden()); + this.advancedOptions = this.advancedOptions.filter(i => !i.hidden()); + this.isShowAdvancedOption = this.advancedOptions.length > 0; + this.optionChange(null); } optionChange(event) { diff --git a/src/app/elements/connect/connect-dialog/connect-dialog.component.html b/src/app/elements/connect/connect-dialog/connect-dialog.component.html index 17a01b88..06df663a 100644 --- a/src/app/elements/connect/connect-dialog/connect-dialog.component.html +++ b/src/app/elements/connect/connect-dialog/connect-dialog.component.html @@ -1,62 +1,62 @@

{{"Connect" | translate}} - {{ asset.name | truncatechars:30 }}

-
- -
+
- + {{ "Automatic login next" | translate }}
- diff --git a/src/app/elements/connect/connect-dialog/connect-dialog.component.scss b/src/app/elements/connect/connect-dialog/connect-dialog.component.scss index 9bba186a..91b50769 100644 --- a/src/app/elements/connect/connect-dialog/connect-dialog.component.scss +++ b/src/app/elements/connect/connect-dialog/connect-dialog.component.scss @@ -6,7 +6,7 @@ } .mat-form-field-infix { - border-top-width: 0!important; + border-top-width: 0 !important; } .auto-connect { @@ -29,6 +29,7 @@ //padding: 0 20px !important; min-width: 20px !important; } + .mat-tab-link:focus { background-color: white; } @@ -64,3 +65,7 @@ .mat-button-toggle-checked { } +button.mat-raised-button { + margin-left: 0; +} + diff --git a/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.html b/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.html index 867e45d4..33386063 100644 --- a/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.html +++ b/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.html @@ -2,7 +2,7 @@ -
+
+ +
+ {{ "No available connect method" | translate }} +
+
diff --git a/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.ts b/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.ts index d4d479da..fb076493 100644 --- a/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.ts +++ b/src/app/elements/connect/connect-dialog/connect-method/connect-method.component.ts @@ -46,7 +46,7 @@ export class ElementConnectMethodComponent implements OnInit { currentConnectMethodTypeIndex() { const i = this.connectMethodTypes .map((item) => item.value) - .indexOf(this.connectMethod.type); + .indexOf(this.connectMethod && this.connectMethod.type); if (i === -1) { return 0; } diff --git a/src/app/elements/connect/connect-dialog/select-account/select-account.component.ts b/src/app/elements/connect/connect-dialog/select-account/select-account.component.ts index b7ab91b0..6120dc9a 100644 --- a/src/app/elements/connect/connect-dialog/select-account/select-account.component.ts +++ b/src/app/elements/connect/connect-dialog/select-account/select-account.component.ts @@ -1,8 +1,8 @@ import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild} from '@angular/core'; -import {Account, AccountGroup, AuthInfo, Asset} from '@app/model'; +import {Account, AccountGroup, Asset, AuthInfo} from '@app/model'; import {BehaviorSubject, ReplaySubject, Subject} from 'rxjs'; import {FormControl, Validators} from '@angular/forms'; -import {AppService, LocalStorageService, LogService, SettingService, I18nService} from '@app/services'; +import {AppService, I18nService, LocalStorageService, LogService, SettingService} from '@app/services'; import {takeUntil} from 'rxjs/operators'; @Component({ @@ -28,14 +28,12 @@ export class ElementSelectAccountComponent implements OnInit, OnDestroy { filteredOptions: AuthInfo[]; accountManualAuthInit = false; usernamePlaceholder: string = 'Username'; - - protected _onDestroy = new Subject(); public accountSelected: Account; public groupedAccounts: AccountGroup[]; public accountCtl: FormControl = new FormControl(); public accountFilterCtl: FormControl = new FormControl(); public filteredUsersGroups: ReplaySubject = new ReplaySubject(1); - public compareFn = (f1, f2) => f1 && f2 && f1.id === f2.id; + protected _onDestroy = new Subject(); constructor(private _logger: LogService, private _appSvc: AppService, @@ -43,14 +41,17 @@ export class ElementSelectAccountComponent implements OnInit, OnDestroy { private _settingSvc: SettingService, private _cdRef: ChangeDetectorRef, private _localStorage: LocalStorageService - ) {} + ) { + } get noSecretAccounts() { return this.accounts .filter((item) => !item.has_secret) .sort((a, b) => { const eq = +a.username.startsWith('@') - +b.username.startsWith('@'); - if (eq !== 0) { return eq; } + if (eq !== 0) { + return eq; + } if (a.name === 'root') { return -1; } @@ -66,6 +67,8 @@ export class ElementSelectAccountComponent implements OnInit, OnDestroy { }); } + public compareFn = (f1, f2) => f1 && f2 && f1.alias === f2.alias; + ngOnInit() { this.groupedAccounts = this.groupAccounts(); this.filteredUsersGroups.next(this.groupedAccounts.slice()); @@ -155,31 +158,18 @@ export class ElementSelectAccountComponent implements OnInit, OnDestroy { if (!search) { this.filteredUsersGroups.next(this.groupedAccounts.slice()); return; - } else { - search = search.toLowerCase(); } - this.filteredUsersGroups.next( - accountsGroupsCopy.filter(group => { - const showGroup = group.name.toLowerCase().indexOf(search) > -1; - if (!showGroup) { - group.accounts = group.accounts.filter(account => { - return account.name.toLowerCase().indexOf(search) > -1; - }); - } - return group.accounts.length > 0; - }) - ); - } - - protected copyGroupedAccounts(groups) { - const accountsCopy = []; - groups.forEach(group => { - accountsCopy.push({ - name: group.name, - accounts: group.accounts.slice() - }); + search = search.toLowerCase(); + const filteredGroups = accountsGroupsCopy.filter(group => { + const showGroup = group.name.toLowerCase().indexOf(search) > -1; + if (!showGroup) { + group.accounts = group.accounts.filter(account => { + return account.name.toLowerCase().indexOf(search) > -1; + }); + } + return group.accounts.length > 0; }); - return accountsCopy; + this.filteredUsersGroups.next(filteredGroups); } setUsernamePlaceholder() { @@ -238,4 +228,15 @@ export class ElementSelectAccountComponent implements OnInit, OnDestroy { getSavedAuthInfos() { this.localAuthItems = this._appSvc.getAccountLocalAuth(this.asset.id); } + + protected copyGroupedAccounts(groups) { + const accountsCopy = []; + groups.forEach(group => { + accountsCopy.push({ + name: group.name, + accounts: group.accounts.slice() + }); + }); + return accountsCopy; + } } diff --git a/src/app/elements/connect/connect.component.ts b/src/app/elements/connect/connect.component.ts index e0c94e8c..77365e2b 100644 --- a/src/app/elements/connect/connect.component.ts +++ b/src/app/elements/connect/connect.component.ts @@ -1,9 +1,9 @@ -import {Component, OnInit, Output, OnDestroy, EventEmitter} from '@angular/core'; +import {Component, EventEmitter, OnDestroy, OnInit, Output} from '@angular/core'; import 'rxjs/add/operator/toPromise'; import {connectEvt} from '@app/globals'; import {MatDialog} from '@angular/material'; -import {AppService, HttpService, LogService, SettingService, DialogService, I18nService, ConnectTokenService} from '@app/services'; -import {Account, ConnectData, Asset, ConnectionToken, View, K8sInfo} from '@app/model'; +import {AppService, ConnectTokenService, DialogService, HttpService, I18nService, LogService, SettingService} from '@app/services'; +import {Account, Asset, ConnectData, ConnectionToken, K8sInfo, View} from '@app/model'; import {ElementConnectDialogComponent} from './connect-dialog/connect-dialog.component'; import {ElementDownloadDialogComponent} from './download-dialog/download-dialog.component'; import {launchLocalApp} from '@app/utils/common'; @@ -202,13 +202,13 @@ export class ElementConnectComponent implements OnInit, OnDestroy { const url = response['url']; launchLocalApp(url, () => { const downLoadStatus = localStorage.getItem('hasDownLoadApp'); - if (downLoadStatus !== '1') { - this._dialog.open(ElementDownloadDialogComponent, { - height: 'auto', - width: '800px', - disableClose: true - }); - } + if (downLoadStatus !== '1') { + this._dialog.open(ElementDownloadDialogComponent, { + height: 'auto', + width: '800px', + disableClose: true + }); + } }); } @@ -276,7 +276,7 @@ export class ElementConnectComponent implements OnInit, OnDestroy { }); return new Promise(resolve => { - dialogRef.afterClosed().subscribe(outputData => { + dialogRef.afterClosed().subscribe(outputData => { resolve(outputData); }); }); diff --git a/src/app/elements/elements.component.ts b/src/app/elements/elements.component.ts index 55f338e4..6c939ad1 100644 --- a/src/app/elements/elements.component.ts +++ b/src/app/elements/elements.component.ts @@ -3,14 +3,14 @@ import {ElementLeftBarComponent} from './left-bar/left-bar.component'; import {ElementContentComponent} from './content/content.component'; import {ElementContentWindowComponent} from './content/content-window/content-window.component'; import {ElementContentTabComponent} from './content/content-tab/content-tab.component'; -import {ElementAssetTreeComponent, DisabledAssetsDialogComponent} from './asset-tree/asset-tree.component'; +import {DisabledAssetsDialogComponent, ElementAssetTreeComponent} from './asset-tree/asset-tree.component'; import {ElementTreeFilterComponent} from './tree-filter/tree-filter.component'; import {ElementOrganizationComponent} from './organization/organization.component'; import {ElementUserFileComponent} from './profile/profile.component'; import {ElementTermComponent} from './term/term.component'; import {ChangLanWarningDialogComponent, ElementNavComponent} from './nav/nav.component'; import {ElementIframeComponent} from './iframe/iframe.component'; -import {ElementDialogAlertComponent} from './dialog/dialog.service'; +import {ElementDialogAlertComponent} from '@app/services/dialog/dialog.service'; import {ElementConnectComponent} from './connect/connect.component'; import {ElementConnectDialogComponent} from './connect/connect-dialog/connect-dialog.component'; import {ElementSettingComponent} from './setting/setting.component'; @@ -25,8 +25,8 @@ import {ElementReplayAsciicastComponent} from '@app/elements/replay/asciicast/as import {ElementAdvancedOptionComponent} from './connect/connect-dialog/advanced-option/advanced-option.component'; import {ElementConnectMethodComponent} from './connect/connect-dialog/connect-method/connect-method.component'; import {ElementDownloadDialogComponent} from './connect/download-dialog/download-dialog.component'; -import {ElementACLDialogComponent} from './connect/acl-dialog/acl-dialog.component'; -import { ElementsReplayMp4Component } from './replay/mp4/mp4.component'; +import {ElementACLDialogComponent} from '@app/services/connect-token/acl-dialog/acl-dialog.component'; +import {ElementsReplayMp4Component} from './replay/mp4/mp4.component'; export const ElementComponents = [ diff --git a/src/app/elements/iframe/iframe.component.ts b/src/app/elements/iframe/iframe.component.ts index 1a0a3887..0814ed29 100644 --- a/src/app/elements/iframe/iframe.component.ts +++ b/src/app/elements/iframe/iframe.component.ts @@ -1,9 +1,8 @@ -import {Component, Input, OnInit, AfterViewInit, ViewChild, ElementRef, Output, EventEmitter, OnDestroy} from '@angular/core'; -import {ConnectionToken, View} from '@app/model'; -import {HttpService, I18nService, LogService, ConnectTokenService} from '@app/services'; +import {AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild} from '@angular/core'; +import {View} from '@app/model'; +import {ConnectTokenService, HttpService, I18nService, LogService} from '@app/services'; import {MatDialog} from '@angular/material'; import {environment} from '@src/environments/environment'; -import {ElementACLDialogComponent} from '@app/elements/connect/acl-dialog/acl-dialog.component'; @Component({ selector: 'elements-iframe', @@ -93,18 +92,18 @@ export class ElementIframeComponent implements OnInit, AfterViewInit, OnDestroy sendCommand(data) { this._logger.info(`[Luna] Send CMD to: ${this.id}`); - this.iframeWindow.postMessage({name: 'CMD', data: data.data}, '*' ); + this.iframeWindow.postMessage({name: 'CMD', data: data.data}, '*'); } 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 - const url = this.src.replace(oldConnectToken.id, newConnectToken.id) + this.view.connectToken = newConnectToken; + const url = this.src.replace(oldConnectToken.id, newConnectToken.id); this.src = 'about:blank'; setTimeout(() => { this.src = url; diff --git a/src/app/elements/replay/mp4/mp4.component.scss b/src/app/elements/replay/mp4/mp4.component.scss index f0558f76..2fb50171 100644 --- a/src/app/elements/replay/mp4/mp4.component.scss +++ b/src/app/elements/replay/mp4/mp4.component.scss @@ -13,6 +13,7 @@ #display .command-results { height: 100%; overflow-y: auto; + border-right: #000 solid 2px; } #display .item{ @@ -50,6 +51,14 @@ width: 3px; } +video { + width: 100%; + height: calc(100vh - 35px); + top: 0; + left: 0; + bottom: 0; +} + #screen * {} #player .notification-container { diff --git a/src/app/elements/setting/setting.component.html b/src/app/elements/setting/setting.component.html index 43716507..17bdd4a4 100644 --- a/src/app/elements/setting/setting.component.html +++ b/src/app/elements/setting/setting.component.html @@ -18,6 +18,17 @@

+ + + + + {{item.label}} + + + +
@@ -33,17 +44,6 @@

Web {{ 'Client' | translate }} - - - - - - {{item.label}} - - -

diff --git a/src/app/elements/setting/setting.component.ts b/src/app/elements/setting/setting.component.ts index 6b2618f3..c6885f44 100644 --- a/src/app/elements/setting/setting.component.ts +++ b/src/app/elements/setting/setting.component.ts @@ -4,6 +4,7 @@ import {SettingService} from '@app/services'; import {GlobalSetting, Setting} from '@app/model'; import {I18nService} from '@app/services/i18n'; import {MAT_DIALOG_DATA} from '@angular/material/dialog'; +import {resolutionsChoices} from '@app/globals'; @Component({ @@ -12,12 +13,12 @@ import {MAT_DIALOG_DATA} from '@angular/material/dialog'; styleUrls: ['./setting.component.css'] }) export class ElementSettingComponent implements OnInit { - resolutionsChoices = ['Auto', '1024x768', '1366x768', '1600x900', '1920x1080']; public boolChoices: any[]; public keyboardLayoutOptions: any[]; setting: Setting; globalSetting: GlobalSetting; type = 'general'; + resolutionsChoices = resolutionsChoices; constructor(public dialogRef: MatDialogRef, private _i18n: I18nService, @@ -28,13 +29,13 @@ export class ElementSettingComponent implements OnInit { {name: _i18n.instant('No'), value: '0'} ]; this.keyboardLayoutOptions = [ - { value: 'en-us-qwerty', label: _i18n.instant('US English keyboard layout') }, - { value: 'en-gb-qwerty', label: _i18n.instant('UK English keyboard layout') }, - { value: 'ja-jp-qwerty', label: _i18n.instant('Japanese keyboard layout') }, - { value: 'fr-fr-azerty', label: _i18n.instant('French keyboard layout') }, - { value: 'fr-ch-qwertz', label: _i18n.instant('Swiss French keyboard layout') }, - { value: 'fr-be-azerty', label: _i18n.instant('Belgian French keyboard layout') }, - { value: 'tr-tr-qwerty', label: _i18n.instant('Turkey keyboard layout') } + {value: 'en-us-qwerty', label: _i18n.instant('US English keyboard layout')}, + {value: 'en-gb-qwerty', label: _i18n.instant('UK English keyboard layout')}, + {value: 'ja-jp-qwerty', label: _i18n.instant('Japanese keyboard layout')}, + {value: 'fr-fr-azerty', label: _i18n.instant('French keyboard layout')}, + {value: 'fr-ch-qwertz', label: _i18n.instant('Swiss French keyboard layout')}, + {value: 'fr-be-azerty', label: _i18n.instant('Belgian French keyboard layout')}, + {value: 'tr-tr-qwerty', label: _i18n.instant('Turkey keyboard layout')} ]; } diff --git a/src/app/globals.ts b/src/app/globals.ts index e1145665..da519b15 100644 --- a/src/app/globals.ts +++ b/src/app/globals.ts @@ -1,8 +1,7 @@ 'use strict'; import {EventEmitter} from 'events/events'; 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'; +import {Browser as _Browser, ConnectEvt, DataStore as _DataStore, Monitor as _Monitor, User as _User, Video as _Video} from './model'; export let TermWS = null; export const emitter = new (EventEmitter); @@ -34,3 +33,4 @@ export const ROOT_ORG_ID = '00000000-0000-0000-0000-000000000000'; export const connectEvt = new BehaviorSubject(new ConnectEvt(null, null)); +export const resolutionsChoices = ['Auto', '1024x768', '1366x768', '1600x900', '1920x1080']; diff --git a/src/app/model.ts b/src/app/model.ts index 2f46a688..80a10c88 100644 --- a/src/app/model.ts +++ b/src/app/model.ts @@ -20,7 +20,7 @@ export class User { date_joined: string; last_login: string; date_expired: string; - groups: Array ; + groups: Array; logined: boolean; } @@ -158,7 +158,9 @@ export class View { getConnectOption(field: string) { const connectOptions = this.connectOptions || []; - if (connectOptions.length === 0) { return ''; } + if (connectOptions.length === 0) { + return ''; + } const filteredField = connectOptions.find(i => i.field === field); return filteredField ? filteredField.value.toString() : ''; } @@ -344,7 +346,7 @@ export class AuthInfo { } export class ConnectOption { - type: 'checkbox' | 'radio'; + type: 'checkbox' | 'radio' | 'select'; field: string; hidden: Function; label: string; @@ -398,7 +400,7 @@ export class ConnectionToken { from_ticket_info: FromTicketInfo; } -export class Protocol { +export class Protocol { name: string; port: number; public: boolean; @@ -450,6 +452,7 @@ export interface Organization { is_root?: boolean; is_default?: boolean; } + export class InitTreeConfig { refresh: boolean; apiName?: string; diff --git a/src/app/elements/connect/acl-dialog/acl-dialog.component.html b/src/app/services/connect-token/acl-dialog/acl-dialog.component.html similarity index 100% rename from src/app/elements/connect/acl-dialog/acl-dialog.component.html rename to src/app/services/connect-token/acl-dialog/acl-dialog.component.html diff --git a/src/app/elements/connect/acl-dialog/acl-dialog.component.scss b/src/app/services/connect-token/acl-dialog/acl-dialog.component.scss similarity index 100% rename from src/app/elements/connect/acl-dialog/acl-dialog.component.scss rename to src/app/services/connect-token/acl-dialog/acl-dialog.component.scss diff --git a/src/app/elements/connect/acl-dialog/acl-dialog.component.ts b/src/app/services/connect-token/acl-dialog/acl-dialog.component.ts similarity index 97% rename from src/app/elements/connect/acl-dialog/acl-dialog.component.ts rename to src/app/services/connect-token/acl-dialog/acl-dialog.component.ts index ad586074..17f99e2a 100644 --- a/src/app/elements/connect/acl-dialog/acl-dialog.component.ts +++ b/src/app/services/connect-token/acl-dialog/acl-dialog.component.ts @@ -1,7 +1,8 @@ import {Component, Inject, OnInit} from '@angular/core'; import {Asset, ConnectData, ConnectionToken} from '@app/model'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; -import {HttpService, I18nService} from '@app/services'; +import {I18nService} from '@app/services/i18n'; +import {HttpService} from '@app/services/http'; import {ToastrService} from 'ngx-toastr'; import {HttpErrorResponse} from '@angular/common/http'; diff --git a/src/app/services/connect-token.ts b/src/app/services/connect-token/index.ts similarity index 94% rename from src/app/services/connect-token.ts rename to src/app/services/connect-token/index.ts index 7d2dfcd7..2e3a6743 100644 --- a/src/app/services/connect-token.ts +++ b/src/app/services/connect-token/index.ts @@ -1,8 +1,8 @@ import {Injectable} from '@angular/core'; import {Asset, ConnectData, ConnectionToken} from '@app/model'; -import {ElementACLDialogComponent} from '@app/elements/connect/acl-dialog/acl-dialog.component'; import {HttpService} from '@app/services/http'; import {MatDialog} from '@angular/material'; +import {ElementACLDialogComponent} from './acl-dialog/acl-dialog.component'; @Injectable() export class ConnectTokenService { diff --git a/src/app/elements/dialog/alert.html b/src/app/services/dialog/alert.html similarity index 100% rename from src/app/elements/dialog/alert.html rename to src/app/services/dialog/alert.html diff --git a/src/app/elements/dialog/alert.scss b/src/app/services/dialog/alert.scss similarity index 100% rename from src/app/elements/dialog/alert.scss rename to src/app/services/dialog/alert.scss diff --git a/src/app/elements/dialog/dialog.service.ts b/src/app/services/dialog/dialog.service.ts similarity index 86% rename from src/app/elements/dialog/dialog.service.ts rename to src/app/services/dialog/dialog.service.ts index 1c0ff9bc..8cdd30d5 100644 --- a/src/app/elements/dialog/dialog.service.ts +++ b/src/app/services/dialog/dialog.service.ts @@ -1,6 +1,7 @@ import {Component, Inject, Injectable} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material'; -import {I18nService, LogService} from '@app/services'; +import {I18nService} from '../i18n'; +import {LogService} from '../share'; @Component({ @@ -45,9 +46,9 @@ export class DialogService { loading() { } - async alert(msg: string, title: string='') { + async alert(msg: string, title: string = '') { if (!title) { - title = await this._i18n.t('Tips') + title = await this._i18n.t('Tips'); } this._dialog.open(ElementDialogAlertComponent, { height: 'auto', diff --git a/src/app/services/http.ts b/src/app/services/http.ts index e35a0147..7abdca0a 100644 --- a/src/app/services/http.ts +++ b/src/app/services/http.ts @@ -1,14 +1,10 @@ import {Injectable} from '@angular/core'; -import {HttpClient, HttpHeaders, HttpParams, HttpErrorResponse} from '@angular/common/http'; -import {Browser, SYSTEM_ORG_ID} from '@app/globals'; -import {retryWhen, delay, scan, map, catchError} from 'rxjs/operators'; -import { - Account, TreeNode, User as _User, Session, - ConnectionToken, Endpoint, ConnectData, Asset -} from '@app/model'; -import {User} from '@app/globals'; +import {HttpClient, HttpErrorResponse, HttpHeaders, HttpParams} from '@angular/common/http'; +import {Browser, User} from '@app/globals'; +import {catchError, delay, map, retryWhen, scan} from 'rxjs/operators'; +import {Account, Asset, ConnectData, ConnectionToken, Endpoint, Session, TreeNode, User as _User} from '@app/model'; import {getCsrfTokenFromCookie, getQueryParamFromURL} from '@app/utils/common'; -import {Observable, throwError} from 'rxjs'; +import {Observable} from 'rxjs'; import {I18nService} from '@app/services/i18n'; import {CookieService} from 'ngx-cookie-service'; import {encryptPassword} from '@app/utils/crypto'; @@ -18,12 +14,15 @@ export class HttpService { headers = new HttpHeaders(); constructor(private http: HttpClient, - private _i18n: I18nService, - private _cookie: CookieService) {} + private _i18n: I18nService, + private _cookie: CookieService) { + } setOptionsCSRFToken(options) { const csrfToken = getCsrfTokenFromCookie(); - if (!options) { options = {}; } + if (!options) { + options = {}; + } let headers = options.headers || new HttpHeaders(); headers = headers.set('X-CSRFToken', csrfToken); options.headers = headers; @@ -31,7 +30,9 @@ export class HttpService { } setOrgIDToRequestHeader(url, options) { - if (!options) { options = {}; } + if (!options) { + options = {}; + } const headers = options.headers || new HttpHeaders(); const orgID = this._cookie.get('X-JMS-LUNA-ORG') || this._cookie.get('X-JMS-ORG'); options.headers = headers.set('X-JMS-ORG', orgID); @@ -207,16 +208,16 @@ export class HttpService { getCommandsData(sid: string, page: number) { const params = new HttpParams() - .set('session_id', sid) - .set('limit', '30') - .set('offset', String(30 * page)) - .set('order', 'timestamp'); + .set('session_id', sid) + .set('limit', '30') + .set('offset', String(30 * page)) + .set('order', 'timestamp'); return this.get('/api/v1/terminal/commands/', {params: params}); } cleanRDPParams(params) { const cleanedParams = {}; - const {rdpResolution, rdpFullScreen, rdpDrivesRedirect } = params; + const {rdpResolution, rdpFullScreen, rdpDrivesRedirect} = params; if (rdpResolution && rdpResolution.indexOf('x') > -1) { const [width, height] = rdpResolution.split('x'); @@ -235,9 +236,13 @@ export class HttpService { createConnectToken(asset: Asset, connectData: ConnectData, createTicket = false) { const params = createTicket ? '?create_ticket=1' : ''; const url = '/api/v1/authentication/connection-token/' + params; - const { account, protocol, manualAuthInfo, connectMethod } = connectData; + const {account, protocol, manualAuthInfo, connectMethod} = connectData; const username = account.username.startsWith('@') ? manualAuthInfo.username : account.username; const secret = encryptPassword(manualAuthInfo.secret); + const connectOption = {}; + for (const option of connectData.connectOptions) { + connectOption[option.field] = option.value; + } const data = { asset: asset.id, account: account.alias, @@ -245,6 +250,7 @@ export class HttpService { input_username: username, input_secret: secret, connect_method: connectMethod.value, + connect_options: connectOption }; return this.post(url, data).pipe( catchError(this.handleConnectMethodExpiredError.bind(this)) @@ -254,7 +260,7 @@ export class HttpService { exchangeConnectToken(tokenID: string, createTicket = false) { const params = createTicket ? '?create_ticket=1' : ''; const url = '/api/v1/authentication/connection-token/exchange/' + params; - const data = { 'id': tokenID}; + const data = {'id': tokenID}; return this.post(url, data); } @@ -288,7 +294,7 @@ export class HttpService { } async handleConnectMethodExpiredError(error) { - if (error.status === 400 ) { + 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); @@ -297,7 +303,7 @@ export class HttpService { throw error; } - getSmartEndpoint({ assetId, sessionId, token }, protocol ): Promise { + getSmartEndpoint({assetId, sessionId, token}, protocol): Promise { const url = new URL('/api/v1/terminal/endpoints/smart/', window.location.origin); url.searchParams.append('protocol', protocol); diff --git a/src/app/services/index.ts b/src/app/services/index.ts index b091b35a..aad515b2 100644 --- a/src/app/services/index.ts +++ b/src/app/services/index.ts @@ -1,34 +1,35 @@ -import {LogService, LocalStorageService, UUIDService} from './share'; +import {LocalStorageService, LogService, UUIDService} from './share'; +import {AppService} from './app'; +import {HttpService} from './http'; +import {NavService} from './nav'; +import {TreeFilterService} from './treeFilter'; +import {SettingService} from './setting'; +import {ViewService} from './view'; +import {OrganizationService} from './organization'; +import {I18nService} from './i18n'; +import {DialogService} from './dialog/dialog.service'; +import {ConnectTokenService} from './connect-token/'; + 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'; -import {OrganizationService} from './organization'; export {OrganizationService} from './organization'; -import {I18nService} from './i18n'; export {I18nService}; -import {DialogService} from '@app/elements/dialog/dialog.service'; export {DialogService}; -import {ConnectTokenService} from '@app/services/connect-token'; export {ConnectTokenService}; diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index a854aa4b..994427fd 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -75,6 +75,7 @@ "Select account": "Select account", "No matching found": "No matching found", "Connect Method": "Connect Method", + "No available connect method": "No available connect method", "Need to use": "Need to use", "Download the client": "Please download", "Yes": "Yes", diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json index 5f6e3a0f..33bfdeb7 100644 --- a/src/assets/i18n/ja.json +++ b/src/assets/i18n/ja.json @@ -75,8 +75,8 @@ "Select account": "システムユーザーの選択", "No matching found": "マッチがありません", "Connect Method": "接続方法", + "No available connect method": "接続方法がありません", "Need to use": "使用する必要がある", - "Yes": "はい", "No": "いいえ", "Web Terminal": "Web端末", @@ -116,7 +116,6 @@ "General": "基本構成", "GUI": "グラフィカル", "CLI": "コマンドライン", - "Asset not found or You have no permission to access it, please refresh asset tree": "アセットが見つからないか、アクセスする権限がありません。アセット ツリーを更新してください", "Run it by client": "クライアントで実行する", "Name": "めいしょう", @@ -126,7 +125,6 @@ "Tips": "ヒント", "Applet connect method": "アプレット接続方法", "Client": "お客様", - "Keyboard layout": "キーボードレイアウト", "UK English keyboard layout": "UK English (Qwerty)", "US English keyboard layout": "US English (Qwerty)", @@ -149,5 +147,6 @@ "No account available": "アカウントがありません", "Set reusable": "再利用可能な", "Re-use for a long time after opening": "開いた後、長い間再利用する", + "Charset": "文字セット", "The connection method is invalid, please refresh the page": "接続方法が無効です。ページを更新してください" } diff --git a/src/assets/i18n/zh.json b/src/assets/i18n/zh.json index 207a2677..9c189cbc 100644 --- a/src/assets/i18n/zh.json +++ b/src/assets/i18n/zh.json @@ -75,6 +75,7 @@ "Select account": "选择账号", "No matching found": "没有匹配项", "Connect Method": "连接方式", + "No available connect method": "没有可用的连接方法", "Need to use": "需要使用", "Yes": "是", "No": "否", @@ -125,7 +126,7 @@ "General": "基本配置", "Applet connect method": "远程应用连接方式", "Client": "客户端", - + "Charset": "字符集", "Keyboard layout": "键盘布局", "UK English keyboard layout": "UK English (Qwerty)", "US English keyboard layout": "US English (Qwerty)",