Skip to content

Commit a4bd420

Browse files
authored
Merge pull request #1186 from jumpserver/dev
v4.4.0
2 parents 940d592 + f7cf3c2 commit a4bd420

21 files changed

+355
-16
lines changed

src/app/app.module.ts

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import {ClipboardService} from 'ngx-clipboard';
3636
import {ElementsReplayMp4Component} from './elements/replay/mp4/mp4.component';
3737
import {ElementCommandDialogComponent} from '@app/elements/content/command-dialog/command-dialog.component';
3838
import {ElementSendCommandDialogComponent} from '@app/elements/content/send-command-dialog/send-command-dialog.component';
39+
import {ElementSendCommandWithVariableDialogComponent} from '@app/elements/content/send-command-with-variable-dialog/send-command-with-variable-dialog.component';
40+
import {DynamicFormComponent} from '@app/elements/content/variable-dynamic-form/variable-dynamic-form.component';
3941
import {version} from '../environments/environment';
4042
import {BehaviorSubject, forkJoin, Observable, of} from 'rxjs';
4143
import {catchError, mergeMap} from 'rxjs/operators';
@@ -115,6 +117,8 @@ export class CustomLoader implements TranslateLoader {
115117
ElementDialogAlertComponent,
116118
ElementCommandDialogComponent,
117119
ElementSendCommandDialogComponent,
120+
DynamicFormComponent,
121+
ElementSendCommandWithVariableDialogComponent
118122
],
119123
bootstrap: [AppComponent],
120124
providers: [

src/app/elements/connect/connect-dialog/connect-method/connect-method.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class ElementConnectMethodComponent implements OnInit {
9797
return false;
9898
}
9999
if (this.account && !this.account.has_secret) {
100-
const aliases = ['@USER', '@INPUT'];
100+
const aliases = ['@USER', '@INPUT', '@ANON'];
101101
// 同名账号、手动输入可以下载RDP文件
102102
if (!aliases.includes(this.account.alias) || (!this.manualAuthInfo.username)) {
103103
return false;

src/app/elements/content/content-window/content-view/content-view.component.html

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616
[view]="view"
1717
>
1818
</elements-connector-koko>
19+
<elements-connector-nec
20+
*ngSwitchCase="'nec'"
21+
[view]="view"
22+
>
23+
</elements-connector-nec>
1924
<elements-connector-default
2025
*ngSwitchDefault
2126
[connector]="connector"
2227
[view]="view"
2328
>
2429
</elements-connector-default>
2530
</div>
26-
</div>
31+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<elements-connector-guide
2+
[assetType]="('Host' | translate)"
3+
[canReuse]="true"
4+
[commands]="commands"
5+
[infoItems]="infoItems"
6+
[token]="token"
7+
></elements-connector-guide>

src/app/elements/content/content-window/nec/nec.component.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import {Component, Input, OnInit} from '@angular/core';
2+
import {Account, Asset, ConnectionToken, Endpoint, View} from '@app/model';
3+
import {ConnectTokenService, HttpService, I18nService, SettingService} from '@app/services';
4+
import {ToastrService} from 'ngx-toastr';
5+
6+
import {Command, InfoItem} from '../guide/model';
7+
import {User} from '@app/globals';
8+
9+
@Component({
10+
selector: 'elements-connector-nec',
11+
templateUrl: './nec.component.html',
12+
styleUrls: ['./nec.component.scss']
13+
})
14+
15+
export class ElementConnectorNecComponent implements OnInit {
16+
@Input() view: View;
17+
18+
asset: Asset;
19+
account: Account;
20+
protocol: string;
21+
endpoint: Endpoint;
22+
name: string;
23+
cli: string;
24+
infoItems: Array<InfoItem> = [];
25+
info: any;
26+
globalSetting: any;
27+
loading = true;
28+
passwordMask = '******';
29+
passwordShow = '******';
30+
token: ConnectionToken;
31+
showSetReusable: boolean;
32+
commands: Array<Command> = [];
33+
34+
constructor(private _http: HttpService,
35+
private _i18n: I18nService,
36+
private _toastr: ToastrService,
37+
private _connectTokenSvc: ConnectTokenService,
38+
private _settingSvc: SettingService
39+
) {
40+
this.globalSetting = this._settingSvc.globalSetting;
41+
this.showSetReusable = this.globalSetting.CONNECTION_TOKEN_REUSABLE;
42+
}
43+
44+
async ngOnInit() {
45+
const {asset, account, protocol, smartEndpoint, connectToken} = this.view;
46+
this.token = connectToken;
47+
this.asset = asset;
48+
this.account = account;
49+
this.protocol = protocol;
50+
this.endpoint = smartEndpoint;
51+
52+
const oriHost = this.asset.address;
53+
this.name = `${this.asset.name}(${oriHost})`;
54+
this.setConnectionInfo();
55+
this.genConnCli();
56+
this.loading = false;
57+
this.view.termComp = this;
58+
}
59+
60+
setConnectionInfo() {
61+
this.infoItems = [
62+
{name: 'name', value: this.name, label: this._i18n.t('Name')},
63+
{name: 'host', value: this.endpoint.getHost(), label: this._i18n.t('Host')},
64+
{name: 'port', value: this.endpoint.getPort(this.protocol), label: this._i18n.t('Port')},
65+
{name: 'username', value: this.token.id, label: this._i18n.t('Username')},
66+
{name: 'password', value: this.token.value, label: this._i18n.t('Password')},
67+
{name: 'protocol', value: this.protocol, label: this._i18n.t('Protocol')},
68+
{name: 'date_expired', value: `${this.token.date_expired}`, label: this._i18n.t('Expire time')},
69+
];
70+
if (this.showSetReusable) {
71+
this.infoItems.push({name: 'set_reusable', value: '', label: this._i18n.t('Set reusable')});
72+
}
73+
74+
this.info = this.infoItems.reduce((pre, current) => {
75+
pre[current.name] = current.value;
76+
return pre;
77+
}, {});
78+
}
79+
80+
genConnCli() {
81+
const {password, host, port, protocol} = this.info;
82+
// Password placeholders. Because there is a safe cli, the secret needs to be hidden, so the placeholders are replaced
83+
const passwordHolder = `@${password}@`;
84+
let cli = '';
85+
86+
switch (this.protocol) {
87+
case 'vnc':
88+
cli = `vncviewer` +
89+
` -UserName=${this.token.id}` +
90+
` ${host}:${port || '5900'}`;
91+
break;
92+
93+
default:
94+
cli = `Protocol '${protocol}' Not support now`;
95+
}
96+
const cliSafe = cli.replace(passwordHolder, this.passwordMask);
97+
const cliValue = cli.replace(passwordHolder, password);
98+
this.cli = cliValue;
99+
const vncPort = port || '5900';
100+
const cliDirect = `vncviewer -UserName=${User.username}#${this.account.username}#${this.asset.id} ${this.endpoint.host}:${vncPort}`;
101+
102+
this.commands = [
103+
{
104+
title: this._i18n.instant('Connect command line') + ' (' + this._i18n.instant('Using token') + ')',
105+
value: cliValue,
106+
safeValue: cliSafe,
107+
helpText: this._i18n.instant('Password is token password on the table'),
108+
callClient: false
109+
},
110+
{
111+
title: this._i18n.instant('Connect command line') + ' (' + this._i18n.instant('Directly') + ')',
112+
value: cliDirect,
113+
safeValue: cliDirect,
114+
helpText: this._i18n.instant('Password is your password login to system'),
115+
callClient: false
116+
}
117+
];
118+
}
119+
120+
async reconnect() {
121+
const oldConnectToken = this.view.connectToken;
122+
const newConnectToken = await this._connectTokenSvc.exchange(oldConnectToken);
123+
if (!newConnectToken) {
124+
return;
125+
}
126+
// 更新当前 view 的 connectToken
127+
this.view.connectToken = newConnectToken;
128+
await this.ngOnInit();
129+
// 刷新完成隐藏密码
130+
this.passwordShow = this.passwordMask;
131+
}
132+
}

src/app/elements/content/content.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
*ngFor="let command of quickCommands"
8989
[matTooltip]="command.args"
9090
class="command-box"
91+
[ngClass]="{'command-box-variable': command.variable.length !== 0}"
9192
matTooltipPosition="above"
9293
>
9394
<i [ngClass]="command.module.value + '_ico_docu'" class="view_icon"></i>

src/app/elements/content/content.component.scss

+4-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ $-border-y: var(--el-main-bg-color);
217217
user-select: none;
218218
color: #d6cbcb;
219219
background-color: #463e3e;
220-
221220
&:hover {
222221
color: #ffffff;
223222
}
@@ -226,6 +225,10 @@ $-border-y: var(--el-main-bg-color);
226225
color: #ccc8c8;
227226
}
228227
}
228+
.command-box-variable {
229+
@extend .command-box;
230+
background-color: #6f4f3a;
231+
}
229232
}
230233

