Skip to content

Commit

Permalink
Merge pull request #1186 from jumpserver/dev
Browse files Browse the repository at this point in the history
v4.4.0
  • Loading branch information
BaiJiangJie authored Nov 21, 2024
2 parents 940d592 + f7cf3c2 commit a4bd420
Show file tree
Hide file tree
Showing 21 changed files with 355 additions and 16 deletions.
4 changes: 4 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {ClipboardService} from 'ngx-clipboard';
import {ElementsReplayMp4Component} from './elements/replay/mp4/mp4.component';
import {ElementCommandDialogComponent} from '@app/elements/content/command-dialog/command-dialog.component';
import {ElementSendCommandDialogComponent} from '@app/elements/content/send-command-dialog/send-command-dialog.component';
import {ElementSendCommandWithVariableDialogComponent} from '@app/elements/content/send-command-with-variable-dialog/send-command-with-variable-dialog.component';
import {DynamicFormComponent} from '@app/elements/content/variable-dynamic-form/variable-dynamic-form.component';
import {version} from '../environments/environment';
import {BehaviorSubject, forkJoin, Observable, of} from 'rxjs';
import {catchError, mergeMap} from 'rxjs/operators';
Expand Down Expand Up @@ -115,6 +117,8 @@ export class CustomLoader implements TranslateLoader {
ElementDialogAlertComponent,
ElementCommandDialogComponent,
ElementSendCommandDialogComponent,
DynamicFormComponent,
ElementSendCommandWithVariableDialogComponent
],
bootstrap: [AppComponent],
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export class ElementConnectMethodComponent implements OnInit {
return false;
}
if (this.account && !this.account.has_secret) {
const aliases = ['@USER', '@INPUT'];
const aliases = ['@USER', '@INPUT', '@ANON'];
// 同名账号、手动输入可以下载RDP文件
if (!aliases.includes(this.account.alias) || (!this.manualAuthInfo.username)) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
[view]="view"
>
</elements-connector-koko>
<elements-connector-nec
*ngSwitchCase="'nec'"
[view]="view"
>
</elements-connector-nec>
<elements-connector-default
*ngSwitchDefault
[connector]="connector"
[view]="view"
>
</elements-connector-default>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<elements-connector-guide
[assetType]="('Host' | translate)"
[canReuse]="true"
[commands]="commands"
[infoItems]="infoItems"
[token]="token"
></elements-connector-guide>
Empty file.
132 changes: 132 additions & 0 deletions src/app/elements/content/content-window/nec/nec.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {Component, Input, OnInit} from '@angular/core';
import {Account, Asset, ConnectionToken, Endpoint, View} from '@app/model';
import {ConnectTokenService, HttpService, I18nService, SettingService} from '@app/services';
import {ToastrService} from 'ngx-toastr';

import {Command, InfoItem} from '../guide/model';
import {User} from '@app/globals';

@Component({
selector: 'elements-connector-nec',
templateUrl: './nec.component.html',
styleUrls: ['./nec.component.scss']
})

export class ElementConnectorNecComponent implements OnInit {
@Input() view: View;

asset: Asset;
account: Account;
protocol: string;
endpoint: Endpoint;
name: string;
cli: string;
infoItems: Array<InfoItem> = [];
info: any;
globalSetting: any;
loading = true;
passwordMask = '******';
passwordShow = '******';
token: ConnectionToken;
showSetReusable: boolean;
commands: Array<Command> = [];

constructor(private _http: HttpService,
private _i18n: I18nService,
private _toastr: ToastrService,
private _connectTokenSvc: ConnectTokenService,
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;
this.token = connectToken;
this.asset = asset;
this.account = account;
this.protocol = protocol;
this.endpoint = smartEndpoint;

const oriHost = this.asset.address;
this.name = `${this.asset.name}(${oriHost})`;
this.setConnectionInfo();
this.genConnCli();
this.loading = false;
this.view.termComp = this;
}

setConnectionInfo() {
this.infoItems = [
{name: 'name', value: this.name, label: this._i18n.t('Name')},
{name: 'host', value: this.endpoint.getHost(), label: this._i18n.t('Host')},
{name: 'port', value: this.endpoint.getPort(this.protocol), 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: 'protocol', value: this.protocol, label: this._i18n.t('Protocol')},
{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;
}, {});
}

genConnCli() {
const {password, host, port, protocol} = this.info;
// Password placeholders. Because there is a safe cli, the secret needs to be hidden, so the placeholders are replaced
const passwordHolder = `@${password}@`;
let cli = '';

switch (this.protocol) {
case 'vnc':
cli = `vncviewer` +
` -UserName=${this.token.id}` +
` ${host}:${port || '5900'}`;
break;

default:
cli = `Protocol '${protocol}' Not support now`;
}
const cliSafe = cli.replace(passwordHolder, this.passwordMask);
const cliValue = cli.replace(passwordHolder, password);
this.cli = cliValue;
const vncPort = port || '5900';
const cliDirect = `vncviewer -UserName=${User.username}#${this.account.username}#${this.asset.id} ${this.endpoint.host}:${vncPort}`;

this.commands = [
{
title: this._i18n.instant('Connect command line') + ' (' + this._i18n.instant('Using token') + ')',
value: cliValue,
safeValue: cliSafe,
helpText: this._i18n.instant('Password is token password on the table'),
callClient: false
},
{
title: this._i18n.instant('Connect command line') + ' (' + this._i18n.instant('Directly') + ')',
value: cliDirect,
safeValue: cliDirect,
helpText: this._i18n.instant('Password is your password login to system'),
callClient: false
}
];
}

async reconnect() {
const oldConnectToken = this.view.connectToken;
const newConnectToken = await this._connectTokenSvc.exchange(oldConnectToken);
if (!newConnectToken) {
return;
}
// 更新当前 view 的 connectToken
this.view.connectToken = newConnectToken;
await this.ngOnInit();
// 刷新完成隐藏密码
this.passwordShow = this.passwordMask;
}
}
1 change: 1 addition & 0 deletions src/app/elements/content/content.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
*ngFor="let command of quickCommands"
[matTooltip]="command.args"
class="command-box"
[ngClass]="{'command-box-variable': command.variable.length !== 0}"
matTooltipPosition="above"
>
<i [ngClass]="command.module.value + '_ico_docu'" class="view_icon"></i>
Expand Down
5 changes: 4 additions & 1 deletion src/app/elements/content/content.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ $-border-y: var(--el-main-bg-color);
user-select: none;
color: #d6cbcb;
background-color: #463e3e;

&:hover {
color: #ffffff;
}
Expand All @@ -226,6 +225,10 @@ $-border-y: var(--el-main-bg-color);
color: #ccc8c8;
}
}
.command-box-variable {
@extend .command-box;
background-color: #6f4f3a;
}
}