231234
.not-command {

src/app/elements/content/content.component.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {ConnectTokenService, HttpService, I18nService, LogService, SettingServic
44
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';
55
import {MatDialog} from '@angular/material';
66
import {ElementCommandDialogComponent} from '@app/elements/content/command-dialog/command-dialog.component';
7+
import {ElementSendCommandWithVariableDialogComponent} from '@app/elements/content/send-command-with-variable-dialog/send-command-with-variable-dialog.component';
78
import {fromEvent, Subscription} from 'rxjs';
89
import * as jQuery from 'jquery/dist/jquery.min.js';
910

@@ -197,7 +198,26 @@ export class ElementContentComponent implements OnInit, OnDestroy {
197198

198199
sendQuickCommand(command) {
199200
this.batchCommand = command.args;
200-
this.sendBatchCommand();
201+
if(command.variable.length>0){
202+
const dialogRef=this._dialog.open(
203+
ElementSendCommandWithVariableDialogComponent,
204+
{
205+
height: 'auto',
206+
width: '500px',
207+
data: {command:command}
208+
}
209+
)
210+
dialogRef.afterClosed().subscribe(result => {
211+
if (result) {
212+
this.batchCommand = result
213+
this.sendBatchCommand();
214+
}
215+
})
216+
}
217+
else{
218+
this.sendBatchCommand();
219+
}
220+
201221
}
202222

203223
rMenuItems() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<h1 mat-dialog-title>
2+
{{"Send command"| translate}}
3+
</h1>
4+
5+
<div>
6+
<variable-dynamic-form [formConfig]="formConfig" [command]="command" (formSubmitted)="onFormSubmitted($event)"></variable-dynamic-form>
7+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {Component, Inject, OnInit} from '@angular/core';
2+
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
3+
import {HttpService} from '@app/services';
4+
5+
@Component({
6+
selector: 'elements-send-with-variable-command-dialog',
7+
templateUrl: './send-command-with-variable-dialog.component.html',
8+
})
9+
export class ElementSendCommandWithVariableDialogComponent implements OnInit {
10+
public formConfig = [];
11+
public command = {};
12+
constructor(public dialogRef: MatDialogRef<ElementSendCommandWithVariableDialogComponent>,
13+
private _http: HttpService,
14+
@Inject(MAT_DIALOG_DATA) public data: any
15+
) {}
16+
17+
ngOnInit() {
18+
this.getVariableFormMeta()
19+
}
20+
async getVariableFormMeta() {
21+
const adhoc = this.data.command.id
22+
const url=`/api/v1/ops/variable/form_data/?t=${new Date().getTime()}&adhoc=${adhoc}`
23+
const res: any = await this._http.options(url).toPromise();
24+
this.formConfig = res.actions.GET;
25+
this.command = this.data.command;
26+
}
27+
onFormSubmitted(data: any) {
28+
setTimeout(() => {
29+
this.dialogRef.close(data.sendCommand);
30+
});
31+
}
32+
33+
protected readonly Component = Component;
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Injectable } from '@angular/core';
2+
import { FormControl, FormGroup, Validators } from '@angular/forms';
3+
4+
@Injectable({
5+
providedIn: 'root'
6+
})
7+
export class DynamicFormService {
8+
createFormGroup(fields: any): FormGroup {
9+
const group: any = {};
10+
for (const field in fields) {
11+
const fieldDefinition = fields[field];
12+
const validators = [];
13+
if (fieldDefinition.required) {
14+
validators.push(Validators.required);
15+
}
16+
group['jms_'+field] = new FormControl(fieldDefinition.default || '', validators)
17+
}
18+
group["sendCommand"] = new FormControl("")
19+
return new FormGroup(group);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<form *ngIf="dynamicForm && formConfig" [formGroup]="dynamicForm" (ngSubmit)="onSubmit()">
2+
<div *ngFor="let fieldKey of fieldKeys" [ngSwitch]="formConfig[fieldKey].type">
3+
<mat-form-field *ngSwitchCase="'string'" class="full-width">
4+
<label class="zone-label">{{ formConfig[fieldKey].label }}</label>
5+
<input matInput [formControlName]="'jms_'+fieldKey" />
6+
<mat-hint>{{ formConfig[fieldKey].help_text }}</mat-hint>
7+
</mat-form-field>
8+
<div *ngSwitchCase="'labeled_choice'" class="radio-group full-width">
9+
<label class="zone-label">{{ formConfig[fieldKey].label }}</label>
10+
<mat-radio-group style="display: block; padding: 5px 0;" [formControlName]="'jms_'+fieldKey">
11+
<mat-radio-button *ngFor="let choice of formConfig[fieldKey].choices"
12+
[value]="choice.value">
13+
{{ choice.label }}
14+
</mat-radio-button>
15+
</mat-radio-group>
16+
<mat-hint>{{ formConfig[fieldKey].help_text }}</mat-hint>
17+
</div>
18+
</div>
19+
<mat-form-field *ngIf="command" class="full-width">
20+
<label class="zone-label">{{ 'Command'| translate }}</label>
21+
<textarea rows="5" matInput [formControlName]="'sendCommand'" [value]="command.args"></textarea>
22+
</mat-form-field>
23+
<div style="float: right; padding-top: 20px">
24+
<button mat-raised-button color="primary" type="submit" [disabled]="dynamicForm.invalid">{{"Confirm"| translate}}</button>
25+
</div>
26+
</form>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.full-width {
2+
width: 100%;
3+
}
4+
mat-hint {
5+
font-size: 10px;
6+
}
7+
.zone-label {
8+
color: rgba(0, 0, 0, 0.54);
9+
font-size:10px;
10+
cursor: pointer;
11+
}

0 commit comments

Comments
 (0)