.not-command {
Expand Down
22 changes: 21 additions & 1 deletion src/app/elements/content/content.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {ConnectTokenService, HttpService, I18nService, LogService, SettingServic
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';
import {MatDialog} from '@angular/material';
import {ElementCommandDialogComponent} from '@app/elements/content/command-dialog/command-dialog.component';
import {ElementSendCommandWithVariableDialogComponent} from '@app/elements/content/send-command-with-variable-dialog/send-command-with-variable-dialog.component';
import {fromEvent, Subscription} from 'rxjs';
import * as jQuery from 'jquery/dist/jquery.min.js';

Expand Down Expand Up @@ -197,7 +198,26 @@ export class ElementContentComponent implements OnInit, OnDestroy {

sendQuickCommand(command) {
this.batchCommand = command.args;
this.sendBatchCommand();
if(command.variable.length>0){
const dialogRef=this._dialog.open(
ElementSendCommandWithVariableDialogComponent,
{
height: 'auto',
width: '500px',
data: {command:command}
}
)
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.batchCommand = result
this.sendBatchCommand();
}
})
}
else{
this.sendBatchCommand();
}

}

rMenuItems() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<h1 mat-dialog-title>
{{"Send command"| translate}}
</h1>

<div>
<variable-dynamic-form [formConfig]="formConfig" [command]="command" (formSubmitted)="onFormSubmitted($event)"></variable-dynamic-form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {HttpService} from '@app/services';

@Component({
selector: 'elements-send-with-variable-command-dialog',
templateUrl: './send-command-with-variable-dialog.component.html',
})
export class ElementSendCommandWithVariableDialogComponent implements OnInit {
public formConfig = [];
public command = {};
constructor(public dialogRef: MatDialogRef<ElementSendCommandWithVariableDialogComponent>,
private _http: HttpService,
@Inject(MAT_DIALOG_DATA) public data: any
) {}

ngOnInit() {
this.getVariableFormMeta()
}
async getVariableFormMeta() {
const adhoc = this.data.command.id
const url=`/api/v1/ops/variable/form_data/?t=${new Date().getTime()}&adhoc=${adhoc}`
const res: any = await this._http.options(url).toPromise();
this.formConfig = res.actions.GET;
this.command = this.data.command;
}
onFormSubmitted(data: any) {
setTimeout(() => {
this.dialogRef.close(data.sendCommand);
});
}

protected readonly Component = Component;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

@Injectable({
providedIn: 'root'
})
export class DynamicFormService {
createFormGroup(fields: any): FormGroup {
const group: any = {};
for (const field in fields) {
const fieldDefinition = fields[field];
const validators = [];
if (fieldDefinition.required) {
validators.push(Validators.required);
}
group['jms_'+field] = new FormControl(fieldDefinition.default || '', validators)
}
group["sendCommand"] = new FormControl("")
return new FormGroup(group);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<form *ngIf="dynamicForm && formConfig" [formGroup]="dynamicForm" (ngSubmit)="onSubmit()">
<div *ngFor="let fieldKey of fieldKeys" [ngSwitch]="formConfig[fieldKey].type">
<mat-form-field *ngSwitchCase="'string'" class="full-width">
<label class="zone-label">{{ formConfig[fieldKey].label }}</label>
<input matInput [formControlName]="'jms_'+fieldKey" />
<mat-hint>{{ formConfig[fieldKey].help_text }}</mat-hint>
</mat-form-field>
<div *ngSwitchCase="'labeled_choice'" class="radio-group full-width">
<label class="zone-label">{{ formConfig[fieldKey].label }}</label>
<mat-radio-group style="display: block; padding: 5px 0;" [formControlName]="'jms_'+fieldKey">
<mat-radio-button *ngFor="let choice of formConfig[fieldKey].choices"
[value]="choice.value">
{{ choice.label }}
</mat-radio-button>
</mat-radio-group>
<mat-hint>{{ formConfig[fieldKey].help_text }}</mat-hint>
</div>
</div>
<mat-form-field *ngIf="command" class="full-width">
<label class="zone-label">{{ 'Command'| translate }}</label>
<textarea rows="5" matInput [formControlName]="'sendCommand'" [value]="command.args"></textarea>
</mat-form-field>
<div style="float: right; padding-top: 20px">
<button mat-raised-button color="primary" type="submit" [disabled]="dynamicForm.invalid">{{"Confirm"| translate}}</button>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.full-width {
width: 100%;
}
mat-hint {
font-size: 10px;
}
.zone-label {
color: rgba(0, 0, 0, 0.54);
font-size:10px;
cursor: pointer;
}
Loading

0 comments on commit a4bd420

Please sign in to comment.