mirror of https://github.com/goharbor/harbor.git
migrate tslint to eslint (#16856)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
54a857f2aa
commit
73295ff891
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"root": true,
|
||||
"ignorePatterns": [
|
||||
"projects/**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"tsconfig.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"createDefaultProgram": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/recommended",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/template/recommended"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"excludedFiles": ["*inline-template-*.component.html"],
|
||||
"extends": ["plugin:prettier/recommended"],
|
||||
"rules": {
|
||||
"prettier/prettier": ["error", { "parser": "angular" }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
node_modules/
|
||||
proxy.config.json
|
||||
coverage/
|
||||
ng-swagger-gen/
|
||||
docker-build/nginx.conf
|
||||
.angular/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"trailingComma": "es5",
|
||||
"bracketSameLine": true,
|
||||
"printWidth": 80
|
||||
}
|
|
@ -119,10 +119,19 @@
|
|||
"protractor": "^7.0.0",
|
||||
|
||||
// For code grammar checking. Optional
|
||||
"eslint": "8.1.0",
|
||||
"eslint": "^8.12.0",
|
||||
"@angular-eslint/eslint-plugin": "13.2.1",
|
||||
"@angular-eslint/eslint-plugin-template": "13.2.1",
|
||||
"@angular-eslint/schematics": "13.2.1",
|
||||
"@angular-eslint/template-parser": "13.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.17.0",
|
||||
"@typescript-eslint/parser": "5.17.0",
|
||||
|
||||
// For code checking. Optional
|
||||
"codelyzer": "^6.0.2",
|
||||
// For code format
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-eslint": "^14.0.2",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -132,6 +132,15 @@
|
|||
"src/i18n"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -148,7 +157,7 @@
|
|||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"tsConfig": [],
|
||||
"exclude": []
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,8 +7,8 @@
|
|||
"postinstall": "node scripts/convert-yaml-to-json.js && ng-swagger-gen -i ng-swagger-gen/swagger.json -o ng-swagger-gen && node scripts/delete-swagger-json.js",
|
||||
"start": "node --max_old_space_size=2048 ./node_modules/@angular/cli/bin/ng serve --ssl true --host 0.0.0.0 --proxy-config proxy.config.json",
|
||||
"start:prod": "node --max_old_space_size=2048 ./node_modules/@angular/cli/bin/ng serve --ssl true --host 0.0.0.0 --proxy-config proxy.config.json --configuration production",
|
||||
"lint": "tslint \"src/**/*.ts\"",
|
||||
"lint_fix": "tslint --fix \"src/**/*.ts\"",
|
||||
"lint": "ng lint",
|
||||
"lint_fix": "ng lint --fix",
|
||||
"test": "node --max_old_space_size=2048 ./node_modules/@angular/cli/bin/ng test --code-coverage",
|
||||
"test:watch": "ng test --code-coverage --watch",
|
||||
"test:debug": "ng test --code-coverage --source-map false",
|
||||
|
@ -34,32 +34,41 @@
|
|||
"@angular/platform-browser": "~13.3.4",
|
||||
"@angular/platform-browser-dynamic": "~13.3.4",
|
||||
"@angular/router": "~13.3.4",
|
||||
"rxjs": "^7.4.0",
|
||||
"tslib": "^2.2.0",
|
||||
"zone.js": "~0.11.4",
|
||||
"@clr/angular": "13.0.2",
|
||||
"@cds/core": "5.6.4",
|
||||
"@clr/angular": "13.0.2",
|
||||
"@clr/icons": "13.0.2",
|
||||
"@clr/ui": "13.0.2",
|
||||
"@ngx-translate/core": "^13.0.0",
|
||||
"@ngx-translate/http-loader": "^6.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"cron-validator": "^1.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ngx-clipboard": "^12.3.1",
|
||||
"ngx-cookie": "^5.0.2",
|
||||
"ngx-markdown": "13.1.0",
|
||||
"swagger-ui": "~4.10.3",
|
||||
"buffer": "^6.0.3",
|
||||
"rxjs": "^7.4.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"js-yaml": "^4.1.0"
|
||||
"swagger-ui": "~4.10.3",
|
||||
"tslib": "^2.2.0",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~13.3.4",
|
||||
"@angular-eslint/builder": "13.2.1",
|
||||
"@angular-eslint/eslint-plugin": "13.2.1",
|
||||
"@angular-eslint/eslint-plugin-template": "13.2.1",
|
||||
"@angular-eslint/schematics": "13.2.1",
|
||||
"@angular-eslint/template-parser": "13.2.1",
|
||||
"@angular/cli": "~13.3.4",
|
||||
"@angular/compiler-cli": "~13.3.4",
|
||||
"@types/express": "^4.17.12",
|
||||
"@types/jasmine": "~3.10.1",
|
||||
"@types/node": "^16.11.6",
|
||||
"codelyzer": "^6.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.17.0",
|
||||
"@typescript-eslint/parser": "5.17.0",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"express": "^4.17.1",
|
||||
"jasmine-core": "^4.0.0",
|
||||
"jasmine-spec-reporter": "^7.0.0",
|
||||
|
@ -69,8 +78,9 @@
|
|||
"karma-jasmine": "~4.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.7.0",
|
||||
"ng-swagger-gen": "^1.8.1",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-eslint": "^14.0.2",
|
||||
"protractor": "^7.0.0",
|
||||
"eslint": "8.1.0",
|
||||
"typescript": "~4.5.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,31 +16,26 @@ import { RouterModule, Routes } from '@angular/router';
|
|||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SignUpComponent } from './sign-up/sign-up.component';
|
||||
import { SignUpPageComponent } from './sign-up/sign-up-page.component';
|
||||
import { SignInComponent } from "./sign-in/sign-in.component";
|
||||
import { SignInGuard } from "../shared/router-guard/sign-in-guard-activate.service";
|
||||
import { TopRepoService } from "./sign-in/top-repo/top-repository.service";
|
||||
import { TopRepoComponent } from "./sign-in/top-repo/top-repo.component";
|
||||
import { SignInComponent } from './sign-in/sign-in.component';
|
||||
import { SignInGuard } from '../shared/router-guard/sign-in-guard-activate.service';
|
||||
import { TopRepoService } from './sign-in/top-repo/top-repository.service';
|
||||
import { TopRepoComponent } from './sign-in/top-repo/top-repo.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'sign-in',
|
||||
canActivate: [SignInGuard],
|
||||
component: SignInComponent
|
||||
}
|
||||
{
|
||||
path: 'sign-in',
|
||||
canActivate: [SignInGuard],
|
||||
component: SignInComponent,
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
SignUpComponent,
|
||||
SignUpPageComponent,
|
||||
SignInComponent,
|
||||
TopRepoComponent,
|
||||
],
|
||||
providers: [
|
||||
TopRepoService
|
||||
]
|
||||
imports: [RouterModule.forChild(routes), SharedModule],
|
||||
declarations: [
|
||||
SignUpComponent,
|
||||
SignUpPageComponent,
|
||||
SignInComponent,
|
||||
TopRepoComponent,
|
||||
],
|
||||
providers: [TopRepoService],
|
||||
})
|
||||
export class AccountModule { }
|
||||
export class AccountModule {}
|
||||
|
|
|
@ -2,77 +2,168 @@
|
|||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showDialogModalAction)="openModal($event)"></navigator>
|
||||
<search-result></search-result>
|
||||
<div class="login-wrapper"
|
||||
[ngStyle]="{'background-image': customLoginBgImg? 'url(/images/' + customLoginBgImg + ')': ''}">
|
||||
<div
|
||||
class="login-wrapper"
|
||||
[ngStyle]="{
|
||||
'background-image': customLoginBgImg
|
||||
? 'url(/images/' + customLoginBgImg + ')'
|
||||
: ''
|
||||
}">
|
||||
<form #signInForm="ngForm" class="login">
|
||||
<span *ngIf="isOidcLoginMode && steps===2" (click)="steps=1" class="back-icon">
|
||||
<span
|
||||
*ngIf="isOidcLoginMode && steps === 2"
|
||||
(click)="steps = 1"
|
||||
class="back-icon">
|
||||
<clr-icon shape="angle left"></clr-icon>
|
||||
<div class="margin-left-3">{{'SEARCH.BACK' | translate }}</div>
|
||||
<div class="margin-left-3">{{ 'SEARCH.BACK' | translate }}</div>
|
||||
</span>
|
||||
<label class="title"> {{customAppTitle ? customAppTitle : (appTitle | translate)}}
|
||||
<label class="title">
|
||||
{{ customAppTitle ? customAppTitle : (appTitle | translate) }}
|
||||
</label>
|
||||
<div class="login-group">
|
||||
<ng-container *ngIf="isOidcLoginMode && steps ===1">
|
||||
<ng-container *ngIf="isOidcLoginMode && steps === 1">
|
||||
<a href="/c/oidc/login">
|
||||
<button type="button" id="log_oidc" class="btn btn-primary btn-block">
|
||||
<span>{{'BUTTON.LOG_IN_OIDC' | translate }}</span>
|
||||
<button
|
||||
type="button"
|
||||
id="log_oidc"
|
||||
class="btn btn-primary btn-block">
|
||||
<span>{{ 'BUTTON.LOG_IN_OIDC' | translate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
<div class="divider-container mt-1 mb-1">
|
||||
<hr class="divider" size="1" noshade/>
|
||||
<h4 class="m-0"><span>{{'SIGN_IN.OR' | translate }}</span></h4>
|
||||
<hr class="divider" size="1" noshade/>
|
||||
<hr class="divider" size="1" noshade />
|
||||
<h4 class="m-0">
|
||||
<span>{{ 'SIGN_IN.OR' | translate }}</span>
|
||||
</h4>
|
||||
<hr class="divider" size="1" noshade />
|
||||
</div>
|
||||
<a href="javascript:void(0)" (click)="steps=2">
|
||||
<button type="button" id="login-db" class="btn btn-primary btn-block mt-0">
|
||||
<span>{{'SIGN_IN.VIA_LOCAL_DB' | translate }}</span>
|
||||
<a href="javascript:void(0)" (click)="steps = 2">
|
||||
<button
|
||||
type="button"
|
||||
id="login-db"
|
||||
class="btn btn-primary btn-block mt-0">
|
||||
<span>{{
|
||||
'SIGN_IN.VIA_LOCAL_DB' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="(!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
(!isOidcLoginMode && steps === 1) ||
|
||||
(isOidcLoginMode && steps === 2)
|
||||
">
|
||||
<clr-input-container class="mt-3">
|
||||
<input clrInput class="username" type="text" required [(ngModel)]="signInCredential.principal"
|
||||
name="login_username" id="login_username"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_NAME" | translate}}' #userNameInput='ngModel'>
|
||||
<clr-control-error>{{ 'TOOLTIP.SIGN_IN_USERNAME' | translate }}</clr-control-error>
|
||||
<input
|
||||
clrInput
|
||||
class="username"
|
||||
type="text"
|
||||
required
|
||||
[(ngModel)]="signInCredential.principal"
|
||||
name="login_username"
|
||||
id="login_username"
|
||||
placeholder="{{
|
||||
'PLACEHOLDER.SIGN_IN_NAME' | translate
|
||||
}}"
|
||||
#userNameInput="ngModel" />
|
||||
<clr-control-error>{{
|
||||
'TOOLTIP.SIGN_IN_USERNAME' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control">
|
||||
<div class="clr-control-container"
|
||||
[class.clr-error]="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="
|
||||
passwordInput.invalid &&
|
||||
(passwordInput.dirty || passwordInput.touched)
|
||||
">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="clr-input pwd-input" [type]="showPwd?'text':'password'" required
|
||||
[(ngModel)]="signInCredential.password" name="login_password" id="login_password"
|
||||
placeholder='{{"PLACEHOLDER.SIGN_IN_PWD" | translate}}' #passwordInput="ngModel">
|
||||
<clr-icon *ngIf="!showPwd" shape="eye" class="pw-eye"
|
||||
(click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon *ngIf="showPwd" shape="eye-hide" class="pw-eye"
|
||||
(click)="showPwd =!showPwd"></clr-icon>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<input
|
||||
class="clr-input pwd-input"
|
||||
[type]="showPwd ? 'text' : 'password'"
|
||||
required
|
||||
[(ngModel)]="signInCredential.password"
|
||||
name="login_password"
|
||||
id="login_password"
|
||||
placeholder="{{
|
||||
'PLACEHOLDER.SIGN_IN_PWD' | translate
|
||||
}}"
|
||||
#passwordInput="ngModel" />
|
||||
<clr-icon
|
||||
*ngIf="!showPwd"
|
||||
shape="eye"
|
||||
class="pw-eye"
|
||||
(click)="showPwd = !showPwd"></clr-icon>
|
||||
<clr-icon
|
||||
*ngIf="showPwd"
|
||||
shape="eye-hide"
|
||||
class="pw-eye"
|
||||
(click)="showPwd = !showPwd"></clr-icon>
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error
|
||||
*ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||
*ngIf="
|
||||
passwordInput.invalid &&
|
||||
(passwordInput.dirty ||
|
||||
passwordInput.touched)
|
||||
">
|
||||
{{ 'TOOLTIP.SIGN_IN_PWD' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="display-flex">
|
||||
<clr-checkbox-wrapper>
|
||||
<input clrCheckbox type="checkbox" id="rememberme" #rememberMeBox
|
||||
(click)="clickRememberMe($event)" [checked]="rememberMe">
|
||||
<label class="reset-label" for="rememberme">{{ 'SIGN_IN.REMEMBER' | translate }}</label>
|
||||
<input
|
||||
clrCheckbox
|
||||
type="checkbox"
|
||||
id="rememberme"
|
||||
#rememberMeBox
|
||||
(click)="clickRememberMe($event)"
|
||||
[checked]="rememberMe" />
|
||||
<label class="reset-label" for="rememberme">{{
|
||||
'SIGN_IN.REMEMBER' | translate
|
||||
}}</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
<div *ngIf="isError" class="error active">
|
||||
<span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
|
||||
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
|
||||
<span *ngIf="isCoreServiceAvailable">{{
|
||||
'SIGN_IN.INVALID_MSG' | translate
|
||||
}}</span>
|
||||
<span *ngIf="!isCoreServiceAvailable">{{
|
||||
'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary mt-2" (click)="signIn()"
|
||||
id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
|
||||
<a href="javascript:void(0)" class="signup" (click)="signUp()"
|
||||
*ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
|
||||
<button
|
||||
[disabled]="isOnGoing || !isValid"
|
||||
type="submit"
|
||||
class="btn btn-primary mt-2"
|
||||
(click)="signIn()"
|
||||
id="log_in">
|
||||
{{ 'BUTTON.LOG_IN' | translate }}
|
||||
</button>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="signup"
|
||||
(click)="signUp()"
|
||||
*ngIf="selfSignUp"
|
||||
>{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a
|
||||
>
|
||||
</ng-container>
|
||||
<div class="more-info" [ngClass]="{'pt-1': (!isOidcLoginMode && steps === 1) || (isOidcLoginMode && steps ===2)}">
|
||||
<a rel='noopener noreferrer' href="https://github.com/goharbor/harbor" target="_blank">{{ 'BUTTON.MORE_INFO' | translate }}</a>
|
||||
<div
|
||||
class="more-info"
|
||||
[ngClass]="{
|
||||
'pt-1':
|
||||
(!isOidcLoginMode && steps === 1) ||
|
||||
(isOidcLoginMode && steps === 2)
|
||||
}">
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/goharbor/harbor"
|
||||
target="_blank"
|
||||
>{{ 'BUTTON.MORE_INFO' | translate }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -3,13 +3,13 @@ import { SignInComponent } from './sign-in.component';
|
|||
import { AppConfigService } from '../../services/app-config.service';
|
||||
import { SessionService } from '../../shared/services/session.service';
|
||||
import { CookieService } from 'ngx-cookie';
|
||||
import { SkinableConfig } from "../../services/skinable-config.service";
|
||||
import { SkinableConfig } from '../../services/skinable-config.service';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { of } from "rxjs";
|
||||
import { of } from 'rxjs';
|
||||
import { throwError as observableThrowError } from 'rxjs/internal/observable/throwError';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { SharedTestingModule } from "../../shared/shared.module";
|
||||
import { UserPermissionService } from "../../shared/services";
|
||||
import { SharedTestingModule } from '../../shared/shared.module';
|
||||
import { UserPermissionService } from '../../shared/services';
|
||||
|
||||
describe('SignInComponent', () => {
|
||||
let component: SignInComponent;
|
||||
|
@ -20,54 +20,54 @@ describe('SignInComponent', () => {
|
|||
},
|
||||
getCurrentUser() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
};
|
||||
const mockedUserPermissionService = {
|
||||
clearPermissionCache() {
|
||||
}
|
||||
clearPermissionCache() {},
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [SignInComponent],
|
||||
providers: [
|
||||
{ provide: UserPermissionService, useValue: mockedUserPermissionService},
|
||||
{ provide: SessionService, useValue: mockedSessionService},
|
||||
{
|
||||
provide: AppConfigService, useValue: {
|
||||
provide: UserPermissionService,
|
||||
useValue: mockedUserPermissionService,
|
||||
},
|
||||
{ provide: SessionService, useValue: mockedSessionService },
|
||||
{
|
||||
provide: AppConfigService,
|
||||
useValue: {
|
||||
load: function () {
|
||||
return of({
|
||||
|
||||
});
|
||||
},
|
||||
isIntegrationMode() {
|
||||
return of({});
|
||||
},
|
||||
isIntegrationMode() {},
|
||||
getConfig() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CookieService, useValue: {
|
||||
provide: CookieService,
|
||||
useValue: {
|
||||
get: function (key) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SkinableConfig, useValue: {
|
||||
provide: SkinableConfig,
|
||||
useValue: {
|
||||
getSkinConfig: function () {
|
||||
return {
|
||||
loginBgImg: "abc",
|
||||
appTitle: "Harbor"
|
||||
loginBgImg: 'abc',
|
||||
appTitle: 'Harbor',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@ -83,41 +83,62 @@ describe('SignInComponent', () => {
|
|||
|
||||
it('should show core service is not available', async () => {
|
||||
expect(component).toBeTruthy();
|
||||
const sessionService = TestBed.get<SessionService>(SessionService);
|
||||
const spy: jasmine.Spy = spyOn(sessionService, "signIn").and.returnValue(observableThrowError( new HttpErrorResponse({
|
||||
error: 'test 501 error',
|
||||
status: 501
|
||||
})));
|
||||
const sessionService = TestBed.get<SessionService>(SessionService);
|
||||
const spy: jasmine.Spy = spyOn(
|
||||
sessionService,
|
||||
'signIn'
|
||||
).and.returnValue(
|
||||
observableThrowError(
|
||||
new HttpErrorResponse({
|
||||
error: 'test 501 error',
|
||||
status: 501,
|
||||
})
|
||||
)
|
||||
);
|
||||
signIn();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(spy.calls.count()).toEqual(1);
|
||||
const errorSpan: HTMLSpanElement = fixture.nativeElement.querySelector(".error>span");
|
||||
expect(errorSpan.innerText).toEqual('SIGN_IN.CORE_SERVICE_NOT_AVAILABLE');
|
||||
const errorSpan: HTMLSpanElement =
|
||||
fixture.nativeElement.querySelector('.error>span');
|
||||
expect(errorSpan.innerText).toEqual(
|
||||
'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE'
|
||||
);
|
||||
});
|
||||
it('should show invalid username or password', async () => {
|
||||
expect(component).toBeTruthy();
|
||||
const sessionService = TestBed.get<SessionService>(SessionService);
|
||||
const spy: jasmine.Spy = spyOn(sessionService, "signIn").and.returnValue(observableThrowError( new HttpErrorResponse({
|
||||
error: 'test 404 error',
|
||||
status: 404,
|
||||
statusText: 'Not Found'
|
||||
})));
|
||||
const spy: jasmine.Spy = spyOn(
|
||||
sessionService,
|
||||
'signIn'
|
||||
).and.returnValue(
|
||||
observableThrowError(
|
||||
new HttpErrorResponse({
|
||||
error: 'test 404 error',
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
})
|
||||
)
|
||||
);
|
||||
signIn();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(spy.calls.count()).toEqual(1);
|
||||
const errorSpan: HTMLSpanElement = fixture.nativeElement.querySelector(".error>span");
|
||||
const errorSpan: HTMLSpanElement =
|
||||
fixture.nativeElement.querySelector('.error>span');
|
||||
expect(errorSpan.innerText).toEqual('SIGN_IN.INVALID_MSG');
|
||||
});
|
||||
function signIn() {
|
||||
const nameInput: HTMLInputElement = fixture.nativeElement.querySelector("#login_username");
|
||||
nameInput.value = "admin";
|
||||
const nameInput: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#login_username');
|
||||
nameInput.value = 'admin';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
const passwordInput: HTMLInputElement = fixture.nativeElement.querySelector("#login_password");
|
||||
passwordInput.value = "Harbor12345";
|
||||
const passwordInput: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#login_password');
|
||||
passwordInput.value = 'Harbor12345';
|
||||
passwordInput.dispatchEvent(new Event('input'));
|
||||
const signButton: HTMLAnchorElement = fixture.nativeElement.querySelector("#log_in");
|
||||
const signButton: HTMLAnchorElement =
|
||||
fixture.nativeElement.querySelector('#log_in');
|
||||
signButton.click();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -11,7 +11,13 @@
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { AfterViewChecked, Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import {
|
||||
AfterViewChecked,
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { SessionService } from '../../shared/services/session.service';
|
||||
|
@ -21,40 +27,42 @@ import { AppConfigService } from '../../services/app-config.service';
|
|||
import { AppConfig } from '../../services/app-config';
|
||||
import { User } from '../../base/left-side-nav/user/user';
|
||||
import { CookieOptions, CookieService } from 'ngx-cookie';
|
||||
import { SkinableConfig } from "../../services/skinable-config.service";
|
||||
import { ModalEvent } from "../../base/modal-event";
|
||||
import { modalEvents } from "../../base/modal-events.const";
|
||||
import { AboutDialogComponent } from "../../shared/components/about-dialog/about-dialog.component";
|
||||
import { CommonRoutes, CONFIG_AUTH_MODE } from "../../shared/entities/shared.const";
|
||||
import { SignInCredential } from "./sign-in-credential";
|
||||
import { UserPermissionService } from "../../shared/services";
|
||||
import {finalize} from "rxjs/operators";
|
||||
import { SkinableConfig } from '../../services/skinable-config.service';
|
||||
import { ModalEvent } from '../../base/modal-event';
|
||||
import { modalEvents } from '../../base/modal-events.const';
|
||||
import { AboutDialogComponent } from '../../shared/components/about-dialog/about-dialog.component';
|
||||
import {
|
||||
CommonRoutes,
|
||||
CONFIG_AUTH_MODE,
|
||||
} from '../../shared/entities/shared.const';
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
import { UserPermissionService } from '../../shared/services';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
// Define status flags for signing in states
|
||||
export const signInStatusNormal = 0;
|
||||
export const signInStatusOnGoing = 1;
|
||||
export const signInStatusError = -1;
|
||||
const remCookieKey = "rem-username";
|
||||
const remCookieKey = 'rem-username';
|
||||
const expireDays = 10;
|
||||
|
||||
@Component({
|
||||
selector: 'sign-in',
|
||||
templateUrl: "sign-in.component.html",
|
||||
styleUrls: ['sign-in.component.scss']
|
||||
templateUrl: 'sign-in.component.html',
|
||||
styleUrls: ['sign-in.component.scss'],
|
||||
})
|
||||
|
||||
export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
showPwd: boolean = false;
|
||||
redirectUrl: string = "";
|
||||
redirectUrl: string = '';
|
||||
// Remeber me indicator
|
||||
rememberMe: boolean = false;
|
||||
rememberedName: string = "";
|
||||
rememberedName: string = '';
|
||||
|
||||
customLoginBgImg: string;
|
||||
customAppTitle: string;
|
||||
// Form reference
|
||||
signInForm: NgForm;
|
||||
@ViewChild('signInForm', {static: true}) currentForm: NgForm;
|
||||
@ViewChild('signInForm', { static: true }) currentForm: NgForm;
|
||||
@ViewChild('signupDialog') signUpDialog: SignUpComponent;
|
||||
@ViewChild('forgotPwdDialog') forgotPwdDialog: ForgotPasswordComponent;
|
||||
@ViewChild(AboutDialogComponent) aboutDialog: AboutDialogComponent;
|
||||
|
@ -64,8 +72,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
|
||||
// Initialize sign in credential
|
||||
@Input() signInCredential: SignInCredential = {
|
||||
principal: "",
|
||||
password: ""
|
||||
principal: '',
|
||||
password: '',
|
||||
};
|
||||
isCoreServiceAvailable: boolean = true;
|
||||
steps: number = 1;
|
||||
|
@ -77,7 +85,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
private appConfigService: AppConfigService,
|
||||
private cookie: CookieService,
|
||||
private skinableConfig: SkinableConfig,
|
||||
private userPermissionService: UserPermissionService) { }
|
||||
private userPermissionService: UserPermissionService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// custom skin
|
||||
|
@ -90,17 +99,16 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
this.customAppTitle = customSkinObj.loginTitle;
|
||||
}
|
||||
}
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
this.redirectUrl = params["redirect_url"] || "";
|
||||
let isSignUp = params["sign_up"] || "";
|
||||
if (isSignUp !== "") {
|
||||
this.signUp(); // Open sign up
|
||||
}
|
||||
});
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.redirectUrl = params['redirect_url'] || '';
|
||||
let isSignUp = params['sign_up'] || '';
|
||||
if (isSignUp !== '') {
|
||||
this.signUp(); // Open sign up
|
||||
}
|
||||
});
|
||||
|
||||
let remUsername = this.cookie.get(remCookieKey);
|
||||
remUsername = remUsername ? remUsername.trim() : "";
|
||||
remUsername = remUsername ? remUsername.trim() : '';
|
||||
if (remUsername) {
|
||||
this.signInCredential.principal = remUsername;
|
||||
this.rememberMe = true;
|
||||
|
@ -110,11 +118,14 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
|
||||
// App title
|
||||
public get appTitle(): string {
|
||||
if (this.appConfigService.getConfig() && this.appConfigService.getConfig().with_admiral) {
|
||||
return "APP_TITLE.VIC";
|
||||
if (
|
||||
this.appConfigService.getConfig() &&
|
||||
this.appConfigService.getConfig().with_admiral
|
||||
) {
|
||||
return 'APP_TITLE.VIC';
|
||||
}
|
||||
|
||||
return "APP_TITLE.VMW_HARBOR";
|
||||
return 'APP_TITLE.VMW_HARBOR';
|
||||
}
|
||||
|
||||
// For template accessing
|
||||
|
@ -133,11 +144,19 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
|
||||
// Whether show the 'sign up' link
|
||||
public get selfSignUp(): boolean {
|
||||
return this.appConfigService.getConfig() && this.appConfigService.getConfig().auth_mode === CONFIG_AUTH_MODE.DB_AUTH
|
||||
&& this.appConfigService.getConfig().self_registration;
|
||||
return (
|
||||
this.appConfigService.getConfig() &&
|
||||
this.appConfigService.getConfig().auth_mode ===
|
||||
CONFIG_AUTH_MODE.DB_AUTH &&
|
||||
this.appConfigService.getConfig().self_registration
|
||||
);
|
||||
}
|
||||
public get isOidcLoginMode(): boolean {
|
||||
return this.appConfigService.getConfig() && this.appConfigService.getConfig().auth_mode === CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
return (
|
||||
this.appConfigService.getConfig() &&
|
||||
this.appConfigService.getConfig().auth_mode ===
|
||||
CONFIG_AUTH_MODE.OIDC_AUTH
|
||||
);
|
||||
}
|
||||
clickRememberMe($event: any): void {
|
||||
if ($event && $event.target) {
|
||||
|
@ -145,7 +164,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
if (!this.rememberMe) {
|
||||
// Remove cookie data
|
||||
this.cookie.remove(remCookieKey);
|
||||
this.rememberedName = "";
|
||||
this.rememberedName = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,10 +176,14 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
let expires: number = expireDays * 3600 * 24 * 1000;
|
||||
let date = new Date(Date.now() + expires);
|
||||
let cookieptions: CookieOptions = {
|
||||
path: "/",
|
||||
expires: date
|
||||
path: '/',
|
||||
expires: date,
|
||||
};
|
||||
this.cookie.put(remCookieKey, this.signInCredential.principal, cookieptions);
|
||||
this.cookie.put(
|
||||
remCookieKey,
|
||||
this.signInCredential.principal,
|
||||
cookieptions
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,8 +193,10 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
// Set error status
|
||||
this.signInStatus = signInStatusError;
|
||||
|
||||
let message = error.status ? error.status + ":" + error.statusText : error;
|
||||
console.error("An error occurred when signing in:", message);
|
||||
let message = error.status
|
||||
? error.status + ':' + error.statusText
|
||||
: error;
|
||||
console.error('An error occurred when signing in:', message);
|
||||
}
|
||||
|
||||
// Hande form values changes
|
||||
|
@ -181,22 +206,19 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
}
|
||||
this.signInForm = this.currentForm;
|
||||
if (this.signInForm) {
|
||||
this.signInForm.valueChanges
|
||||
.subscribe(data => {
|
||||
this.updateState();
|
||||
});
|
||||
this.signInForm.valueChanges.subscribe(data => {
|
||||
this.updateState();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fill the new user info into the sign in form
|
||||
handleUserCreation(user: User): void {
|
||||
if (user) {
|
||||
this.currentForm.setValue({
|
||||
"login_username": user.username,
|
||||
"login_password": ""
|
||||
login_username: user.username,
|
||||
login_password: '',
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,8 +255,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
this.signInStatus = signInStatusOnGoing;
|
||||
|
||||
// Call the service to send out the http request
|
||||
this.session.signIn(this.signInCredential)
|
||||
.subscribe(() => {
|
||||
this.session.signIn(this.signInCredential).subscribe(
|
||||
() => {
|
||||
// Set status
|
||||
// Keep it ongoing to keep the button 'disabled'
|
||||
// this.signInStatus = signInStatusNormal;
|
||||
|
@ -244,7 +266,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
this.remeberMe();
|
||||
|
||||
// Redirect to the right router-guard
|
||||
if (this.redirectUrl === "") {
|
||||
if (this.redirectUrl === '') {
|
||||
// Routing to the default location
|
||||
this.router.navigateByUrl(CommonRoutes.HARBOR_DEFAULT);
|
||||
} else {
|
||||
|
@ -253,25 +275,28 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
this.isCoreServiceAvailable = true;
|
||||
|
||||
// after login successfully: Make sure the updated configuration can be loaded
|
||||
this.appConfigService.load()
|
||||
.subscribe();
|
||||
}, error => {
|
||||
this.appConfigService.load().subscribe();
|
||||
},
|
||||
error => {
|
||||
// 403 oidc login no body;
|
||||
if (this.isOidcLoginMode && error && error.status === 403) {
|
||||
try {
|
||||
let redirect_location = '';
|
||||
redirect_location = error.error && error.error.redirect_location ?
|
||||
error.error.redirect_location : JSON.parse(error.error).redirect_location;
|
||||
redirect_location =
|
||||
error.error && error.error.redirect_location
|
||||
? error.error.redirect_location
|
||||
: JSON.parse(error.error).redirect_location;
|
||||
window.location.href = redirect_location;
|
||||
return;
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
}
|
||||
// core service is not available for error code 5xx
|
||||
if (error && /5[0-9][0-9]/.test(error.status)) {
|
||||
this.isCoreServiceAvailable = false;
|
||||
}
|
||||
this.handleError(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Open sign up dialog
|
||||
|
@ -295,5 +320,3 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,16 +3,17 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
|
|||
import { SignInService } from './sign-in.service';
|
||||
|
||||
describe('SignInService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [SignInService]
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [SignInService],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([SignInService], (service: SignInService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
it('should be created', inject(
|
||||
[SignInService],
|
||||
(service: SignInService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}
|
||||
));
|
||||
});
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
// limitations under the License.
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { map, catchError } from "rxjs/operators";
|
||||
import { Observable, throwError as observableThrowError } from "rxjs";
|
||||
import { HTTP_FORM_OPTIONS } from "../../shared/units/utils";
|
||||
import { SignInCredential } from "./sign-in-credential";
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
import { Observable, throwError as observableThrowError } from 'rxjs';
|
||||
import { HTTP_FORM_OPTIONS } from '../../shared/units/utils';
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
const signInUrl = '/c/login';
|
||||
/**
|
||||
*
|
||||
|
@ -27,7 +27,6 @@ const signInUrl = '/c/login';
|
|||
*/
|
||||
@Injectable()
|
||||
export class SignInService {
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
// Handle the related exceptions
|
||||
|
@ -38,15 +37,17 @@ export class SignInService {
|
|||
// Submit signin form to backend (NOT restful service)
|
||||
signIn(signInCredential: SignInCredential): Observable<any> {
|
||||
// Build the form package
|
||||
let body = new HttpParams();
|
||||
let body = new HttpParams();
|
||||
body = body.set('principal', signInCredential.principal);
|
||||
body = body.set('password', signInCredential.password);
|
||||
|
||||
// Trigger HttpClient
|
||||
return this.http.post(signInUrl, body.toString(), HTTP_FORM_OPTIONS)
|
||||
.pipe(map(() => null)
|
||||
, catchError(error => observableThrowError(error)));
|
||||
|
||||
return this.http
|
||||
.post(signInUrl, body.toString(), HTTP_FORM_OPTIONS)
|
||||
.pipe(
|
||||
map(() => null),
|
||||
catchError(error => observableThrowError(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<div class="repo-wrapper">
|
||||
<div>
|
||||
<h3 class="pop-repos">{{'REPOSITORY.POP_REPOS' | translate}}</h3>
|
||||
<h3 class="pop-repos">{{ 'REPOSITORY.POP_REPOS' | translate }}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<list-repository-ro [repositories]="topRepos"></list-repository-ro>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,27 +10,25 @@ describe('TopRepoComponent', () => {
|
|||
let component: TopRepoComponent;
|
||||
let fixture: ComponentFixture<TopRepoComponent>;
|
||||
const mockMessageHandlerService = {
|
||||
showSuccess: () => { },
|
||||
handleError: () => { },
|
||||
isAppLevel: () => { },
|
||||
showSuccess: () => {},
|
||||
handleError: () => {},
|
||||
isAppLevel: () => {},
|
||||
};
|
||||
const mockTopRepoService = {
|
||||
getTopRepos: () => of([])
|
||||
getTopRepos: () => of([]),
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [TopRepoComponent],
|
||||
providers: [
|
||||
{ provide: TopRepoService, useValue: mockTopRepoService },
|
||||
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
|
||||
|
||||
]
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: mockMessageHandlerService,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
|
|
@ -14,16 +14,15 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { TopRepoService } from './top-repository.service';
|
||||
import { Repository } from "../../../../../ng-swagger-gen/models/repository";
|
||||
import { ListMode } from "../../../shared/entities/shared.const";
|
||||
|
||||
import { Repository } from '../../../../../ng-swagger-gen/models/repository';
|
||||
import { ListMode } from '../../../shared/entities/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'top-repo',
|
||||
templateUrl: "top-repo.component.html",
|
||||
templateUrl: 'top-repo.component.html',
|
||||
styleUrls: ['top-repo.component.scss'],
|
||||
|
||||
providers: [TopRepoService]
|
||||
providers: [TopRepoService],
|
||||
})
|
||||
export class TopRepoComponent implements OnInit {
|
||||
topRepos: Repository[] = [];
|
||||
|
@ -31,7 +30,7 @@ export class TopRepoComponent implements OnInit {
|
|||
constructor(
|
||||
private topRepoService: TopRepoService,
|
||||
private messageHandlerService: MessageHandlerService
|
||||
) { }
|
||||
) {}
|
||||
|
||||
public get listMode(): string {
|
||||
return ListMode.READONLY;
|
||||
|
@ -44,10 +43,11 @@ export class TopRepoComponent implements OnInit {
|
|||
|
||||
// Get top popular repositories
|
||||
getTopRepos() {
|
||||
this.topRepoService.getTopRepos()
|
||||
.subscribe(repos => this.topRepos = repos
|
||||
, error => {
|
||||
this.topRepoService.getTopRepos().subscribe(
|
||||
repos => (this.topRepos = repos),
|
||||
error => {
|
||||
this.messageHandlerService.handleError(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,17 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
|
|||
import { TopRepoService } from './top-repository.service';
|
||||
|
||||
describe('TopRepoService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [TopRepoService]
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [TopRepoService],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([TopRepoService], (service: TopRepoService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
it('should be created', inject(
|
||||
[TopRepoService],
|
||||
(service: TopRepoService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}
|
||||
));
|
||||
});
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
// limitations under the License.
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { catchError, map } from "rxjs/operators";
|
||||
import { Observable, throwError as observableThrowError } from "rxjs";
|
||||
import { Repository } from "../../../../../ng-swagger-gen/models/repository";
|
||||
import { CURRENT_BASE_HREF, HTTP_GET_OPTIONS } from "../../../shared/units/utils";
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { Observable, throwError as observableThrowError } from 'rxjs';
|
||||
import { Repository } from '../../../../../ng-swagger-gen/models/repository';
|
||||
import {
|
||||
CURRENT_BASE_HREF,
|
||||
HTTP_GET_OPTIONS,
|
||||
} from '../../../shared/units/utils';
|
||||
|
||||
export const topRepoEndpoint = CURRENT_BASE_HREF + "/repositories/top";
|
||||
export const topRepoEndpoint = CURRENT_BASE_HREF + '/repositories/top';
|
||||
/**
|
||||
* Declare service to handle the top repositories
|
||||
*
|
||||
|
@ -28,8 +31,7 @@ export const topRepoEndpoint = CURRENT_BASE_HREF + "/repositories/top";
|
|||
*/
|
||||
@Injectable()
|
||||
export class TopRepoService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
/**
|
||||
* Get top popular repositories
|
||||
|
@ -40,8 +42,9 @@ export class TopRepoService {
|
|||
* @memberOf GlobalSearchService
|
||||
*/
|
||||
getTopRepos(): Observable<Repository[]> {
|
||||
return this.http.get(topRepoEndpoint, HTTP_GET_OPTIONS)
|
||||
.pipe(map(response => response as Repository[])
|
||||
, catchError(error => observableThrowError(error)));
|
||||
return this.http.get(topRepoEndpoint, HTTP_GET_OPTIONS).pipe(
|
||||
map(response => response as Repository[]),
|
||||
catchError(error => observableThrowError(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,22 @@
|
|||
<h3 class="modal-title">{{'SIGN_UP.TITLE' | translate}}</h3>
|
||||
<new-user-form isSelfRegistration="true" (valueChange)="formValueChange($event)"></new-user-form>
|
||||
<h3 class="modal-title">{{ 'SIGN_UP.TITLE' | translate }}</h3>
|
||||
<new-user-form
|
||||
isSelfRegistration="true"
|
||||
(valueChange)="formValueChange($event)"></new-user-form>
|
||||
<div>
|
||||
<span class="spinner spinner-inline loading-top" [hidden]="!inProgress"> </span>
|
||||
<button type="button" class="btn btn-outline" [disabled]="!canBeCancelled" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isValid || inProgress" (click)="create()">{{ 'BUTTON.SIGN_UP' | translate }}</button>
|
||||
</div>
|
||||
<span class="spinner spinner-inline loading-top" [hidden]="!inProgress">
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
[disabled]="!canBeCancelled"
|
||||
(click)="cancel()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!isValid || inProgress"
|
||||
(click)="create()">
|
||||
{{ 'BUTTON.SIGN_UP' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('SignUpPageComponent', () => {
|
|||
imports: [
|
||||
FormsModule,
|
||||
RouterTestingModule,
|
||||
TranslateModule.forRoot()
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
MessageService,
|
||||
|
@ -29,7 +29,7 @@ describe('SignUpPageComponent', () => {
|
|||
{ provide: UserService, useValue: fakeUserService },
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@ -37,7 +37,7 @@ describe('SignUpPageComponent', () => {
|
|||
fixture = TestBed.createComponent(SignUpPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.newUserForm =
|
||||
TestBed.createComponent(NewUserFormComponent).componentInstance;
|
||||
TestBed.createComponent(NewUserFormComponent).componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
|
|
@ -18,12 +18,12 @@ import { NewUserFormComponent } from '../../shared/components/new-user-form/new-
|
|||
import { User } from '../../base/left-side-nav/user/user';
|
||||
import { UserService } from '../../base/left-side-nav/user/user.service';
|
||||
import { MessageService } from '../../shared/components/global-message/message.service';
|
||||
import { AlertType } from "../../shared/entities/shared.const";
|
||||
import { AlertType } from '../../shared/entities/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'sign-up-page',
|
||||
templateUrl: "sign-up-page.component.html",
|
||||
styleUrls: ['../../common.scss']
|
||||
templateUrl: 'sign-up-page.component.html',
|
||||
styleUrls: ['../../common.scss'],
|
||||
})
|
||||
export class SignUpPageComponent implements OnInit {
|
||||
error: any;
|
||||
|
@ -33,7 +33,8 @@ export class SignUpPageComponent implements OnInit {
|
|||
constructor(
|
||||
private userService: UserService,
|
||||
private msgService: MessageService,
|
||||
private router: Router) { }
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
@ViewChild(NewUserFormComponent)
|
||||
newUserForm: NewUserFormComponent;
|
||||
|
@ -51,7 +52,11 @@ export class SignUpPageComponent implements OnInit {
|
|||
}
|
||||
|
||||
public get canBeCancelled(): boolean {
|
||||
return this.formValueChanged && this.newUserForm && !this.newUserForm.isEmpty();
|
||||
return (
|
||||
this.formValueChanged &&
|
||||
this.newUserForm &&
|
||||
!this.newUserForm.isEmpty()
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -91,16 +96,22 @@ export class SignUpPageComponent implements OnInit {
|
|||
// Start process
|
||||
this.onGoing = true;
|
||||
|
||||
this.userService.addUser(u)
|
||||
.subscribe(() => {
|
||||
this.userService.addUser(u).subscribe(
|
||||
() => {
|
||||
this.onGoing = false;
|
||||
this.msgService.announceMessage(200, "", AlertType.SUCCESS);
|
||||
this.msgService.announceMessage(200, '', AlertType.SUCCESS);
|
||||
// Navigate to embeded sign-in
|
||||
this.router.navigate(['harbor', 'sign-in']);
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.onGoing = false;
|
||||
this.error = error;
|
||||
this.msgService.announceMessage(error.status || 500, "", AlertType.WARNING);
|
||||
});
|
||||
this.msgService.announceMessage(
|
||||
error.status || 500,
|
||||
'',
|
||||
AlertType.WARNING
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,35 @@
|
|||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="false">
|
||||
<h3 class="modal-title">{{'SIGN_UP.TITLE' | translate}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<clr-modal
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalStaticBackdrop]="staticBackdrop"
|
||||
[clrModalClosable]="false">
|
||||
<h3 class="modal-title">{{ 'SIGN_UP.TITLE' | translate }}</h3>
|
||||
<inline-alert
|
||||
class="modal-title"
|
||||
(confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<div class="modal-body body-format">
|
||||
<new-user-form isSelfRegistration="true" (valueChange)="formValueChange($event)"></new-user-form>
|
||||
<new-user-form
|
||||
isSelfRegistration="true"
|
||||
(valueChange)="formValueChange($event)"></new-user-form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline loading-top" [hidden]="inProgress === false"> </span>
|
||||
<button type="button" class="btn btn-outline" id="close-btn" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button id="sign-up" type="button" class="btn btn-primary" [disabled]="!isValid || inProgress" (click)="create()">{{ 'BUTTON.SIGN_UP' | translate }}</button>
|
||||
<span
|
||||
class="spinner spinner-inline loading-top"
|
||||
[hidden]="inProgress === false">
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
id="close-btn"
|
||||
(click)="close()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
id="sign-up"
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!isValid || inProgress"
|
||||
(click)="create()">
|
||||
{{ 'BUTTON.SIGN_UP' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</clr-modal>
|
||||
|
|
|
@ -5,17 +5,17 @@ import { UserService } from '../../base/left-side-nav/user/user.service';
|
|||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { NewUserFormComponent } from '../../shared/components/new-user-form/new-user-form.component';
|
||||
import { of } from 'rxjs';
|
||||
import { InlineAlertComponent } from "../../shared/components/inline-alert/inline-alert.component";
|
||||
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||
import { SharedTestingModule } from '../../shared/shared.module';
|
||||
|
||||
describe('SignUpComponent', () => {
|
||||
let component: SignUpComponent;
|
||||
let fixture: ComponentFixture<SignUpComponent>;
|
||||
let fakeSessionService = {
|
||||
checkUserExisting: () => of(true)
|
||||
checkUserExisting: () => of(true),
|
||||
};
|
||||
let fakeUserService = {
|
||||
addUser: () => of(null)
|
||||
addUser: () => of(null),
|
||||
};
|
||||
const mockUser = {
|
||||
user_id: 1,
|
||||
|
@ -34,15 +34,17 @@ describe('SignUpComponent', () => {
|
|||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SignUpComponent, NewUserFormComponent, InlineAlertComponent],
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
declarations: [
|
||||
SignUpComponent,
|
||||
NewUserFormComponent,
|
||||
InlineAlertComponent,
|
||||
],
|
||||
imports: [SharedTestingModule],
|
||||
providers: [
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: UserService, useValue: fakeUserService },
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@ -64,13 +66,14 @@ describe('SignUpComponent', () => {
|
|||
it('should close when no form change', async () => {
|
||||
component.open();
|
||||
await fixture.whenStable();
|
||||
const closeBtn: HTMLButtonElement = fixture.nativeElement.querySelector("#close-btn");
|
||||
const closeBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#close-btn');
|
||||
expect(closeBtn).toBeTruthy();
|
||||
closeBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
const closeBtn1: HTMLButtonElement = fixture.nativeElement.querySelector("#close-btn");
|
||||
const closeBtn1: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#close-btn');
|
||||
expect(closeBtn1).toBeNull();
|
||||
|
||||
});
|
||||
it('should create new user', async () => {
|
||||
component.open();
|
||||
|
@ -80,8 +83,8 @@ describe('SignUpComponent', () => {
|
|||
createBtn.dispatchEvent(new Event('click'));
|
||||
|
||||
await fixture.whenStable();
|
||||
const closeBtn1: HTMLButtonElement = fixture.nativeElement.querySelector("#close-btn");
|
||||
const closeBtn1: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#close-btn');
|
||||
expect(closeBtn1).toBeNull();
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,12 +17,12 @@ import { NewUserFormComponent } from '../../shared/components/new-user-form/new-
|
|||
import { User } from '../../base/left-side-nav/user/user';
|
||||
import { SessionService } from '../../shared/services/session.service';
|
||||
import { UserService } from '../../base/left-side-nav/user/user.service';
|
||||
import { InlineAlertComponent } from "../../shared/components/inline-alert/inline-alert.component";
|
||||
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||
|
||||
@Component({
|
||||
selector: 'sign-up',
|
||||
templateUrl: "sign-up.component.html",
|
||||
styleUrls: ['../../common.scss']
|
||||
templateUrl: 'sign-up.component.html',
|
||||
styleUrls: ['../../common.scss'],
|
||||
})
|
||||
export class SignUpComponent {
|
||||
opened: boolean = false;
|
||||
|
@ -35,9 +35,10 @@ export class SignUpComponent {
|
|||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private userService: UserService) { }
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
@ViewChild(NewUserFormComponent, {static: true})
|
||||
@ViewChild(NewUserFormComponent, { static: true })
|
||||
newUserForm: NewUserFormComponent;
|
||||
|
||||
@ViewChild(InlineAlertComponent)
|
||||
|
@ -86,7 +87,7 @@ export class SignUpComponent {
|
|||
} else {
|
||||
// Need user confirmation
|
||||
this.inlineAlert.showInlineConfirmation({
|
||||
message: "ALERT.FORM_CHANGE_CONFIRMATION"
|
||||
message: 'ALERT.FORM_CHANGE_CONFIRMATION',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -116,16 +117,18 @@ export class SignUpComponent {
|
|||
// Start process
|
||||
this.onGoing = true;
|
||||
|
||||
this.userService.addUser(u)
|
||||
.subscribe(() => {
|
||||
this.userService.addUser(u).subscribe(
|
||||
() => {
|
||||
this.onGoing = false;
|
||||
this.opened = false;
|
||||
this.modal.close();
|
||||
this.userCreation.emit(u);
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.onGoing = false;
|
||||
this.error = error;
|
||||
this.inlineAlert.showInlineError(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<router-outlet></router-outlet>
|
||||
<router-outlet></router-outlet>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/* tslint:disable:no-unused-variable */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
@ -20,9 +20,9 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|||
import { SessionService } from './shared/services/session.service';
|
||||
import { AppConfigService } from './services/app-config.service';
|
||||
import { AppComponent } from './app.component';
|
||||
import { APP_BASE_HREF } from "@angular/common";
|
||||
import { SharedTestingModule } from "./shared/shared.module";
|
||||
import { SkinableConfig } from "./services/skinable-config.service";
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { SharedTestingModule } from './shared/shared.module';
|
||||
import { SkinableConfig } from './services/skinable-config.service';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
@ -31,45 +31,39 @@ describe('AppComponent', () => {
|
|||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
}
|
||||
},
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
isIntegrationMode: function () {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
let fakeTitle = {
|
||||
setTitle: function () {
|
||||
}
|
||||
setTitle: function () {},
|
||||
};
|
||||
const fakeSkinableConfig = {
|
||||
getSkinConfig() {
|
||||
return {
|
||||
"headerBgColor": {
|
||||
"darkMode": "",
|
||||
"lightMode": ""
|
||||
headerBgColor: {
|
||||
darkMode: '',
|
||||
lightMode: '',
|
||||
},
|
||||
loginBgImg: '',
|
||||
loginTitle: '',
|
||||
product: {
|
||||
name: 'test',
|
||||
logo: '',
|
||||
introduction: '',
|
||||
},
|
||||
"loginBgImg": "",
|
||||
"loginTitle": "",
|
||||
"product": {
|
||||
"name": "test",
|
||||
"logo": "",
|
||||
"introduction": ""
|
||||
}
|
||||
};
|
||||
},
|
||||
setTitleIcon() {
|
||||
}
|
||||
setTitleIcon() {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
imports: [SharedTestingModule],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: CookieService, useValue: fakeCookieService },
|
||||
|
@ -78,14 +72,12 @@ describe('AppComponent', () => {
|
|||
{ provide: Title, useValue: fakeTitle },
|
||||
{ provide: SkinableConfig, useValue: fakeSkinableConfig },
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
compiled = fixture.nativeElement;
|
||||
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -95,6 +87,4 @@ describe('AppComponent', () => {
|
|||
it('should create the app', () => {
|
||||
expect(compiled).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
|
@ -16,17 +16,25 @@ import { Title } from '@angular/platform-browser';
|
|||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AppConfigService } from './services/app-config.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { CustomStyle, HAS_STYLE_MODE, THEME_ARRAY, ThemeInterface } from './services/theme';
|
||||
import {
|
||||
CustomStyle,
|
||||
HAS_STYLE_MODE,
|
||||
THEME_ARRAY,
|
||||
ThemeInterface,
|
||||
} from './services/theme';
|
||||
import { clone } from './shared/units/utils';
|
||||
import { DEFAULT_LANG_LOCALSTORAGE_KEY, DeFaultLang, supportedLangs, SupportedLanguage } from "./shared/entities/shared.const";
|
||||
import { SkinableConfig } from "./services/skinable-config.service";
|
||||
import {
|
||||
DEFAULT_LANG_LOCALSTORAGE_KEY,
|
||||
DeFaultLang,
|
||||
supportedLangs,
|
||||
SupportedLanguage,
|
||||
} from './shared/entities/shared.const';
|
||||
import { SkinableConfig } from './services/skinable-config.service';
|
||||
import { isSupportedLanguage } from './shared/units/shared.utils';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'harbor-app',
|
||||
templateUrl: 'app.component.html'
|
||||
templateUrl: 'app.component.html',
|
||||
})
|
||||
export class AppComponent {
|
||||
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
|
||||
|
@ -37,19 +45,23 @@ export class AppComponent {
|
|||
private titleService: Title,
|
||||
public theme: ThemeService,
|
||||
private skinableConfig: SkinableConfig
|
||||
|
||||
) {
|
||||
// init language
|
||||
) {
|
||||
// init language
|
||||
this.initLanguage();
|
||||
// Override page title
|
||||
let key: string = "APP_TITLE.HARBOR";
|
||||
let key: string = 'APP_TITLE.HARBOR';
|
||||
if (this.appConfigService.isIntegrationMode()) {
|
||||
key = "APP_TITLE.REG";
|
||||
key = 'APP_TITLE.REG';
|
||||
}
|
||||
|
||||
translate.get(key).subscribe((res: string) => {
|
||||
const customSkinData: CustomStyle = this.skinableConfig.getSkinConfig();
|
||||
if (customSkinData && customSkinData.product && customSkinData.product.name) {
|
||||
const customSkinData: CustomStyle =
|
||||
this.skinableConfig.getSkinConfig();
|
||||
if (
|
||||
customSkinData &&
|
||||
customSkinData.product &&
|
||||
customSkinData.product.name
|
||||
) {
|
||||
this.titleService.setTitle(customSkinData.product.name);
|
||||
this.skinableConfig.setTitleIcon();
|
||||
} else {
|
||||
|
@ -58,15 +70,16 @@ export class AppComponent {
|
|||
});
|
||||
this.setTheme();
|
||||
}
|
||||
setTheme () {
|
||||
setTheme() {
|
||||
let styleMode = this.themeArray[0].showStyle;
|
||||
const localHasStyle = localStorage && localStorage.getItem(HAS_STYLE_MODE);
|
||||
const localHasStyle =
|
||||
localStorage && localStorage.getItem(HAS_STYLE_MODE);
|
||||
if (localHasStyle) {
|
||||
styleMode = localStorage.getItem(HAS_STYLE_MODE);
|
||||
} else {
|
||||
localStorage.setItem(HAS_STYLE_MODE, styleMode);
|
||||
}
|
||||
this.themeArray.forEach((themeItem) => {
|
||||
this.themeArray.forEach(themeItem => {
|
||||
if (themeItem.showStyle === styleMode) {
|
||||
this.theme.loadStyle(themeItem.currentFileName);
|
||||
}
|
||||
|
@ -77,16 +90,25 @@ export class AppComponent {
|
|||
this.translate.setDefaultLang(DeFaultLang);
|
||||
let selectedLang: SupportedLanguage;
|
||||
const savedLang = localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY);
|
||||
if (isSupportedLanguage(savedLang)) {// If user has selected lang, then directly use it
|
||||
if (isSupportedLanguage(savedLang)) {
|
||||
// If user has selected lang, then directly use it
|
||||
selectedLang = savedLang;
|
||||
} else if (savedLang !== null) { // If there is a saved value, but it is not a supported language, warn and use the default language.
|
||||
console.warn(`Invalid saved language setting ${JSON.stringify(savedLang)}; defaulting to ${JSON.stringify(DeFaultLang)}.`);
|
||||
} else if (savedLang !== null) {
|
||||
// If there is a saved value, but it is not a supported language, warn and use the default language.
|
||||
console.warn(
|
||||
`Invalid saved language setting ${JSON.stringify(
|
||||
savedLang
|
||||
)}; defaulting to ${JSON.stringify(DeFaultLang)}.`
|
||||
);
|
||||
selectedLang = DeFaultLang;
|
||||
} else {// If user has not selected lang, then use browser language(if contained in supportedLangs)
|
||||
} else {
|
||||
// If user has not selected lang, then use browser language(if contained in supportedLangs)
|
||||
const browserCultureLang: string = this.translate
|
||||
.getBrowserCultureLang()
|
||||
.toLowerCase();
|
||||
selectedLang = isSupportedLanguage(browserCultureLang) ? browserCultureLang : DeFaultLang;
|
||||
selectedLang = isSupportedLanguage(browserCultureLang)
|
||||
? browserCultureLang
|
||||
: DeFaultLang;
|
||||
}
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, selectedLang);
|
||||
// use method will load related language json from backend server
|
||||
|
|
|
@ -12,32 +12,39 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule, APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import {
|
||||
NgModule,
|
||||
APP_INITIALIZER,
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
} from '@angular/core';
|
||||
import { AppComponent } from './app.component';
|
||||
import { InterceptHttpService } from './services/intercept-http.service';
|
||||
import { HarborRoutingModule } from './harbor-routing.module';
|
||||
import { AppConfigService } from './services/app-config.service';
|
||||
import { SkinableConfig } from "./services/skinable-config.service";
|
||||
import { SkinableConfig } from './services/skinable-config.service';
|
||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { CookieModule } from "ngx-cookie";
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { CookieModule } from 'ngx-cookie';
|
||||
import {
|
||||
MissingTranslationHandler,
|
||||
MissingTranslationHandlerParams,
|
||||
TranslateLoader,
|
||||
TranslateModule
|
||||
} from "@ngx-translate/core";
|
||||
TranslateModule,
|
||||
} from '@ngx-translate/core';
|
||||
import {
|
||||
ProjectDefaultService,
|
||||
ProjectService,
|
||||
UserPermissionDefaultService,
|
||||
UserPermissionService
|
||||
} from "./shared/services";
|
||||
import { ErrorHandler } from "./shared/units/error-handler";
|
||||
import { MessageHandlerService } from "./shared/services/message-handler.service";
|
||||
import { HarborTranslateLoaderService } from "./services/harbor-translate-loader.service";
|
||||
UserPermissionService,
|
||||
} from './shared/services';
|
||||
import { ErrorHandler } from './shared/units/error-handler';
|
||||
import { MessageHandlerService } from './shared/services/message-handler.service';
|
||||
import { HarborTranslateLoaderService } from './services/harbor-translate-loader.service';
|
||||
|
||||
function initConfig(configService: AppConfigService, skinableService: SkinableConfig) {
|
||||
function initConfig(
|
||||
configService: AppConfigService,
|
||||
skinableService: SkinableConfig
|
||||
) {
|
||||
return () => {
|
||||
skinableService.getCustomFile().subscribe();
|
||||
configService.load().subscribe();
|
||||
|
@ -46,25 +53,23 @@ function initConfig(configService: AppConfigService, skinableService: SkinableCo
|
|||
|
||||
class MyMissingTranslationHandler implements MissingTranslationHandler {
|
||||
handle(params: MissingTranslationHandlerParams) {
|
||||
const missingText: string = "{Harbor}";
|
||||
const missingText: string = '{Harbor}';
|
||||
return params.key || missingText;
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
imports: [
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: HarborTranslateLoaderService
|
||||
useClass: HarborTranslateLoaderService,
|
||||
},
|
||||
missingTranslationHandler: {
|
||||
provide: MissingTranslationHandler,
|
||||
useClass: MyMissingTranslationHandler
|
||||
}
|
||||
useClass: MyMissingTranslationHandler,
|
||||
},
|
||||
}),
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
|
@ -79,16 +84,21 @@ class MyMissingTranslationHandler implements MissingTranslationHandler {
|
|||
provide: APP_INITIALIZER,
|
||||
useFactory: initConfig,
|
||||
deps: [AppConfigService, SkinableConfig],
|
||||
multi: true
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: InterceptHttpService,
|
||||
multi: true,
|
||||
},
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: InterceptHttpService, multi: true },
|
||||
{ provide: ProjectService, useClass: ProjectDefaultService },
|
||||
{ provide: ErrorHandler, useClass: MessageHandlerService },
|
||||
{ provide: UserPermissionService, useClass: UserPermissionDefaultService },
|
||||
{
|
||||
provide: UserPermissionService,
|
||||
useClass: UserPermissionDefaultService,
|
||||
},
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule { }
|
||||
export class AppModule {}
|
||||
|
|
|
@ -3,17 +3,17 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
|
|||
import { AccountSettingsModalService } from './account-settings-modal-service.service';
|
||||
|
||||
describe('AccountSettingsModalServiceService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientTestingModule
|
||||
],
|
||||
providers: [
|
||||
AccountSettingsModalService
|
||||
]
|
||||
}));
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [AccountSettingsModalService],
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AccountSettingsModalService = TestBed.get(AccountSettingsModalService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
it('should be created', () => {
|
||||
const service: AccountSettingsModalService = TestBed.get(
|
||||
AccountSettingsModalService
|
||||
);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,19 +2,19 @@ import { Injectable } from '@angular/core';
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { throwError as observableThrowError, Observable } from 'rxjs';
|
||||
import { CURRENT_BASE_HREF } from "../../shared/units/utils";
|
||||
|
||||
|
||||
|
||||
import { CURRENT_BASE_HREF } from '../../shared/units/utils';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AccountSettingsModalService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
saveNewCli(userId, secretObj): Observable<any> {
|
||||
return this.http.put(`${ CURRENT_BASE_HREF }/users/${userId}/cli_secret`, secretObj).pipe( map(response => response)
|
||||
, catchError(error => observableThrowError(error)));
|
||||
}
|
||||
constructor(private http: HttpClient) {}
|
||||
saveNewCli(userId, secretObj): Observable<any> {
|
||||
return this.http
|
||||
.put(`${CURRENT_BASE_HREF}/users/${userId}/cli_secret`, secretObj)
|
||||
.pipe(
|
||||
map(response => response),
|
||||
catchError(error => observableThrowError(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,142 +1,349 @@
|
|||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="false">
|
||||
<h3 class="modal-title">{{'PROFILE.TITLE' | translate}}</h3>
|
||||
<clr-modal
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalStaticBackdrop]="staticBackdrop"
|
||||
[clrModalClosable]="false">
|
||||
<h3 class="modal-title">{{ 'PROFILE.TITLE' | translate }}</h3>
|
||||
<div class="modal-body body-format">
|
||||
<inline-alert #accountSettingInlineAlert (confirmEvt)="confirmYes($event)" (closeEvt)="confirmNo($event)"></inline-alert>
|
||||
<form #accountSettingsFrom="ngForm" class="clr-form clr-form-horizontal">
|
||||
<inline-alert
|
||||
#accountSettingInlineAlert
|
||||
(confirmEvt)="confirmYes($event)"
|
||||
(closeEvt)="confirmNo($event)"></inline-alert>
|
||||
<form
|
||||
#accountSettingsFrom="ngForm"
|
||||
class="clr-form clr-form-horizontal">
|
||||
<div class="clr-form-control">
|
||||
<label for="account_settings_username" aria-haspopup="true"
|
||||
class="clr-control-label">{{'PROFILE.USER_NAME' | translate}}</label>
|
||||
<label
|
||||
for="account_settings_username"
|
||||
aria-haspopup="true"
|
||||
class="clr-control-label"
|
||||
>{{ 'PROFILE.USER_NAME' | translate }}</label
|
||||
>
|
||||
<div class="clr-control-container display-flex">
|
||||
<input class="clr-input" type="text" name="account_settings_username" [(ngModel)]="account.username"
|
||||
disabled id="account_settings_username" size="30">
|
||||
<input
|
||||
class="clr-input"
|
||||
type="text"
|
||||
name="account_settings_username"
|
||||
[(ngModel)]="account.username"
|
||||
disabled
|
||||
id="account_settings_username"
|
||||
size="30" />
|
||||
<div *ngIf="canRename" class="rename-tool">
|
||||
<button id="rename-btn" [disabled]="RenameOnGoing" (click)="onRename()" class="btn btn-outline ">
|
||||
{{'PROFILE.ADMIN_RENAME_BUTTON' | translate}}
|
||||
<button
|
||||
id="rename-btn"
|
||||
[disabled]="RenameOnGoing"
|
||||
(click)="onRename()"
|
||||
class="btn btn-outline">
|
||||
{{ 'PROFILE.ADMIN_RENAME_BUTTON' | translate }}
|
||||
</button>
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="bottom-left" clrSize="md" *clrIfOpen>
|
||||
<span> {{'PROFILE.ADMIN_RENAME_TIP' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-left"
|
||||
clrSize="md"
|
||||
*clrIfOpen>
|
||||
<span>
|
||||
{{
|
||||
'PROFILE.ADMIN_RENAME_TIP' | translate
|
||||
}}</span
|
||||
>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control">
|
||||
<label for="account_settings_email"
|
||||
class="required clr-control-label">{{'PROFILE.EMAIL' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!getValidationState('account_settings_email')">
|
||||
<label
|
||||
for="account_settings_email"
|
||||
class="required clr-control-label"
|
||||
>{{ 'PROFILE.EMAIL' | translate }}</label
|
||||
>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="
|
||||
!getValidationState('account_settings_email')
|
||||
">
|
||||
<div class="clr-input-wrapper">
|
||||
<input name="account_settings_email" type="text" #eamilInput="ngModel" class="clr-input"
|
||||
[(ngModel)]="account.email" required email id="account_settings_email" size="30"
|
||||
(input)='handleValidation("account_settings_email", false)'
|
||||
(blur)='handleValidation("account_settings_email", true)'>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkProgress"></span>
|
||||
<input
|
||||
name="account_settings_email"
|
||||
type="text"
|
||||
#eamilInput="ngModel"
|
||||
class="clr-input"
|
||||
[(ngModel)]="account.email"
|
||||
required
|
||||
email
|
||||
id="account_settings_email"
|
||||
size="30"
|
||||
(input)="
|
||||
handleValidation(
|
||||
'account_settings_email',
|
||||
false
|
||||
)
|
||||
"
|
||||
(blur)="
|
||||
handleValidation('account_settings_email', true)
|
||||
" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
<span
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="!checkProgress"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!getValidationState('account_settings_email')">
|
||||
{{emailTooltip | translate}}
|
||||
<clr-control-error
|
||||
*ngIf="!getValidationState('account_settings_email')">
|
||||
{{ emailTooltip | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<clr-input-container>
|
||||
<label [class.required]="!account.oidc_user_meta">{{'PROFILE.FULL_NAME' | translate}}</label>
|
||||
<input clrInput type="text" name="account_settings_full_name" #fullNameInput="ngModel"
|
||||
[(ngModel)]="account.realname" [required]="!account.oidc_user_meta" maxLengthExt="30"
|
||||
id="account_settings_full_name" size="30"
|
||||
(input)='handleValidation("account_settings_full_name", false)'
|
||||
(blur)='handleValidation("account_settings_full_name", true)'>
|
||||
<clr-control-error *ngIf="!getValidationState('account_settings_full_name')">
|
||||
{{'TOOLTIP.FULL_NAME' | translate}}
|
||||
<label [class.required]="!account.oidc_user_meta">{{
|
||||
'PROFILE.FULL_NAME' | translate
|
||||
}}</label>
|
||||
<input
|
||||
clrInput
|
||||
type="text"
|
||||
name="account_settings_full_name"
|
||||
#fullNameInput="ngModel"
|
||||
[(ngModel)]="account.realname"
|
||||
[required]="!account.oidc_user_meta"
|
||||
maxLengthExt="30"
|
||||
id="account_settings_full_name"
|
||||
size="30"
|
||||
(input)="
|
||||
handleValidation('account_settings_full_name', false)
|
||||
"
|
||||
(blur)="
|
||||
handleValidation('account_settings_full_name', true)
|
||||
" />
|
||||
<clr-control-error
|
||||
*ngIf="!getValidationState('account_settings_full_name')">
|
||||
{{ 'TOOLTIP.FULL_NAME' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label>{{'PROFILE.COMMENT' | translate}}</label>
|
||||
<input clrInput type="text" #commentInput="ngModel" maxlength="30" size="30"
|
||||
name="account_settings_comments" [(ngModel)]="account.comment" id="account_settings_comments">
|
||||
<clr-control-error *ngIf="commentInput.invalid && (commentInput.dirty || commentInput.touched)">
|
||||
{{'TOOLTIP.COMMENT' | translate}}
|
||||
<label>{{ 'PROFILE.COMMENT' | translate }}</label>
|
||||
<input
|
||||
clrInput
|
||||
type="text"
|
||||
#commentInput="ngModel"
|
||||
maxlength="30"
|
||||
size="30"
|
||||
name="account_settings_comments"
|
||||
[(ngModel)]="account.comment"
|
||||
id="account_settings_comments" />
|
||||
<clr-control-error
|
||||
*ngIf="
|
||||
commentInput.invalid &&
|
||||
(commentInput.dirty || commentInput.touched)
|
||||
">
|
||||
{{ 'TOOLTIP.COMMENT' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
|
||||
<div class="clr-form-control cli-secret" *ngIf="account.oidc_user_meta">
|
||||
<label class="clr-control-label">{{'PROFILE.CLI_PASSWORD' | translate}}
|
||||
<div
|
||||
class="clr-form-control cli-secret"
|
||||
*ngIf="account.oidc_user_meta">
|
||||
<label class="clr-control-label"
|
||||
>{{ 'PROFILE.CLI_PASSWORD' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="20"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="md" *clrIfOpen>
|
||||
<span> {{'PROFILE.CLI_PASSWORD_TIP' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="20"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="md"
|
||||
*clrIfOpen>
|
||||
<span>
|
||||
{{
|
||||
'PROFILE.CLI_PASSWORD_TIP' | translate
|
||||
}}</span
|
||||
>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<input id="cli_password" class="clr-input input-cli" type="password" name="cli_password" disabled
|
||||
[ngModel]="'account.oidc_user_meta.secret'" size="33">
|
||||
<input
|
||||
id="cli_password"
|
||||
class="clr-input input-cli"
|
||||
type="password"
|
||||
name="cli_password"
|
||||
disabled
|
||||
[ngModel]="'account.oidc_user_meta.secret'"
|
||||
size="33" />
|
||||
|
||||
<button (click)="generateCli(account.user_id)" id="generate-cli-btn"
|
||||
class="btn btn-outline btn-padding-less" *ngIf="showGenerateCli">
|
||||
{{'PROFILE.ADMIN_CLI_SECRET_BUTTON' | translate}}
|
||||
</button>
|
||||
<button (click)="openSecretDetail()" id="reset-cli-btn" class="btn btn-outline btn-padding-less"
|
||||
<button
|
||||
(click)="generateCli(account.user_id)"
|
||||
id="generate-cli-btn"
|
||||
class="btn btn-outline btn-padding-less"
|
||||
*ngIf="showGenerateCli">
|
||||
{{'PROFILE.ADMIN_CLI_SECRET_RESET_BUTTON' | translate}}
|
||||
{{ 'PROFILE.ADMIN_CLI_SECRET_BUTTON' | translate }}
|
||||
</button>
|
||||
<button
|
||||
(click)="openSecretDetail()"
|
||||
id="reset-cli-btn"
|
||||
class="btn btn-outline btn-padding-less"
|
||||
*ngIf="showGenerateCli">
|
||||
{{ 'PROFILE.ADMIN_CLI_SECRET_RESET_BUTTON' | translate }}
|
||||
</button>
|
||||
<div class="rename-tool reset-cli">
|
||||
<hbr-copy-input #copyInput (onCopySuccess)="onSuccess($event)" (onCopyError)="onError($event)"
|
||||
iconMode="true" [defaultValue]="account.oidc_user_meta.secret"></hbr-copy-input>
|
||||
<hbr-copy-input
|
||||
#copyInput
|
||||
(onCopySuccess)="onSuccess($event)"
|
||||
(onCopyError)="onError($event)"
|
||||
iconMode="true"
|
||||
[defaultValue]="
|
||||
account.oidc_user_meta.secret
|
||||
"></hbr-copy-input>
|
||||
</div>
|
||||
<div
|
||||
(click)="showGenerateCliFn()"
|
||||
*ngIf="!showGenerateCli"
|
||||
id="hidden-generate-cli"
|
||||
class="hidden-generate-cli">
|
||||
···
|
||||
</div>
|
||||
<div (click)="showGenerateCliFn()" *ngIf="!showGenerateCli" id="hidden-generate-cli"
|
||||
class="hidden-generate-cli">···</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="spinner spinner-inline loading-top" [hidden]="showProgress === false"></span>
|
||||
<button type="button" id="cancel-btn" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<span
|
||||
class="spinner spinner-inline loading-top"
|
||||
[hidden]="showProgress === false"></span>
|
||||
<button
|
||||
type="button"
|
||||
id="cancel-btn"
|
||||
class="btn btn-outline"
|
||||
(click)="close()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
|
||||
<button type="button" id="submit-btn" class="btn btn-primary" [disabled]="!isValid || showProgress || !isUserDataChange()"
|
||||
(click)="submit()">{{'BUTTON.OK' | translate}}</button>
|
||||
<button
|
||||
type="button"
|
||||
id="submit-btn"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!isValid || showProgress || !isUserDataChange()"
|
||||
(click)="submit()">
|
||||
{{ 'BUTTON.OK' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<clr-modal [(clrModalOpen)]="showSecretDetail" [clrModalSize]="'sm'" [clrModalStaticBackdrop]="staticBackdrop"
|
||||
[clrModalClosable]="false">
|
||||
|
||||
<h3 class="modal-title">{{'PROFILE.ADMIN_CLI_SECRET_RESET_BUTTON' | translate}}</h3>
|
||||
<clr-modal
|
||||
[(clrModalOpen)]="showSecretDetail"
|
||||
[clrModalSize]="'sm'"
|
||||
[clrModalStaticBackdrop]="staticBackdrop"
|
||||
[clrModalClosable]="false">
|
||||
<h3 class="modal-title">
|
||||
{{ 'PROFILE.ADMIN_CLI_SECRET_RESET_BUTTON' | translate }}
|
||||
</h3>
|
||||
<div class="modal-body upload-secret">
|
||||
<inline-alert #resetSecretInlineAlert class="modal-title"></inline-alert>
|
||||
<form #resetSecretFrom="ngForm" class="clr-form reset-cli-form clr-form-horizontal">
|
||||
<inline-alert
|
||||
#resetSecretInlineAlert
|
||||
class="modal-title"></inline-alert>
|
||||
<form
|
||||
#resetSecretFrom="ngForm"
|
||||
class="clr-form reset-cli-form clr-form-horizontal">
|
||||
<div class="clr-form-control">
|
||||
<label for="input-secret" class="clr-control-label">{{"SYSTEM_ROBOT.CONFIRM_SECRET" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="(inputSecret.dirty || inputSecret.touched) && inputSecret.invalid">
|
||||
<label for="input-secret" class="clr-control-label">{{
|
||||
'SYSTEM_ROBOT.CONFIRM_SECRET' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="
|
||||
(inputSecret.dirty || inputSecret.touched) &&
|
||||
inputSecret.invalid
|
||||
">
|
||||
<div class="clr-input-wrapper">
|
||||
<input [type]="showInputSecret?'text':'password'" id="new-token" #inputSecret="ngModel" class="clr-input pwd-input" type="password" maxlength="30" size="30" required pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,128}$"
|
||||
name="input_secret" [(ngModel)]="resetForms.input_secret" id="input-secret">
|
||||
<clr-icon *ngIf="!showInputSecret" shape="eye" class="pw-eye" (click)="showInputSecret =!showInputSecret"></clr-icon>
|
||||
<clr-icon *ngIf="showInputSecret" shape="eye-hide" class="pw-eye" (click)="showInputSecret =!showInputSecret"></clr-icon>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<input
|
||||
[type]="showInputSecret ? 'text' : 'password'"
|
||||
id="new-token"
|
||||
#inputSecret="ngModel"
|
||||
class="clr-input pwd-input"
|
||||
type="password"
|
||||
maxlength="30"
|
||||
size="30"
|
||||
required
|
||||
pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,128}$"
|
||||
name="input_secret"
|
||||
[(ngModel)]="resetForms.input_secret"
|
||||
id="input-secret" />
|
||||
<clr-icon
|
||||
*ngIf="!showInputSecret"
|
||||
shape="eye"
|
||||
class="pw-eye"
|
||||
(click)="
|
||||
showInputSecret = !showInputSecret
|
||||
"></clr-icon>
|
||||
<clr-icon
|
||||
*ngIf="showInputSecret"
|
||||
shape="eye-hide"
|
||||
class="pw-eye"
|
||||
(click)="
|
||||
showInputSecret = !showInputSecret
|
||||
"></clr-icon>
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="(inputSecret.dirty || inputSecret.touched) && inputSecret.invalid">
|
||||
{{'TOOLTIP.NEW_SECRET' | translate}}
|
||||
<clr-control-error
|
||||
*ngIf="
|
||||
(inputSecret.dirty || inputSecret.touched) &&
|
||||
inputSecret.invalid
|
||||
">
|
||||
{{ 'TOOLTIP.NEW_SECRET' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clr-form-control">
|
||||
<label for="confirm-secret" class="clr-control-label">{{'PROFILE.CONFIRM_SECRET' | translate}}</label>
|
||||
<label for="confirm-secret" class="clr-control-label">{{
|
||||
'PROFILE.CONFIRM_SECRET' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<input [type]="showConfirmSecret?'text':'password'" class="clr-input pwd-input" type="password" maxlength="30" size="30"
|
||||
[(ngModel)]="resetForms.confirm_secret" name="confirm_secret" id="confirm-secret">
|
||||
<clr-icon *ngIf="!showConfirmSecret" shape="eye" class="pw-eye" (click)="showConfirmSecret =!showConfirmSecret"></clr-icon>
|
||||
<clr-icon *ngIf="showConfirmSecret" shape="eye-hide" class="pw-eye" (click)="showConfirmSecret =!showConfirmSecret"></clr-icon>
|
||||
<input
|
||||
[type]="showConfirmSecret ? 'text' : 'password'"
|
||||
class="clr-input pwd-input"
|
||||
type="password"
|
||||
maxlength="30"
|
||||
size="30"
|
||||
[(ngModel)]="resetForms.confirm_secret"
|
||||
name="confirm_secret"
|
||||
id="confirm-secret" />
|
||||
<clr-icon
|
||||
*ngIf="!showConfirmSecret"
|
||||
shape="eye"
|
||||
class="pw-eye"
|
||||
(click)="
|
||||
showConfirmSecret = !showConfirmSecret
|
||||
"></clr-icon>
|
||||
<clr-icon
|
||||
*ngIf="showConfirmSecret"
|
||||
shape="eye-hide"
|
||||
class="pw-eye"
|
||||
(click)="
|
||||
showConfirmSecret = !showConfirmSecret
|
||||
"></clr-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="closeReset()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="disableChangeCliSecret()" (click)="resetCliSecret(resetSecretFrom.value.input_secret)">{{'BUTTON.CONFIRM' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="closeReset()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
[disabled]="disableChangeCliSecret()"
|
||||
(click)="resetCliSecret(resetSecretFrom.value.input_secret)">
|
||||
{{ 'BUTTON.CONFIRM' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<confirmation-dialog #confirmationDialog (confirmAction)="confirmGenerate($event)"></confirmation-dialog>
|
||||
<confirmation-dialog
|
||||
#confirmationDialog
|
||||
(confirmAction)="confirmGenerate($event)"></confirmation-dialog>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AccountSettingsModalComponent } from './account-settings-modal.component';
|
||||
import { SessionService } from "../../shared/services/session.service";
|
||||
import { MessageHandlerService } from "../../shared/services/message-handler.service";
|
||||
import { SearchTriggerService } from "../../shared/components/global-search/search-trigger.service";
|
||||
import { SessionService } from '../../shared/services/session.service';
|
||||
import { MessageHandlerService } from '../../shared/services/message-handler.service';
|
||||
import { SearchTriggerService } from '../../shared/components/global-search/search-trigger.service';
|
||||
import { AccountSettingsModalService } from './account-settings-modal-service.service';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef } from '@angular/core';
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
@ -14,10 +14,10 @@ import { of } from 'rxjs';
|
|||
import { Router } from '@angular/router';
|
||||
import { clone } from '../../shared/units/utils';
|
||||
import { ErrorHandler } from '../../shared/units/error-handler';
|
||||
import { ConfirmationDialogComponent } from "../../shared/components/confirmation-dialog";
|
||||
import { InlineAlertComponent } from "../../shared/components/inline-alert/inline-alert.component";
|
||||
import { ConfirmationDialogService } from "../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationMessage } from "../global-confirmation-dialog/confirmation-message";
|
||||
import { ConfirmationDialogComponent } from '../../shared/components/confirmation-dialog';
|
||||
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||
import { ConfirmationDialogService } from '../global-confirmation-dialog/confirmation-dialog.service';
|
||||
import { ConfirmationMessage } from '../global-confirmation-dialog/confirmation-message';
|
||||
|
||||
describe('AccountSettingsModalComponent', () => {
|
||||
let component: AccountSettingsModalComponent;
|
||||
|
@ -27,8 +27,8 @@ describe('AccountSettingsModalComponent', () => {
|
|||
let oidcUserMeta1 = {
|
||||
id: 1,
|
||||
user_id: 1,
|
||||
secret: "Asdf12345",
|
||||
subiss: "string",
|
||||
secret: 'Asdf12345',
|
||||
subiss: 'string',
|
||||
};
|
||||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
|
@ -36,12 +36,12 @@ describe('AccountSettingsModalComponent', () => {
|
|||
has_admin_role: true,
|
||||
user_id: 1,
|
||||
username: 'admin',
|
||||
email: "",
|
||||
realname: "admin",
|
||||
role_name: "admin",
|
||||
email: '',
|
||||
realname: 'admin',
|
||||
role_name: 'admin',
|
||||
role_id: 1,
|
||||
comment: "string",
|
||||
oidc_user_meta: oidcUserMeta
|
||||
comment: 'string',
|
||||
oidc_user_meta: oidcUserMeta,
|
||||
};
|
||||
},
|
||||
checkUserExisting: () => of(userExisting),
|
||||
|
@ -49,47 +49,63 @@ describe('AccountSettingsModalComponent', () => {
|
|||
renameAdmin: () => of(null),
|
||||
};
|
||||
let fakeMessageHandlerService = {
|
||||
showSuccess: () => { }
|
||||
showSuccess: () => {},
|
||||
};
|
||||
let fakeSearchTriggerService = {
|
||||
closeSearch: () => { }
|
||||
closeSearch: () => {},
|
||||
};
|
||||
let fakeAccountSettingsModalService = {
|
||||
saveNewCli: () => of(null)
|
||||
saveNewCli: () => of(null),
|
||||
};
|
||||
let fakeConfirmationDialogService = {
|
||||
cancel: () => of(null),
|
||||
confirm: () => of(null),
|
||||
confirmationAnnouced$: of(new ConfirmationMessage("null", "null", "null", "null", null, null))
|
||||
confirmationAnnouced$: of(
|
||||
new ConfirmationMessage('null', 'null', 'null', 'null', null, null)
|
||||
),
|
||||
};
|
||||
let fakeRouter = {
|
||||
navigate: () => { }
|
||||
navigate: () => {},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AccountSettingsModalComponent, InlineAlertComponent, ConfirmationDialogComponent],
|
||||
declarations: [
|
||||
AccountSettingsModalComponent,
|
||||
InlineAlertComponent,
|
||||
ConfirmationDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
ClarityModule,
|
||||
TranslateModule.forRoot(),
|
||||
FormsModule,
|
||||
BrowserAnimationsModule
|
||||
BrowserAnimationsModule,
|
||||
],
|
||||
providers: [
|
||||
ChangeDetectorRef,
|
||||
TranslateService,
|
||||
ErrorHandler,
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{ provide: SearchTriggerService, useValue: fakeSearchTriggerService },
|
||||
{ provide: AccountSettingsModalService, useValue: fakeAccountSettingsModalService },
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: fakeMessageHandlerService,
|
||||
},
|
||||
{
|
||||
provide: SearchTriggerService,
|
||||
useValue: fakeSearchTriggerService,
|
||||
},
|
||||
{
|
||||
provide: AccountSettingsModalService,
|
||||
useValue: fakeAccountSettingsModalService,
|
||||
},
|
||||
{ provide: Router, useValue: fakeRouter },
|
||||
{ provide: ConfirmationDialogService, useValue: fakeConfirmationDialogService }
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
{
|
||||
provide: ConfirmationDialogService,
|
||||
useValue: fakeConfirmationDialogService,
|
||||
},
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@ -112,7 +128,9 @@ describe('AccountSettingsModalComponent', () => {
|
|||
await fixture.whenStable();
|
||||
// Update the title input
|
||||
userExisting = true;
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||
'#account_settings_email'
|
||||
);
|
||||
emailInput.value = 'email@qq.com';
|
||||
emailInput.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
@ -130,90 +148,118 @@ describe('AccountSettingsModalComponent', () => {
|
|||
|
||||
it('should update settings', async () => {
|
||||
await fixture.whenStable();
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||
'#account_settings_email'
|
||||
);
|
||||
emailInput.value = 'email@qq.com';
|
||||
emailInput.dispatchEvent(new Event('input'));
|
||||
let fullNameInput: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_full_name');
|
||||
let fullNameInput: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#account_settings_full_name');
|
||||
fullNameInput.value = 'system guest';
|
||||
fullNameInput.dispatchEvent(new Event('input'));
|
||||
let submitBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#submit-btn');
|
||||
let submitBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#submit-btn');
|
||||
submitBtn.dispatchEvent(new Event('click'));
|
||||
const emailInput1: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
const emailInput1: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#account_settings_email');
|
||||
expect(emailInput1).toEqual(null);
|
||||
});
|
||||
it('admin should rename', async () => {
|
||||
await fixture.whenStable();
|
||||
let renameBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#rename-btn');
|
||||
let renameBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#rename-btn');
|
||||
renameBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
const userNameInput: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_username');
|
||||
const userNameInput: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#account_settings_username');
|
||||
expect(userNameInput.value).toEqual('admin@harbor.local');
|
||||
expect(component.RenameOnGoing).toEqual(true);
|
||||
});
|
||||
it('admin should save when it click save button 2 times after rename', async () => {
|
||||
await fixture.whenStable();
|
||||
let renameBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#rename-btn');
|
||||
let renameBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#rename-btn');
|
||||
renameBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||
'#account_settings_email'
|
||||
);
|
||||
emailInput.value = 'email@qq.com';
|
||||
emailInput.dispatchEvent(new Event('input'));
|
||||
let submitBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#submit-btn');
|
||||
let submitBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#submit-btn');
|
||||
submitBtn.dispatchEvent(new Event('click'));
|
||||
const alertTextElement: HTMLSpanElement = fixture.nativeElement.querySelector('.alert-text');
|
||||
expect(alertTextElement.innerText).toEqual(' PROFILE.RENAME_CONFIRM_INFO ');
|
||||
const alertTextElement: HTMLSpanElement =
|
||||
fixture.nativeElement.querySelector('.alert-text');
|
||||
expect(alertTextElement.innerText).toEqual(
|
||||
' PROFILE.RENAME_CONFIRM_INFO '
|
||||
);
|
||||
|
||||
submitBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
const emailInput1: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
const emailInput1: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#account_settings_email');
|
||||
// rename success
|
||||
expect(emailInput1).toEqual(null);
|
||||
});
|
||||
it('should click cancel and close when has data change and no rename', async () => {
|
||||
await fixture.whenStable();
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
let emailInput: HTMLInputElement = fixture.nativeElement.querySelector(
|
||||
'#account_settings_email'
|
||||
);
|
||||
emailInput.value = 'email@qq.com';
|
||||
emailInput.dispatchEvent(new Event('input'));
|
||||
let cancelBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#cancel-btn');
|
||||
let cancelBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#cancel-btn');
|
||||
cancelBtn.dispatchEvent(new Event('click'));
|
||||
const alertTextElement: HTMLSpanElement = fixture.nativeElement.querySelector('.alert-text');
|
||||
expect(alertTextElement.innerText).toEqual(' ALERT.FORM_CHANGE_CONFIRMATION ');
|
||||
|
||||
const alertTextElement: HTMLSpanElement =
|
||||
fixture.nativeElement.querySelector('.alert-text');
|
||||
expect(alertTextElement.innerText).toEqual(
|
||||
' ALERT.FORM_CHANGE_CONFIRMATION '
|
||||
);
|
||||
});
|
||||
it('should click cancel and close when has data change and has rename', async () => {
|
||||
await fixture.whenStable();
|
||||
let renameBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#rename-btn');
|
||||
let renameBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#rename-btn');
|
||||
renameBtn.dispatchEvent(new Event('click'));
|
||||
let cancelBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#cancel-btn');
|
||||
let cancelBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#cancel-btn');
|
||||
cancelBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
expect(component.RenameOnGoing).toEqual(false);
|
||||
const emailInput1: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
const emailInput1: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#account_settings_email');
|
||||
expect(emailInput1).toEqual(null);
|
||||
|
||||
});
|
||||
it('should click cancel and close when has no data change', async () => {
|
||||
await fixture.whenStable();
|
||||
let cancelBtn: HTMLButtonElement = fixture.nativeElement.querySelector('#cancel-btn');
|
||||
let cancelBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#cancel-btn');
|
||||
cancelBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
const emailInput1: HTMLInputElement = fixture.nativeElement.querySelector('#account_settings_email');
|
||||
const emailInput1: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('#account_settings_email');
|
||||
expect(emailInput1).toEqual(null);
|
||||
|
||||
});
|
||||
it('should generate cli secret when oidc mode', async () => {
|
||||
await fixture.whenStable();
|
||||
component.account.oidc_user_meta = clone(oidcUserMeta1);
|
||||
await fixture.whenStable();
|
||||
const hiddenGenerateCliButton: HTMLButtonElement = fixture.nativeElement.querySelector('#hidden-generate-cli');
|
||||
const hiddenGenerateCliButton: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#hidden-generate-cli');
|
||||
expect(hiddenGenerateCliButton).toBeTruthy();
|
||||
hiddenGenerateCliButton.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
const hiddenGenerateCliButton1: HTMLButtonElement = fixture.nativeElement.querySelector('#hidden-generate-cli');
|
||||
const hiddenGenerateCliButton1: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#hidden-generate-cli');
|
||||
expect(hiddenGenerateCliButton1).toBeNull();
|
||||
const generateCliButton: HTMLButtonElement = fixture.nativeElement.querySelector('#generate-cli-btn');
|
||||
const generateCliButton: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#generate-cli-btn');
|
||||
expect(generateCliButton).toBeTruthy();
|
||||
component.confirmationDialogComponent = TestBed.createComponent(ConfirmationDialogComponent).componentInstance;
|
||||
component.confirmationDialogComponent = TestBed.createComponent(
|
||||
ConfirmationDialogComponent
|
||||
).componentInstance;
|
||||
generateCliButton.dispatchEvent(new Event('click'));
|
||||
component.confirmGenerate(null);
|
||||
await fixture.whenStable();
|
||||
|
|
|
@ -12,26 +12,30 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { AfterViewChecked, Component, OnInit, ViewChild } from "@angular/core";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { NavigationExtras, Router } from "@angular/router";
|
||||
import { SessionUser } from "../../shared/entities/session-user";
|
||||
import { SessionService } from "../../shared/services/session.service";
|
||||
import { MessageHandlerService } from "../../shared/services/message-handler.service";
|
||||
import { SearchTriggerService } from "../../shared/components/global-search/search-trigger.service";
|
||||
import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { NavigationExtras, Router } from '@angular/router';
|
||||
import { SessionUser } from '../../shared/entities/session-user';
|
||||
import { SessionService } from '../../shared/services/session.service';
|
||||
import { MessageHandlerService } from '../../shared/services/message-handler.service';
|
||||
import { SearchTriggerService } from '../../shared/components/global-search/search-trigger.service';
|
||||
import { AccountSettingsModalService } from './account-settings-modal-service.service';
|
||||
import { randomWord } from '../../shared/units/shared.utils';
|
||||
import { ResetSecret } from './account';
|
||||
import { CopyInputComponent } from "../../shared/components/push-image/copy-input.component";
|
||||
import { CommonRoutes, ConfirmationButtons, ConfirmationTargets } from "../../shared/entities/shared.const";
|
||||
import { ConfirmationDialogComponent } from "../../shared/components/confirmation-dialog";
|
||||
import { InlineAlertComponent } from "../../shared/components/inline-alert/inline-alert.component";
|
||||
import { ConfirmationMessage } from "../global-confirmation-dialog/confirmation-message";
|
||||
import { CopyInputComponent } from '../../shared/components/push-image/copy-input.component';
|
||||
import {
|
||||
CommonRoutes,
|
||||
ConfirmationButtons,
|
||||
ConfirmationTargets,
|
||||
} from '../../shared/entities/shared.const';
|
||||
import { ConfirmationDialogComponent } from '../../shared/components/confirmation-dialog';
|
||||
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||
import { ConfirmationMessage } from '../global-confirmation-dialog/confirmation-message';
|
||||
|
||||
@Component({
|
||||
selector: "account-settings-modal",
|
||||
templateUrl: "account-settings-modal.component.html",
|
||||
styleUrls: ["./account-settings-modal.component.scss", "../../common.scss"]
|
||||
selector: 'account-settings-modal',
|
||||
templateUrl: 'account-settings-modal.component.html',
|
||||
styleUrls: ['./account-settings-modal.component.scss', '../../common.scss'],
|
||||
})
|
||||
export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
||||
opened = false;
|
||||
|
@ -39,27 +43,28 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
originalStaticData: SessionUser;
|
||||
account: SessionUser;
|
||||
error: any = null;
|
||||
emailTooltip = "TOOLTIP.EMAIL";
|
||||
emailTooltip = 'TOOLTIP.EMAIL';
|
||||
mailAlreadyChecked = {};
|
||||
isOnCalling = false;
|
||||
formValueChanged = false;
|
||||
checkOnGoing = false;
|
||||
RenameOnGoing = false;
|
||||
originAdminName = "admin";
|
||||
newAdminName = "admin@harbor.local";
|
||||
originAdminName = 'admin';
|
||||
newAdminName = 'admin@harbor.local';
|
||||
renameConfirmation = false;
|
||||
showSecretDetail = false;
|
||||
resetForms = new ResetSecret();
|
||||
showGenerateCli: boolean = false;
|
||||
@ViewChild("confirmationDialog")
|
||||
@ViewChild('confirmationDialog')
|
||||
confirmationDialogComponent: ConfirmationDialogComponent;
|
||||
|
||||
accountFormRef: NgForm;
|
||||
@ViewChild("accountSettingsFrom", {static: true}) accountForm: NgForm;
|
||||
@ViewChild("resetSecretFrom", {static: true}) resetSecretFrom: NgForm;
|
||||
@ViewChild("accountSettingInlineAlert") inlineAlert: InlineAlertComponent;
|
||||
@ViewChild("resetSecretInlineAlert") resetSecretInlineAlert: InlineAlertComponent;
|
||||
@ViewChild("copyInput") copyInput: CopyInputComponent;
|
||||
@ViewChild('accountSettingsFrom', { static: true }) accountForm: NgForm;
|
||||
@ViewChild('resetSecretFrom', { static: true }) resetSecretFrom: NgForm;
|
||||
@ViewChild('accountSettingInlineAlert') inlineAlert: InlineAlertComponent;
|
||||
@ViewChild('resetSecretInlineAlert')
|
||||
resetSecretInlineAlert: InlineAlertComponent;
|
||||
@ViewChild('copyInput') copyInput: CopyInputComponent;
|
||||
showInputSecret: boolean = false;
|
||||
showConfirmSecret: boolean = false;
|
||||
|
||||
|
@ -68,13 +73,12 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
private msgHandler: MessageHandlerService,
|
||||
private router: Router,
|
||||
private searchTrigger: SearchTriggerService,
|
||||
private accountSettingsService: AccountSettingsModalService,
|
||||
) {
|
||||
}
|
||||
private accountSettingsService: AccountSettingsModalService
|
||||
) {}
|
||||
|
||||
private validationStateMap: any = {
|
||||
account_settings_email: true,
|
||||
account_settings_full_name: true
|
||||
account_settings_full_name: true,
|
||||
};
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -84,7 +88,10 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
refreshAccount() {
|
||||
// Value copy
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
|
||||
this.originalStaticData = Object.assign(
|
||||
{},
|
||||
this.session.getCurrentUser()
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewChecked(): void {
|
||||
|
@ -115,17 +122,17 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
if (cont) {
|
||||
this.validationStateMap[key] = cont.valid;
|
||||
// Check email existing from backend
|
||||
if (cont.valid && key === "account_settings_email") {
|
||||
if (cont.valid && key === 'account_settings_email') {
|
||||
if (
|
||||
this.formValueChanged &&
|
||||
this.account.email !== this.originalStaticData.email
|
||||
) {
|
||||
if (this.mailAlreadyChecked[this.account.email]) {
|
||||
this.validationStateMap[key] = !this.mailAlreadyChecked[
|
||||
this.account.email
|
||||
].result;
|
||||
this.validationStateMap[key] =
|
||||
!this.mailAlreadyChecked[this.account.email]
|
||||
.result;
|
||||
if (!this.validationStateMap[key]) {
|
||||
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
|
||||
this.emailTooltip = 'TOOLTIP.EMAIL_EXISTING';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -133,27 +140,33 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
// Mail changed
|
||||
this.checkOnGoing = true;
|
||||
this.session
|
||||
.checkUserExisting("email", this.account.email)
|
||||
.subscribe((res: boolean) => {
|
||||
this.checkOnGoing = false;
|
||||
this.validationStateMap[key] = !res;
|
||||
if (res) {
|
||||
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
|
||||
.checkUserExisting('email', this.account.email)
|
||||
.subscribe(
|
||||
(res: boolean) => {
|
||||
this.checkOnGoing = false;
|
||||
this.validationStateMap[key] = !res;
|
||||
if (res) {
|
||||
this.emailTooltip =
|
||||
'TOOLTIP.EMAIL_EXISTING';
|
||||
}
|
||||
this.mailAlreadyChecked[
|
||||
this.account.email
|
||||
] = {
|
||||
result: res,
|
||||
}; // Tag it checked
|
||||
},
|
||||
error => {
|
||||
this.checkOnGoing = false;
|
||||
this.validationStateMap[key] = false; // Not valid @ backend
|
||||
}
|
||||
this.mailAlreadyChecked[this.account.email] = {
|
||||
result: res
|
||||
}; // Tag it checked
|
||||
}, error => {
|
||||
this.checkOnGoing = false;
|
||||
this.validationStateMap[key] = false; // Not valid @ backend
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Reset
|
||||
this.validationStateMap[key] = true;
|
||||
this.emailTooltip = "TOOLTIP.EMAIL";
|
||||
this.emailTooltip = 'TOOLTIP.EMAIL';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +187,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
this.accountForm &&
|
||||
this.accountForm.valid &&
|
||||
this.error === null &&
|
||||
this.validationStateMap["account_settings_email"]
|
||||
this.validationStateMap['account_settings_email']
|
||||
); // backend check is valid as well
|
||||
}
|
||||
|
||||
|
@ -190,7 +203,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
return (
|
||||
this.account &&
|
||||
this.account.has_admin_role &&
|
||||
this.originalStaticData.username === "admin" &&
|
||||
this.originalStaticData.username === 'admin' &&
|
||||
this.account.user_id === 1
|
||||
);
|
||||
}
|
||||
|
@ -202,18 +215,22 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
|
||||
confirmRename(): void {
|
||||
if (this.canRename) {
|
||||
this.session
|
||||
.updateAccountSettings(this.account)
|
||||
.subscribe(() => {
|
||||
this.session.renameAdmin(this.account)
|
||||
.subscribe(() => {
|
||||
this.msgHandler.showSuccess("PROFILE.RENAME_SUCCESS");
|
||||
this.session.updateAccountSettings(this.account).subscribe(
|
||||
() => {
|
||||
this.session.renameAdmin(this.account).subscribe(
|
||||
() => {
|
||||
this.msgHandler.showSuccess(
|
||||
'PROFILE.RENAME_SUCCESS'
|
||||
);
|
||||
this.opened = false;
|
||||
this.logOut();
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}, error => {
|
||||
}
|
||||
);
|
||||
},
|
||||
error => {
|
||||
this.isOnCalling = false;
|
||||
this.error = error;
|
||||
if (this.msgHandler.isAppLevel(error)) {
|
||||
|
@ -222,7 +239,8 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
} else {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,7 +249,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
// Naviagte to the sign in router-guard
|
||||
// Appending 'signout' means destroy session cache
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: {signout: true}
|
||||
queryParams: { signout: true },
|
||||
};
|
||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
|
||||
// Confirm search result panel is close
|
||||
|
@ -240,7 +258,10 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
|
||||
open() {
|
||||
// Keep the initial data for future diff
|
||||
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
|
||||
this.originalStaticData = Object.assign(
|
||||
{},
|
||||
this.session.getCurrentUser()
|
||||
);
|
||||
this.account = Object.assign({}, this.session.getCurrentUser());
|
||||
this.formValueChanged = false;
|
||||
|
||||
|
@ -253,7 +274,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
// Reset validation status
|
||||
this.validationStateMap = {
|
||||
account_settings_email: true,
|
||||
account_settings_full_name: true
|
||||
account_settings_full_name: true,
|
||||
};
|
||||
this.showGenerateCli = false;
|
||||
this.opened = true;
|
||||
|
@ -270,7 +291,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
} else {
|
||||
// Need user confirmation
|
||||
this.inlineAlert.showInlineConfirmation({
|
||||
message: "ALERT.FORM_CHANGE_CONFIRMATION"
|
||||
message: 'ALERT.FORM_CHANGE_CONFIRMATION',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -293,7 +314,7 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
if (this.RenameOnGoing && !this.renameConfirmation) {
|
||||
this.renameConfirmation = true;
|
||||
this.inlineAlert.showInlineWarning({
|
||||
message: "PROFILE.RENAME_CONFIRM_INFO"
|
||||
message: 'PROFILE.RENAME_CONFIRM_INFO',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -303,17 +324,17 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
if (this.RenameOnGoing && this.renameConfirmation) {
|
||||
this.confirmRename();
|
||||
} else {
|
||||
this.session
|
||||
.updateAccountSettings(this.account)
|
||||
.subscribe(() => {
|
||||
this.session.updateAccountSettings(this.account).subscribe(
|
||||
() => {
|
||||
this.isOnCalling = false;
|
||||
this.opened = false;
|
||||
this.msgHandler.showSuccess("PROFILE.SAVE_SUCCESS");
|
||||
this.msgHandler.showSuccess('PROFILE.SAVE_SUCCESS');
|
||||
// get user info from back-end then refresh account
|
||||
this.session.retrieveUser().subscribe(() => {
|
||||
this.refreshAccount();
|
||||
});
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.isOnCalling = false;
|
||||
this.error = error;
|
||||
if (this.msgHandler.isAppLevel(error)) {
|
||||
|
@ -322,7 +343,8 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
} else {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,11 +369,11 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
}
|
||||
|
||||
onSuccess(event) {
|
||||
this.inlineAlert.showInlineSuccess({message: 'PROFILE.COPY_SUCCESS'});
|
||||
this.inlineAlert.showInlineSuccess({ message: 'PROFILE.COPY_SUCCESS' });
|
||||
}
|
||||
|
||||
onError(event) {
|
||||
this.inlineAlert.showInlineError({message: 'PROFILE.COPY_ERROR'});
|
||||
this.inlineAlert.showInlineError({ message: 'PROFILE.COPY_ERROR' });
|
||||
}
|
||||
|
||||
generateCli(userId): void {
|
||||
|
@ -361,7 +383,8 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
'',
|
||||
userId,
|
||||
ConfirmationTargets.TARGET,
|
||||
ConfirmationButtons.CONFIRM_CANCEL);
|
||||
ConfirmationButtons.CONFIRM_CANCEL
|
||||
);
|
||||
this.confirmationDialogComponent.open(generateCliMessage);
|
||||
}
|
||||
|
||||
|
@ -376,17 +399,30 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
|
|||
|
||||
resetCliSecret(secret) {
|
||||
let userId = this.account.user_id;
|
||||
this.accountSettingsService.saveNewCli(userId, {secret: secret}).subscribe(cliSecret => {
|
||||
this.account.oidc_user_meta.secret = secret;
|
||||
this.closeReset();
|
||||
this.inlineAlert.showInlineSuccess({message: 'PROFILE.GENERATE_SUCCESS'});
|
||||
}, error => {
|
||||
this.resetSecretInlineAlert.showInlineError({message: 'PROFILE.GENERATE_ERROR'});
|
||||
});
|
||||
this.accountSettingsService
|
||||
.saveNewCli(userId, { secret: secret })
|
||||
.subscribe(
|
||||
cliSecret => {
|
||||
this.account.oidc_user_meta.secret = secret;
|
||||
this.closeReset();
|
||||
this.inlineAlert.showInlineSuccess({
|
||||
message: 'PROFILE.GENERATE_SUCCESS',
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.resetSecretInlineAlert.showInlineError({
|
||||
message: 'PROFILE.GENERATE_ERROR',
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
disableChangeCliSecret() {
|
||||
return this.resetSecretFrom.invalid || (this.resetSecretFrom.value.input_secret !== this.resetSecretFrom.value.confirm_secret);
|
||||
return (
|
||||
this.resetSecretFrom.invalid ||
|
||||
this.resetSecretFrom.value.input_secret !==
|
||||
this.resetSecretFrom.value.confirm_secret
|
||||
);
|
||||
}
|
||||
|
||||
closeReset() {
|
||||
|
|
|
@ -2,7 +2,7 @@ export class ResetSecret {
|
|||
input_secret: string;
|
||||
confirm_secret: string;
|
||||
constructor() {
|
||||
this.confirm_secret = "";
|
||||
this.input_secret = "";
|
||||
this.confirm_secret = '';
|
||||
this.input_secret = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,126 +15,166 @@ import { NgModule } from '@angular/core';
|
|||
import { SharedModule } from '../shared/shared.module';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { HarborShellComponent } from './harbor-shell/harbor-shell.component';
|
||||
import { SystemAdminGuard } from "../shared/router-guard/system-admin-activate.service";
|
||||
import { MemberGuard } from "../shared/router-guard/member-guard-activate.service";
|
||||
import { ProjectRoutingResolver } from "../services/routing-resolvers/project-routing-resolver.service";
|
||||
import { PasswordSettingComponent } from "./password-setting/password-setting.component";
|
||||
import { AccountSettingsModalComponent } from "./account-settings/account-settings-modal.component";
|
||||
import { ForgotPasswordComponent } from "./password-setting/forgot-password/forgot-password.component";
|
||||
import { GlobalConfirmationDialogComponent } from "./global-confirmation-dialog/global-confirmation-dialog.component";
|
||||
import { SystemAdminGuard } from '../shared/router-guard/system-admin-activate.service';
|
||||
import { MemberGuard } from '../shared/router-guard/member-guard-activate.service';
|
||||
import { ProjectRoutingResolver } from '../services/routing-resolvers/project-routing-resolver.service';
|
||||
import { PasswordSettingComponent } from './password-setting/password-setting.component';
|
||||
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
||||
import { ForgotPasswordComponent } from './password-setting/forgot-password/forgot-password.component';
|
||||
import { GlobalConfirmationDialogComponent } from './global-confirmation-dialog/global-confirmation-dialog.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HarborShellComponent,
|
||||
children: [
|
||||
{ path: '', redirectTo: 'projects', pathMatch: 'full' },
|
||||
{
|
||||
path: 'projects',
|
||||
loadChildren: () => import('./left-side-nav/projects/projects.module').then(m => m.ProjectsModule)
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
loadChildren: () => import('./left-side-nav/log/log.module').then(m => m.LogModule)
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/user/user.module').then(m => m.UserModule)
|
||||
},
|
||||
{
|
||||
path: 'robot-accounts',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/system-robot-accounts/system-robot-accounts.module')
|
||||
.then(m => m.SystemRobotAccountsModule)
|
||||
},
|
||||
{
|
||||
path: 'groups',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/group/group.module').then(m => m.GroupModule)
|
||||
},
|
||||
{
|
||||
path: 'registries',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/registries/endpoint.module').then(m => m.EndpointModule)
|
||||
},
|
||||
{
|
||||
path: 'replications',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/replication/replication.module').then(m => m.ReplicationModule)
|
||||
},
|
||||
{
|
||||
path: 'distribution',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/distribution/distribution.module').then(m => m.DistributionModule)
|
||||
},
|
||||
{
|
||||
path: 'interrogation-services',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/interrogation-services/interrogation-services.module')
|
||||
.then(m => m.InterrogationServicesModule)
|
||||
},
|
||||
{
|
||||
path: 'labels',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/labels/labels.module').then(m => m.LabelsModule)
|
||||
},
|
||||
{
|
||||
path: 'project-quotas',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/project-quotas/project-quotas.module').then(m => m.ProjectQuotasModule)
|
||||
},
|
||||
{
|
||||
path: 'gc',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/gc-page/gc.module').then(m => m.GcModule)
|
||||
},
|
||||
{
|
||||
path: 'configs',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () => import('./left-side-nav/config/config.module').then(m => m.ConfigurationModule)
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
loadChildren: () => import('./project/project.module').then(m => m.ProjectModule),
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'projects/:id/repositories',
|
||||
loadChildren: () => import('./project/repository/artifact/artifact.module').then(m => m.ArtifactModule),
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'projects/:id/helm-charts',
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
loadChildren: () => import('./project/helm-chart/helm-chart-detail/helm-chart-detail.module').then(m => m.HelmChartListModule),
|
||||
},
|
||||
]
|
||||
}
|
||||
{
|
||||
path: '',
|
||||
component: HarborShellComponent,
|
||||
children: [
|
||||
{ path: '', redirectTo: 'projects', pathMatch: 'full' },
|
||||
{
|
||||
path: 'projects',
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/projects/projects.module').then(
|
||||
m => m.ProjectsModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/log/log.module').then(
|
||||
m => m.LogModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/user/user.module').then(
|
||||
m => m.UserModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'robot-accounts',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./left-side-nav/system-robot-accounts/system-robot-accounts.module'
|
||||
).then(m => m.SystemRobotAccountsModule),
|
||||
},
|
||||
{
|
||||
path: 'groups',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/group/group.module').then(
|
||||
m => m.GroupModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'registries',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/registries/endpoint.module').then(
|
||||
m => m.EndpointModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'replications',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./left-side-nav/replication/replication.module'
|
||||
).then(m => m.ReplicationModule),
|
||||
},
|
||||
{
|
||||
path: 'distribution',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./left-side-nav/distribution/distribution.module'
|
||||
).then(m => m.DistributionModule),
|
||||
},
|
||||
{
|
||||
path: 'interrogation-services',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./left-side-nav/interrogation-services/interrogation-services.module'
|
||||
).then(m => m.InterrogationServicesModule),
|
||||
},
|
||||
{
|
||||
path: 'labels',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/labels/labels.module').then(
|
||||
m => m.LabelsModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'project-quotas',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./left-side-nav/project-quotas/project-quotas.module'
|
||||
).then(m => m.ProjectQuotasModule),
|
||||
},
|
||||
{
|
||||
path: 'gc',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/gc-page/gc.module').then(
|
||||
m => m.GcModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'configs',
|
||||
canActivate: [SystemAdminGuard],
|
||||
loadChildren: () =>
|
||||
import('./left-side-nav/config/config.module').then(
|
||||
m => m.ConfigurationModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
loadChildren: () =>
|
||||
import('./project/project.module').then(
|
||||
m => m.ProjectModule
|
||||
),
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'projects/:id/repositories',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./project/repository/artifact/artifact.module'
|
||||
).then(m => m.ArtifactModule),
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'projects/:id/helm-charts',
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver,
|
||||
},
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./project/helm-chart/helm-chart-detail/helm-chart-detail.module'
|
||||
).then(m => m.HelmChartListModule),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes),
|
||||
],
|
||||
declarations: [
|
||||
HarborShellComponent,
|
||||
PasswordSettingComponent,
|
||||
AccountSettingsModalComponent,
|
||||
ForgotPasswordComponent,
|
||||
GlobalConfirmationDialogComponent
|
||||
]
|
||||
imports: [SharedModule, RouterModule.forChild(routes)],
|
||||
declarations: [
|
||||
HarborShellComponent,
|
||||
PasswordSettingComponent,
|
||||
AccountSettingsModalComponent,
|
||||
ForgotPasswordComponent,
|
||||
GlobalConfirmationDialogComponent,
|
||||
],
|
||||
})
|
||||
export class BaseModule {
|
||||
|
||||
}
|
||||
export class BaseModule {}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/**
|
||||
* Created by pengf on 11/22/2017.
|
||||
*/
|
||||
|
@ -18,17 +17,22 @@ export class BatchInfo {
|
|||
}
|
||||
}
|
||||
|
||||
export function BathInfoChanges(batchInfo: BatchInfo, status: string, loading = false, errStatus = false, errorInfo = '') {
|
||||
batchInfo.status = status;
|
||||
batchInfo.loading = loading;
|
||||
batchInfo.errorState = errStatus;
|
||||
batchInfo.errorInfo = errorInfo;
|
||||
return batchInfo;
|
||||
export function BathInfoChanges(
|
||||
batchInfo: BatchInfo,
|
||||
status: string,
|
||||
loading = false,
|
||||
errStatus = false,
|
||||
errorInfo = ''
|
||||
) {
|
||||
batchInfo.status = status;
|
||||
batchInfo.loading = loading;
|
||||
batchInfo.errorState = errStatus;
|
||||
batchInfo.errorInfo = errorInfo;
|
||||
return batchInfo;
|
||||
}
|
||||
|
||||
export enum BatchOperations {
|
||||
Idle,
|
||||
Delete,
|
||||
ChangeRole
|
||||
ChangeRole,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,16 @@ import { TestBed, inject } from '@angular/core/testing';
|
|||
import { ConfirmationDialogService } from './confirmation-dialog.service';
|
||||
|
||||
describe('ConfirmationDialogService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ConfirmationDialogService]
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ConfirmationDialogService],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([ConfirmationDialogService], (service: ConfirmationDialogService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
it('should be created', inject(
|
||||
[ConfirmationDialogService],
|
||||
(service: ConfirmationDialogService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}
|
||||
));
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from "rxjs";
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { ConfirmationMessage } from './confirmation-message';
|
||||
import { ConfirmationAcknowledgement } from './confirmation-state-message';
|
||||
|
|
|
@ -12,30 +12,30 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import {
|
||||
ConfirmationTargets,
|
||||
ConfirmationButtons
|
||||
} from "../../shared/entities/shared.const";
|
||||
ConfirmationTargets,
|
||||
ConfirmationButtons,
|
||||
} from '../../shared/entities/shared.const';
|
||||
|
||||
export class ConfirmationMessage {
|
||||
public constructor(
|
||||
title: string,
|
||||
message: string,
|
||||
param: string,
|
||||
data: any,
|
||||
targetId: ConfirmationTargets,
|
||||
buttons?: ConfirmationButtons
|
||||
) {
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
this.targetId = targetId;
|
||||
this.param = param;
|
||||
this.buttons = buttons ? buttons : ConfirmationButtons.CONFIRM_CANCEL;
|
||||
}
|
||||
title: string;
|
||||
message: string;
|
||||
data: any = {}; // default is empty
|
||||
targetId: ConfirmationTargets = ConfirmationTargets.EMPTY;
|
||||
param: string;
|
||||
buttons: ConfirmationButtons;
|
||||
public constructor(
|
||||
title: string,
|
||||
message: string,
|
||||
param: string,
|
||||
data: any,
|
||||
targetId: ConfirmationTargets,
|
||||
buttons?: ConfirmationButtons
|
||||
) {
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
this.targetId = targetId;
|
||||
this.param = param;
|
||||
this.buttons = buttons ? buttons : ConfirmationButtons.CONFIRM_CANCEL;
|
||||
}
|
||||
title: string;
|
||||
message: string;
|
||||
data: any = {}; // default is empty
|
||||
targetId: ConfirmationTargets = ConfirmationTargets.EMPTY;
|
||||
param: string;
|
||||
buttons: ConfirmationButtons;
|
||||
}
|
||||
|
|
|
@ -11,10 +11,17 @@
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { ConfirmationState, ConfirmationTargets } from '../../shared/entities/shared.const';
|
||||
import {
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
} from '../../shared/entities/shared.const';
|
||||
|
||||
export class ConfirmationAcknowledgement {
|
||||
constructor(state: ConfirmationState, data: any, source: ConfirmationTargets) {
|
||||
constructor(
|
||||
state: ConfirmationState,
|
||||
data: any,
|
||||
source: ConfirmationTargets
|
||||
) {
|
||||
this.state = state;
|
||||
this.data = data;
|
||||
this.source = source;
|
||||
|
|
|
@ -1,39 +1,95 @@
|
|||
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title confirmation-title">{{dialogTitle}}</h3>
|
||||
<clr-modal
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalClosable]="false"
|
||||
[clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title confirmation-title">{{ dialogTitle }}</h3>
|
||||
<div class="modal-body">
|
||||
<div class="confirmation-icon-inline" *ngIf="buttons!==2">
|
||||
<div class="confirmation-icon-inline" *ngIf="buttons !== 2">
|
||||
<clr-icon shape="warning" class="is-warning" size="64"></clr-icon>
|
||||
</div>
|
||||
<div class="confirmation-content">{{dialogContent}}</div>
|
||||
<div class="confirmation-content">{{ dialogContent }}</div>
|
||||
</div>
|
||||
<div class="modal-footer" [ngSwitch]="buttons">
|
||||
<ng-template [ngSwitchCase]="0">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirm()">{{ 'BUTTON.CONFIRM' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="1">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.NO' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirm()">{{ 'BUTTON.YES' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="2">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-danger" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="3">
|
||||
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="4">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" id="dialog-action-enable" class="btn btn-primary" (click)="confirm()">{{'BUTTON.ENABLE' | translate}}</button>
|
||||
<ng-template [ngSwitchCase]="0">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirm()">
|
||||
{{ 'BUTTON.CONFIRM' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="1">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||
{{ 'BUTTON.NO' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" (click)="confirm()">
|
||||
{{ 'BUTTON.YES' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
(click)="cancel()"
|
||||
[hidden]="isDelete">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger"
|
||||
(click)="confirm()"
|
||||
[hidden]="isDelete">
|
||||
{{ 'BUTTON.DELETE' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="3">
|
||||
<button type="button" class="btn btn-primary" (click)="cancel()">
|
||||
{{ 'BUTTON.CLOSE' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="4">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="dialog-action-enable"
|
||||
class="btn btn-primary"
|
||||
(click)="confirm()">
|
||||
{{ 'BUTTON.ENABLE' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="5">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" id="dialog-action-disable" class="btn btn-danger" (click)="confirm()">{{'BUTTON.DISABLE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="dialog-action-disable"
|
||||
class="btn btn-danger"
|
||||
(click)="confirm()">
|
||||
{{ 'BUTTON.DISABLE' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="6">
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [hidden]="isDelete">{{'BUTTON.SWITCH' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="'true'" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
(click)="cancel()"
|
||||
[hidden]="isDelete">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" [hidden]="isDelete">
|
||||
{{ 'BUTTON.SWITCH' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
(click)="cancel()"
|
||||
[disabled]="'true'"
|
||||
[hidden]="!isDelete">
|
||||
{{ 'BUTTON.CLOSE' | translate }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</clr-modal>
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import {
|
||||
BrowserAnimationsModule,
|
||||
NoopAnimationsModule,
|
||||
} from '@angular/platform-browser/animations';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { ConfirmationDialogService } from './confirmation-dialog.service';
|
||||
import { GlobalConfirmationDialogComponent } from "./global-confirmation-dialog.component";
|
||||
import { GlobalConfirmationDialogComponent } from './global-confirmation-dialog.component';
|
||||
|
||||
describe('ConfirmationDialogComponent', () => {
|
||||
let component: GlobalConfirmationDialogComponent;
|
||||
let fixture: ComponentFixture<GlobalConfirmationDialogComponent>;
|
||||
const mockConfirmationDialogService = {
|
||||
confirmationAnnouced$: of({
|
||||
title: "title",
|
||||
message: "title",
|
||||
param: "AAA"
|
||||
title: 'title',
|
||||
message: 'title',
|
||||
param: 'AAA',
|
||||
}),
|
||||
cancel: () => { },
|
||||
confirm: () => { },
|
||||
cancel: () => {},
|
||||
confirm: () => {},
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
|
@ -34,14 +35,16 @@ describe('ConfirmationDialogComponent', () => {
|
|||
FormsModule,
|
||||
RouterTestingModule,
|
||||
NoopAnimationsModule,
|
||||
HttpClientTestingModule
|
||||
HttpClientTestingModule,
|
||||
],
|
||||
declarations: [GlobalConfirmationDialogComponent],
|
||||
providers: [
|
||||
TranslateService,
|
||||
{ provide: ConfirmationDialogService, useValue: mockConfirmationDialogService },
|
||||
|
||||
]
|
||||
{
|
||||
provide: ConfirmationDialogService,
|
||||
useValue: mockConfirmationDialogService,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
|
|
@ -12,41 +12,49 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from "rxjs";
|
||||
import { Subscription } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ConfirmationMessage } from './confirmation-message';
|
||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../shared/entities/shared.const";
|
||||
import { ConfirmationDialogService } from "./confirmation-dialog.service";
|
||||
import { ConfirmationAcknowledgement } from "./confirmation-state-message";
|
||||
|
||||
import {
|
||||
ConfirmationButtons,
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
} from '../../shared/entities/shared.const';
|
||||
import { ConfirmationDialogService } from './confirmation-dialog.service';
|
||||
import { ConfirmationAcknowledgement } from './confirmation-state-message';
|
||||
|
||||
@Component({
|
||||
selector: 'global-confirmation-dialog',
|
||||
templateUrl: 'global-confirmation-dialog.component.html',
|
||||
styleUrls: ['global-confirmation-dialog.component.scss']
|
||||
styleUrls: ['global-confirmation-dialog.component.scss'],
|
||||
})
|
||||
|
||||
export class GlobalConfirmationDialogComponent implements OnDestroy {
|
||||
opened: boolean = false;
|
||||
dialogTitle: string = "";
|
||||
dialogContent: string = "";
|
||||
dialogTitle: string = '';
|
||||
dialogContent: string = '';
|
||||
message: ConfirmationMessage;
|
||||
annouceSubscription: Subscription;
|
||||
buttons: ConfirmationButtons;
|
||||
isDelete: boolean = false;
|
||||
constructor(
|
||||
private confirmationService: ConfirmationDialogService,
|
||||
private translate: TranslateService) {
|
||||
this.annouceSubscription = confirmationService.confirmationAnnouced$.subscribe(msg => {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
this.message = msg;
|
||||
this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
|
||||
this.translate.get(this.dialogContent, { 'param': msg.param }).subscribe((res: string) => this.dialogContent = res);
|
||||
// Open dialog
|
||||
this.buttons = msg.buttons;
|
||||
this.open();
|
||||
});
|
||||
private translate: TranslateService
|
||||
) {
|
||||
this.annouceSubscription =
|
||||
confirmationService.confirmationAnnouced$.subscribe(msg => {
|
||||
this.dialogTitle = msg.title;
|
||||
this.dialogContent = msg.message;
|
||||
this.message = msg;
|
||||
this.translate
|
||||
.get(this.dialogTitle)
|
||||
.subscribe((res: string) => (this.dialogTitle = res));
|
||||
this.translate
|
||||
.get(this.dialogContent, { param: msg.param })
|
||||
.subscribe((res: string) => (this.dialogContent = res));
|
||||
// Open dialog
|
||||
this.buttons = msg.buttons;
|
||||
this.open();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -59,10 +67,14 @@ export class GlobalConfirmationDialogComponent implements OnDestroy {
|
|||
if (msg) {
|
||||
this.dialogTitle = msg.title;
|
||||
this.message = msg;
|
||||
this.translate.get(this.dialogTitle).subscribe((res: string) => this.dialogTitle = res);
|
||||
this.translate.get(msg.message, { 'param': msg.param }).subscribe((res: string) => {
|
||||
this.dialogContent = res;
|
||||
});
|
||||
this.translate
|
||||
.get(this.dialogTitle)
|
||||
.subscribe((res: string) => (this.dialogTitle = res));
|
||||
this.translate
|
||||
.get(msg.message, { param: msg.param })
|
||||
.subscribe((res: string) => {
|
||||
this.dialogContent = res;
|
||||
});
|
||||
// Open dialog
|
||||
this.buttons = msg.buttons;
|
||||
}
|
||||
|
@ -81,12 +93,16 @@ export class GlobalConfirmationDialogComponent implements OnDestroy {
|
|||
}
|
||||
|
||||
let data: any = this.message.data ? this.message.data : {};
|
||||
let target = this.message.targetId ? this.message.targetId : ConfirmationTargets.EMPTY;
|
||||
this.confirmationService.cancel(new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CANCEL,
|
||||
data,
|
||||
target
|
||||
));
|
||||
let target = this.message.targetId
|
||||
? this.message.targetId
|
||||
: ConfirmationTargets.EMPTY;
|
||||
this.confirmationService.cancel(
|
||||
new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CANCEL,
|
||||
data,
|
||||
target
|
||||
)
|
||||
);
|
||||
this.isDelete = false;
|
||||
this.close();
|
||||
}
|
||||
|
@ -99,12 +115,16 @@ export class GlobalConfirmationDialogComponent implements OnDestroy {
|
|||
}
|
||||
|
||||
let data: any = this.message.data ? this.message.data : {};
|
||||
let target = this.message.targetId ? this.message.targetId : ConfirmationTargets.EMPTY;
|
||||
this.confirmationService.confirm(new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CONFIRMED,
|
||||
data,
|
||||
target
|
||||
));
|
||||
let target = this.message.targetId
|
||||
? this.message.targetId
|
||||
: ConfirmationTargets.EMPTY;
|
||||
this.confirmationService.confirm(
|
||||
new ConfirmationAcknowledgement(
|
||||
ConfirmationState.CONFIRMED,
|
||||
data,
|
||||
target
|
||||
)
|
||||
);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,109 +1,218 @@
|
|||
<clr-main-container>
|
||||
<div class="clr-row scanner-info" *ngIf="showScannerInfo && isSystemAdmin ">
|
||||
<div class="clr-row scanner-info" *ngIf="showScannerInfo && isSystemAdmin">
|
||||
<div class="clr-col-2"></div>
|
||||
<div class="clr-col text-center">
|
||||
<clr-icon shape="info-standard" size="20"></clr-icon>
|
||||
<span class="ml-05">{{'SCANNER.HELP_INFO_1' | translate }}
|
||||
<a rel='noopener noreferrer' target="_blank" href="{{scannerDocUrl}}">{{'SCANNER.HELP_INFO_2' | translate }}</a>
|
||||
<span class="ml-05"
|
||||
>{{ 'SCANNER.HELP_INFO_1' | translate }}
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href="{{ scannerDocUrl }}"
|
||||
>{{ 'SCANNER.HELP_INFO_2' | translate }}</a
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="clr-col-2 right">
|
||||
<a class="all-scanners" href="#"
|
||||
routerLink="/harbor/interrogation-services/scanners">{{'SCANNER.ALL_SCANNERS' | translate }}</a>
|
||||
<clr-icon (click)="closeInfo()" class="close-icon" shape="times" size="24"></clr-icon>
|
||||
<a
|
||||
class="all-scanners"
|
||||
href="#"
|
||||
routerLink="/harbor/interrogation-services/scanners"
|
||||
>{{ 'SCANNER.ALL_SCANNERS' | translate }}</a
|
||||
>
|
||||
<clr-icon
|
||||
(click)="closeInfo()"
|
||||
class="close-icon"
|
||||
shape="times"
|
||||
size="24"></clr-icon>
|
||||
</div>
|
||||
</div>
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showAccountSettingsModal)="openModal($event)" (showDialogModalAction)="openModal($event)"></navigator>
|
||||
<navigator
|
||||
(showAccountSettingsModal)="openModal($event)"
|
||||
(showDialogModalAction)="openModal($event)"></navigator>
|
||||
<div class="content-container">
|
||||
<div #scrollDiv class="content-area" [class.container-override]="showSearch"
|
||||
<div
|
||||
#scrollDiv
|
||||
class="content-area"
|
||||
[class.container-override]="showSearch"
|
||||
[class.content-area-override]="!shouldOverrideContent"
|
||||
[class.start-content-padding]="shouldOverrideContent"
|
||||
(scroll)="publishScrollEvent()">
|
||||
(scroll)="publishScrollEvent()">
|
||||
<global-message [isAppLevel]="false"></global-message>
|
||||
<!-- Only appear when searching -->
|
||||
<search-result></search-result>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<clr-vertical-nav [clrVerticalNavCollapsible]="true" *ngIf="isUserExisting">
|
||||
<clr-vertical-nav
|
||||
[clrVerticalNavCollapsible]="true"
|
||||
*ngIf="isUserExisting">
|
||||
<div>
|
||||
<a clrVerticalNavLink routerLinkActive="active" routerLink="/harbor/projects">
|
||||
<clr-icon shape="organization" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.PROJECTS' | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLinkActive="active"
|
||||
routerLink="/harbor/projects">
|
||||
<clr-icon
|
||||
shape="organization"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.PROJECTS' | translate }}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLinkActive="active" routerLink="/harbor/logs">
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLinkActive="active"
|
||||
routerLink="/harbor/logs">
|
||||
<clr-icon shape="list" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.LOGS' | translate}}
|
||||
{{ 'SIDE_NAV.LOGS' | translate }}
|
||||
</a>
|
||||
<clr-vertical-nav-group *ngIf="isSystemAdmin" routerLinkActive="active">
|
||||
<clr-icon shape="administrator" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.NAME' | translate}}
|
||||
<clr-vertical-nav-group
|
||||
*ngIf="isSystemAdmin"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="administrator"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.SYSTEM_MGMT.NAME' | translate }}
|
||||
<a routerLink="#" hidden aria-hidden="true"></a>
|
||||
<clr-vertical-nav-group-children *clrIfExpanded="true">
|
||||
<a clrVerticalNavLink routerLink="/harbor/users" routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/users"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="users"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.SYSTEM_MGMT.USER' | translate }}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLink="/harbor/robot-accounts" routerLinkActive="active">
|
||||
<clr-icon shape="robot-head" clrVerticalNavIcon></clr-icon>
|
||||
{{"SYSTEM_ROBOT.ROBOT_ACCOUNT_NAV" | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/robot-accounts"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="robot-head"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SYSTEM_ROBOT.ROBOT_ACCOUNT_NAV' | translate }}
|
||||
</a>
|
||||
<a *ngIf='isLdapMode || isHttpAuthMode || isOidcMode' clrVerticalNavLink
|
||||
routerLink="/harbor/groups" routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.GROUP' | translate}}
|
||||
<a
|
||||
*ngIf="isLdapMode || isHttpAuthMode || isOidcMode"
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/groups"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="users"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.SYSTEM_MGMT.GROUP' | translate }}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLink="/harbor/registries" routerLinkActive="active">
|
||||
<clr-icon shape="block" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/registries"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="block"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.SYSTEM_MGMT.REGISTRY' | translate }}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLink="/harbor/replications" routerLinkActive="active">
|
||||
<clr-icon shape="cloud-traffic" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/replications"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="cloud-traffic"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate }}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLink="/harbor/distribution/instances" routerLinkActive="active">
|
||||
<clr-icon shape="share" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.DISTRIBUTIONS.NAME' | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/distribution/instances"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="share"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.DISTRIBUTIONS.NAME' | translate }}
|
||||
</a>
|
||||
<a *ngIf="!withAdmiral" clrVerticalNavLink routerLink="/harbor/labels"
|
||||
<a
|
||||
*ngIf="!withAdmiral"
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/labels"
|
||||
routerLinkActive="active">
|
||||
<clr-icon shape="tag" clrVerticalNavIcon></clr-icon>
|
||||
{{'CONFIG.LABEL' | translate }}
|
||||
{{ 'CONFIG.LABEL' | translate }}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLink="/harbor/project-quotas" routerLinkActive="active">
|
||||
<clr-icon shape="resource-pool" clrVerticalNavIcon></clr-icon>
|
||||
{{'CONFIG.PROJECT_QUOTAS' | translate }}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/project-quotas"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="resource-pool"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'CONFIG.PROJECT_QUOTAS' | translate }}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLink="/harbor/interrogation-services" routerLinkActive="active">
|
||||
<clr-icon shape="shield" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.INTERROGATION_SERVICES' | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/interrogation-services"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="shield"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{
|
||||
'SIDE_NAV.SYSTEM_MGMT.INTERROGATION_SERVICES'
|
||||
| translate
|
||||
}}
|
||||
</a>
|
||||
<a clrVerticalNavLink *ngIf="hasAdminRole" routerLink="/harbor/gc" routerLinkActive="active">
|
||||
<clr-icon shape="trash" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.GARBAGE_COLLECTION' | translate}}
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
*ngIf="hasAdminRole"
|
||||
routerLink="/harbor/gc"
|
||||
routerLinkActive="active">
|
||||
<clr-icon
|
||||
shape="trash"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{
|
||||
'SIDE_NAV.SYSTEM_MGMT.GARBAGE_COLLECTION'
|
||||
| translate
|
||||
}}
|
||||
</a>
|
||||
<a clrVerticalNavLink routerLinkActive="active" routerLink="/harbor/configs">
|
||||
<a
|
||||
clrVerticalNavLink
|
||||
routerLinkActive="active"
|
||||
routerLink="/harbor/configs">
|
||||
<clr-icon shape="cog" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate}}
|
||||
{{ 'SIDE_NAV.SYSTEM_MGMT.CONFIG' | translate }}
|
||||
</a>
|
||||
</clr-vertical-nav-group-children>
|
||||
</clr-vertical-nav-group>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<ng-container *ngFor="let theme of themeArray;let i=index">
|
||||
<ng-container *ngFor="let theme of themeArray; let i = index">
|
||||
<ng-container *ngIf="theme.showStyle === styleMode">
|
||||
<a class="pointer" clrVerticalNavLink (click)="themeChanged(theme)">
|
||||
<clr-icon clrVerticalNavIcon size="20" *ngIf="styleMode ==='DARK'" shape="sun"
|
||||
class="is-solid"></clr-icon>
|
||||
<clr-icon clrVerticalNavIcon size="20" *ngIf="styleMode ==='LIGHT'" shape="moon"
|
||||
class="is-solid"></clr-icon>
|
||||
<a
|
||||
class="pointer"
|
||||
clrVerticalNavLink
|
||||
(click)="themeChanged(theme)">
|
||||
<clr-icon
|
||||
clrVerticalNavIcon
|
||||
size="20"
|
||||
*ngIf="styleMode === 'DARK'"
|
||||
shape="sun"
|
||||
class="is-solid"></clr-icon>
|
||||
<clr-icon
|
||||
clrVerticalNavIcon
|
||||
size="20"
|
||||
*ngIf="styleMode === 'LIGHT'"
|
||||
shape="moon"
|
||||
class="is-solid"></clr-icon>
|
||||
{{ theme.text | translate }}
|
||||
</a>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<a rel='noopener noreferrer' clrVerticalNavLink routerLinkActive="active" target="_blank" routerLink="/devcenter-api-2.0">
|
||||
<clr-icon shape="network-globe" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.HARBOR_API_MANAGEMENT' | translate}}
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
clrVerticalNavLink
|
||||
routerLinkActive="active"
|
||||
target="_blank"
|
||||
routerLink="/devcenter-api-2.0">
|
||||
<clr-icon
|
||||
shape="network-globe"
|
||||
clrVerticalNavIcon></clr-icon>
|
||||
{{ 'SIDE_NAV.HARBOR_API_MANAGEMENT' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</clr-vertical-nav>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { SearchTriggerService } from '../../shared/components/global-search/search-trigger.service';
|
||||
import { HarborShellComponent } from './harbor-shell.component';
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { of } from 'rxjs';
|
||||
import { modalEvents } from '../modal-events.const';
|
||||
import { PasswordSettingComponent } from '../password-setting/password-setting.component';
|
||||
|
@ -15,15 +15,15 @@ import { FormsModule } from '@angular/forms';
|
|||
import { MessageHandlerService } from '../../shared/services/message-handler.service';
|
||||
import { PasswordSettingService } from '../password-setting/password-setting.service';
|
||||
import { SkinableConfig } from '../../services/skinable-config.service';
|
||||
import { AppConfigService } from "../../services/app-config.service";
|
||||
import { AppConfigService } from '../../services/app-config.service';
|
||||
import { ErrorHandler } from '../../shared/units/error-handler';
|
||||
import { AccountSettingsModalComponent } from "../account-settings/account-settings-modal.component";
|
||||
import { InlineAlertComponent } from "../../shared/components/inline-alert/inline-alert.component";
|
||||
import { AccountSettingsModalService } from "../account-settings/account-settings-modal-service.service";
|
||||
import { ScannerService } from "../../../../ng-swagger-gen/services/scanner.service";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { Registry } from "../../../../ng-swagger-gen/models/registry";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
||||
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||
import { AccountSettingsModalService } from '../account-settings/account-settings-modal-service.service';
|
||||
import { ScannerService } from '../../../../ng-swagger-gen/services/scanner.service';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { Registry } from '../../../../ng-swagger-gen/models/registry';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
describe('HarborShellComponent', () => {
|
||||
let component: HarborShellComponent;
|
||||
|
@ -31,12 +31,11 @@ describe('HarborShellComponent', () => {
|
|||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
}
|
||||
},
|
||||
};
|
||||
let fakeSearchTriggerService = {
|
||||
searchTriggerChan$: of('null')
|
||||
,
|
||||
searchCloseChan$: of(null)
|
||||
searchTriggerChan$: of('null'),
|
||||
searchCloseChan$: of(null),
|
||||
};
|
||||
let mockMessageHandlerService = null;
|
||||
let mockAccountSettingsModalService = null;
|
||||
|
@ -44,19 +43,19 @@ describe('HarborShellComponent', () => {
|
|||
let mockSkinableConfig = {
|
||||
getSkinConfig: function () {
|
||||
return {
|
||||
"headerBgColor": {
|
||||
"darkMode": "",
|
||||
"lightMode": ""
|
||||
headerBgColor: {
|
||||
darkMode: '',
|
||||
lightMode: '',
|
||||
},
|
||||
loginBgImg: '',
|
||||
loginTitle: '',
|
||||
product: {
|
||||
name: '',
|
||||
logo: '',
|
||||
introduction: '',
|
||||
},
|
||||
"loginBgImg": "",
|
||||
"loginTitle": "",
|
||||
"product": {
|
||||
"name": "",
|
||||
"logo": "",
|
||||
"introduction": ""
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
isLdapMode: function () {
|
||||
|
@ -70,21 +69,25 @@ describe('HarborShellComponent', () => {
|
|||
},
|
||||
getConfig: function () {
|
||||
return {
|
||||
with_trivy: true
|
||||
with_trivy: true,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
let fakeScannerService = {
|
||||
listScannersResponse() {
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({'x-total-count': [].length.toString()}),
|
||||
body: []
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<
|
||||
Array<Registry>
|
||||
>({
|
||||
headers: new HttpHeaders({
|
||||
'x-total-count': [].length.toString(),
|
||||
}),
|
||||
body: [],
|
||||
});
|
||||
return of(response).pipe(delay(0));
|
||||
},
|
||||
listScanners() {
|
||||
return of([]).pipe(delay(0));
|
||||
}
|
||||
},
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
|
@ -93,23 +96,40 @@ describe('HarborShellComponent', () => {
|
|||
TranslateModule.forRoot(),
|
||||
ClarityModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule
|
||||
FormsModule,
|
||||
],
|
||||
declarations: [
|
||||
HarborShellComponent,
|
||||
AccountSettingsModalComponent,
|
||||
PasswordSettingComponent,
|
||||
AboutDialogComponent,
|
||||
InlineAlertComponent,
|
||||
],
|
||||
declarations: [HarborShellComponent, AccountSettingsModalComponent
|
||||
, PasswordSettingComponent, AboutDialogComponent, InlineAlertComponent],
|
||||
providers: [
|
||||
TranslateService,
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: SearchTriggerService, useValue: fakeSearchTriggerService },
|
||||
{
|
||||
provide: SearchTriggerService,
|
||||
useValue: fakeSearchTriggerService,
|
||||
},
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: ScannerService, useValue: fakeScannerService },
|
||||
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
|
||||
{ provide: AccountSettingsModalService, useValue: mockAccountSettingsModalService },
|
||||
{ provide: PasswordSettingService, useValue: mockPasswordSettingService },
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: mockMessageHandlerService,
|
||||
},
|
||||
{
|
||||
provide: AccountSettingsModalService,
|
||||
useValue: mockAccountSettingsModalService,
|
||||
},
|
||||
{
|
||||
provide: PasswordSettingService,
|
||||
useValue: mockPasswordSettingService,
|
||||
},
|
||||
{ provide: SkinableConfig, useValue: mockSkinableConfig },
|
||||
ErrorHandler
|
||||
ErrorHandler,
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@ -117,10 +137,16 @@ describe('HarborShellComponent', () => {
|
|||
fixture = TestBed.createComponent(HarborShellComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.showScannerInfo = true;
|
||||
component.accountSettingsModal = TestBed.createComponent(AccountSettingsModalComponent).componentInstance;
|
||||
component.accountSettingsModal.inlineAlert = TestBed.createComponent(InlineAlertComponent).componentInstance;
|
||||
component.pwdSetting = TestBed.createComponent(PasswordSettingComponent).componentInstance;
|
||||
component.aboutDialog = TestBed.createComponent(AboutDialogComponent).componentInstance;
|
||||
component.accountSettingsModal = TestBed.createComponent(
|
||||
AccountSettingsModalComponent
|
||||
).componentInstance;
|
||||
component.accountSettingsModal.inlineAlert =
|
||||
TestBed.createComponent(InlineAlertComponent).componentInstance;
|
||||
component.pwdSetting = TestBed.createComponent(
|
||||
PasswordSettingComponent
|
||||
).componentInstance;
|
||||
component.aboutDialog =
|
||||
TestBed.createComponent(AboutDialogComponent).componentInstance;
|
||||
fixture.autoDetectChanges();
|
||||
});
|
||||
|
||||
|
@ -128,21 +154,30 @@ describe('HarborShellComponent', () => {
|
|||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should open users profile', async () => {
|
||||
component.openModal({modalName: modalEvents.USER_PROFILE, modalFlag: false });
|
||||
component.openModal({
|
||||
modalName: modalEvents.USER_PROFILE,
|
||||
modalFlag: false,
|
||||
});
|
||||
await fixture.whenStable();
|
||||
const accountSettingsUsernameInput = fixture.nativeElement.querySelector("#account_settings_username");
|
||||
const accountSettingsUsernameInput =
|
||||
fixture.nativeElement.querySelector('#account_settings_username');
|
||||
expect(accountSettingsUsernameInput).toBeTruthy();
|
||||
});
|
||||
it('should open users changPwd', async () => {
|
||||
component.openModal({modalName: modalEvents.CHANGE_PWD, modalFlag: false });
|
||||
component.openModal({
|
||||
modalName: modalEvents.CHANGE_PWD,
|
||||
modalFlag: false,
|
||||
});
|
||||
await fixture.whenStable();
|
||||
const oldPasswordInput = fixture.nativeElement.querySelector("#oldPassword");
|
||||
const oldPasswordInput =
|
||||
fixture.nativeElement.querySelector('#oldPassword');
|
||||
expect(oldPasswordInput).toBeTruthy();
|
||||
});
|
||||
it('should open users about-dialog', async () => {
|
||||
component.openModal({modalName: modalEvents.ABOUT, modalFlag: false });
|
||||
component.openModal({ modalName: modalEvents.ABOUT, modalFlag: false });
|
||||
await fixture.whenStable();
|
||||
const aboutVersionEl = fixture.nativeElement.querySelector(".about-version");
|
||||
const aboutVersionEl =
|
||||
fixture.nativeElement.querySelector('.about-version');
|
||||
expect(aboutVersionEl).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,9 +11,16 @@
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, ViewChild, OnDestroy, ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
OnDestroy,
|
||||
ElementRef,
|
||||
ChangeDetectorRef,
|
||||
} from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { forkJoin, Observable, Subscription } from "rxjs";
|
||||
import { forkJoin, Observable, Subscription } from 'rxjs';
|
||||
import { AppConfigService } from '../../services/app-config.service';
|
||||
import { ModalEvent } from '../modal-event';
|
||||
import { modalEvents } from '../modal-events.const';
|
||||
|
@ -22,15 +29,18 @@ import { NavigatorComponent } from '../../shared/components/navigator/navigator.
|
|||
import { SessionService } from '../../shared/services/session.service';
|
||||
import { AboutDialogComponent } from '../../shared/components/about-dialog/about-dialog.component';
|
||||
import { SearchTriggerService } from '../../shared/components/global-search/search-trigger.service';
|
||||
import { CommonRoutes } from "../../shared/entities/shared.const";
|
||||
import { THEME_ARRAY, ThemeInterface } from "../../services/theme";
|
||||
import { clone, DEFAULT_PAGE_SIZE } from "../../shared/units/utils";
|
||||
import { ThemeService } from "../../services/theme.service";
|
||||
import { AccountSettingsModalComponent } from "../account-settings/account-settings-modal.component";
|
||||
import { EventService, HarborEvent } from "../../services/event-service/event.service";
|
||||
import { SCANNERS_DOC } from "../left-side-nav/interrogation-services/scanner/scanner";
|
||||
import { ScannerService } from "../../../../ng-swagger-gen/services/scanner.service";
|
||||
import { Project } from "../../../../ng-swagger-gen/models/project";
|
||||
import { CommonRoutes } from '../../shared/entities/shared.const';
|
||||
import { THEME_ARRAY, ThemeInterface } from '../../services/theme';
|
||||
import { clone, DEFAULT_PAGE_SIZE } from '../../shared/units/utils';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
||||
import {
|
||||
EventService,
|
||||
HarborEvent,
|
||||
} from '../../services/event-service/event.service';
|
||||
import { SCANNERS_DOC } from '../left-side-nav/interrogation-services/scanner/scanner';
|
||||
import { ScannerService } from '../../../../ng-swagger-gen/services/scanner.service';
|
||||
import { Project } from '../../../../ng-swagger-gen/models/project';
|
||||
|
||||
const HAS_SHOWED_SCANNER_INFO: string = 'hasShowScannerInfo';
|
||||
const YES: string = 'yes';
|
||||
|
@ -39,11 +49,9 @@ const HAS_STYLE_MODE: string = 'styleModeLocal';
|
|||
@Component({
|
||||
selector: 'harbor-shell',
|
||||
templateUrl: 'harbor-shell.component.html',
|
||||
styleUrls: ["harbor-shell.component.scss"]
|
||||
styleUrls: ['harbor-shell.component.scss'],
|
||||
})
|
||||
|
||||
export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild(AccountSettingsModalComponent)
|
||||
accountSettingsModal: AccountSettingsModalComponent;
|
||||
|
||||
|
@ -82,16 +90,19 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
public theme: ThemeService,
|
||||
private event: EventService,
|
||||
private cd: ChangeDetectorRef
|
||||
) { }
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.scrollToPositionSub) {
|
||||
this.scrollToPositionSub = this.event.subscribe( HarborEvent.SCROLL_TO_POSITION, scrollTop => {
|
||||
if (this.scrollDiv && this.scrollDiv.nativeElement) {
|
||||
this.cd.detectChanges();
|
||||
this.scrollDiv.nativeElement.scrollTop = scrollTop;
|
||||
this.scrollToPositionSub = this.event.subscribe(
|
||||
HarborEvent.SCROLL_TO_POSITION,
|
||||
scrollTop => {
|
||||
if (this.scrollDiv && this.scrollDiv.nativeElement) {
|
||||
this.cd.detectChanges();
|
||||
this.scrollDiv.nativeElement.scrollTop = scrollTop;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
if (this.appConfigService.isLdapMode()) {
|
||||
this.isLdapMode = true;
|
||||
|
@ -100,16 +111,25 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
} else if (this.appConfigService.isOidcMode()) {
|
||||
this.isOidcMode = true;
|
||||
}
|
||||
this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(searchEvt => {
|
||||
if (searchEvt && searchEvt.trim() !== "") {
|
||||
this.isSearchResultsOpened = true;
|
||||
this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(
|
||||
searchEvt => {
|
||||
if (searchEvt && searchEvt.trim() !== '') {
|
||||
this.isSearchResultsOpened = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
this.searchCloseSub = this.searchTrigger.searchCloseChan$.subscribe(close => {
|
||||
this.isSearchResultsOpened = false;
|
||||
});
|
||||
if (!(localStorage && localStorage.getItem(HAS_SHOWED_SCANNER_INFO) === YES)) {
|
||||
this.searchCloseSub = this.searchTrigger.searchCloseChan$.subscribe(
|
||||
close => {
|
||||
this.isSearchResultsOpened = false;
|
||||
}
|
||||
);
|
||||
if (
|
||||
!(
|
||||
localStorage &&
|
||||
localStorage.getItem(HAS_SHOWED_SCANNER_INFO) === YES
|
||||
)
|
||||
) {
|
||||
if (this.isSystemAdmin) {
|
||||
this.getDefaultScanner();
|
||||
}
|
||||
|
@ -123,7 +143,7 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
if (this.scrollDiv && this.scrollDiv.nativeElement) {
|
||||
this.event.publish(HarborEvent.SCROLL, {
|
||||
url: this.router.url,
|
||||
scrollTop: this.scrollDiv.nativeElement.scrollTop
|
||||
scrollTop: this.scrollDiv.nativeElement.scrollTop,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -135,37 +155,49 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
getDefaultScanner() {
|
||||
this.scannerService.listScannersResponse({
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
page: 1
|
||||
}).subscribe(res => {
|
||||
this.scannerService
|
||||
.listScannersResponse({
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
page: 1,
|
||||
})
|
||||
.subscribe(res => {
|
||||
if (res.headers) {
|
||||
const xHeader: string = res.headers.get("X-Total-Count");
|
||||
const xHeader: string = res.headers.get('X-Total-Count');
|
||||
const totalCount = parseInt(xHeader, 0);
|
||||
let arr = res.body || [];
|
||||
if (totalCount <= DEFAULT_PAGE_SIZE) { // already gotten all scanners
|
||||
if (totalCount <= DEFAULT_PAGE_SIZE) {
|
||||
// already gotten all scanners
|
||||
if (arr && arr.length) {
|
||||
this.showScannerInfo = arr.some(scanner => scanner.is_default);
|
||||
this.showScannerInfo = arr.some(
|
||||
scanner => scanner.is_default
|
||||
);
|
||||
}
|
||||
} else { // get all the scanners in specified times
|
||||
const times: number = Math.ceil(totalCount / DEFAULT_PAGE_SIZE);
|
||||
} else {
|
||||
// get all the scanners in specified times
|
||||
const times: number = Math.ceil(
|
||||
totalCount / DEFAULT_PAGE_SIZE
|
||||
);
|
||||
const observableList: Observable<Project[]>[] = [];
|
||||
for (let i = 2; i <= times; i++) {
|
||||
observableList.push(this.scannerService.listScanners({
|
||||
page: i,
|
||||
pageSize: DEFAULT_PAGE_SIZE
|
||||
}));
|
||||
observableList.push(
|
||||
this.scannerService.listScanners({
|
||||
page: i,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
})
|
||||
);
|
||||
}
|
||||
forkJoin(observableList).subscribe(response => {
|
||||
if (response && response.length) {
|
||||
response.forEach(item => {
|
||||
arr = arr.concat(item);
|
||||
});
|
||||
this.showScannerInfo = arr.some(scanner => scanner.is_default);
|
||||
this.showScannerInfo = arr.some(
|
||||
scanner => scanner.is_default
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
|
@ -183,7 +215,9 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
public get shouldOverrideContent(): boolean {
|
||||
return this.router.routerState.snapshot.url.toString().startsWith(CommonRoutes.EMBEDDED_SIGN_IN);
|
||||
return this.router.routerState.snapshot.url
|
||||
.toString()
|
||||
.startsWith(CommonRoutes.EMBEDDED_SIGN_IN);
|
||||
}
|
||||
|
||||
public get showSearch(): boolean {
|
||||
|
@ -200,8 +234,10 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
return account != null;
|
||||
}
|
||||
public get hasAdminRole(): boolean {
|
||||
return this.session.getCurrentUser() &&
|
||||
this.session.getCurrentUser().has_admin_role;
|
||||
return (
|
||||
this.session.getCurrentUser() &&
|
||||
this.session.getCurrentUser().has_admin_role
|
||||
);
|
||||
}
|
||||
public get withAdmiral(): boolean {
|
||||
return this.appConfigService.getConfig().with_admiral;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,24 +5,24 @@ import { ConfigurationService } from '../../../../services/config.service';
|
|||
import { ConfigurationAuthComponent } from './config-auth.component';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { of } from 'rxjs';
|
||||
import { SystemInfoService } from "../../../../shared/services";
|
||||
import { ConfigService } from "../config.service";
|
||||
import { SystemInfoService } from '../../../../shared/services';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { Configuration } from '../config';
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
|
||||
describe('ConfigurationAuthComponent', () => {
|
||||
let component: ConfigurationAuthComponent;
|
||||
let fixture: ComponentFixture<ConfigurationAuthComponent>;
|
||||
let fakeMessageHandlerService = {
|
||||
showSuccess: () => null
|
||||
showSuccess: () => null,
|
||||
};
|
||||
let fakeConfigurationService = {
|
||||
saveConfiguration: () => of(null),
|
||||
testLDAPServer: () => of(null),
|
||||
testOIDCServer: () => of(null)
|
||||
testOIDCServer: () => of(null),
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
load: () => of(null)
|
||||
load: () => of(null),
|
||||
};
|
||||
const fakeConfigService = {
|
||||
config: new Configuration(),
|
||||
|
@ -38,33 +38,35 @@ describe('ConfigurationAuthComponent', () => {
|
|||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
resetConfig() {
|
||||
}
|
||||
updateConfig() {},
|
||||
resetConfig() {},
|
||||
};
|
||||
let fakeSystemInfoService = {
|
||||
getSystemInfo: function () {
|
||||
return of({
|
||||
external_url: "expectedUrl"
|
||||
external_url: 'expectedUrl',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [ConfigurationAuthComponent],
|
||||
providers: [
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{ provide: ConfigurationService, useValue: fakeConfigurationService },
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: fakeMessageHandlerService,
|
||||
},
|
||||
{
|
||||
provide: ConfigurationService,
|
||||
useValue: fakeConfigurationService,
|
||||
},
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
{ provide: SystemInfoService, useValue: fakeSystemInfoService }
|
||||
{ provide: SystemInfoService, useValue: fakeSystemInfoService },
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
|
|
@ -16,20 +16,23 @@ import { NgForm } from '@angular/forms';
|
|||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { SystemInfoService } from "../../../../shared/services";
|
||||
import { isEmpty, getChanges as getChangesFunc } from "../../../../shared/units/utils";
|
||||
import { CONFIG_AUTH_MODE } from "../../../../shared/entities/shared.const";
|
||||
import { errorHandler } from "../../../../shared/units/shared.utils";
|
||||
import { Configuration } from "../config";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { ConfigService } from "../config.service";
|
||||
import { SystemInfoService } from '../../../../shared/services';
|
||||
import {
|
||||
isEmpty,
|
||||
getChanges as getChangesFunc,
|
||||
} from '../../../../shared/units/utils';
|
||||
import { CONFIG_AUTH_MODE } from '../../../../shared/entities/shared.const';
|
||||
import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
import { Configuration } from '../config';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { ConfigService } from '../config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'config-auth',
|
||||
templateUrl: 'config-auth.component.html',
|
||||
styleUrls: ['./config-auth.component.scss', '../config.component.scss']
|
||||
styleUrls: ['./config-auth.component.scss', '../config.component.scss'],
|
||||
})
|
||||
export class ConfigurationAuthComponent implements OnInit {
|
||||
export class ConfigurationAuthComponent implements OnInit {
|
||||
testingOnGoing = false;
|
||||
onGoing = false;
|
||||
redirectUrl: string;
|
||||
|
@ -48,46 +51,68 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
private configService: ConfigurationService,
|
||||
private appConfigService: AppConfigService,
|
||||
private conf: ConfigService,
|
||||
private systemInfo: SystemInfoService,
|
||||
) {
|
||||
}
|
||||
private systemInfo: SystemInfoService
|
||||
) {}
|
||||
ngOnInit() {
|
||||
this.conf.resetConfig();
|
||||
this.getSystemInfo();
|
||||
}
|
||||
getSystemInfo(): void {
|
||||
this.systemInfo.getSystemInfo()
|
||||
.subscribe(systemInfo => (this.redirectUrl = systemInfo.external_url)
|
||||
, error => this.msgHandler.error(error));
|
||||
this.systemInfo.getSystemInfo().subscribe(
|
||||
systemInfo => (this.redirectUrl = systemInfo.external_url),
|
||||
error => this.msgHandler.error(error)
|
||||
);
|
||||
}
|
||||
get checkable() {
|
||||
return this.currentConfig &&
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.self_registration &&
|
||||
this.currentConfig.self_registration.value === true;
|
||||
this.currentConfig.self_registration.value === true
|
||||
);
|
||||
}
|
||||
public get showLdap(): boolean {
|
||||
return this.currentConfig &&
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.auth_mode &&
|
||||
this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.LDAP_AUTH;
|
||||
this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.LDAP_AUTH
|
||||
);
|
||||
}
|
||||
|
||||
public get showUAA(): boolean {
|
||||
return this.currentConfig && this.currentConfig.auth_mode && this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.UAA_AUTH;
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.auth_mode &&
|
||||
this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.UAA_AUTH
|
||||
);
|
||||
}
|
||||
public get showOIDC(): boolean {
|
||||
return this.currentConfig && this.currentConfig.auth_mode && this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.auth_mode &&
|
||||
this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.OIDC_AUTH
|
||||
);
|
||||
}
|
||||
public get showHttpAuth(): boolean {
|
||||
return this.currentConfig && this.currentConfig.auth_mode && this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.HTTP_AUTH;
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.auth_mode &&
|
||||
this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.HTTP_AUTH
|
||||
);
|
||||
}
|
||||
public get showSelfReg(): boolean {
|
||||
if (!this.currentConfig || !this.currentConfig.auth_mode) {
|
||||
return true;
|
||||
} else {
|
||||
return this.currentConfig.auth_mode.value !== CONFIG_AUTH_MODE.LDAP_AUTH
|
||||
&& this.currentConfig.auth_mode.value !== CONFIG_AUTH_MODE.UAA_AUTH
|
||||
&& this.currentConfig.auth_mode.value !== CONFIG_AUTH_MODE.HTTP_AUTH
|
||||
&& this.currentConfig.auth_mode.value !== CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
return (
|
||||
this.currentConfig.auth_mode.value !==
|
||||
CONFIG_AUTH_MODE.LDAP_AUTH &&
|
||||
this.currentConfig.auth_mode.value !==
|
||||
CONFIG_AUTH_MODE.UAA_AUTH &&
|
||||
this.currentConfig.auth_mode.value !==
|
||||
CONFIG_AUTH_MODE.HTTP_AUTH &&
|
||||
this.currentConfig.auth_mode.value !==
|
||||
CONFIG_AUTH_MODE.OIDC_AUTH
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +139,6 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
|
||||
let settings = {};
|
||||
if (this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.LDAP_AUTH) {
|
||||
|
||||
for (let prop in this.currentConfig) {
|
||||
if (prop.startsWith('ldap_')) {
|
||||
settings[prop] = this.currentConfig[prop].value;
|
||||
|
@ -134,21 +158,32 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
// Fix: Confirm ldap scope is number
|
||||
settings['ldap_scope'] = +settings['ldap_scope'];
|
||||
|
||||
this.configService.testLDAPServer(settings)
|
||||
.pipe(finalize(() => this.testingOnGoing = false))
|
||||
.subscribe(res => {
|
||||
if (res && res.success) {
|
||||
this.msgHandler.showSuccess('CONFIG.TEST_LDAP_SUCCESS');
|
||||
} else if (res && res.message) {
|
||||
this.msgHandler.showError('CONFIG.TEST_LDAP_FAILED', { 'param': res.message });
|
||||
this.configService
|
||||
.testLDAPServer(settings)
|
||||
.pipe(finalize(() => (this.testingOnGoing = false)))
|
||||
.subscribe(
|
||||
res => {
|
||||
if (res && res.success) {
|
||||
this.msgHandler.showSuccess(
|
||||
'CONFIG.TEST_LDAP_SUCCESS'
|
||||
);
|
||||
} else if (res && res.message) {
|
||||
this.msgHandler.showError(
|
||||
'CONFIG.TEST_LDAP_FAILED',
|
||||
{ param: res.message }
|
||||
);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
let err = errorHandler(error);
|
||||
if (!err || !err.trim()) {
|
||||
err = 'UNKNOWN';
|
||||
}
|
||||
this.msgHandler.showError('CONFIG.TEST_LDAP_FAILED', {
|
||||
param: err,
|
||||
});
|
||||
}
|
||||
}, error => {
|
||||
let err = errorHandler(error);
|
||||
if (!err || !err.trim()) {
|
||||
err = 'UNKNOWN';
|
||||
}
|
||||
this.msgHandler.showError('CONFIG.TEST_LDAP_FAILED', { 'param': err });
|
||||
});
|
||||
);
|
||||
} else {
|
||||
for (let prop in this.currentConfig) {
|
||||
if (prop === 'oidc_endpoint') {
|
||||
|
@ -158,42 +193,51 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
this.testingOnGoing = true;
|
||||
this.configService.testOIDCServer(settings)
|
||||
.subscribe(respone => {
|
||||
this.configService.testOIDCServer(settings).subscribe(
|
||||
respone => {
|
||||
this.testingOnGoing = false;
|
||||
this.msgHandler.showSuccess('CONFIG.TEST_OIDC_SUCCESS');
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.testingOnGoing = false;
|
||||
this.msgHandler.error(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public get showTestingServerBtn(): boolean {
|
||||
return this.currentConfig.auth_mode &&
|
||||
(this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.LDAP_AUTH
|
||||
|| this.currentConfig.auth_mode.value === CONFIG_AUTH_MODE.OIDC_AUTH);
|
||||
return (
|
||||
this.currentConfig.auth_mode &&
|
||||
(this.currentConfig.auth_mode.value ===
|
||||
CONFIG_AUTH_MODE.LDAP_AUTH ||
|
||||
this.currentConfig.auth_mode.value ===
|
||||
CONFIG_AUTH_MODE.OIDC_AUTH)
|
||||
);
|
||||
}
|
||||
|
||||
public isConfigValidForTesting(): boolean {
|
||||
if (!this.authForm || !this.currentConfig) {
|
||||
return true;
|
||||
}
|
||||
return this.isValid() &&
|
||||
!this.testingOnGoing && !this.inProcess();
|
||||
return this.isValid() && !this.testingOnGoing && !this.inProcess();
|
||||
}
|
||||
|
||||
public getChanges() {
|
||||
let allChanges = getChangesFunc(this.conf.getOriginalConfig(), this.currentConfig);
|
||||
let allChanges = getChangesFunc(
|
||||
this.conf.getOriginalConfig(),
|
||||
this.currentConfig
|
||||
);
|
||||
let changes = {};
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith('ldap_')
|
||||
|| prop.startsWith('uaa_')
|
||||
|| prop.startsWith('oidc_')
|
||||
|| prop === 'auth_mode'
|
||||
|| prop === 'project_creattion_restriction'
|
||||
|| prop === 'self_registration'
|
||||
|| prop.startsWith('http_')
|
||||
if (
|
||||
prop.startsWith('ldap_') ||
|
||||
prop.startsWith('uaa_') ||
|
||||
prop.startsWith('oidc_') ||
|
||||
prop === 'auth_mode' ||
|
||||
prop === 'project_creattion_restriction' ||
|
||||
prop === 'self_registration' ||
|
||||
prop.startsWith('http_')
|
||||
) {
|
||||
changes[prop] = allChanges[prop];
|
||||
}
|
||||
|
@ -210,10 +254,14 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
}
|
||||
|
||||
handleOnChange($event: any): void {
|
||||
if ($event && $event.target && $event.target["value"]) {
|
||||
let authMode = $event.target["value"];
|
||||
if (authMode === CONFIG_AUTH_MODE.LDAP_AUTH || authMode === CONFIG_AUTH_MODE.UAA_AUTH || authMode === CONFIG_AUTH_MODE.HTTP_AUTH
|
||||
|| authMode === CONFIG_AUTH_MODE.OIDC_AUTH) {
|
||||
if ($event && $event.target && $event.target['value']) {
|
||||
let authMode = $event.target['value'];
|
||||
if (
|
||||
authMode === CONFIG_AUTH_MODE.LDAP_AUTH ||
|
||||
authMode === CONFIG_AUTH_MODE.UAA_AUTH ||
|
||||
authMode === CONFIG_AUTH_MODE.HTTP_AUTH ||
|
||||
authMode === CONFIG_AUTH_MODE.OIDC_AUTH
|
||||
) {
|
||||
if (this.currentConfig.self_registration.value) {
|
||||
this.currentConfig.self_registration.value = false; // unselect
|
||||
}
|
||||
|
@ -222,27 +270,35 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Save the changed values
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
*
|
||||
* Save the changed values
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
public save(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!isEmpty(changes)) {
|
||||
this.onGoing = true;
|
||||
this.configService.saveConfiguration(changes)
|
||||
.subscribe(response => {
|
||||
this.configService.saveConfiguration(changes).subscribe(
|
||||
response => {
|
||||
this.onGoing = false;
|
||||
this.conf.updateConfig();
|
||||
// Reload bootstrap option
|
||||
this.appConfigService.load().subscribe(() => { }
|
||||
, error => console.error('Failed to reload bootstrap option with error: ', error));
|
||||
this.appConfigService.load().subscribe(
|
||||
() => {},
|
||||
error =>
|
||||
console.error(
|
||||
'Failed to reload bootstrap option with error: ',
|
||||
error
|
||||
)
|
||||
);
|
||||
this.msgHandler.showSuccess('CONFIG.SAVE_SUCCESS');
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.onGoing = false;
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Inprop situation, should not come here
|
||||
console.error('Save abort because nothing changed');
|
||||
|
@ -266,7 +322,7 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
}
|
||||
changeAutoOnBoard() {
|
||||
if (!this.currentConfig.oidc_auto_onboard.value) {
|
||||
this.currentConfig.oidc_user_claim.value = "";
|
||||
this.currentConfig.oidc_user_claim.value = '';
|
||||
}
|
||||
}
|
||||
trimSpace(e: any) {
|
||||
|
@ -274,7 +330,7 @@ export class ConfigurationAuthComponent implements OnInit {
|
|||
if (e.target.value) {
|
||||
e.target.value = e.target.value.trim();
|
||||
} else {
|
||||
e.target.value = "";
|
||||
e.target.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,41 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="custom-h2 config-title">{{'CONFIG.TITLE' | translate }}<span class="spinner spinner-inline ml-1 v-mid" [hidden]="inProgress === false"></span></h2>
|
||||
<h2 class="custom-h2 config-title">
|
||||
{{ 'CONFIG.TITLE' | translate
|
||||
}}<span
|
||||
class="spinner spinner-inline ml-1 v-mid"
|
||||
[hidden]="inProgress === false"></span>
|
||||
</h2>
|
||||
<ul class="nav" role="tablist">
|
||||
<li role="presentation" class="nav-item" >
|
||||
<button id="config-auth" class="btn btn-link nav-link" type="button"
|
||||
routerLink="auth"
|
||||
routerLinkActive="active">{{'CONFIG.AUTH' | translate}}</button>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button
|
||||
id="config-auth"
|
||||
class="btn btn-link nav-link"
|
||||
type="button"
|
||||
routerLink="auth"
|
||||
routerLinkActive="active">
|
||||
{{ 'CONFIG.AUTH' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item" >
|
||||
<button id="config-email" class="btn btn-link nav-link" type="button"
|
||||
routerLink="email"
|
||||
routerLinkActive="active">{{'CONFIG.EMAIL' | translate}}</button>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button
|
||||
id="config-email"
|
||||
class="btn btn-link nav-link"
|
||||
type="button"
|
||||
routerLink="email"
|
||||
routerLinkActive="active">
|
||||
{{ 'CONFIG.EMAIL' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item" >
|
||||
<button id="config-system" class="btn btn-link nav-link" type="button"
|
||||
routerLink="setting"
|
||||
routerLinkActive="active">{{'CONFIG.SYSTEM' | translate}}</button>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button
|
||||
id="config-system"
|
||||
class="btn btn-link nav-link"
|
||||
type="button"
|
||||
routerLink="setting"
|
||||
routerLinkActive="active">
|
||||
{{ 'CONFIG.SYSTEM' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ConfigurationComponent } from './config.component';
|
||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
||||
import { ConfigService } from "./config.service";
|
||||
import { Configuration } from "./config";
|
||||
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||
import { ConfigService } from './config.service';
|
||||
import { Configuration } from './config';
|
||||
|
||||
describe('ConfigurationComponent', () => {
|
||||
let component: ConfigurationComponent;
|
||||
|
@ -18,27 +18,25 @@ describe('ConfigurationComponent', () => {
|
|||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
initConfig() {
|
||||
}
|
||||
updateConfig() {},
|
||||
initConfig() {},
|
||||
};
|
||||
let initSpy: jasmine.Spy;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
imports: [SharedTestingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [ConfigurationComponent],
|
||||
providers: [
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
]
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
initSpy = spyOn(fakeConfigService, "initConfig").and.returnValue(undefined);
|
||||
initSpy = spyOn(fakeConfigService, 'initConfig').and.returnValue(
|
||||
undefined
|
||||
);
|
||||
fixture = TestBed.createComponent(ConfigurationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
|
|
@ -12,24 +12,22 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ConfigService } from "./config.service";
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'config',
|
||||
templateUrl: 'config.component.html',
|
||||
styleUrls: ['config.component.scss']
|
||||
styleUrls: ['config.component.scss'],
|
||||
})
|
||||
export class ConfigurationComponent implements OnInit {
|
||||
get inProgress(): boolean {
|
||||
return this.conf.getLoadingConfigStatus();
|
||||
}
|
||||
|
||||
constructor(private conf: ConfigService) {
|
||||
}
|
||||
constructor(private conf: ConfigService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// First load
|
||||
this.conf.initConfig();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from "@angular/core";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { ConfigurationComponent } from "./config.component";
|
||||
import { ConfigurationAuthComponent } from "./auth/config-auth.component";
|
||||
import { ConfigurationEmailComponent } from "./email/config-email.component";
|
||||
import { SystemSettingsComponent } from "./system/system-settings.component";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { ConfigService } from "./config.service";
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { ConfigurationComponent } from './config.component';
|
||||
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
import { SystemSettingsComponent } from './system/system-settings.component';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -28,38 +28,32 @@ const routes: Routes = [
|
|||
children: [
|
||||
{
|
||||
path: 'auth',
|
||||
component: ConfigurationAuthComponent
|
||||
component: ConfigurationAuthComponent,
|
||||
},
|
||||
{
|
||||
path: 'email',
|
||||
component: ConfigurationEmailComponent
|
||||
component: ConfigurationEmailComponent,
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
component: SystemSettingsComponent
|
||||
component: SystemSettingsComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'auth',
|
||||
pathMatch: 'full'
|
||||
}
|
||||
]
|
||||
}
|
||||
pathMatch: 'full',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
imports: [SharedModule, RouterModule.forChild(routes)],
|
||||
declarations: [
|
||||
ConfigurationComponent,
|
||||
ConfigurationAuthComponent,
|
||||
ConfigurationEmailComponent,
|
||||
SystemSettingsComponent
|
||||
SystemSettingsComponent,
|
||||
],
|
||||
providers: [
|
||||
ConfigService,
|
||||
]
|
||||
providers: [ConfigService],
|
||||
})
|
||||
export class ConfigurationModule {
|
||||
}
|
||||
export class ConfigurationModule {}
|
||||
|
|
|
@ -1,41 +1,48 @@
|
|||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { ConfigureService } from 'ng-swagger-gen/services/configure.service';
|
||||
import { of } from 'rxjs';
|
||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
||||
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||
import { Configuration } from './config';
|
||||
import { ConfigService } from "./config.service";
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
describe('ConfigService', () => {
|
||||
const fakedConfigureService = {
|
||||
getConfigurations(): any {
|
||||
return of(null);
|
||||
}
|
||||
};
|
||||
let getConfigSpy: jasmine.Spy;
|
||||
beforeEach(() => {
|
||||
getConfigSpy = spyOn(fakedConfigureService, 'getConfigurations').and.returnValue(of(new Configuration()));
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
providers: [
|
||||
ConfigService,
|
||||
{ provide: ConfigureService, useValue: fakedConfigureService },
|
||||
]
|
||||
const fakedConfigureService = {
|
||||
getConfigurations(): any {
|
||||
return of(null);
|
||||
},
|
||||
};
|
||||
let getConfigSpy: jasmine.Spy;
|
||||
beforeEach(() => {
|
||||
getConfigSpy = spyOn(
|
||||
fakedConfigureService,
|
||||
'getConfigurations'
|
||||
).and.returnValue(of(new Configuration()));
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
providers: [
|
||||
ConfigService,
|
||||
{ provide: ConfigureService, useValue: fakedConfigureService },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([ConfigService], (service: ConfigService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
it('should be created', inject(
|
||||
[ConfigService],
|
||||
(service: ConfigService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}
|
||||
));
|
||||
|
||||
it('should init config', inject([ConfigService], (service: ConfigService) => {
|
||||
expect(getConfigSpy.calls.count()).toEqual(0);
|
||||
service.initConfig();
|
||||
expect(getConfigSpy.calls.count()).toEqual(1);
|
||||
// only init once
|
||||
service.initConfig();
|
||||
expect(getConfigSpy.calls.count()).toEqual(1);
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
it('should init config', inject(
|
||||
[ConfigService],
|
||||
(service: ConfigService) => {
|
||||
expect(getConfigSpy.calls.count()).toEqual(0);
|
||||
service.initConfig();
|
||||
expect(getConfigSpy.calls.count()).toEqual(1);
|
||||
// only init once
|
||||
service.initConfig();
|
||||
expect(getConfigSpy.calls.count()).toEqual(1);
|
||||
expect(service).toBeTruthy();
|
||||
}
|
||||
));
|
||||
});
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationState, ConfirmationTargets } from "../../../shared/entities/shared.const";
|
||||
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
||||
import { Configuration, StringValueItem } from "./config";
|
||||
import { ConfirmationDialogService } from '../../global-confirmation-dialog/confirmation-dialog.service';
|
||||
import {
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
} from '../../../shared/entities/shared.const';
|
||||
import { ConfirmationMessage } from '../../global-confirmation-dialog/confirmation-message';
|
||||
import { Configuration, StringValueItem } from './config';
|
||||
import { ConfigureService } from 'ng-swagger-gen/services/configure.service';
|
||||
import { clone } from "../../../shared/units/utils";
|
||||
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { Subscription } from "rxjs";
|
||||
import { clone } from '../../../shared/units/utils';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
const fakePass = 'aWpLOSYkIzJTTU4wMDkx';
|
||||
|
||||
|
@ -19,15 +22,21 @@ export class ConfigService {
|
|||
private _currentConfig: Configuration = new Configuration();
|
||||
private _originalConfig: Configuration;
|
||||
|
||||
constructor(private confirmService: ConfirmationDialogService,
|
||||
private configureService: ConfigureService,
|
||||
private msgHandler: MessageHandlerService) {
|
||||
this._confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||
if (confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED) {
|
||||
this.resetConfig();
|
||||
constructor(
|
||||
private confirmService: ConfirmationDialogService,
|
||||
private configureService: ConfigureService,
|
||||
private msgHandler: MessageHandlerService
|
||||
) {
|
||||
this._confirmSub = this.confirmService.confirmationConfirm$.subscribe(
|
||||
confirmation => {
|
||||
if (
|
||||
confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED
|
||||
) {
|
||||
this.resetConfig();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
getConfig(): Configuration {
|
||||
|
@ -38,7 +47,6 @@ export class ConfigService {
|
|||
this._currentConfig = c;
|
||||
}
|
||||
|
||||
|
||||
getOriginalConfig(): Configuration {
|
||||
return this._originalConfig;
|
||||
}
|
||||
|
@ -60,26 +68,38 @@ export class ConfigService {
|
|||
|
||||
updateConfig() {
|
||||
this._loadingConfig = true;
|
||||
this.configureService.getConfigurations()
|
||||
.pipe(finalize(() => this._loadingConfig = false))
|
||||
.subscribe(res => {
|
||||
this._currentConfig = res as Configuration;
|
||||
// Add password fields
|
||||
this._currentConfig.email_password = new StringValueItem(fakePass, true);
|
||||
this._currentConfig.ldap_search_password = new StringValueItem(fakePass, true);
|
||||
this._currentConfig.uaa_client_secret = new StringValueItem(fakePass, true);
|
||||
this._currentConfig.oidc_client_secret = new StringValueItem(fakePass, true);
|
||||
// Keep the original copy of the data
|
||||
this._originalConfig = clone(this._currentConfig);
|
||||
// Handle read only
|
||||
if (this._originalConfig?.read_only?.value) {
|
||||
this.msgHandler.handleReadOnly();
|
||||
} else {
|
||||
this.msgHandler.clear();
|
||||
this.configureService
|
||||
.getConfigurations()
|
||||
.pipe(finalize(() => (this._loadingConfig = false)))
|
||||
.subscribe(
|
||||
res => {
|
||||
this._currentConfig = res as Configuration;
|
||||
// Add password fields
|
||||
this._currentConfig.email_password = new StringValueItem(
|
||||
fakePass,
|
||||
true
|
||||
);
|
||||
this._currentConfig.ldap_search_password =
|
||||
new StringValueItem(fakePass, true);
|
||||
this._currentConfig.uaa_client_secret = new StringValueItem(
|
||||
fakePass,
|
||||
true
|
||||
);
|
||||
this._currentConfig.oidc_client_secret =
|
||||
new StringValueItem(fakePass, true);
|
||||
// Keep the original copy of the data
|
||||
this._originalConfig = clone(this._currentConfig);
|
||||
// Handle read only
|
||||
if (this._originalConfig?.read_only?.value) {
|
||||
this.msgHandler.handleReadOnly();
|
||||
} else {
|
||||
this.msgHandler.clear();
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.msgHandler.handleError(error);
|
||||
}
|
||||
}, error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
resetConfig() {
|
||||
|
@ -99,4 +119,3 @@ export class ConfigService {
|
|||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ export class ComplexValueItem {
|
|||
}
|
||||
|
||||
export class Configuration {
|
||||
[key: string]: any | any[]
|
||||
[key: string]: any | any[];
|
||||
auth_mode: StringValueItem;
|
||||
project_creation_restriction: StringValueItem;
|
||||
self_registration: BoolValueItem;
|
||||
|
@ -109,52 +109,61 @@ export class Configuration {
|
|||
oidc_groups_claim: StringValueItem;
|
||||
oidc_admin_group: StringValueItem;
|
||||
public constructor() {
|
||||
this.auth_mode = new StringValueItem("db_auth", true);
|
||||
this.project_creation_restriction = new StringValueItem("everyone", true);
|
||||
this.auth_mode = new StringValueItem('db_auth', true);
|
||||
this.project_creation_restriction = new StringValueItem(
|
||||
'everyone',
|
||||
true
|
||||
);
|
||||
this.self_registration = new BoolValueItem(false, true);
|
||||
this.ldap_base_dn = new StringValueItem("", true);
|
||||
this.ldap_filter = new StringValueItem("", true);
|
||||
this.ldap_base_dn = new StringValueItem('', true);
|
||||
this.ldap_filter = new StringValueItem('', true);
|
||||
this.ldap_scope = new NumberValueItem(0, true);
|
||||
this.ldap_search_dn = new StringValueItem("", true);
|
||||
this.ldap_search_password = new StringValueItem("", true);
|
||||
this.ldap_search_dn = new StringValueItem('', true);
|
||||
this.ldap_search_password = new StringValueItem('', true);
|
||||
this.ldap_timeout = new NumberValueItem(5, true);
|
||||
this.ldap_uid = new StringValueItem("", true);
|
||||
this.ldap_url = new StringValueItem("", true);
|
||||
this.ldap_uid = new StringValueItem('', true);
|
||||
this.ldap_url = new StringValueItem('', true);
|
||||
this.ldap_verify_cert = new BoolValueItem(true, true);
|
||||
this.ldap_group_base_dn = new StringValueItem("", true);
|
||||
this.ldap_group_search_filter = new StringValueItem("", true);
|
||||
this.ldap_group_attribute_name = new StringValueItem("", true);
|
||||
this.ldap_group_base_dn = new StringValueItem('', true);
|
||||
this.ldap_group_search_filter = new StringValueItem('', true);
|
||||
this.ldap_group_attribute_name = new StringValueItem('', true);
|
||||
this.ldap_group_search_scope = new NumberValueItem(0, true);
|
||||
this.ldap_group_membership_attribute = new StringValueItem("", true);
|
||||
this.ldap_group_admin_dn = new StringValueItem("", true);
|
||||
this.uaa_client_id = new StringValueItem("", true);
|
||||
this.uaa_client_secret = new StringValueItem("", true);
|
||||
this.uaa_endpoint = new StringValueItem("", true);
|
||||
this.ldap_group_membership_attribute = new StringValueItem('', true);
|
||||
this.ldap_group_admin_dn = new StringValueItem('', true);
|
||||
this.uaa_client_id = new StringValueItem('', true);
|
||||
this.uaa_client_secret = new StringValueItem('', true);
|
||||
this.uaa_endpoint = new StringValueItem('', true);
|
||||
this.uaa_verify_cert = new BoolValueItem(false, true);
|
||||
this.email_host = new StringValueItem("", true);
|
||||
this.email_identity = new StringValueItem("", true);
|
||||
this.email_from = new StringValueItem("", true);
|
||||
this.email_host = new StringValueItem('', true);
|
||||
this.email_identity = new StringValueItem('', true);
|
||||
this.email_from = new StringValueItem('', true);
|
||||
this.email_port = new NumberValueItem(25, true);
|
||||
this.email_ssl = new BoolValueItem(false, true);
|
||||
this.email_username = new StringValueItem("", true);
|
||||
this.email_password = new StringValueItem("", true);
|
||||
this.email_username = new StringValueItem('', true);
|
||||
this.email_password = new StringValueItem('', true);
|
||||
this.email_insecure = new BoolValueItem(false, true);
|
||||
this.token_expiration = new NumberValueItem(30, true);
|
||||
this.robot_name_prefix = new StringValueItem("", true);
|
||||
this.robot_name_prefix = new StringValueItem('', true);
|
||||
this.robot_token_duration = new NumberValueItem(30, true);
|
||||
this.cfg_expiration = new NumberValueItem(30, true);
|
||||
this.verify_remote_cert = new BoolValueItem(false, true);
|
||||
this.scan_all_policy = new ComplexValueItem({
|
||||
type: "daily",
|
||||
parameter: {
|
||||
daily_time: 0
|
||||
}
|
||||
}, true);
|
||||
this.scan_all_policy = new ComplexValueItem(
|
||||
{
|
||||
type: 'daily',
|
||||
parameter: {
|
||||
daily_time: 0,
|
||||
},
|
||||
},
|
||||
true
|
||||
);
|
||||
this.read_only = new BoolValueItem(false, true);
|
||||
this.notification_enable = new BoolValueItem(false, true);
|
||||
this.http_authproxy_admin_groups = new StringValueItem("", true);
|
||||
this.http_authproxy_endpoint = new StringValueItem("", true);
|
||||
this.http_authproxy_tokenreview_endpoint = new StringValueItem("", true);
|
||||
this.http_authproxy_admin_groups = new StringValueItem('', true);
|
||||
this.http_authproxy_endpoint = new StringValueItem('', true);
|
||||
this.http_authproxy_tokenreview_endpoint = new StringValueItem(
|
||||
'',
|
||||
true
|
||||
);
|
||||
this.http_authproxy_verify_cert = new BoolValueItem(false, true);
|
||||
this.http_authproxy_skip_search = new BoolValueItem(false, true);
|
||||
this.oidc_name = new StringValueItem('', true);
|
||||
|
@ -183,7 +192,7 @@ export class ScanningMetrics {
|
|||
ongoing: boolean;
|
||||
}
|
||||
export enum Triggers {
|
||||
MANUAL= 'Manual',
|
||||
MANUAL = 'Manual',
|
||||
SCHEDULE = 'Schedule',
|
||||
EVENT = 'Event'
|
||||
EVENT = 'Event',
|
||||
}
|
||||
|
|
|
@ -1,86 +1,184 @@
|
|||
<form #mailConfigFrom="ngForm" class="clr-form clr-form-horizontal">
|
||||
<section class="form-block">
|
||||
<clr-input-container>
|
||||
<label for="mailServer" class="required">{{'CONFIG.MAIL_SERVER' | translate}}</label>
|
||||
<input clrInput name="mailServer" type="text" #mailServerInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_host.value" required id="mailServer" size="40"
|
||||
<label for="mailServer" class="required">{{
|
||||
'CONFIG.MAIL_SERVER' | translate
|
||||
}}</label>
|
||||
<input
|
||||
clrInput
|
||||
name="mailServer"
|
||||
type="text"
|
||||
#mailServerInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_host.value"
|
||||
required
|
||||
id="mailServer"
|
||||
size="40"
|
||||
[disabled]="disabled(currentConfig.email_host)" />
|
||||
<clr-control-error>{{'TOOLTIP.ITEM_REQUIRED' | translate}}</clr-control-error>
|
||||
<clr-control-error>{{
|
||||
'TOOLTIP.ITEM_REQUIRED' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label for="emailPort" class="required">{{'CONFIG.MAIL_SERVER_PORT' | translate}}</label>
|
||||
<input clrInput name="emailPort" type="text" #emailPortInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_port.value" required port id="emailPort" size="40"
|
||||
<label for="emailPort" class="required">{{
|
||||
'CONFIG.MAIL_SERVER_PORT' | translate
|
||||
}}</label>
|
||||
<input
|
||||
clrInput
|
||||
name="emailPort"
|
||||
type="text"
|
||||
#emailPortInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_port.value"
|
||||
required
|
||||
port
|
||||
id="emailPort"
|
||||
size="40"
|
||||
[disabled]="disabled(currentConfig.email_port)" />
|
||||
<clr-control-error>{{'TOOLTIP.PORT_REQUIRED' | translate}}</clr-control-error>
|
||||
<clr-control-error>{{
|
||||
'TOOLTIP.PORT_REQUIRED' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label for="emailUsername">{{'CONFIG.MAIL_USERNAME' | translate}}</label>
|
||||
<input clrInput name="emailUsername" type="text" #emailUsernameInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_username.value" id="emailUsername" size="40"
|
||||
<label for="emailUsername">{{
|
||||
'CONFIG.MAIL_USERNAME' | translate
|
||||
}}</label>
|
||||
<input
|
||||
clrInput
|
||||
name="emailUsername"
|
||||
type="text"
|
||||
#emailUsernameInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_username.value"
|
||||
id="emailUsername"
|
||||
size="40"
|
||||
[disabled]="disabled(currentConfig.email_username)" />
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label for="emailPassword">{{'CONFIG.MAIL_PASSWORD' | translate}}</label>
|
||||
<label for="emailPassword">{{
|
||||
'CONFIG.MAIL_PASSWORD' | translate
|
||||
}}</label>
|
||||
<!--disable property should be consistent with 'email_username', as the back end API won't return this fielder, -->
|
||||
<input clrInput name="emailPassword" type="password" #emailPasswordInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_password.value" id="emailPassword" size="40"
|
||||
<input
|
||||
clrInput
|
||||
name="emailPassword"
|
||||
type="password"
|
||||
#emailPasswordInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_password.value"
|
||||
id="emailPassword"
|
||||
size="40"
|
||||
[disabled]="disabled(currentConfig.email_username)" />
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label for="emailFrom" class="required">{{'CONFIG.MAIL_FROM' | translate}}</label>
|
||||
<input clrInput name="emailFrom" type="text" #emailFromInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_from.value" required id="emailFrom" size="40"
|
||||
<label for="emailFrom" class="required">{{
|
||||
'CONFIG.MAIL_FROM' | translate
|
||||
}}</label>
|
||||
<input
|
||||
clrInput
|
||||
name="emailFrom"
|
||||
type="text"
|
||||
#emailFromInput="ngModel"
|
||||
[(ngModel)]="currentConfig.email_from.value"
|
||||
required
|
||||
id="emailFrom"
|
||||
size="40"
|
||||
[disabled]="disabled(currentConfig.email_from)" />
|
||||
<clr-control-error>{{'TOOLTIP.ITEM_REQUIRED' | translate}}</clr-control-error>
|
||||
<clr-control-error>{{
|
||||
'TOOLTIP.ITEM_REQUIRED' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control" [class.clr-form-control-disabled]="disabled(currentConfig.email_ssl)">
|
||||
<label class="clr-control-label" for="emailSSL">{{'CONFIG.MAIL_SSL' | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.SSL_TOOLTIP' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
<div
|
||||
class="clr-form-control"
|
||||
[class.clr-form-control-disabled]="
|
||||
disabled(currentConfig.email_ssl)
|
||||
">
|
||||
<label class="clr-control-label" for="emailSSL"
|
||||
>{{ 'CONFIG.MAIL_SSL' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{ 'CONFIG.SSL_TOOLTIP' | translate }}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-checkbox-wrapper" id="emailSSL-wrapper">
|
||||
<input type="checkbox" name="emailSSL" id="emailSSL" [(ngModel)]="currentConfig.email_ssl.value"
|
||||
<input
|
||||
type="checkbox"
|
||||
name="emailSSL"
|
||||
id="emailSSL"
|
||||
[(ngModel)]="currentConfig.email_ssl.value"
|
||||
[disabled]="disabled(currentConfig.email_ssl)" />
|
||||
<label class="clr-control-label" for="emailSSL">
|
||||
|
||||
</label>
|
||||
<label class="clr-control-label" for="emailSSL"> </label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" [class.clr-form-control-disabled]="disabled(currentConfig.email_insecure)">
|
||||
<label class="clr-control-label" for="emailInsecure">{{'CONFIG.MAIL_INSECURE' | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.INSECURE_TOOLTIP' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
<div
|
||||
class="clr-form-control"
|
||||
[class.clr-form-control-disabled]="
|
||||
disabled(currentConfig.email_insecure)
|
||||
">
|
||||
<label class="clr-control-label" for="emailInsecure"
|
||||
>{{ 'CONFIG.MAIL_INSECURE' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{ 'CONFIG.INSECURE_TOOLTIP' | translate }}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-checkbox-wrapper" id="emailInsecure-wrapper">
|
||||
<input type="checkbox" name="emailInsecure" id="emailInsecure"
|
||||
<input
|
||||
type="checkbox"
|
||||
name="emailInsecure"
|
||||
id="emailInsecure"
|
||||
[ngModel]="!currentConfig.email_insecure.value"
|
||||
[disabled]="disabled(currentConfig.email_insecure)"
|
||||
(ngModelChange)="setInsecureValue($event)" />
|
||||
<label class="clr-control-label" for="emailInsecure"></label>
|
||||
<label
|
||||
class="clr-control-label"
|
||||
for="emailInsecure"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<div>
|
||||
<button type="button" id="config_email_save" class="btn btn-primary" (click)="save()"
|
||||
[disabled]="!isValid() || !hasChanges() || inProgress()">{{'BUTTON.SAVE'
|
||||
| translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges() || inProgress()">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
<button type="button" id="ping-test" class="btn btn-outline" (click)="testMailServer()" [disabled]="!isMailConfigValid()">{{'BUTTON.TEST_MAIL'
|
||||
| translate}}</button>
|
||||
<span id="forTestingMail" class="spinner spinner-inline" [hidden]="hideMailTestingSpinner"></span>
|
||||
<button
|
||||
type="button"
|
||||
id="config_email_save"
|
||||
class="btn btn-primary"
|
||||
(click)="save()"
|
||||
[disabled]="!isValid() || !hasChanges() || inProgress()">
|
||||
{{ 'BUTTON.SAVE' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
(click)="cancel()"
|
||||
[disabled]="!isValid() || !hasChanges() || inProgress()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="ping-test"
|
||||
class="btn btn-outline"
|
||||
(click)="testMailServer()"
|
||||
[disabled]="!isMailConfigValid()">
|
||||
{{ 'BUTTON.TEST_MAIL' | translate }}
|
||||
</button>
|
||||
<span
|
||||
id="forTestingMail"
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="hideMailTestingSpinner"></span>
|
||||
</div>
|
||||
|
|
|
@ -4,19 +4,19 @@ import { ConfigurationService } from '../../../../services/config.service';
|
|||
import { ConfigurationEmailComponent } from './config-email.component';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { of } from 'rxjs';
|
||||
import { ConfigService } from "../config.service";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { Configuration } from "../config";
|
||||
import { ConfigService } from '../config.service';
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
import { Configuration } from '../config';
|
||||
|
||||
describe('ConfigurationEmailComponent', () => {
|
||||
let component: ConfigurationEmailComponent;
|
||||
let fixture: ComponentFixture<ConfigurationEmailComponent>;
|
||||
let fakeConfigurationService = {
|
||||
saveConfiguration: () => of(null),
|
||||
testMailServer: () => of(null)
|
||||
testMailServer: () => of(null),
|
||||
};
|
||||
let fakeMessageHandlerService = {
|
||||
showSuccess: () => null
|
||||
showSuccess: () => null,
|
||||
};
|
||||
const fakeConfigService = {
|
||||
config: new Configuration(),
|
||||
|
@ -32,23 +32,25 @@ describe('ConfigurationEmailComponent', () => {
|
|||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
resetConfig() {
|
||||
}
|
||||
updateConfig() {},
|
||||
resetConfig() {},
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [ConfigurationEmailComponent],
|
||||
providers: [
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: fakeMessageHandlerService,
|
||||
},
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
{ provide: ConfigurationService, useValue: fakeConfigurationService }
|
||||
{
|
||||
provide: ConfigurationService,
|
||||
useValue: fakeConfigurationService,
|
||||
},
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
@ -67,7 +69,8 @@ describe('ConfigurationEmailComponent', () => {
|
|||
component.currentConfig.email_port.value = 25;
|
||||
component.currentConfig.email_from.value = 'smtp.mydomain.com';
|
||||
await fixture.whenStable();
|
||||
const configEmailSaveBtn: HTMLButtonElement = fixture.nativeElement.querySelector("#config_email_save");
|
||||
const configEmailSaveBtn: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#config_email_save');
|
||||
component.onGoing = true;
|
||||
configEmailSaveBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
|
@ -78,7 +81,7 @@ describe('ConfigurationEmailComponent', () => {
|
|||
component.currentConfig.email_port.value = 25;
|
||||
component.currentConfig.email_from.value = 'smtp.mydomain.com';
|
||||
await fixture.whenStable();
|
||||
const pingTestBtn = fixture.nativeElement.querySelector("#ping-test");
|
||||
const pingTestBtn = fixture.nativeElement.querySelector('#ping-test');
|
||||
expect(pingTestBtn).toBeTruthy();
|
||||
pingTestBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
|
|
|
@ -15,20 +15,23 @@ import { Component, OnInit, ViewChild } from '@angular/core';
|
|||
import { NgForm } from '@angular/forms';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { Configuration } from "../config";
|
||||
import { isEmpty, getChanges as getChangesFunc } from "../../../../shared/units/utils";
|
||||
import { errorHandler } from "../../../../shared/units/shared.utils";
|
||||
import { ConfigService } from "../config.service";
|
||||
import { Configuration } from '../config';
|
||||
import {
|
||||
isEmpty,
|
||||
getChanges as getChangesFunc,
|
||||
} from '../../../../shared/units/utils';
|
||||
import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
import { ConfigService } from '../config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'config-email',
|
||||
templateUrl: "config-email.component.html",
|
||||
styleUrls: ['./config-email.component.scss', '../config.component.scss']
|
||||
templateUrl: 'config-email.component.html',
|
||||
styleUrls: ['./config-email.component.scss', '../config.component.scss'],
|
||||
})
|
||||
export class ConfigurationEmailComponent implements OnInit {
|
||||
export class ConfigurationEmailComponent implements OnInit {
|
||||
testingMailOnGoing = false;
|
||||
onGoing = false;
|
||||
@ViewChild("mailConfigFrom", {static: true}) mailForm: NgForm;
|
||||
@ViewChild('mailConfigFrom', { static: true }) mailForm: NgForm;
|
||||
get currentConfig(): Configuration {
|
||||
return this.conf.getConfig();
|
||||
}
|
||||
|
@ -40,8 +43,8 @@ export class ConfigurationEmailComponent implements OnInit {
|
|||
constructor(
|
||||
private msgHandler: MessageHandlerService,
|
||||
private configService: ConfigurationService,
|
||||
private conf: ConfigService) {
|
||||
}
|
||||
private conf: ConfigService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.conf.resetConfig();
|
||||
|
@ -64,11 +67,14 @@ export class ConfigurationEmailComponent implements OnInit {
|
|||
}
|
||||
|
||||
public hasChanges(): boolean {
|
||||
return !isEmpty(this.getChanges());
|
||||
return !isEmpty(this.getChanges());
|
||||
}
|
||||
|
||||
public getChanges() {
|
||||
let allChanges = getChangesFunc(this.conf.getOriginalConfig(), this.currentConfig);
|
||||
let allChanges = getChangesFunc(
|
||||
this.conf.getOriginalConfig(),
|
||||
this.currentConfig
|
||||
);
|
||||
let changes = {};
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith('email_')) {
|
||||
|
@ -77,7 +83,7 @@ export class ConfigurationEmailComponent implements OnInit {
|
|||
}
|
||||
return changes;
|
||||
}
|
||||
/**
|
||||
/**
|
||||
*
|
||||
* Test the connection of specified mail server
|
||||
*
|
||||
|
@ -105,18 +111,22 @@ export class ConfigurationEmailComponent implements OnInit {
|
|||
}
|
||||
|
||||
this.testingMailOnGoing = true;
|
||||
this.configService.testMailServer(mailSettings)
|
||||
.subscribe(response => {
|
||||
this.configService.testMailServer(mailSettings).subscribe(
|
||||
response => {
|
||||
this.testingMailOnGoing = false;
|
||||
this.msgHandler.showSuccess('CONFIG.TEST_MAIL_SUCCESS');
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.testingMailOnGoing = false;
|
||||
let err = errorHandler(error);
|
||||
let err = errorHandler(error);
|
||||
if (!err) {
|
||||
err = 'UNKNOWN';
|
||||
}
|
||||
this.msgHandler.showError('CONFIG.TEST_MAIL_FAILED', { 'param': err });
|
||||
});
|
||||
this.msgHandler.showError('CONFIG.TEST_MAIL_FAILED', {
|
||||
param: err,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public get hideMailTestingSpinner(): boolean {
|
||||
|
@ -124,8 +134,7 @@ export class ConfigurationEmailComponent implements OnInit {
|
|||
}
|
||||
|
||||
public isMailConfigValid(): boolean {
|
||||
return this.isValid() &&
|
||||
!this.testingMailOnGoing && !this.inProgress();
|
||||
return this.isValid() && !this.testingMailOnGoing && !this.inProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,16 +147,18 @@ export class ConfigurationEmailComponent implements OnInit {
|
|||
let changes = this.getChanges();
|
||||
if (!isEmpty(changes)) {
|
||||
this.onGoing = true;
|
||||
this.configService.saveConfiguration(changes)
|
||||
.subscribe(response => {
|
||||
this.configService.saveConfiguration(changes).subscribe(
|
||||
response => {
|
||||
this.onGoing = false;
|
||||
// refresh allConfig
|
||||
this.conf.updateConfig();
|
||||
this.msgHandler.showSuccess('CONFIG.SAVE_SUCCESS');
|
||||
}, error => {
|
||||
},
|
||||
error => {
|
||||
this.onGoing = false;
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Inprop situation, should not come here
|
||||
console.error('Save abort because nothing changed');
|
||||
|
|
|
@ -1,139 +1,284 @@
|
|||
<form #systemConfigFrom="ngForm" class="clr-form clr-form-horizontal">
|
||||
<section>
|
||||
<clr-select-container>
|
||||
<label for="proCreation">{{'CONFIG.PRO_CREATION_RESTRICTION' | translate}}
|
||||
<label for="proCreation"
|
||||
>{{ 'CONFIG.PRO_CREATION_RESTRICTION' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.TOOLTIP.PRO_CREATION_RESTRICTION' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'CONFIG.TOOLTIP.PRO_CREATION_RESTRICTION'
|
||||
| translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<select clrSelect id="proCreation" name="proCreation"
|
||||
<select
|
||||
clrSelect
|
||||
id="proCreation"
|
||||
name="proCreation"
|
||||
[(ngModel)]="currentConfig.project_creation_restriction.value"
|
||||
[disabled]="disabled(currentConfig.project_creation_restriction)">
|
||||
<option value="everyone">{{'CONFIG.PRO_CREATION_EVERYONE' | translate }}</option>
|
||||
<option value="adminonly">{{'CONFIG.PRO_CREATION_ADMIN' | translate }}</option>
|
||||
[disabled]="
|
||||
disabled(currentConfig.project_creation_restriction)
|
||||
">
|
||||
<option value="everyone">
|
||||
{{ 'CONFIG.PRO_CREATION_EVERYONE' | translate }}
|
||||
</option>
|
||||
<option value="adminonly">
|
||||
{{ 'CONFIG.PRO_CREATION_ADMIN' | translate }}
|
||||
</option>
|
||||
</select>
|
||||
</clr-select-container>
|
||||
<clr-input-container>
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}
|
||||
<label for="tokenExpiration" class="required"
|
||||
>{{ 'CONFIG.TOKEN_EXPIRATION' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<input clrInput name="tokenExpiration" type="text" #tokenExpirationInput="ngModel"
|
||||
[(ngModel)]="tokenExpirationValue" required pattern="^[1-9]{1}[0-9]*$"
|
||||
id="tokenExpiration" size="20" [disabled]="!editable" />
|
||||
<clr-control-error>{{'TOOLTIP.NUMBER_REQUIRED' | translate}}</clr-control-error>
|
||||
|
||||
<input
|
||||
clrInput
|
||||
name="tokenExpiration"
|
||||
type="text"
|
||||
#tokenExpirationInput="ngModel"
|
||||
[(ngModel)]="tokenExpirationValue"
|
||||
required
|
||||
pattern="^[1-9]{1}[0-9]*$"
|
||||
id="tokenExpiration"
|
||||
size="20"
|
||||
[disabled]="!editable" />
|
||||
<clr-control-error>{{
|
||||
'TOOLTIP.NUMBER_REQUIRED' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label for="robotNamePrefix" class="required">{{'ROBOT_ACCOUNT.NAME_PREFIX' | translate}}
|
||||
<label for="robotNamePrefix" class="required"
|
||||
>{{ 'ROBOT_ACCOUNT.NAME_PREFIX' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.TOOLTIP.ROBOT_NAME_PREFIX' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'CONFIG.TOOLTIP.ROBOT_NAME_PREFIX' | translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<input clrInput name="robotNamePrefix" type="text"
|
||||
[(ngModel)]="currentConfig.robot_name_prefix.value"
|
||||
required
|
||||
autocomplete="off"
|
||||
id="robotNamePrefix" size="20" [disabled]="!robotNamePrefixEditable()" />
|
||||
<clr-control-error>{{'ROBOT_ACCOUNT.NAME_PREFIX_REQUIRED' | translate}}</clr-control-error>
|
||||
<input
|
||||
clrInput
|
||||
name="robotNamePrefix"
|
||||
type="text"
|
||||
[(ngModel)]="currentConfig.robot_name_prefix.value"
|
||||
required
|
||||
autocomplete="off"
|
||||
id="robotNamePrefix"
|
||||
size="20"
|
||||
[disabled]="!robotNamePrefixEditable()" />
|
||||
<clr-control-error>{{
|
||||
'ROBOT_ACCOUNT.NAME_PREFIX_REQUIRED' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label for="robotTokenExpiration" class="required">{{'ROBOT_ACCOUNT.TOKEN_EXPIRATION' | translate}}
|
||||
<label for="robotTokenExpiration" class="required"
|
||||
>{{ 'ROBOT_ACCOUNT.TOKEN_EXPIRATION' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.TOOLTIP.ROBOT_TOKEN_EXPIRATION' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'CONFIG.TOOLTIP.ROBOT_TOKEN_EXPIRATION' | translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<input clrInput name="robotTokenExpiration" type="text" #robotTokenExpirationInput="ngModel"
|
||||
[(ngModel)]="robotTokenExpirationValue" required
|
||||
pattern="^[1-9]{1}[0-9]*$" id="robotTokenExpiration" size="20" [disabled]="!robotExpirationEditable" />
|
||||
<clr-control-error>{{'ROBOT_ACCOUNT.NUMBER_REQUIRED' | translate}}</clr-control-error>
|
||||
|
||||
<input
|
||||
clrInput
|
||||
name="robotTokenExpiration"
|
||||
type="text"
|
||||
#robotTokenExpirationInput="ngModel"
|
||||
[(ngModel)]="robotTokenExpirationValue"
|
||||
required
|
||||
pattern="^[1-9]{1}[0-9]*$"
|
||||
id="robotTokenExpiration"
|
||||
size="20"
|
||||
[disabled]="!robotExpirationEditable" />
|
||||
<clr-control-error>{{
|
||||
'ROBOT_ACCOUNT.NUMBER_REQUIRED' | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<label *ngIf="canDownloadCert"
|
||||
class="clr-control-label cert-down-label mt-1">{{'CONFIG.ROOT_CERT' | translate}}
|
||||
<label
|
||||
*ngIf="canDownloadCert"
|
||||
class="clr-control-label cert-down-label mt-1"
|
||||
>{{ 'CONFIG.ROOT_CERT' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.TOOLTIP.ROOT_CERT_DOWNLOAD' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'CONFIG.TOOLTIP.ROOT_CERT_DOWNLOAD' | translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
<a rel='noopener noreferrer' #certDownloadLink class="cert-down" [href]="downloadLink" target="_blank">{{'CONFIG.ROOT_CERT_LINK' | translate}}</a>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
#certDownloadLink
|
||||
class="cert-down"
|
||||
[href]="downloadLink"
|
||||
target="_blank"
|
||||
>{{ 'CONFIG.ROOT_CERT_LINK' | translate }}</a
|
||||
>
|
||||
</label>
|
||||
<clr-checkbox-container>
|
||||
<label id="repo_read_only_lbl" for="repoReadOnly">{{'CONFIG.REPO_READ_ONLY' | translate}}
|
||||
<label id="repo_read_only_lbl" for="repoReadOnly"
|
||||
>{{ 'CONFIG.REPO_READ_ONLY' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.TOOLTIP.REPO_TOOLTIP' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'CONFIG.TOOLTIP.REPO_TOOLTIP' | translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" [disabled]="!currentConfig.read_only.editable" clrCheckbox name="repoReadOnly" id="repoReadOnly"
|
||||
[ngModel]="currentConfig.read_only.value" (ngModelChange)="setRepoReadOnlyValue($event)" />
|
||||
<input
|
||||
type="checkbox"
|
||||
[disabled]="!currentConfig.read_only.editable"
|
||||
clrCheckbox
|
||||
name="repoReadOnly"
|
||||
id="repoReadOnly"
|
||||
[ngModel]="currentConfig.read_only.value"
|
||||
(ngModelChange)="setRepoReadOnlyValue($event)" />
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-container>
|
||||
|
||||
|
||||
<div class="clr-form-control d-f">
|
||||
<label
|
||||
class="clr-control-label">{{'CVE_ALLOWLIST.DEPLOYMENT_SECURITY'|translate}}</label>
|
||||
<label class="clr-control-label">{{
|
||||
'CVE_ALLOWLIST.DEPLOYMENT_SECURITY' | translate
|
||||
}}</label>
|
||||
<div class="form-content">
|
||||
<div class="font-size-13">
|
||||
<div class="mt-05">
|
||||
<span class="title font-size-13">{{'CVE_ALLOWLIST.CVE_ALLOWLIST'|translate}}</span>
|
||||
<span class="title font-size-13">{{
|
||||
'CVE_ALLOWLIST.CVE_ALLOWLIST' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="mt-05">
|
||||
<span>{{'CVE_ALLOWLIST.SYS_ALLOWLIST_EXPLAIN'|translate}}</span>
|
||||
<span>{{
|
||||
'CVE_ALLOWLIST.SYS_ALLOWLIST_EXPLAIN' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="mt-05">
|
||||
<span>{{'CVE_ALLOWLIST.ADD_SYS'|translate}}</span>
|
||||
<span>{{ 'CVE_ALLOWLIST.ADD_SYS' | translate }}</span>
|
||||
</div>
|
||||
<div class="mt-05" *ngIf="hasExpired">
|
||||
<span class="label label-warning">{{'CVE_ALLOWLIST.WARNING_SYS'|translate}}</span>
|
||||
<span class="label label-warning">{{
|
||||
'CVE_ALLOWLIST.WARNING_SYS' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row width-90per">
|
||||
<div class="position-relative pl-05">
|
||||
<div>
|
||||
<button id="show-add-modal-button" (click)="showAddModal=!showAddModal"
|
||||
class="btn btn-link">{{'CVE_ALLOWLIST.ADD'|translate}}</button>
|
||||
<button
|
||||
id="show-add-modal-button"
|
||||
(click)="showAddModal = !showAddModal"
|
||||
class="btn btn-link">
|
||||
{{ 'CVE_ALLOWLIST.ADD' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="add-modal add-modal-dark" *ngIf="showAddModal">
|
||||
<clr-icon (click)="showAddModal=false" class="float-lg-right margin-top-4"
|
||||
<div
|
||||
class="add-modal add-modal-dark"
|
||||
*ngIf="showAddModal">
|
||||
<clr-icon
|
||||
(click)="showAddModal = false"
|
||||
class="float-lg-right margin-top-4"
|
||||
shape="window-close"></clr-icon>
|
||||
<div>
|
||||
<clr-textarea-container class="flex-direction-column">
|
||||
<label>{{'CVE_ALLOWLIST.ENTER'|translate}}</label>
|
||||
<textarea id="allowlist-textarea" class="w-100 font-italic" clrTextarea [(ngModel)]="cveIds"
|
||||
<clr-textarea-container
|
||||
class="flex-direction-column">
|
||||
<label>{{
|
||||
'CVE_ALLOWLIST.ENTER' | translate
|
||||
}}</label>
|
||||
<textarea
|
||||
id="allowlist-textarea"
|
||||
class="w-100 font-italic"
|
||||
clrTextarea
|
||||
[(ngModel)]="cveIds"
|
||||
name="cveIds"></textarea>
|
||||
<clr-control-helper>{{'CVE_ALLOWLIST.HELP'|translate}}</clr-control-helper>
|
||||
<clr-control-helper>{{
|
||||
'CVE_ALLOWLIST.HELP' | translate
|
||||
}}</clr-control-helper>
|
||||
</clr-textarea-container>
|
||||
</div>
|
||||
<div>
|
||||
<button id="add-to-system" [disabled]="isDisabled()" (click)="addToSystemAllowlist()"
|
||||
class="btn btn-link">{{'CVE_ALLOWLIST.ADD'|translate}}</button>
|
||||
<button
|
||||
id="add-to-system"
|
||||
[disabled]="isDisabled()"
|
||||
(click)="addToSystemAllowlist()"
|
||||
class="btn btn-link">
|
||||
{{ 'CVE_ALLOWLIST.ADD' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="allowlist-window">
|
||||
<li *ngIf="systemAllowlist?.items?.length<1" class="none">{{'CVE_ALLOWLIST.NONE'|translate}}
|
||||
<li
|
||||
*ngIf="systemAllowlist?.items?.length < 1"
|
||||
class="none">
|
||||
{{ 'CVE_ALLOWLIST.NONE' | translate }}
|
||||
</li>
|
||||
<li *ngFor="let item of systemAllowlist?.items;let i = index;">
|
||||
<a href="javascript:void(0)" (click)="goToDetail(item.cve_id)">{{item.cve_id}}</a>
|
||||
<a class="float-lg-right" href="javascript:void(0)" (click)="deleteItem(i)">
|
||||
<li
|
||||
*ngFor="
|
||||
let item of systemAllowlist?.items;
|
||||
let i = index
|
||||
">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
(click)="goToDetail(item.cve_id)"
|
||||
>{{ item.cve_id }}</a
|
||||
>
|
||||
<a
|
||||
class="float-lg-right"
|
||||
href="javascript:void(0)"
|
||||
(click)="deleteItem(i)">
|
||||
<clr-icon shape="times-circle"></clr-icon>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -141,19 +286,37 @@
|
|||
</div>
|
||||
<div class="clr-col padding-top-8">
|
||||
<div class="clr-row expire-data">
|
||||
<label class="bottom-line clr-col-4">{{'CVE_ALLOWLIST.EXPIRES_AT'|translate}}</label>
|
||||
<label class="bottom-line clr-col-4">{{
|
||||
'CVE_ALLOWLIST.EXPIRES_AT' | translate
|
||||
}}</label>
|
||||
<div>
|
||||
<input #dateInput placeholder="{{'CVE_ALLOWLIST.NEVER_EXPIRES'|translate}}" readonly
|
||||
type="date" [(clrDate)]="expiresDate" newFormLayout="true">
|
||||
<input
|
||||
#dateInput
|
||||
placeholder="{{
|
||||
'CVE_ALLOWLIST.NEVER_EXPIRES'
|
||||
| translate
|
||||
}}"
|
||||
readonly
|
||||
type="date"
|
||||
[(clrDate)]="expiresDate"
|
||||
newFormLayout="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<label class="clr-col-4"></label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input [checked]="neverExpires" [(ngModel)]="neverExpires" type="checkbox" clrCheckbox
|
||||
name="neverExpires" id="neverExpires" />
|
||||
<input
|
||||
[checked]="neverExpires"
|
||||
[(ngModel)]="neverExpires"
|
||||
type="checkbox"
|
||||
clrCheckbox
|
||||
name="neverExpires"
|
||||
id="neverExpires" />
|
||||
<label>
|
||||
{{'CVE_ALLOWLIST.NEVER_EXPIRES'|translate}}
|
||||
{{
|
||||
'CVE_ALLOWLIST.NEVER_EXPIRES'
|
||||
| translate
|
||||
}}
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
|
@ -162,16 +325,29 @@
|
|||
</div>
|
||||
</div>
|
||||
<clr-checkbox-container>
|
||||
<label for="webhookNotificationEnabled">{{'CONFIG.WEBHOOK_NOTIFICATION_ENABLED' | translate}}
|
||||
<label for="webhookNotificationEnabled"
|
||||
>{{ 'CONFIG.WEBHOOK_NOTIFICATION_ENABLED' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.TOOLTIP.WEBHOOK_TOOLTIP' | translate}}</span>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-right"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'CONFIG.TOOLTIP.WEBHOOK_TOOLTIP' | translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox name="webhookNotificationEnabled" id="webhookNotificationEnabled"
|
||||
<input
|
||||
type="checkbox"
|
||||
clrCheckbox
|
||||
name="webhookNotificationEnabled"
|
||||
id="webhookNotificationEnabled"
|
||||
[ngModel]="currentConfig.notification_enable.value"
|
||||
(ngModelChange)="setWebhookNotificationEnabledValue($event)"
|
||||
[disabled]="!currentConfig.notification_enable.editable" />
|
||||
|
@ -180,10 +356,26 @@
|
|||
</section>
|
||||
</form>
|
||||
<div>
|
||||
<button type="button" id="config_system_save" class="btn btn-primary" (click)="save()"
|
||||
[disabled]="(!isValid() || !hasChanges()) && (!hasAllowlistChanged) || inProgress">{{'BUTTON.SAVE'
|
||||
| translate}}</button>
|
||||
<button type="button" id="config_system_cancel" class="btn btn-outline" (click)="cancel()"
|
||||
[disabled]="(!isValid() || !hasChanges()) && (!hasAllowlistChanged) || inProgress">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
<button
|
||||
type="button"
|
||||
id="config_system_save"
|
||||
class="btn btn-primary"
|
||||
(click)="save()"
|
||||
[disabled]="
|
||||
((!isValid() || !hasChanges()) && !hasAllowlistChanged) ||
|
||||
inProgress
|
||||
">
|
||||
{{ 'BUTTON.SAVE' | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="config_system_cancel"
|
||||
class="btn btn-outline"
|
||||
(click)="cancel()"
|
||||
[disabled]="
|
||||
((!isValid() || !hasChanges()) && !hasAllowlistChanged) ||
|
||||
inProgress
|
||||
">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,115 +1,127 @@
|
|||
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
|
||||
import { SystemSettingsComponent } from "./system-settings.component";
|
||||
import { SystemInfoService } from "../../../../shared/services";
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { of } from "rxjs";
|
||||
import { Configuration, StringValueItem } from "../config";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { ConfigService } from "../config.service";
|
||||
import { AppConfigService } from "../../../../services/app-config.service";
|
||||
import {
|
||||
ComponentFixture,
|
||||
ComponentFixtureAutoDetect,
|
||||
TestBed,
|
||||
} from '@angular/core/testing';
|
||||
import { SystemSettingsComponent } from './system-settings.component';
|
||||
import { SystemInfoService } from '../../../../shared/services';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { of } from 'rxjs';
|
||||
import { Configuration, StringValueItem } from '../config';
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
describe('SystemSettingsComponent', () => {
|
||||
let component: SystemSettingsComponent;
|
||||
let fixture: ComponentFixture<SystemSettingsComponent>;
|
||||
const mockedAllowlist = {
|
||||
id: 1,
|
||||
project_id: 1,
|
||||
expires_at: null,
|
||||
items: [
|
||||
{cve_id: 'CVE-2019-1234'}
|
||||
]
|
||||
};
|
||||
const fakedSystemInfoService = {
|
||||
getSystemAllowlist() {
|
||||
return of(mockedAllowlist);
|
||||
},
|
||||
getSystemInfo() {
|
||||
return of({});
|
||||
},
|
||||
updateSystemAllowlist() {
|
||||
return of(true);
|
||||
}
|
||||
};
|
||||
const fakedErrorHandler = {
|
||||
info() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const fakeConfigService = {
|
||||
config: new Configuration(),
|
||||
getConfig() {
|
||||
return this.config;
|
||||
},
|
||||
setConfig(c) {
|
||||
this.config = c;
|
||||
},
|
||||
getOriginalConfig() {
|
||||
return new Configuration();
|
||||
},
|
||||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
confirmUnsavedChanges() {
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
resetConfig() {
|
||||
}
|
||||
};
|
||||
const fakedAppConfigService = {
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
load() {
|
||||
return of(null);
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: AppConfigService, useValue: fakedAppConfigService },
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
{ provide: ErrorHandler, useValue: fakedErrorHandler },
|
||||
{ provide: SystemInfoService, useValue: fakedSystemInfoService },
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
declarations: [
|
||||
SystemSettingsComponent
|
||||
]
|
||||
let component: SystemSettingsComponent;
|
||||
let fixture: ComponentFixture<SystemSettingsComponent>;
|
||||
const mockedAllowlist = {
|
||||
id: 1,
|
||||
project_id: 1,
|
||||
expires_at: null,
|
||||
items: [{ cve_id: 'CVE-2019-1234' }],
|
||||
};
|
||||
const fakedSystemInfoService = {
|
||||
getSystemAllowlist() {
|
||||
return of(mockedAllowlist);
|
||||
},
|
||||
getSystemInfo() {
|
||||
return of({});
|
||||
},
|
||||
updateSystemAllowlist() {
|
||||
return of(true);
|
||||
},
|
||||
};
|
||||
const fakedErrorHandler = {
|
||||
info() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const fakeConfigService = {
|
||||
config: new Configuration(),
|
||||
getConfig() {
|
||||
return this.config;
|
||||
},
|
||||
setConfig(c) {
|
||||
this.config = c;
|
||||
},
|
||||
getOriginalConfig() {
|
||||
return new Configuration();
|
||||
},
|
||||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
confirmUnsavedChanges() {},
|
||||
updateConfig() {},
|
||||
resetConfig() {},
|
||||
};
|
||||
const fakedAppConfigService = {
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
load() {
|
||||
return of(null);
|
||||
},
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
providers: [
|
||||
{ provide: AppConfigService, useValue: fakedAppConfigService },
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
{ provide: ErrorHandler, useValue: fakedErrorHandler },
|
||||
{
|
||||
provide: SystemInfoService,
|
||||
useValue: fakedSystemInfoService,
|
||||
},
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true },
|
||||
],
|
||||
declarations: [SystemSettingsComponent],
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SystemSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.currentConfig.auth_mode = new StringValueItem(
|
||||
'db_auth',
|
||||
false
|
||||
);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SystemSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.currentConfig.auth_mode = new StringValueItem("db_auth", false );
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('cancel button should works', () => {
|
||||
const spy: jasmine.Spy = spyOn(fakeConfigService, 'confirmUnsavedChanges').and.returnValue(undefined);
|
||||
component.systemAllowlist.items.push({cve_id: 'CVE-2019-456'});
|
||||
const readOnly: HTMLElement = fixture.nativeElement.querySelector('#repoReadOnly');
|
||||
readOnly.click();
|
||||
fixture.detectChanges();
|
||||
const cancel: HTMLButtonElement = fixture.nativeElement.querySelector('#config_system_cancel');
|
||||
cancel.click();
|
||||
fixture.detectChanges();
|
||||
expect(spy.calls.count()).toEqual(1);
|
||||
});
|
||||
it('save button should works', () => {
|
||||
component.systemAllowlist.items[0].cve_id = 'CVE-2019-789';
|
||||
const readOnly: HTMLElement = fixture.nativeElement.querySelector('#repoReadOnly');
|
||||
readOnly.click();
|
||||
fixture.detectChanges();
|
||||
const save: HTMLButtonElement = fixture.nativeElement.querySelector('#config_system_save');
|
||||
save.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.systemAllowlistOrigin.items[0].cve_id).toEqual('CVE-2019-789');
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('cancel button should works', () => {
|
||||
const spy: jasmine.Spy = spyOn(
|
||||
fakeConfigService,
|
||||
'confirmUnsavedChanges'
|
||||
).and.returnValue(undefined);
|
||||
component.systemAllowlist.items.push({ cve_id: 'CVE-2019-456' });
|
||||
const readOnly: HTMLElement =
|
||||
fixture.nativeElement.querySelector('#repoReadOnly');
|
||||
readOnly.click();
|
||||
fixture.detectChanges();
|
||||
const cancel: HTMLButtonElement = fixture.nativeElement.querySelector(
|
||||
'#config_system_cancel'
|
||||
);
|
||||
cancel.click();
|
||||
fixture.detectChanges();
|
||||
expect(spy.calls.count()).toEqual(1);
|
||||
});
|
||||
it('save button should works', () => {
|
||||
component.systemAllowlist.items[0].cve_id = 'CVE-2019-789';
|
||||
const readOnly: HTMLElement =
|
||||
fixture.nativeElement.querySelector('#repoReadOnly');
|
||||
readOnly.click();
|
||||
fixture.detectChanges();
|
||||
const save: HTMLButtonElement = fixture.nativeElement.querySelector(
|
||||
'#config_system_save'
|
||||
);
|
||||
save.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.systemAllowlistOrigin.items[0].cve_id).toEqual(
|
||||
'CVE-2019-789'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,24 +1,37 @@
|
|||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Configuration } from '../config';
|
||||
import { clone, compareValue, CURRENT_BASE_HREF, getChanges, isEmpty } from '../../../../shared/units/utils';
|
||||
import {
|
||||
clone,
|
||||
compareValue,
|
||||
CURRENT_BASE_HREF,
|
||||
getChanges,
|
||||
isEmpty,
|
||||
} from '../../../../shared/units/utils';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { ConfirmationState, ConfirmationTargets } from '../../../../shared/entities/shared.const';
|
||||
import {
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
} from '../../../../shared/entities/shared.const';
|
||||
import { ConfirmationAcknowledgement } from '../../../global-confirmation-dialog/confirmation-state-message';
|
||||
import { SystemCVEAllowlist, SystemInfo, SystemInfoService, } from '../../../../shared/services';
|
||||
import { forkJoin } from "rxjs";
|
||||
import { ConfigurationService } from "../../../../services/config.service";
|
||||
import { ConfigService } from "../config.service";
|
||||
import { AppConfigService } from "../../../../services/app-config.service";
|
||||
import {
|
||||
SystemCVEAllowlist,
|
||||
SystemInfo,
|
||||
SystemInfoService,
|
||||
} from '../../../../shared/services';
|
||||
import { forkJoin } from 'rxjs';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
|
||||
const ONE_THOUSAND: number = 1000;
|
||||
const CVE_DETAIL_PRE_URL = `https://nvd.nist.gov/vuln/detail/`;
|
||||
const TARGET_BLANK = "_blank";
|
||||
const TARGET_BLANK = '_blank';
|
||||
|
||||
@Component({
|
||||
selector: 'system-settings',
|
||||
templateUrl: './system-settings.component.html',
|
||||
styleUrls: ['./system-settings.component.scss']
|
||||
styleUrls: ['./system-settings.component.scss'],
|
||||
})
|
||||
export class SystemSettingsComponent implements OnInit {
|
||||
onGoing = false;
|
||||
|
@ -35,19 +48,23 @@ export class SystemSettingsComponent implements OnInit {
|
|||
set currentConfig(cfg: Configuration) {
|
||||
this.conf.setConfig(cfg);
|
||||
}
|
||||
@ViewChild("systemConfigFrom") systemSettingsForm: NgForm;
|
||||
@ViewChild('systemConfigFrom') systemSettingsForm: NgForm;
|
||||
@ViewChild('dateInput') dateInput: ElementRef;
|
||||
|
||||
get editable(): boolean {
|
||||
return this.currentConfig &&
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.token_expiration &&
|
||||
this.currentConfig.token_expiration.editable;
|
||||
this.currentConfig.token_expiration.editable
|
||||
);
|
||||
}
|
||||
|
||||
get robotExpirationEditable(): boolean {
|
||||
return this.currentConfig &&
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.robot_token_duration &&
|
||||
this.currentConfig.robot_token_duration.editable;
|
||||
this.currentConfig.robot_token_duration.editable
|
||||
);
|
||||
}
|
||||
|
||||
get tokenExpirationValue() {
|
||||
|
@ -69,9 +86,11 @@ export class SystemSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
robotNamePrefixEditable(): boolean {
|
||||
return this.currentConfig &&
|
||||
return (
|
||||
this.currentConfig &&
|
||||
this.currentConfig.robot_name_prefix &&
|
||||
this.currentConfig.robot_name_prefix.editable;
|
||||
this.currentConfig.robot_name_prefix.editable
|
||||
);
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
|
@ -83,7 +102,10 @@ export class SystemSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
public getChanges() {
|
||||
let allChanges = getChanges(this.conf.getOriginalConfig(), this.currentConfig);
|
||||
let allChanges = getChanges(
|
||||
this.conf.getOriginalConfig(),
|
||||
this.currentConfig
|
||||
);
|
||||
if (allChanges) {
|
||||
return this.getSystemChanges(allChanges);
|
||||
}
|
||||
|
@ -93,8 +115,14 @@ export class SystemSettingsComponent implements OnInit {
|
|||
public getSystemChanges(allChanges: any) {
|
||||
let changes = {};
|
||||
for (let prop in allChanges) {
|
||||
if (prop === 'token_expiration' || prop === 'read_only' || prop === 'project_creation_restriction'
|
||||
|| prop === 'robot_token_duration' || prop === 'notification_enable' || prop === 'robot_name_prefix') {
|
||||
if (
|
||||
prop === 'token_expiration' ||
|
||||
prop === 'read_only' ||
|
||||
prop === 'project_creation_restriction' ||
|
||||
prop === 'robot_token_duration' ||
|
||||
prop === 'notification_enable' ||
|
||||
prop === 'robot_name_prefix'
|
||||
) {
|
||||
changes[prop] = allChanges[prop];
|
||||
}
|
||||
}
|
||||
|
@ -125,37 +153,61 @@ export class SystemSettingsComponent implements OnInit {
|
|||
*/
|
||||
public save(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!isEmpty(changes) || !compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
||||
if (
|
||||
!isEmpty(changes) ||
|
||||
!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)
|
||||
) {
|
||||
this.onGoing = true;
|
||||
let observables = [];
|
||||
if (!isEmpty(changes)) {
|
||||
observables.push(this.configService.saveConfiguration(changes));
|
||||
}
|
||||
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
||||
observables.push(this.systemInfoService.updateSystemAllowlist(this.systemAllowlist));
|
||||
if (
|
||||
!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)
|
||||
) {
|
||||
observables.push(
|
||||
this.systemInfoService.updateSystemAllowlist(
|
||||
this.systemAllowlist
|
||||
)
|
||||
);
|
||||
}
|
||||
forkJoin(observables).subscribe(result => {
|
||||
this.onGoing = false;
|
||||
if (!isEmpty(changes)) {
|
||||
// API should return the updated configurations here
|
||||
// Unfortunately API does not do that
|
||||
// To refresh the view, we can clone the original data copy
|
||||
// or force refresh by calling service.
|
||||
// HERE we choose force way
|
||||
this.conf.updateConfig();
|
||||
// Reload bootstrap option
|
||||
this.appConfigService.load().subscribe(() => {
|
||||
}
|
||||
, error => console.error('Failed to reload bootstrap option with error: ', error));
|
||||
forkJoin(observables).subscribe(
|
||||
result => {
|
||||
this.onGoing = false;
|
||||
if (!isEmpty(changes)) {
|
||||
// API should return the updated configurations here
|
||||
// Unfortunately API does not do that
|
||||
// To refresh the view, we can clone the original data copy
|
||||
// or force refresh by calling service.
|
||||
// HERE we choose force way
|
||||
this.conf.updateConfig();
|
||||
// Reload bootstrap option
|
||||
this.appConfigService.load().subscribe(
|
||||
() => {},
|
||||
error =>
|
||||
console.error(
|
||||
'Failed to reload bootstrap option with error: ',
|
||||
error
|
||||
)
|
||||
);
|
||||
}
|
||||
if (
|
||||
!compareValue(
|
||||
this.systemAllowlistOrigin,
|
||||
this.systemAllowlist
|
||||
)
|
||||
) {
|
||||
this.systemAllowlistOrigin = clone(
|
||||
this.systemAllowlist
|
||||
);
|
||||
}
|
||||
this.errorHandler.info('CONFIG.SAVE_SUCCESS');
|
||||
},
|
||||
error => {
|
||||
this.onGoing = false;
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
||||
this.systemAllowlistOrigin = clone(this.systemAllowlist);
|
||||
}
|
||||
this.errorHandler.info('CONFIG.SAVE_SUCCESS');
|
||||
}, error => {
|
||||
this.onGoing = false;
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
);
|
||||
} else {
|
||||
// Inprop situation, should not come here
|
||||
console.error('Save abort because nothing changed');
|
||||
|
@ -163,16 +215,20 @@ export class SystemSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
confirmCancel(ack: ConfirmationAcknowledgement): void {
|
||||
if (ack && ack.source === ConfirmationTargets.CONFIG &&
|
||||
ack.state === ConfirmationState.CONFIRMED) {
|
||||
if (
|
||||
ack &&
|
||||
ack.source === ConfirmationTargets.CONFIG &&
|
||||
ack.state === ConfirmationState.CONFIRMED
|
||||
) {
|
||||
this.conf.resetConfig();
|
||||
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
||||
if (
|
||||
!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)
|
||||
) {
|
||||
this.systemAllowlist = clone(this.systemAllowlistOrigin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing || this.conf.getLoadingConfigStatus();
|
||||
}
|
||||
|
@ -185,7 +241,10 @@ export class SystemSettingsComponent implements OnInit {
|
|||
*/
|
||||
public cancel(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!isEmpty(changes) || !compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
||||
if (
|
||||
!isEmpty(changes) ||
|
||||
!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)
|
||||
) {
|
||||
this.conf.confirmUnsavedChanges(changes);
|
||||
} else {
|
||||
// Invalid situation, should not come here
|
||||
|
@ -193,12 +252,14 @@ export class SystemSettingsComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
constructor(private appConfigService: AppConfigService,
|
||||
private configService: ConfigurationService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private systemInfoService: SystemInfoService,
|
||||
private conf: ConfigService) {
|
||||
this.downloadLink = CURRENT_BASE_HREF + "/systeminfo/getcert";
|
||||
constructor(
|
||||
private appConfigService: AppConfigService,
|
||||
private configService: ConfigurationService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private systemInfoService: SystemInfoService,
|
||||
private conf: ConfigService
|
||||
) {
|
||||
this.downloadLink = CURRENT_BASE_HREF + '/systeminfo/getcert';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -208,30 +269,34 @@ export class SystemSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
getSystemInfo() {
|
||||
this.systemInfoService.getSystemInfo()
|
||||
.subscribe(systemInfo => this.systemInfo = systemInfo
|
||||
, error => this.errorHandler.error(error));
|
||||
this.systemInfoService.getSystemInfo().subscribe(
|
||||
systemInfo => (this.systemInfo = systemInfo),
|
||||
error => this.errorHandler.error(error)
|
||||
);
|
||||
}
|
||||
|
||||
getSystemAllowlist() {
|
||||
this.onGoing = true;
|
||||
this.systemInfoService.getSystemAllowlist()
|
||||
.subscribe((systemAllowlist) => {
|
||||
this.onGoing = false;
|
||||
if (!systemAllowlist.items) {
|
||||
systemAllowlist.items = [];
|
||||
}
|
||||
if (!systemAllowlist.expires_at) {
|
||||
systemAllowlist.expires_at = null;
|
||||
}
|
||||
this.systemAllowlist = systemAllowlist;
|
||||
this.systemAllowlistOrigin = clone(systemAllowlist);
|
||||
}, error => {
|
||||
this.onGoing = false;
|
||||
console.error('An error occurred during getting systemAllowlist');
|
||||
// this.errorHandler.error(error);
|
||||
this.systemInfoService.getSystemAllowlist().subscribe(
|
||||
systemAllowlist => {
|
||||
this.onGoing = false;
|
||||
if (!systemAllowlist.items) {
|
||||
systemAllowlist.items = [];
|
||||
}
|
||||
);
|
||||
if (!systemAllowlist.expires_at) {
|
||||
systemAllowlist.expires_at = null;
|
||||
}
|
||||
this.systemAllowlist = systemAllowlist;
|
||||
this.systemAllowlistOrigin = clone(systemAllowlist);
|
||||
},
|
||||
error => {
|
||||
this.onGoing = false;
|
||||
console.error(
|
||||
'An error occurred during getting systemAllowlist'
|
||||
);
|
||||
// this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
deleteItem(index: number) {
|
||||
|
@ -275,7 +340,9 @@ export class SystemSettingsComponent implements OnInit {
|
|||
|
||||
set expiresDate(date) {
|
||||
if (this.systemAllowlist && date) {
|
||||
this.systemAllowlist.expires_at = Math.floor(date.getTime() / ONE_THOUSAND);
|
||||
this.systemAllowlist.expires_at = Math.floor(
|
||||
date.getTime() / ONE_THOUSAND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,13 +355,21 @@ export class SystemSettingsComponent implements OnInit {
|
|||
this.systemAllowlist.expires_at = null;
|
||||
this.systemInfoService.resetDateInput(this.dateInput);
|
||||
} else {
|
||||
this.systemAllowlist.expires_at = Math.floor(new Date().getTime() / ONE_THOUSAND);
|
||||
this.systemAllowlist.expires_at = Math.floor(
|
||||
new Date().getTime() / ONE_THOUSAND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get hasExpired(): boolean {
|
||||
if (this.systemAllowlistOrigin && this.systemAllowlistOrigin.expires_at) {
|
||||
return new Date().getTime() > this.systemAllowlistOrigin.expires_at * ONE_THOUSAND;
|
||||
if (
|
||||
this.systemAllowlistOrigin &&
|
||||
this.systemAllowlistOrigin.expires_at
|
||||
) {
|
||||
return (
|
||||
new Date().getTime() >
|
||||
this.systemAllowlistOrigin.expires_at * ONE_THOUSAND
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -4,52 +4,131 @@
|
|||
{{ 'SIDE_NAV.DISTRIBUTIONS.INSTANCES' | translate }}
|
||||
</h2>
|
||||
<div>
|
||||
<clr-datagrid (clrDgRefresh)="loadData($event)" [clrDgLoading]="inProgress" [(clrDgSelected)]="selectedRow">
|
||||
<clr-datagrid
|
||||
(clrDgRefresh)="loadData($event)"
|
||||
[clrDgLoading]="inProgress"
|
||||
[(clrDgSelected)]="selectedRow">
|
||||
<clr-dg-action-bar>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-7">
|
||||
<button id="new-instance"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="addInstance()"
|
||||
>
|
||||
<clr-icon shape="plus" size="16"></clr-icon> {{
|
||||
'DISTRIBUTION.ADD_ACTION' | translate
|
||||
<button
|
||||
id="new-instance"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="addInstance()">
|
||||
<clr-icon shape="plus" size="16"></clr-icon
|
||||
> {{
|
||||
'DISTRIBUTION.ADD_ACTION' | translate
|
||||
}}
|
||||
</button>
|
||||
<clr-dropdown
|
||||
[clrCloseMenuOnItemClick]="false"
|
||||
class="btn btn-link"
|
||||
clrDropdownTrigger>
|
||||
<span id="member-action">{{ 'BUTTON.ACTIONS' | translate}}<clr-icon shape="caret down"></clr-icon></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
[clrCloseMenuOnItemClick]="false"
|
||||
class="btn btn-link"
|
||||
clrDropdownTrigger>
|
||||
<span id="member-action"
|
||||
>{{ 'BUTTON.ACTIONS' | translate
|
||||
}}<clr-icon shape="caret down"></clr-icon
|
||||
></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<clr-dropdown>
|
||||
<button type="button" class="btn btn-secondary" (click)="editInstance()"
|
||||
[disabled]="!(selectedRow && selectedRow.length === 1)">
|
||||
<clr-icon shape="edit" size="16"></clr-icon>
|
||||
<span id="distribution-edit">{{'BUTTON.EDIT' | translate}}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="editInstance()"
|
||||
[disabled]="
|
||||
!(
|
||||
selectedRow &&
|
||||
selectedRow.length === 1
|
||||
)
|
||||
">
|
||||
<clr-icon
|
||||
shape="edit"
|
||||
size="16"></clr-icon
|
||||
>
|
||||
<span id="distribution-edit">{{
|
||||
'BUTTON.EDIT' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
<button *ngIf="selectedRow && selectedRow.length === 1 && !selectedRow[0].enabled" type="button" class="btn btn-secondary" (click)="operateInstances('enable', selectedRow)"
|
||||
[disabled]="!(selectedRow && selectedRow.length === 1 && !selectedRow[0].enabled)">
|
||||
<clr-icon shape="connect" size="16"></clr-icon>
|
||||
<span id="distribution-enable">{{'WEBHOOK.ENABLED_BUTTON' | translate}}</span>
|
||||
<button
|
||||
*ngIf="
|
||||
selectedRow &&
|
||||
selectedRow.length === 1 &&
|
||||
!selectedRow[0].enabled
|
||||
"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="
|
||||
operateInstances(
|
||||
'enable',
|
||||
selectedRow
|
||||
)
|
||||
"
|
||||
[disabled]="
|
||||
!(
|
||||
selectedRow &&
|
||||
selectedRow.length === 1 &&
|
||||
!selectedRow[0].enabled
|
||||
)
|
||||
">
|
||||
<clr-icon
|
||||
shape="connect"
|
||||
size="16"></clr-icon
|
||||
>
|
||||
<span id="distribution-enable">{{
|
||||
'WEBHOOK.ENABLED_BUTTON'
|
||||
| translate
|
||||
}}</span>
|
||||
</button>
|
||||
<button *ngIf="!(selectedRow && selectedRow.length === 1 && !selectedRow[0].enabled)"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="operateInstances('disable', selectedRow)"
|
||||
[disabled]="!(selectedRow && selectedRow.length === 1 && selectedRow[0].enabled)">
|
||||
<clr-icon shape="disconnect" size="16"></clr-icon>
|
||||
<span id="distribution-disable">{{'WEBHOOK.DISABLED_BUTTON' | translate}}</span>
|
||||
<button
|
||||
*ngIf="
|
||||
!(
|
||||
selectedRow &&
|
||||
selectedRow.length === 1 &&
|
||||
!selectedRow[0].enabled
|
||||
)
|
||||
"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="
|
||||
operateInstances(
|
||||
'disable',
|
||||
selectedRow
|
||||
)
|
||||
"
|
||||
[disabled]="
|
||||
!(
|
||||
selectedRow &&
|
||||
selectedRow.length === 1 &&
|
||||
selectedRow[0].enabled
|
||||
)
|
||||
">
|
||||
<clr-icon
|
||||
shape="disconnect"
|
||||
size="16"></clr-icon
|
||||
>
|
||||
<span id="distribution-disable">{{
|
||||
'WEBHOOK.DISABLED_BUTTON'
|
||||
| translate
|
||||
}}</span>
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="operateInstances('delete', selectedRow)"
|
||||
[disabled]="selectedRow.length < 1">
|
||||
<clr-icon shape="window-close" size="16"></clr-icon>
|
||||
<span id="distribution-delete">{{'BUTTON.DELETE' | translate}}</span>
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="
|
||||
operateInstances(
|
||||
'delete',
|
||||
selectedRow
|
||||
)
|
||||
"
|
||||
[disabled]="selectedRow.length < 1">
|
||||
<clr-icon
|
||||
shape="window-close"
|
||||
size="16"></clr-icon
|
||||
>
|
||||
<span id="distribution-delete">{{
|
||||
'BUTTON.DELETE' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
</clr-dropdown>
|
||||
</clr-dropdown-menu>
|
||||
|
@ -57,62 +136,130 @@
|
|||
</div>
|
||||
<div class="clr-col-5">
|
||||
<div class="action-head-pos">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder="{{'DISTRIBUTION.FILTER_INSTANCE_PLACEHOLDER' | translate}}" (filterEvt)="doFilter($event)"></hbr-filter>
|
||||
<hbr-filter
|
||||
[withDivider]="true"
|
||||
filterPlaceholder="{{
|
||||
'DISTRIBUTION.FILTER_INSTANCE_PLACEHOLDER'
|
||||
| translate
|
||||
}}"
|
||||
(filterEvt)="doFilter($event)"></hbr-filter>
|
||||
<span class="refresh-btn">
|
||||
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress" (click)="refresh()"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
<clr-icon
|
||||
shape="refresh"
|
||||
[hidden]="inProgress"
|
||||
ng-disabled="inProgress"
|
||||
(click)="refresh()"></clr-icon>
|
||||
<span
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="inProgress === false"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column>{{ 'DISTRIBUTION.NAME' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'DISTRIBUTION.ENDPOINT' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'DISTRIBUTION.PROVIDER' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'DISTRIBUTION.STATUS' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'DISTRIBUTION.ENABLED' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.AUTH' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DISTRIBUTION.SETUP_TIMESTAMP' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'DISTRIBUTION.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'DISTRIBUTION.NAME' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'DISTRIBUTION.ENDPOINT' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'DISTRIBUTION.PROVIDER' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'DISTRIBUTION.STATUS' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'DISTRIBUTION.ENABLED' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'SCANNER.AUTH' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'DISTRIBUTION.SETUP_TIMESTAMP' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{
|
||||
'DISTRIBUTION.DESCRIPTION' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{
|
||||
'DISTRIBUTION.NOT_FOUND' | translate
|
||||
}}</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let instance of instances" [clrDgItem]="instance">
|
||||
'DISTRIBUTION.NOT_FOUND' | translate
|
||||
}}</clr-dg-placeholder>
|
||||
<clr-dg-row
|
||||
*ngFor="let instance of instances"
|
||||
[clrDgItem]="instance">
|
||||
<clr-dg-cell>
|
||||
<span>{{ instance.name }}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{ instance.endpoint }}</clr-dg-cell>
|
||||
<clr-dg-cell class="no-wrapper">
|
||||
<span>{{ instance.vendor }}</span>
|
||||
<span>{{ instance.vendor }}</span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span *ngIf="!instance.hasCheckHealth;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>
|
||||
<span
|
||||
*ngIf="
|
||||
!instance.hasCheckHealth;
|
||||
else elseBlockLoading
|
||||
"
|
||||
class="spinner spinner-inline ml-2"></span>
|
||||
<ng-template #elseBlockLoading>
|
||||
<span *ngIf="instance.pingStatus === 'Healthy';else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
<span
|
||||
*ngIf="
|
||||
instance.pingStatus === 'Healthy';
|
||||
else elseBlock
|
||||
"
|
||||
class="label label-success"
|
||||
>{{ 'SCANNER.HEALTHY' | translate }}</span
|
||||
>
|
||||
<ng-template #elseBlock>
|
||||
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
<span class="label label-danger">{{
|
||||
'SCANNER.UNHEALTHY' | translate
|
||||
}}</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div *ngIf="instance.enabled" class="icon-wrap">
|
||||
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
|
||||
<span class="margin-left-5px">{{'WEBHOOK.ENABLED' | translate}}</span>
|
||||
<clr-icon
|
||||
shape="check-circle"
|
||||
size="20"
|
||||
class="is-success enabled-icon"></clr-icon>
|
||||
<span class="margin-left-5px">{{
|
||||
'WEBHOOK.ENABLED' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
<div *ngIf="!instance.enabled" class="icon-wrap">
|
||||
<clr-icon shape="exclamation-triangle" size="20" class="is-warning"></clr-icon>
|
||||
<span class="margin-left-5px">{{'WEBHOOK.DISABLED' | translate}}</span>
|
||||
<clr-icon
|
||||
shape="exclamation-triangle"
|
||||
size="20"
|
||||
class="is-warning"></clr-icon>
|
||||
<span class="margin-left-5px">{{
|
||||
'WEBHOOK.DISABLED' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{ instance.auth_mode }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{fmtTime(instance.setup_timestamp) | harborDatetime: 'short'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
fmtTime(instance.setup_timestamp)
|
||||
| harborDatetime: 'short'
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{ instance.description }}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount" [(clrDgPage)]="currentPage">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span *ngIf="totalCount">{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} {{ 'HELM_CHART.OF' | translate }} </span>
|
||||
<span>{{ totalCount }} {{ 'HELM_CHART.ITEMS' | translate }}</span>
|
||||
<clr-dg-pagination
|
||||
#pagination
|
||||
[clrDgPageSize]="pageSize"
|
||||
[clrDgTotalItems]="totalCount"
|
||||
[(clrDgPage)]="currentPage">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15, 25, 50]">{{
|
||||
'PAGINATION.PAGE_SIZE' | translate
|
||||
}}</clr-dg-page-size>
|
||||
<span *ngIf="totalCount"
|
||||
>{{ pagination.firstItem + 1 }} -
|
||||
{{ pagination.lastItem + 1 }}
|
||||
{{ 'HELM_CHART.OF' | translate }}
|
||||
</span>
|
||||
<span
|
||||
>{{ totalCount }}
|
||||
{{ 'HELM_CHART.ITEMS' | translate }}</span
|
||||
>
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
@ -120,5 +267,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<dist-setup-modal (refresh)="refresh()" [providers]="providers" #setupModal></dist-setup-modal>
|
||||
<dist-setup-modal
|
||||
(refresh)="refresh()"
|
||||
[providers]="providers"
|
||||
#setupModal></dist-setup-modal>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule, } from '@ngx-translate/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { DistributionInstancesComponent } from './distribution-instances.component';
|
||||
import { PreheatService } from "../../../../../../ng-swagger-gen/services/preheat.service";
|
||||
import { PreheatService } from '../../../../../../ng-swagger-gen/services/preheat.service';
|
||||
import { Instance } from '../../../../../../ng-swagger-gen/models/instance';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { of } from 'rxjs';
|
||||
|
@ -14,132 +14,138 @@ import { Metadata } from '../../../../../../ng-swagger-gen/models/metadata';
|
|||
import { DistributionSetupModalComponent } from '../distribution-setup-modal/distribution-setup-modal.component';
|
||||
|
||||
describe('DistributionInstanceComponent', () => {
|
||||
let component: DistributionInstancesComponent;
|
||||
let fixture: ComponentFixture<DistributionInstancesComponent>;
|
||||
let component: DistributionInstancesComponent;
|
||||
let fixture: ComponentFixture<DistributionInstancesComponent>;
|
||||
|
||||
const instance1: Instance = {
|
||||
name: 'Test1',
|
||||
default: true,
|
||||
enabled: true,
|
||||
description: 'Test1',
|
||||
endpoint: 'http://test.com',
|
||||
id: 1,
|
||||
setup_timestamp: new Date().getTime(),
|
||||
auth_mode: 'NONE',
|
||||
vendor: 'kraken',
|
||||
status: 'Healthy'
|
||||
};
|
||||
const instance1: Instance = {
|
||||
name: 'Test1',
|
||||
default: true,
|
||||
enabled: true,
|
||||
description: 'Test1',
|
||||
endpoint: 'http://test.com',
|
||||
id: 1,
|
||||
setup_timestamp: new Date().getTime(),
|
||||
auth_mode: 'NONE',
|
||||
vendor: 'kraken',
|
||||
status: 'Healthy',
|
||||
};
|
||||
|
||||
const instance2: Instance = {
|
||||
name: 'Test2',
|
||||
default: false,
|
||||
enabled: false,
|
||||
description: 'Test2',
|
||||
endpoint: 'http://test2.com',
|
||||
id: 2,
|
||||
setup_timestamp: new Date().getTime() + 3600000,
|
||||
auth_mode: 'BASIC',
|
||||
auth_info: {
|
||||
password: '123',
|
||||
username: 'abc'
|
||||
},
|
||||
vendor: 'kraken',
|
||||
status: 'Healthy'
|
||||
};
|
||||
const instance2: Instance = {
|
||||
name: 'Test2',
|
||||
default: false,
|
||||
enabled: false,
|
||||
description: 'Test2',
|
||||
endpoint: 'http://test2.com',
|
||||
id: 2,
|
||||
setup_timestamp: new Date().getTime() + 3600000,
|
||||
auth_mode: 'BASIC',
|
||||
auth_info: {
|
||||
password: '123',
|
||||
username: 'abc',
|
||||
},
|
||||
vendor: 'kraken',
|
||||
status: 'Healthy',
|
||||
};
|
||||
|
||||
const instance3: Instance = {
|
||||
name: 'Test3',
|
||||
default: false,
|
||||
enabled: true,
|
||||
description: 'Test3',
|
||||
endpoint: 'http://test3.com',
|
||||
id: 3,
|
||||
setup_timestamp: new Date().getTime() + 7200000,
|
||||
auth_mode: 'OAUTH',
|
||||
auth_info: {
|
||||
token: 'xxxxxxxxxxxxxxxxxxxx'
|
||||
},
|
||||
vendor: 'kraken',
|
||||
status: 'Unhealthy'
|
||||
};
|
||||
const instance3: Instance = {
|
||||
name: 'Test3',
|
||||
default: false,
|
||||
enabled: true,
|
||||
description: 'Test3',
|
||||
endpoint: 'http://test3.com',
|
||||
id: 3,
|
||||
setup_timestamp: new Date().getTime() + 7200000,
|
||||
auth_mode: 'OAUTH',
|
||||
auth_info: {
|
||||
token: 'xxxxxxxxxxxxxxxxxxxx',
|
||||
},
|
||||
vendor: 'kraken',
|
||||
status: 'Unhealthy',
|
||||
};
|
||||
|
||||
const mockedProviders: Metadata[] = [{
|
||||
'icon': 'https://raw.githubusercontent.com/alibaba/Dragonfly/master/docs/images/logo.png',
|
||||
'id': 'dragonfly',
|
||||
'maintainers': ['Jin Zhang/taiyun.zj@alibaba-inc.com'],
|
||||
'name': 'Dragonfly',
|
||||
'source': 'https://github.com/alibaba/Dragonfly',
|
||||
'version': '0.10.1'
|
||||
}, {
|
||||
'icon': 'https://github.com/uber/kraken/blob/master/assets/kraken-logo-color.svg',
|
||||
'id': 'kraken',
|
||||
'maintainers': ['mmpei/peimingming@corp.netease.com'],
|
||||
'name': 'Kraken',
|
||||
'source': 'https://github.com/uber/kraken',
|
||||
'version': '0.1.3'
|
||||
}];
|
||||
const mockedProviders: Metadata[] = [
|
||||
{
|
||||
icon: 'https://raw.githubusercontent.com/alibaba/Dragonfly/master/docs/images/logo.png',
|
||||
id: 'dragonfly',
|
||||
maintainers: ['Jin Zhang/taiyun.zj@alibaba-inc.com'],
|
||||
name: 'Dragonfly',
|
||||
source: 'https://github.com/alibaba/Dragonfly',
|
||||
version: '0.10.1',
|
||||
},
|
||||
{
|
||||
icon: 'https://github.com/uber/kraken/blob/master/assets/kraken-logo-color.svg',
|
||||
id: 'kraken',
|
||||
maintainers: ['mmpei/peimingming@corp.netease.com'],
|
||||
name: 'Kraken',
|
||||
source: 'https://github.com/uber/kraken',
|
||||
version: '0.1.3',
|
||||
},
|
||||
];
|
||||
|
||||
const fakedPreheatService = {
|
||||
ListInstancesResponse() {
|
||||
const res: HttpResponse<Array<Instance>> = new HttpResponse<
|
||||
Array<Instance>
|
||||
>({
|
||||
headers: new HttpHeaders({ 'x-total-count': '3' }),
|
||||
body: [instance1, instance2, instance3],
|
||||
});
|
||||
return of(res).pipe(delay(10));
|
||||
},
|
||||
ListProviders() {
|
||||
return of(mockedProviders).pipe(delay(10));
|
||||
},
|
||||
PingInstances() {
|
||||
return of(true);
|
||||
},
|
||||
};
|
||||
|
||||
const fakedPreheatService = {
|
||||
ListInstancesResponse() {
|
||||
const res: HttpResponse<Array<Instance>> = new HttpResponse<Array<Instance>>({
|
||||
headers: new HttpHeaders({'x-total-count': '3'}),
|
||||
body: [instance1, instance2, instance3]
|
||||
});
|
||||
return of(res).pipe(delay(10));
|
||||
},
|
||||
ListProviders() {
|
||||
return of(mockedProviders).pipe(delay(10));
|
||||
},
|
||||
PingInstances() {
|
||||
return of(true);
|
||||
}
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ClarityModule,
|
||||
TranslateModule,
|
||||
SharedTestingModule,
|
||||
HttpClientTestingModule,
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
{ provide: PreheatService, useValue: fakedPreheatService },
|
||||
],
|
||||
declarations: [
|
||||
DistributionInstancesComponent,
|
||||
DistributionSetupModalComponent,
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ClarityModule,
|
||||
TranslateModule,
|
||||
SharedTestingModule,
|
||||
HttpClientTestingModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
{ provide: PreheatService, useValue: fakedPreheatService }
|
||||
],
|
||||
declarations: [
|
||||
DistributionInstancesComponent,
|
||||
DistributionSetupModalComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DistributionInstancesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DistributionInstancesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should render list and get providers', async () => {
|
||||
fixture.autoDetectChanges(true);
|
||||
await fixture.whenStable();
|
||||
expect(component.providers.length).toEqual(2);
|
||||
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
|
||||
expect(rows.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should render list and get providers', async () => {
|
||||
fixture.autoDetectChanges(true);
|
||||
await fixture.whenStable();
|
||||
expect(component.providers.length).toEqual(2);
|
||||
const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row');
|
||||
expect(rows.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should open modal', async () => {
|
||||
fixture.autoDetectChanges(true);
|
||||
await fixture.whenStable();
|
||||
const addButton: HTMLButtonElement = fixture.nativeElement.querySelector("#new-instance");
|
||||
addButton.click();
|
||||
await fixture.whenStable();
|
||||
const modal: HTMLElement = fixture.nativeElement.querySelector("clr-modal");
|
||||
expect(modal).toBeTruthy();
|
||||
});
|
||||
it('should open modal', async () => {
|
||||
fixture.autoDetectChanges(true);
|
||||
await fixture.whenStable();
|
||||
const addButton: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#new-instance');
|
||||
addButton.click();
|
||||
await fixture.whenStable();
|
||||
const modal: HTMLElement =
|
||||
fixture.nativeElement.querySelector('clr-modal');
|
||||
expect(modal).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,35 +1,45 @@
|
|||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
Subscription,
|
||||
Observable,
|
||||
forkJoin,
|
||||
throwError as observableThrowError, of
|
||||
Subscription,
|
||||
Observable,
|
||||
forkJoin,
|
||||
throwError as observableThrowError,
|
||||
of,
|
||||
} from 'rxjs';
|
||||
import { DistributionSetupModalComponent } from '../distribution-setup-modal/distribution-setup-modal.component';
|
||||
import { OperationService } from '../../../../shared/components/operation/operation.service';
|
||||
import {
|
||||
operateChanges,
|
||||
OperateInfo,
|
||||
OperationState
|
||||
operateChanges,
|
||||
OperateInfo,
|
||||
OperationState,
|
||||
} from '../../../../shared/components/operation/operate';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { map, catchError, finalize } from 'rxjs/operators';
|
||||
import { clone, getPageSizeFromLocalStorage, PageSizeMapKeys, setPageSizeToLocalStorage } from '../../../../shared/units/utils';
|
||||
import { Instance } from "../../../../../../ng-swagger-gen/models/instance";
|
||||
import { PreheatService } from "../../../../../../ng-swagger-gen/services/preheat.service";
|
||||
import {
|
||||
clone,
|
||||
getPageSizeFromLocalStorage,
|
||||
PageSizeMapKeys,
|
||||
setPageSizeToLocalStorage,
|
||||
} from '../../../../shared/units/utils';
|
||||
import { Instance } from '../../../../../../ng-swagger-gen/models/instance';
|
||||
import { PreheatService } from '../../../../../../ng-swagger-gen/services/preheat.service';
|
||||
import { Metadata } from '../../../../../../ng-swagger-gen/models/metadata';
|
||||
import { FrontInstance, HEALTHY, UNHEALTHY } from '../distribution-interface';
|
||||
import { ClrDatagridStateInterface } from '@clr/angular';
|
||||
import { ConfirmationDialogService } from "../../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../shared/entities/shared.const";
|
||||
import { errorHandler } from "../../../../shared/units/shared.utils";
|
||||
import { ConfirmationMessage } from "../../../global-confirmation-dialog/confirmation-message";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { ConfirmationDialogService } from '../../../global-confirmation-dialog/confirmation-dialog.service';
|
||||
import {
|
||||
ConfirmationButtons,
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
} from '../../../../shared/entities/shared.const';
|
||||
import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
import { ConfirmationMessage } from '../../../global-confirmation-dialog/confirmation-message';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
interface MultiOperateData {
|
||||
operation: string;
|
||||
instances: Instance[];
|
||||
operation: string;
|
||||
instances: Instance[];
|
||||
}
|
||||
|
||||
const DEFAULT_ICON: string = 'images/harbor-logo.svg';
|
||||
|
@ -37,395 +47,463 @@ const KRAKEN_ICON: string = 'images/kraken-logo-color.svg';
|
|||
const ONE_THOUSAND: number = 1000;
|
||||
const KRAKEN: string = 'kraken';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'dist-instances',
|
||||
templateUrl: './distribution-instances.component.html',
|
||||
styleUrls: ['./distribution-instances.component.scss']
|
||||
selector: 'dist-instances',
|
||||
templateUrl: './distribution-instances.component.html',
|
||||
styleUrls: ['./distribution-instances.component.scss'],
|
||||
})
|
||||
export class DistributionInstancesComponent implements OnInit, OnDestroy {
|
||||
instances: FrontInstance[] = [];
|
||||
selectedRow: FrontInstance[] = [];
|
||||
instances: FrontInstance[] = [];
|
||||
selectedRow: FrontInstance[] = [];
|
||||
|
||||
pageSize: number = getPageSizeFromLocalStorage(PageSizeMapKeys.DISTRIBUTION_INSTANCE_COMPONENT);
|
||||
currentPage: number = 1;
|
||||
totalCount: number = 0;
|
||||
queryString: string;
|
||||
|
||||
chanSub: Subscription;
|
||||
|
||||
private loading: boolean = true;
|
||||
private operationSubscription: Subscription;
|
||||
|
||||
@ViewChild('setupModal')
|
||||
setupModal: DistributionSetupModalComponent;
|
||||
providerMap: {[key: string]: Metadata} = {};
|
||||
providers: Metadata[] = [];
|
||||
constructor(
|
||||
private disService: PreheatService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private translate: TranslateService,
|
||||
private operationDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService
|
||||
) {
|
||||
// subscribe operation
|
||||
this.operationSubscription = operationDialogService.confirmationConfirm$.subscribe(
|
||||
confirmed => {
|
||||
if (
|
||||
confirmed &&
|
||||
confirmed.source === ConfirmationTargets.INSTANCE &&
|
||||
confirmed.state === ConfirmationState.CONFIRMED
|
||||
) {
|
||||
this.operateInstance(confirmed.data);
|
||||
}
|
||||
}
|
||||
pageSize: number = getPageSizeFromLocalStorage(
|
||||
PageSizeMapKeys.DISTRIBUTION_INSTANCE_COMPONENT
|
||||
);
|
||||
}
|
||||
currentPage: number = 1;
|
||||
totalCount: number = 0;
|
||||
queryString: string;
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.loading;
|
||||
}
|
||||
chanSub: Subscription;
|
||||
|
||||
ngOnInit() {
|
||||
this.getProviders();
|
||||
}
|
||||
private loading: boolean = true;
|
||||
private operationSubscription: Subscription;
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.operationSubscription) {
|
||||
this.operationSubscription.unsubscribe();
|
||||
}
|
||||
if (this.chanSub) {
|
||||
this.chanSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
getProviders() {
|
||||
this.disService.ListProviders().subscribe(
|
||||
providers => {
|
||||
if (providers && providers.length) {
|
||||
this.providers = providers;
|
||||
providers.forEach(item => {
|
||||
this.providerMap[item.id] = item;
|
||||
});
|
||||
}
|
||||
},
|
||||
err => this.msgHandler.error(err)
|
||||
);
|
||||
}
|
||||
|
||||
loadData(state?: ClrDatagridStateInterface) {
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
setPageSizeToLocalStorage(PageSizeMapKeys.DISTRIBUTION_INSTANCE_COMPONENT, this.pageSize);
|
||||
}
|
||||
this.selectedRow = [];
|
||||
const queryParam: PreheatService.ListInstancesParams = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize
|
||||
};
|
||||
if (this.queryString) {
|
||||
queryParam.q = encodeURIComponent(`name=~${this.queryString}`);
|
||||
}
|
||||
this.loading = true;
|
||||
this.disService.ListInstancesResponse(queryParam)
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'), 10
|
||||
);
|
||||
this.instances = response.body as Instance[];
|
||||
this.pingInstances();
|
||||
},
|
||||
err => this.msgHandler.error(err)
|
||||
);
|
||||
}
|
||||
pingInstances() {
|
||||
if (this.instances && this.instances.length) {
|
||||
this.instances.forEach((item, index) => {
|
||||
this.disService.PingInstances({instance: this.handleInstance(item)})
|
||||
.pipe(finalize(() => this.instances[index].hasCheckHealth = true))
|
||||
.subscribe(res => {
|
||||
this.instances[index].pingStatus = HEALTHY;
|
||||
}, error => {
|
||||
this.instances[index].pingStatus = UNHEALTHY;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.queryString = null;
|
||||
this.currentPage = 1;
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
doFilter($evt: any) {
|
||||
this.currentPage = 1;
|
||||
this.queryString = $evt;
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
addInstance() {
|
||||
this.setupModal.openSetupModal(false);
|
||||
}
|
||||
|
||||
editInstance() {
|
||||
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||
this.setupModal.openSetupModal(true, clone(this.selectedRow[0]));
|
||||
}
|
||||
}
|
||||
|
||||
setAsDefault() {
|
||||
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||
const operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.SET_AS_DEFAULT';
|
||||
operMessage.data.id = this.selectedRow[0].id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = this.selectedRow[0].name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
const instance: Instance = clone(this.selectedRow[0]);
|
||||
instance.default = true;
|
||||
this.disService.UpdateInstance({
|
||||
instance: this.handleInstance(instance),
|
||||
preheatInstanceName: this.selectedRow[0].name
|
||||
})
|
||||
.subscribe(
|
||||
() => {
|
||||
this.translate.get('DISTRIBUTION.SET_DEFAULT_SUCCESS').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
this.msgHandler.info(msg);
|
||||
@ViewChild('setupModal')
|
||||
setupModal: DistributionSetupModalComponent;
|
||||
providerMap: { [key: string]: Metadata } = {};
|
||||
providers: Metadata[] = [];
|
||||
constructor(
|
||||
private disService: PreheatService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private translate: TranslateService,
|
||||
private operationDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService
|
||||
) {
|
||||
// subscribe operation
|
||||
this.operationSubscription =
|
||||
operationDialogService.confirmationConfirm$.subscribe(confirmed => {
|
||||
if (
|
||||
confirmed &&
|
||||
confirmed.source === ConfirmationTargets.INSTANCE &&
|
||||
confirmed.state === ConfirmationState.CONFIRMED
|
||||
) {
|
||||
this.operateInstance(confirmed.data);
|
||||
}
|
||||
});
|
||||
this.refresh();
|
||||
},
|
||||
error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate.get('DISTRIBUTION.SET_DEFAULT_FAILED').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.failure, msg);
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
this.msgHandler.error(msg + ': ' + errMsg);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getProviders();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.operationSubscription) {
|
||||
this.operationSubscription.unsubscribe();
|
||||
}
|
||||
if (this.chanSub) {
|
||||
this.chanSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
getProviders() {
|
||||
this.disService.ListProviders().subscribe(
|
||||
providers => {
|
||||
if (providers && providers.length) {
|
||||
this.providers = providers;
|
||||
providers.forEach(item => {
|
||||
this.providerMap[item.id] = item;
|
||||
});
|
||||
}
|
||||
},
|
||||
err => this.msgHandler.error(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Operate the specified Instance
|
||||
operateInstances(operation: string, instances: Instance[]): void {
|
||||
let arr: string[] = [];
|
||||
let title: string;
|
||||
let summary: string;
|
||||
let buttons: ConfirmationButtons;
|
||||
|
||||
switch (operation) {
|
||||
case 'delete':
|
||||
title = 'DISTRIBUTION.DELETION_TITLE';
|
||||
summary = 'DISTRIBUTION.DELETION_SUMMARY';
|
||||
buttons = ConfirmationButtons.DELETE_CANCEL;
|
||||
break;
|
||||
case 'enable':
|
||||
title = 'DISTRIBUTION.ENABLE_TITLE';
|
||||
summary = 'DISTRIBUTION.ENABLE_SUMMARY';
|
||||
buttons = ConfirmationButtons.ENABLE_CANCEL;
|
||||
break;
|
||||
case 'disable':
|
||||
title = 'DISTRIBUTION.DISABLE_TITLE';
|
||||
summary = 'DISTRIBUTION.DISABLE_SUMMARY';
|
||||
buttons = ConfirmationButtons.DISABLE_CANCEL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (instances && instances.length) {
|
||||
instances.forEach(instance => {
|
||||
arr.push(instance.name);
|
||||
});
|
||||
}
|
||||
// Confirm
|
||||
let msg: ConfirmationMessage = new ConfirmationMessage(
|
||||
title,
|
||||
summary,
|
||||
arr.join(','),
|
||||
{ operation: operation, instances: instances },
|
||||
ConfirmationTargets.INSTANCE,
|
||||
buttons
|
||||
);
|
||||
this.operationDialogService.openComfirmDialog(msg);
|
||||
}
|
||||
|
||||
operateInstance(data: MultiOperateData) {
|
||||
let observableLists: any[] = [];
|
||||
if (data.instances && data.instances.length) {
|
||||
switch (data.operation) {
|
||||
case 'delete':
|
||||
data.instances.forEach(instance => {
|
||||
observableLists.push(this.deleteInstance(instance));
|
||||
});
|
||||
break;
|
||||
|
||||
case 'enable':
|
||||
data.instances.forEach(instance => {
|
||||
observableLists.push(this.enableInstance(instance));
|
||||
});
|
||||
break;
|
||||
|
||||
case 'disable':
|
||||
data.instances.forEach(instance => {
|
||||
observableLists.push(this.disableInstance(instance));
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
forkJoin(...observableLists).subscribe(resArr => {
|
||||
if (data.operation === 'delete') {
|
||||
let error;
|
||||
let errorCount: number = 0;
|
||||
if (resArr && resArr.length) {
|
||||
resArr.forEach(item => {// only record the last error
|
||||
if (item instanceof HttpErrorResponse) {
|
||||
error = errorHandler(item);
|
||||
errorCount += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (errorCount === 0) {// All successful
|
||||
this.translate.get('DISTRIBUTION.DELETED_SUCCESS').subscribe(msg => {
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
this.selectedRow = [];
|
||||
this.refresh();
|
||||
} else if (resArr && resArr.length === errorCount && error) {// All failed
|
||||
this.msgHandler.handleError(error);
|
||||
} else if (error) { // Partly failed
|
||||
this.msgHandler.handleError(error);
|
||||
this.selectedRow = [];
|
||||
this.refresh();
|
||||
}
|
||||
} else {
|
||||
this.selectedRow = [];
|
||||
this.refresh();
|
||||
loadData(state?: ClrDatagridStateInterface) {
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
setPageSizeToLocalStorage(
|
||||
PageSizeMapKeys.DISTRIBUTION_INSTANCE_COMPONENT,
|
||||
this.pageSize
|
||||
);
|
||||
}
|
||||
}, error => {
|
||||
this.msgHandler.error(error);
|
||||
});
|
||||
this.selectedRow = [];
|
||||
const queryParam: PreheatService.ListInstancesParams = {
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
};
|
||||
if (this.queryString) {
|
||||
queryParam.q = encodeURIComponent(`name=~${this.queryString}`);
|
||||
}
|
||||
this.loading = true;
|
||||
this.disService
|
||||
.ListInstancesResponse(queryParam)
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'),
|
||||
10
|
||||
);
|
||||
this.instances = response.body as Instance[];
|
||||
this.pingInstances();
|
||||
},
|
||||
err => this.msgHandler.error(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
deleteInstance(instance: Instance): Observable<any> {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.DELETE_INSTANCE';
|
||||
operMessage.data.id = instance.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = instance.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
|
||||
return this.disService.DeleteInstance({preheatInstanceName: instance.name}).pipe(
|
||||
map(() => {
|
||||
this.translate.get('DISTRIBUTION.DELETED_SUCCESS').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
});
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate.get('DISTRIBUTION.DELETED_FAILED').subscribe(msg => {
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
operateChanges(operMessage, OperationState.failure, msg + ': ' + errMsg);
|
||||
});
|
||||
});
|
||||
return of(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
enableInstance(instance: Instance) {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.ENABLE_INSTANCE';
|
||||
operMessage.data.id = instance.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = instance.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
const copiedInstance: Instance = clone(instance);
|
||||
copiedInstance.enabled = true;
|
||||
return this.disService
|
||||
.UpdateInstance({
|
||||
instance: this.handleInstance(copiedInstance),
|
||||
preheatInstanceName: instance.name
|
||||
})
|
||||
.pipe(
|
||||
map(() => {
|
||||
this.translate.get('DISTRIBUTION.ENABLE_SUCCESS').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate.get('DISTRIBUTION.ENABLE_FAILED').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.failure, msg);
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
this.msgHandler.error(msg + ': ' + errMsg);
|
||||
pingInstances() {
|
||||
if (this.instances && this.instances.length) {
|
||||
this.instances.forEach((item, index) => {
|
||||
this.disService
|
||||
.PingInstances({ instance: this.handleInstance(item) })
|
||||
.pipe(
|
||||
finalize(
|
||||
() => (this.instances[index].hasCheckHealth = true)
|
||||
)
|
||||
)
|
||||
.subscribe(
|
||||
res => {
|
||||
this.instances[index].pingStatus = HEALTHY;
|
||||
},
|
||||
error => {
|
||||
this.instances[index].pingStatus = UNHEALTHY;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
return observableThrowError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disableInstance(instance: Instance) {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.DISABLE_INSTANCE';
|
||||
operMessage.data.id = instance.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = instance.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
const copiedInstance: Instance = clone(instance);
|
||||
copiedInstance.enabled = false;
|
||||
return this.disService
|
||||
.UpdateInstance({
|
||||
instance: this.handleInstance(copiedInstance),
|
||||
preheatInstanceName: instance.name
|
||||
})
|
||||
.pipe(
|
||||
map(() => {
|
||||
this.translate.get('DISTRIBUTION.DISABLE_SUCCESS').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate.get('DISTRIBUTION.DISABLE_FAILED').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.failure, msg);
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
this.msgHandler.error(msg + ': ' + errMsg);
|
||||
refresh() {
|
||||
this.queryString = null;
|
||||
this.currentPage = 1;
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
doFilter($evt: any) {
|
||||
this.currentPage = 1;
|
||||
this.queryString = $evt;
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
addInstance() {
|
||||
this.setupModal.openSetupModal(false);
|
||||
}
|
||||
|
||||
editInstance() {
|
||||
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||
this.setupModal.openSetupModal(true, clone(this.selectedRow[0]));
|
||||
}
|
||||
}
|
||||
|
||||
setAsDefault() {
|
||||
if (this.selectedRow && this.selectedRow.length === 1) {
|
||||
const operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.SET_AS_DEFAULT';
|
||||
operMessage.data.id = this.selectedRow[0].id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = this.selectedRow[0].name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
const instance: Instance = clone(this.selectedRow[0]);
|
||||
instance.default = true;
|
||||
this.disService
|
||||
.UpdateInstance({
|
||||
instance: this.handleInstance(instance),
|
||||
preheatInstanceName: this.selectedRow[0].name,
|
||||
})
|
||||
.subscribe(
|
||||
() => {
|
||||
this.translate
|
||||
.get('DISTRIBUTION.SET_DEFAULT_SUCCESS')
|
||||
.subscribe(msg => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.success
|
||||
);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
this.refresh();
|
||||
},
|
||||
error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate
|
||||
.get('DISTRIBUTION.SET_DEFAULT_FAILED')
|
||||
.subscribe(msg => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.failure,
|
||||
msg
|
||||
);
|
||||
this.translate
|
||||
.get(message)
|
||||
.subscribe(errMsg => {
|
||||
this.msgHandler.error(
|
||||
msg + ': ' + errMsg
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
// Operate the specified Instance
|
||||
operateInstances(operation: string, instances: Instance[]): void {
|
||||
let arr: string[] = [];
|
||||
let title: string;
|
||||
let summary: string;
|
||||
let buttons: ConfirmationButtons;
|
||||
|
||||
switch (operation) {
|
||||
case 'delete':
|
||||
title = 'DISTRIBUTION.DELETION_TITLE';
|
||||
summary = 'DISTRIBUTION.DELETION_SUMMARY';
|
||||
buttons = ConfirmationButtons.DELETE_CANCEL;
|
||||
break;
|
||||
case 'enable':
|
||||
title = 'DISTRIBUTION.ENABLE_TITLE';
|
||||
summary = 'DISTRIBUTION.ENABLE_SUMMARY';
|
||||
buttons = ConfirmationButtons.ENABLE_CANCEL;
|
||||
break;
|
||||
case 'disable':
|
||||
title = 'DISTRIBUTION.DISABLE_TITLE';
|
||||
summary = 'DISTRIBUTION.DISABLE_SUMMARY';
|
||||
buttons = ConfirmationButtons.DISABLE_CANCEL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (instances && instances.length) {
|
||||
instances.forEach(instance => {
|
||||
arr.push(instance.name);
|
||||
});
|
||||
});
|
||||
return observableThrowError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
// Confirm
|
||||
let msg: ConfirmationMessage = new ConfirmationMessage(
|
||||
title,
|
||||
summary,
|
||||
arr.join(','),
|
||||
{ operation: operation, instances: instances },
|
||||
ConfirmationTargets.INSTANCE,
|
||||
buttons
|
||||
);
|
||||
this.operationDialogService.openComfirmDialog(msg);
|
||||
}
|
||||
|
||||
fmtTime(time: number) {
|
||||
let date = new Date();
|
||||
return date.setTime(time * ONE_THOUSAND);
|
||||
}
|
||||
showDefaultIcon(event: any, vendor: string) {
|
||||
if (event && event.target) {
|
||||
if (KRAKEN === vendor) {
|
||||
event.target.src = KRAKEN_ICON;
|
||||
} else {
|
||||
event.target.src = DEFAULT_ICON;
|
||||
}
|
||||
operateInstance(data: MultiOperateData) {
|
||||
let observableLists: any[] = [];
|
||||
if (data.instances && data.instances.length) {
|
||||
switch (data.operation) {
|
||||
case 'delete':
|
||||
data.instances.forEach(instance => {
|
||||
observableLists.push(this.deleteInstance(instance));
|
||||
});
|
||||
break;
|
||||
|
||||
case 'enable':
|
||||
data.instances.forEach(instance => {
|
||||
observableLists.push(this.enableInstance(instance));
|
||||
});
|
||||
break;
|
||||
|
||||
case 'disable':
|
||||
data.instances.forEach(instance => {
|
||||
observableLists.push(this.disableInstance(instance));
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
forkJoin(...observableLists).subscribe(
|
||||
resArr => {
|
||||
if (data.operation === 'delete') {
|
||||
let error;
|
||||
let errorCount: number = 0;
|
||||
if (resArr && resArr.length) {
|
||||
resArr.forEach(item => {
|
||||
// only record the last error
|
||||
if (item instanceof HttpErrorResponse) {
|
||||
error = errorHandler(item);
|
||||
errorCount += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (errorCount === 0) {
|
||||
// All successful
|
||||
this.translate
|
||||
.get('DISTRIBUTION.DELETED_SUCCESS')
|
||||
.subscribe(msg => {
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
this.selectedRow = [];
|
||||
this.refresh();
|
||||
} else if (
|
||||
resArr &&
|
||||
resArr.length === errorCount &&
|
||||
error
|
||||
) {
|
||||
// All failed
|
||||
this.msgHandler.handleError(error);
|
||||
} else if (error) {
|
||||
// Partly failed
|
||||
this.msgHandler.handleError(error);
|
||||
this.selectedRow = [];
|
||||
this.refresh();
|
||||
}
|
||||
} else {
|
||||
this.selectedRow = [];
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.msgHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleInstance(instance: FrontInstance): FrontInstance {
|
||||
if (instance) {
|
||||
const copyOne: FrontInstance = clone(instance);
|
||||
delete copyOne.hasCheckHealth;
|
||||
delete copyOne.pingStatus;
|
||||
return copyOne;
|
||||
deleteInstance(instance: Instance): Observable<any> {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.DELETE_INSTANCE';
|
||||
operMessage.data.id = instance.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = instance.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
|
||||
return this.disService
|
||||
.DeleteInstance({ preheatInstanceName: instance.name })
|
||||
.pipe(
|
||||
map(() => {
|
||||
this.translate
|
||||
.get('DISTRIBUTION.DELETED_SUCCESS')
|
||||
.subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
});
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate
|
||||
.get('DISTRIBUTION.DELETED_FAILED')
|
||||
.subscribe(msg => {
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.failure,
|
||||
msg + ': ' + errMsg
|
||||
);
|
||||
});
|
||||
});
|
||||
return of(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
enableInstance(instance: Instance) {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.ENABLE_INSTANCE';
|
||||
operMessage.data.id = instance.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = instance.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
const copiedInstance: Instance = clone(instance);
|
||||
copiedInstance.enabled = true;
|
||||
return this.disService
|
||||
.UpdateInstance({
|
||||
instance: this.handleInstance(copiedInstance),
|
||||
preheatInstanceName: instance.name,
|
||||
})
|
||||
.pipe(
|
||||
map(() => {
|
||||
this.translate
|
||||
.get('DISTRIBUTION.ENABLE_SUCCESS')
|
||||
.subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate
|
||||
.get('DISTRIBUTION.ENABLE_FAILED')
|
||||
.subscribe(msg => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.failure,
|
||||
msg
|
||||
);
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
this.msgHandler.error(msg + ': ' + errMsg);
|
||||
});
|
||||
});
|
||||
return observableThrowError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
disableInstance(instance: Instance) {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.DISABLE_INSTANCE';
|
||||
operMessage.data.id = instance.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = instance.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
const copiedInstance: Instance = clone(instance);
|
||||
copiedInstance.enabled = false;
|
||||
return this.disService
|
||||
.UpdateInstance({
|
||||
instance: this.handleInstance(copiedInstance),
|
||||
preheatInstanceName: instance.name,
|
||||
})
|
||||
.pipe(
|
||||
map(() => {
|
||||
this.translate
|
||||
.get('DISTRIBUTION.DISABLE_SUCCESS')
|
||||
.subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
}),
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translate
|
||||
.get('DISTRIBUTION.DISABLE_FAILED')
|
||||
.subscribe(msg => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.failure,
|
||||
msg
|
||||
);
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
this.msgHandler.error(msg + ': ' + errMsg);
|
||||
});
|
||||
});
|
||||
return observableThrowError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fmtTime(time: number) {
|
||||
let date = new Date();
|
||||
return date.setTime(time * ONE_THOUSAND);
|
||||
}
|
||||
showDefaultIcon(event: any, vendor: string) {
|
||||
if (event && event.target) {
|
||||
if (KRAKEN === vendor) {
|
||||
event.target.src = KRAKEN_ICON;
|
||||
} else {
|
||||
event.target.src = DEFAULT_ICON;
|
||||
}
|
||||
}
|
||||
}
|
||||
handleInstance(instance: FrontInstance): FrontInstance {
|
||||
if (instance) {
|
||||
const copyOne: FrontInstance = clone(instance);
|
||||
delete copyOne.hasCheckHealth;
|
||||
delete copyOne.pingStatus;
|
||||
return copyOne;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { Instance } from '../../../../../ng-swagger-gen/models/instance';
|
||||
|
||||
export class AuthMode {
|
||||
static NONE = 'NONE';
|
||||
static BASIC = 'BASIC';
|
||||
static OAUTH = 'OAUTH';
|
||||
static CUSTOM = 'CUSTOM';
|
||||
static NONE = 'NONE';
|
||||
static BASIC = 'BASIC';
|
||||
static OAUTH = 'OAUTH';
|
||||
static CUSTOM = 'CUSTOM';
|
||||
}
|
||||
|
||||
export enum PreheatingStatusEnum {
|
||||
// front status
|
||||
NOT_PREHEATED = 'NOT_PREHEATED',
|
||||
// back-end status
|
||||
PENDING = 'PENDING',
|
||||
RUNNING = 'RUNNING',
|
||||
SUCCESS = 'SUCCESS',
|
||||
FAIL = 'FAIL',
|
||||
// front status
|
||||
NOT_PREHEATED = 'NOT_PREHEATED',
|
||||
// back-end status
|
||||
PENDING = 'PENDING',
|
||||
RUNNING = 'RUNNING',
|
||||
SUCCESS = 'SUCCESS',
|
||||
FAIL = 'FAIL',
|
||||
}
|
||||
|
||||
export interface FrontInstance extends Instance {
|
||||
hasCheckHealth?: boolean;
|
||||
pingStatus?: string;
|
||||
export interface FrontInstance extends Instance {
|
||||
hasCheckHealth?: boolean;
|
||||
pingStatus?: string;
|
||||
}
|
||||
|
||||
export const HEALTHY: string = 'Healthy';
|
||||
|
|
|
@ -1,259 +1,357 @@
|
|||
<clr-modal
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalClosable]="false"
|
||||
[clrModalStaticBackdrop]="true"
|
||||
>
|
||||
<h3 class="modal-title">{{ title | translate }}</h3>
|
||||
<div class="modal-body">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<form #instanceForm="ngForm" class="clr-form clr-form-horizontal">
|
||||
<!-- 1. provider -->
|
||||
<clr-select-container>
|
||||
<label class="required">{{
|
||||
'DISTRIBUTION.PROVIDER' | translate
|
||||
}}</label>
|
||||
<select class="width-280"
|
||||
clrSelect
|
||||
name="provider"
|
||||
id="provider"
|
||||
[(ngModel)]="model.vendor"
|
||||
required
|
||||
>
|
||||
<option class="display-none" value=""></option>
|
||||
<option
|
||||
*ngFor="let provider of providers"
|
||||
value="{{ provider.id }}"
|
||||
>{{ provider.name }}</option
|
||||
>
|
||||
</select>
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-select-container>
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalClosable]="false"
|
||||
[clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">{{ title | translate }}</h3>
|
||||
<div class="modal-body">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<form #instanceForm="ngForm" class="clr-form clr-form-horizontal">
|
||||
<!-- 1. provider -->
|
||||
<clr-select-container>
|
||||
<label class="required">{{
|
||||
'DISTRIBUTION.PROVIDER' | translate
|
||||
}}</label>
|
||||
<select
|
||||
class="width-280"
|
||||
clrSelect
|
||||
name="provider"
|
||||
id="provider"
|
||||
[(ngModel)]="model.vendor"
|
||||
required>
|
||||
<option class="display-none" value=""></option>
|
||||
<option
|
||||
*ngFor="let provider of providers"
|
||||
value="{{ provider.id }}">
|
||||
{{ provider.name }}
|
||||
</option>
|
||||
</select>
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-select-container>
|
||||
|
||||
<!-- 2. name -->
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label" for="name">{{'DISTRIBUTION.NAME' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="((nameNg.dirty || nameNg.touched) && nameNg.invalid) || isNameExisting">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="width-280 clr-input"
|
||||
required
|
||||
type="text"
|
||||
id="name"
|
||||
autocomplete="off"
|
||||
[(ngModel)]="model.name"
|
||||
name="name"
|
||||
#nameNg="ngModel"
|
||||
(input)="inputName()"
|
||||
/>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkNameOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="((nameNg.dirty || nameNg.touched) && nameNg.invalid) || isNameExisting">
|
||||
<span *ngIf="!((nameNg.dirty || nameNg.touched) && nameNg.invalid) && isNameExisting">{{'SCANNER.NAME_EXISTS' | translate}}</span>
|
||||
<span *ngIf="(nameNg.dirty || nameNg.touched) && nameNg.invalid">{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2. name -->
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label" for="name">{{
|
||||
'DISTRIBUTION.NAME' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="
|
||||
((nameNg.dirty || nameNg.touched) && nameNg.invalid) ||
|
||||
isNameExisting
|
||||
">
|
||||
<div class="clr-input-wrapper">
|
||||
<input
|
||||
class="width-280 clr-input"
|
||||
required
|
||||
type="text"
|
||||
id="name"
|
||||
autocomplete="off"
|
||||
[(ngModel)]="model.name"
|
||||
name="name"
|
||||
#nameNg="ngModel"
|
||||
(input)="inputName()" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
<span
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="!checkNameOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error
|
||||
*ngIf="
|
||||
((nameNg.dirty || nameNg.touched) &&
|
||||
nameNg.invalid) ||
|
||||
isNameExisting
|
||||
">
|
||||
<span
|
||||
*ngIf="
|
||||
!(
|
||||
(nameNg.dirty || nameNg.touched) &&
|
||||
nameNg.invalid
|
||||
) && isNameExisting
|
||||
"
|
||||
>{{ 'SCANNER.NAME_EXISTS' | translate }}</span
|
||||
>
|
||||
<span
|
||||
*ngIf="
|
||||
(nameNg.dirty || nameNg.touched) &&
|
||||
nameNg.invalid
|
||||
"
|
||||
>{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}</span
|
||||
>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. description -->
|
||||
<clr-textarea-container>
|
||||
<label>{{ 'DISTRIBUTION.DESCRIPTION' | translate }}</label>
|
||||
<textarea
|
||||
clrTextarea
|
||||
type="text"
|
||||
id="description"
|
||||
class="width-280"
|
||||
row="3"
|
||||
[(ngModel)]="model.description"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
></textarea>
|
||||
</clr-textarea-container>
|
||||
<!-- 4. endpoint -->
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label" for="name">{{'DISTRIBUTION.ENDPOINT' | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="((endpointNg.dirty || endpointNg.touched) && endpointNg.invalid) || isEndpointExisting">
|
||||
<div class="clr-input-wrapper">
|
||||
<input class="width-280 clr-input"
|
||||
required
|
||||
pattern="^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(.*?)*$"
|
||||
type="text"
|
||||
id="endpoint"
|
||||
placeholder="http(s)://192.168.1.1"
|
||||
[(ngModel)]="model.endpoint"
|
||||
name="endpoint"
|
||||
#endpointNg="ngModel"
|
||||
autocomplete="off"
|
||||
(input)="inputEndpoint()"
|
||||
/>
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkEndpointOngoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="((endpointNg.dirty || endpointNg.touched) && endpointNg.invalid) || isEndpointExisting">
|
||||
<span *ngIf="!((endpointNg.dirty || endpointNg.touched) && endpointNg.invalid) && isEndpointExisting">{{'SCANNER.ENDPOINT_EXISTS' | translate}}</span>
|
||||
<span *ngIf="(endpointNg.dirty || endpointNg.touched) && endpointNg.invalid">{{ 'TOOLTIP.ENDPOINT_FORMAT' | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- auth mode -->
|
||||
<clr-radio-container clrInline>
|
||||
<label>{{ 'DISTRIBUTION.AUTH_MODE' | translate }}</label>
|
||||
<clr-radio-wrapper>
|
||||
<input
|
||||
clrRadio
|
||||
type="radio"
|
||||
name="auth_mode"
|
||||
id="none_mode"
|
||||
value="NONE"
|
||||
[(ngModel)]="model.auth_mode"
|
||||
(change)="authModeChange()"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
<label for="none_mode">NONE</label>
|
||||
</clr-radio-wrapper>
|
||||
<clr-radio-wrapper>
|
||||
<input
|
||||
clrRadio
|
||||
type="radio"
|
||||
name="auth_mode"
|
||||
id="basic_mode"
|
||||
value="BASIC"
|
||||
[(ngModel)]="model.auth_mode"
|
||||
(change)="authModeChange()"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
<label for="basic_mode">Basic</label>
|
||||
</clr-radio-wrapper>
|
||||
<clr-radio-wrapper>
|
||||
<input
|
||||
clrRadio
|
||||
type="radio"
|
||||
name="auth_mode"
|
||||
id="token_mode"
|
||||
value="OAUTH"
|
||||
[(ngModel)]="model.auth_mode"
|
||||
(change)="authModeChange()"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
<label for="token_mode">OAuth</label>
|
||||
</clr-radio-wrapper>
|
||||
</clr-radio-container>
|
||||
<!-- auth data -->
|
||||
<span *ngIf="model.auth_mode == 'BASIC'">
|
||||
<clr-input-container>
|
||||
<label class="required clr-control-label" for="auth_data_username">{{
|
||||
'DISTRIBUTION.USERNAME' | translate
|
||||
}}</label>
|
||||
<input
|
||||
class="width-280"
|
||||
clrInput
|
||||
required
|
||||
type="text"
|
||||
id="auth_data_username"
|
||||
[(ngModel)]="authData['username']"
|
||||
placeholder="{{
|
||||
'DISTRIBUTION.SETUP.USERNAME_PLACEHOLDER' | translate
|
||||
}}"
|
||||
name="auth_data_username"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label class="required clr-control-label" for="auth_data_password">{{
|
||||
'DISTRIBUTION.PASSWORD' | translate
|
||||
}}</label>
|
||||
<input
|
||||
class="width-280"
|
||||
clrInput
|
||||
required
|
||||
type="password"
|
||||
id="auth_data_password"
|
||||
[(ngModel)]="authData['password']"
|
||||
placeholder="{{
|
||||
'DISTRIBUTION.SETUP.PASSWORD_PLACEHOLDER' | translate
|
||||
}}"
|
||||
name="auth_data_password"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
</span>
|
||||
<span *ngIf="model.auth_mode == 'OAUTH'">
|
||||
<clr-input-container>
|
||||
<label class="required clr-control-label" for="auth_data_token">{{
|
||||
'DISTRIBUTION.TOKEN' | translate
|
||||
}}</label>
|
||||
<input
|
||||
class="width-280"
|
||||
clrInput
|
||||
required
|
||||
type="password"
|
||||
id="auth_data_token"
|
||||
[(ngModel)]="authData['token']"
|
||||
placeholder="{{
|
||||
'DISTRIBUTION.SETUP.TOKEN_PLACEHOLDER' | translate
|
||||
}}"
|
||||
name="auth_data_token"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
</span>
|
||||
<span *ngIf="model.auth_mode == 'NONE'"></span>
|
||||
<!-- 3. description -->
|
||||
<clr-textarea-container>
|
||||
<label>{{ 'DISTRIBUTION.DESCRIPTION' | translate }}</label>
|
||||
<textarea
|
||||
clrTextarea
|
||||
type="text"
|
||||
id="description"
|
||||
class="width-280"
|
||||
row="3"
|
||||
[(ngModel)]="model.description"
|
||||
[ngModelOptions]="{ standalone: true }"></textarea>
|
||||
</clr-textarea-container>
|
||||
<!-- 4. endpoint -->
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label" for="name">{{
|
||||
'DISTRIBUTION.ENDPOINT' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="
|
||||
((endpointNg.dirty || endpointNg.touched) &&
|
||||
endpointNg.invalid) ||
|
||||
isEndpointExisting
|
||||
">
|
||||
<div class="clr-input-wrapper">
|
||||
<input
|
||||
class="width-280 clr-input"
|
||||
required
|
||||
pattern="^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(.*?)*$"
|
||||
type="text"
|
||||
id="endpoint"
|
||||
placeholder="http(s)://192.168.1.1"
|
||||
[(ngModel)]="model.endpoint"
|
||||
name="endpoint"
|
||||
#endpointNg="ngModel"
|
||||
autocomplete="off"
|
||||
(input)="inputEndpoint()" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
<span
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="!checkEndpointOngoing"></span>
|
||||
</div>
|
||||
<clr-control-error
|
||||
*ngIf="
|
||||
((endpointNg.dirty || endpointNg.touched) &&
|
||||
endpointNg.invalid) ||
|
||||
isEndpointExisting
|
||||
">
|
||||
<span
|
||||
*ngIf="
|
||||
!(
|
||||
(endpointNg.dirty || endpointNg.touched) &&
|
||||
endpointNg.invalid
|
||||
) && isEndpointExisting
|
||||
"
|
||||
>{{ 'SCANNER.ENDPOINT_EXISTS' | translate }}</span
|
||||
>
|
||||
<span
|
||||
*ngIf="
|
||||
(endpointNg.dirty || endpointNg.touched) &&
|
||||
endpointNg.invalid
|
||||
"
|
||||
>{{ 'TOOLTIP.ENDPOINT_FORMAT' | translate }}</span
|
||||
>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<!-- auth mode -->
|
||||
<clr-radio-container clrInline>
|
||||
<label>{{ 'DISTRIBUTION.AUTH_MODE' | translate }}</label>
|
||||
<clr-radio-wrapper>
|
||||
<input
|
||||
clrRadio
|
||||
type="radio"
|
||||
name="auth_mode"
|
||||
id="none_mode"
|
||||
value="NONE"
|
||||
[(ngModel)]="model.auth_mode"
|
||||
(change)="authModeChange()"
|
||||
[ngModelOptions]="{ standalone: true }" />
|
||||
<label for="none_mode">NONE</label>
|
||||
</clr-radio-wrapper>
|
||||
<clr-radio-wrapper>
|
||||
<input
|
||||
clrRadio
|
||||
type="radio"
|
||||
name="auth_mode"
|
||||
id="basic_mode"
|
||||
value="BASIC"
|
||||
[(ngModel)]="model.auth_mode"
|
||||
(change)="authModeChange()"
|
||||
[ngModelOptions]="{ standalone: true }" />
|
||||
<label for="basic_mode">Basic</label>
|
||||
</clr-radio-wrapper>
|
||||
<clr-radio-wrapper>
|
||||
<input
|
||||
clrRadio
|
||||
type="radio"
|
||||
name="auth_mode"
|
||||
id="token_mode"
|
||||
value="OAUTH"
|
||||
[(ngModel)]="model.auth_mode"
|
||||
(change)="authModeChange()"
|
||||
[ngModelOptions]="{ standalone: true }" />
|
||||
<label for="token_mode">OAuth</label>
|
||||
</clr-radio-wrapper>
|
||||
</clr-radio-container>
|
||||
<!-- auth data -->
|
||||
<span *ngIf="model.auth_mode === 'BASIC'">
|
||||
<clr-input-container>
|
||||
<label
|
||||
class="required clr-control-label"
|
||||
for="auth_data_username"
|
||||
>{{ 'DISTRIBUTION.USERNAME' | translate }}</label
|
||||
>
|
||||
<input
|
||||
class="width-280"
|
||||
clrInput
|
||||
required
|
||||
type="text"
|
||||
id="auth_data_username"
|
||||
[(ngModel)]="authData['username']"
|
||||
placeholder="{{
|
||||
'DISTRIBUTION.SETUP.USERNAME_PLACEHOLDER'
|
||||
| translate
|
||||
}}"
|
||||
name="auth_data_username"
|
||||
autocomplete="off" />
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
<clr-input-container>
|
||||
<label
|
||||
class="required clr-control-label"
|
||||
for="auth_data_password"
|
||||
>{{ 'DISTRIBUTION.PASSWORD' | translate }}</label
|
||||
>
|
||||
<input
|
||||
class="width-280"
|
||||
clrInput
|
||||
required
|
||||
type="password"
|
||||
id="auth_data_password"
|
||||
[(ngModel)]="authData['password']"
|
||||
placeholder="{{
|
||||
'DISTRIBUTION.SETUP.PASSWORD_PLACEHOLDER'
|
||||
| translate
|
||||
}}"
|
||||
name="auth_data_password"
|
||||
autocomplete="off" />
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
</span>
|
||||
<span *ngIf="model.auth_mode === 'OAUTH'">
|
||||
<clr-input-container>
|
||||
<label
|
||||
class="required clr-control-label"
|
||||
for="auth_data_token"
|
||||
>{{ 'DISTRIBUTION.TOKEN' | translate }}</label
|
||||
>
|
||||
<input
|
||||
class="width-280"
|
||||
clrInput
|
||||
required
|
||||
type="password"
|
||||
id="auth_data_token"
|
||||
[(ngModel)]="authData['token']"
|
||||
placeholder="{{
|
||||
'DISTRIBUTION.SETUP.TOKEN_PLACEHOLDER' | translate
|
||||
}}"
|
||||
name="auth_data_token"
|
||||
autocomplete="off" />
|
||||
<clr-control-error>
|
||||
{{ 'TOOLTIP.ITEM_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</clr-input-container>
|
||||
</span>
|
||||
<span *ngIf="model.auth_mode === 'NONE'"></span>
|
||||
|
||||
<!-- 5. enabled -->
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{"SCANNER.OPTIONS" | translate}}</label>
|
||||
<div class="clr-control-container padding-top-3">
|
||||
<clr-checkbox-wrapper>
|
||||
<input
|
||||
clrCheckbox
|
||||
id="enabled"
|
||||
name="enabled"
|
||||
type="checkbox"
|
||||
[(ngModel)]="model.enabled"
|
||||
/>
|
||||
<label for="enabled"><span>{{ 'DISTRIBUTION.ENABLED' | translate }}</span></label>
|
||||
</clr-checkbox-wrapper>
|
||||
<clr-checkbox-wrapper>
|
||||
<input name="insecure" clrCheckbox
|
||||
type="checkbox" id="insecure"
|
||||
[(ngModel)]="model.insecure"
|
||||
>
|
||||
<label for="insecure">{{"SCANNER.SKIP" | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon class="color-57" clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen>
|
||||
{{'P2P_PROVIDER.SKIP_CERT_VERIFY' | translate}}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="button-test" type="button" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="!isValid || onTesting || checkNameOnGoing || checkEndpointOngoing || isEndpointExisting || isNameExisting">{{'SCANNER.TEST_CONNECTION' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
id="instance-ok"
|
||||
[clrLoading]="saveBtnState"
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
(click)="submit()"
|
||||
[disabled]="!isValid || !hasChangesForEdit() || checkNameOnGoing || checkEndpointOngoing"
|
||||
>
|
||||
{{ 'BUTTON.OK' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- 5. enabled -->
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{
|
||||
'SCANNER.OPTIONS' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container padding-top-3">
|
||||
<clr-checkbox-wrapper>
|
||||
<input
|
||||
clrCheckbox
|
||||
id="enabled"
|
||||
name="enabled"
|
||||
type="checkbox"
|
||||
[(ngModel)]="model.enabled" />
|
||||
<label for="enabled"
|
||||
><span>{{
|
||||
'DISTRIBUTION.ENABLED' | translate
|
||||
}}</span></label
|
||||
>
|
||||
</clr-checkbox-wrapper>
|
||||
<clr-checkbox-wrapper>
|
||||
<input
|
||||
name="insecure"
|
||||
clrCheckbox
|
||||
type="checkbox"
|
||||
id="insecure"
|
||||
[(ngModel)]="model.insecure" />
|
||||
<label for="insecure"
|
||||
>{{ 'SCANNER.SKIP' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon
|
||||
class="color-57"
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-left"
|
||||
clrSize="md"
|
||||
*clrIfOpen>
|
||||
{{
|
||||
'P2P_PROVIDER.SKIP_CERT_VERIFY'
|
||||
| translate
|
||||
}}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
id="button-test"
|
||||
type="button"
|
||||
[clrLoading]="checkBtnState"
|
||||
class="btn btn-outline"
|
||||
(click)="onTestEndpoint()"
|
||||
[disabled]="
|
||||
!isValid ||
|
||||
onTesting ||
|
||||
checkNameOnGoing ||
|
||||
checkEndpointOngoing ||
|
||||
isEndpointExisting ||
|
||||
isNameExisting
|
||||
">
|
||||
{{ 'SCANNER.TEST_CONNECTION' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
id="instance-ok"
|
||||
[clrLoading]="saveBtnState"
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
(click)="submit()"
|
||||
[disabled]="
|
||||
!isValid ||
|
||||
!hasChangesForEdit() ||
|
||||
checkNameOnGoing ||
|
||||
checkEndpointOngoing
|
||||
">
|
||||
{{ 'BUTTON.OK' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
|
|
@ -5,103 +5,105 @@ import { SharedTestingModule } from '../../../../shared/shared.module';
|
|||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { DistributionSetupModalComponent } from './distribution-setup-modal.component';
|
||||
import { PreheatService } from "../../../../../../ng-swagger-gen/services/preheat.service";
|
||||
import { PreheatService } from '../../../../../../ng-swagger-gen/services/preheat.service';
|
||||
import { Instance } from '../../../../../../ng-swagger-gen/models/instance';
|
||||
import { of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
describe('DistributionSetupModalComponent', () => {
|
||||
let component: DistributionSetupModalComponent;
|
||||
let fixture: ComponentFixture<DistributionSetupModalComponent>;
|
||||
let component: DistributionSetupModalComponent;
|
||||
let fixture: ComponentFixture<DistributionSetupModalComponent>;
|
||||
|
||||
const instance1: Instance = {
|
||||
name: 'Test1',
|
||||
default: true,
|
||||
enabled: true,
|
||||
description: 'Test1',
|
||||
endpoint: 'http://test.com',
|
||||
id: 1,
|
||||
setup_timestamp: new Date().getTime(),
|
||||
auth_mode: 'NONE',
|
||||
vendor: 'kraken',
|
||||
status: 'Healthy'
|
||||
};
|
||||
const fakedPreheatService = {
|
||||
ListInstances() {
|
||||
return of([]).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
const instance1: Instance = {
|
||||
name: 'Test1',
|
||||
default: true,
|
||||
enabled: true,
|
||||
description: 'Test1',
|
||||
endpoint: 'http://test.com',
|
||||
id: 1,
|
||||
setup_timestamp: new Date().getTime(),
|
||||
auth_mode: 'NONE',
|
||||
vendor: 'kraken',
|
||||
status: 'Healthy',
|
||||
};
|
||||
const fakedPreheatService = {
|
||||
ListInstances() {
|
||||
return of([]).pipe(delay(0));
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ClarityModule,
|
||||
TranslateModule,
|
||||
SharedTestingModule,
|
||||
HttpClientTestingModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [ { provide: PreheatService, useValue: fakedPreheatService }],
|
||||
declarations: [DistributionSetupModalComponent]
|
||||
}).compileComponents();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ClarityModule,
|
||||
TranslateModule,
|
||||
SharedTestingModule,
|
||||
HttpClientTestingModule,
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
{ provide: PreheatService, useValue: fakedPreheatService },
|
||||
],
|
||||
declarations: [DistributionSetupModalComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DistributionSetupModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DistributionSetupModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show "name is required"', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component._open();
|
||||
await fixture.whenStable();
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
nameInput.value = "";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('should show "name is required"', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component._open();
|
||||
await fixture.whenStable();
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
nameInput.value = '';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show "endpoint is required"', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component._open();
|
||||
await fixture.whenStable();
|
||||
const endpointInput = fixture.nativeElement.querySelector('#endpoint');
|
||||
endpointInput.value = "svn://test.com";
|
||||
endpointInput.dispatchEvent(new Event('input'));
|
||||
endpointInput.blur();
|
||||
endpointInput.dispatchEvent(new Event('blur'));
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('should show "endpoint is required"', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component._open();
|
||||
await fixture.whenStable();
|
||||
const endpointInput = fixture.nativeElement.querySelector('#endpoint');
|
||||
endpointInput.value = 'svn://test.com';
|
||||
endpointInput.dispatchEvent(new Event('input'));
|
||||
endpointInput.blur();
|
||||
endpointInput.dispatchEvent(new Event('blur'));
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be edit model', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component.openSetupModal(true, instance1);
|
||||
await fixture.whenStable();
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
expect(nameInput.value).toEqual('Test1');
|
||||
});
|
||||
it('should be edit model', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component.openSetupModal(true, instance1);
|
||||
await fixture.whenStable();
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
expect(nameInput.value).toEqual('Test1');
|
||||
});
|
||||
|
||||
it('should be valid', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component._open();
|
||||
await fixture.whenStable();
|
||||
component.model.vendor = 'kraken';
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
nameInput.value = "test";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
const endpointInput = fixture.nativeElement.querySelector('#endpoint');
|
||||
endpointInput.value = "https://test.com";
|
||||
endpointInput.dispatchEvent(new Event('input'));
|
||||
await fixture.whenStable();
|
||||
expect(component.isValid).toBeTruthy();
|
||||
});
|
||||
it('should be valid', async () => {
|
||||
fixture.autoDetectChanges();
|
||||
component._open();
|
||||
await fixture.whenStable();
|
||||
component.model.vendor = 'kraken';
|
||||
const nameInput = fixture.nativeElement.querySelector('#name');
|
||||
nameInput.value = 'test';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
const endpointInput = fixture.nativeElement.querySelector('#endpoint');
|
||||
endpointInput.value = 'https://test.com';
|
||||
endpointInput.dispatchEvent(new Event('input'));
|
||||
await fixture.whenStable();
|
||||
expect(component.isValid).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -16,360 +16,449 @@ import { AuthMode, FrontInstance } from '../distribution-interface';
|
|||
import { clone } from '../../../../shared/units/utils';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
import { Metadata } from '../../../../../../ng-swagger-gen/models/metadata';
|
||||
import { operateChanges, OperateInfo, OperationState } from '../../../../shared/components/operation/operate';
|
||||
import {
|
||||
operateChanges,
|
||||
OperateInfo,
|
||||
OperationState,
|
||||
} from '../../../../shared/components/operation/operate';
|
||||
import { OperationService } from '../../../../shared/components/operation/operation.service';
|
||||
import { debounceTime, distinctUntilChanged, filter, finalize, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
finalize,
|
||||
switchMap,
|
||||
} from 'rxjs/operators';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { InlineAlertComponent } from "../../../../shared/components/inline-alert/inline-alert.component";
|
||||
import { errorHandler } from "../../../../shared/units/shared.utils";
|
||||
import { InlineAlertComponent } from '../../../../shared/components/inline-alert/inline-alert.component';
|
||||
import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
|
||||
const DEFAULT_PROVIDER: string = 'dragonfly';
|
||||
|
||||
@Component({
|
||||
selector: 'dist-setup-modal',
|
||||
templateUrl: './distribution-setup-modal.component.html',
|
||||
styleUrls: ['./distribution-setup-modal.component.scss']
|
||||
selector: 'dist-setup-modal',
|
||||
templateUrl: './distribution-setup-modal.component.html',
|
||||
styleUrls: ['./distribution-setup-modal.component.scss'],
|
||||
})
|
||||
export class DistributionSetupModalComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
providers: Metadata[] = [];
|
||||
model: Instance;
|
||||
originModelForEdit: Instance;
|
||||
opened: boolean = false;
|
||||
editingMode: boolean = false;
|
||||
authData: {[key: string]: any} = {};
|
||||
@ViewChild('instanceForm', { static: true }) instanceForm: NgForm;
|
||||
@ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
@Input()
|
||||
providers: Metadata[] = [];
|
||||
model: Instance;
|
||||
originModelForEdit: Instance;
|
||||
opened: boolean = false;
|
||||
editingMode: boolean = false;
|
||||
authData: { [key: string]: any } = {};
|
||||
@ViewChild('instanceForm', { static: true }) instanceForm: NgForm;
|
||||
@ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent;
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
|
||||
@Output()
|
||||
refresh: EventEmitter<any> = new EventEmitter<any>();
|
||||
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
onTesting: boolean = false;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _endpointSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
private _endpointSubscription: Subscription;
|
||||
isNameExisting: boolean = false;
|
||||
isEndpointExisting: boolean = false;
|
||||
checkNameOnGoing: boolean = false;
|
||||
checkEndpointOngoing: boolean = false;
|
||||
constructor(
|
||||
private distributionService: PreheatService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private translate: TranslateService,
|
||||
private operationService: OperationService
|
||||
) {}
|
||||
@Output()
|
||||
refresh: EventEmitter<any> = new EventEmitter<any>();
|
||||
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
onTesting: boolean = false;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _endpointSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
private _endpointSubscription: Subscription;
|
||||
isNameExisting: boolean = false;
|
||||
isEndpointExisting: boolean = false;
|
||||
checkNameOnGoing: boolean = false;
|
||||
checkEndpointOngoing: boolean = false;
|
||||
constructor(
|
||||
private distributionService: PreheatService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private translate: TranslateService,
|
||||
private operationService: OperationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscribeName();
|
||||
this.subscribeEndpoint();
|
||||
this.reset();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this._nameSubscription) {
|
||||
this._nameSubscription.unsubscribe();
|
||||
this._nameSubscription = null;
|
||||
ngOnInit() {
|
||||
this.subscribeName();
|
||||
this.subscribeEndpoint();
|
||||
this.reset();
|
||||
}
|
||||
if (this._endpointSubscription) {
|
||||
this._endpointSubscription.unsubscribe();
|
||||
this._endpointSubscription = null;
|
||||
ngOnDestroy() {
|
||||
if (this._nameSubscription) {
|
||||
this._nameSubscription.unsubscribe();
|
||||
this._nameSubscription = null;
|
||||
}
|
||||
if (this._endpointSubscription) {
|
||||
this._endpointSubscription.unsubscribe();
|
||||
this._endpointSubscription = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
subscribeName() {
|
||||
if (!this._nameSubscription) {
|
||||
this._nameSubscription = this._nameSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
if (this.editingMode && this.originModelForEdit && this.originModelForEdit.name === name) {
|
||||
return false;
|
||||
}
|
||||
return name.length > 0;
|
||||
}),
|
||||
switchMap((name) => {
|
||||
this.isNameExisting = false;
|
||||
this.checkNameOnGoing = true;
|
||||
return this.distributionService.ListInstances({
|
||||
q: encodeURIComponent(`name=${name}`)
|
||||
}).pipe(finalize(() => this.checkNameOnGoing = false));
|
||||
}))
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isNameExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
subscribeEndpoint() {
|
||||
if (!this._endpointSubscription) {
|
||||
this._endpointSubscription = this._endpointSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(endpoint => {
|
||||
if (this.editingMode && this.originModelForEdit && this.originModelForEdit.endpoint === endpoint) {
|
||||
return false;
|
||||
subscribeName() {
|
||||
if (!this._nameSubscription) {
|
||||
this._nameSubscription = this._nameSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
if (
|
||||
this.editingMode &&
|
||||
this.originModelForEdit &&
|
||||
this.originModelForEdit.name === name
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return name.length > 0;
|
||||
}),
|
||||
switchMap(name => {
|
||||
this.isNameExisting = false;
|
||||
this.checkNameOnGoing = true;
|
||||
return this.distributionService
|
||||
.ListInstances({
|
||||
q: encodeURIComponent(`name=${name}`),
|
||||
})
|
||||
.pipe(
|
||||
finalize(() => (this.checkNameOnGoing = false))
|
||||
);
|
||||
})
|
||||
)
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isNameExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
subscribeEndpoint() {
|
||||
if (!this._endpointSubscription) {
|
||||
this._endpointSubscription = this._endpointSubject
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(endpoint => {
|
||||
if (
|
||||
this.editingMode &&
|
||||
this.originModelForEdit &&
|
||||
this.originModelForEdit.endpoint === endpoint
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return this.instanceForm.control.get('endpoint').valid;
|
||||
}),
|
||||
switchMap(endpoint => {
|
||||
this.isEndpointExisting = false;
|
||||
this.checkEndpointOngoing = true;
|
||||
return this.distributionService
|
||||
.ListInstances({
|
||||
q: encodeURIComponent(`endpoint=${endpoint}`),
|
||||
})
|
||||
.pipe(
|
||||
finalize(
|
||||
() => (this.checkEndpointOngoing = false)
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isEndpointExisting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
inputName() {
|
||||
this._nameSubject.next(this.model.name);
|
||||
}
|
||||
inputEndpoint() {
|
||||
this._endpointSubject.next(this.model.endpoint);
|
||||
}
|
||||
public get isValid(): boolean {
|
||||
return this.instanceForm && this.instanceForm.valid;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.editingMode
|
||||
? 'DISTRIBUTION.EDIT_INSTANCE'
|
||||
: 'DISTRIBUTION.SETUP_NEW_INSTANCE';
|
||||
}
|
||||
|
||||
authModeChange() {
|
||||
if (
|
||||
this.editingMode &&
|
||||
this.model.auth_mode === this.originModelForEdit.auth_mode
|
||||
) {
|
||||
this.authData = clone(this.originModelForEdit.auth_info);
|
||||
} else {
|
||||
switch (this.model.auth_mode) {
|
||||
case AuthMode.BASIC:
|
||||
this.authData = {
|
||||
password: '',
|
||||
username: '',
|
||||
};
|
||||
break;
|
||||
case AuthMode.OAUTH:
|
||||
this.authData = {
|
||||
token: '',
|
||||
};
|
||||
break;
|
||||
default:
|
||||
this.authData = null;
|
||||
break;
|
||||
}
|
||||
return this.instanceForm.control.get('endpoint').valid;
|
||||
}),
|
||||
switchMap((endpoint) => {
|
||||
this.isEndpointExisting = false;
|
||||
this.checkEndpointOngoing = true;
|
||||
return this.distributionService.ListInstances({
|
||||
q: encodeURIComponent(`endpoint=${endpoint}`)
|
||||
}).pipe(finalize(() => this.checkEndpointOngoing = false));
|
||||
}))
|
||||
.subscribe(res => {
|
||||
if (res && res.length > 0) {
|
||||
this.isEndpointExisting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_open() {
|
||||
this.inlineAlert.close();
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
_close() {
|
||||
this.opened = false;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.model = {
|
||||
name: '',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
vendor: '',
|
||||
auth_mode: AuthMode.NONE,
|
||||
auth_info: this.authData,
|
||||
};
|
||||
this.instanceForm.reset({
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
inputName() {
|
||||
this._nameSubject.next(this.model.name);
|
||||
}
|
||||
inputEndpoint() {
|
||||
this._endpointSubject.next(this.model.endpoint);
|
||||
}
|
||||
public get isValid(): boolean {
|
||||
return this.instanceForm && this.instanceForm.valid;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.editingMode
|
||||
? 'DISTRIBUTION.EDIT_INSTANCE'
|
||||
: 'DISTRIBUTION.SETUP_NEW_INSTANCE';
|
||||
}
|
||||
|
||||
authModeChange() {
|
||||
if (this.editingMode && this.model.auth_mode === this.originModelForEdit.auth_mode) {
|
||||
this.authData = clone(this.originModelForEdit.auth_info);
|
||||
} else {
|
||||
switch (this.model.auth_mode) {
|
||||
case AuthMode.BASIC:
|
||||
this.authData = {
|
||||
password: '',
|
||||
username: ''
|
||||
};
|
||||
break;
|
||||
case AuthMode.OAUTH:
|
||||
this.authData = {
|
||||
token: ''
|
||||
};
|
||||
break;
|
||||
default:
|
||||
this.authData = null;
|
||||
break;
|
||||
}
|
||||
cancel() {
|
||||
this._close();
|
||||
}
|
||||
}
|
||||
|
||||
_open() {
|
||||
this.inlineAlert.close();
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
_close() {
|
||||
this.opened = false;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.model = {
|
||||
name: '',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
vendor: '',
|
||||
auth_mode: AuthMode.NONE,
|
||||
auth_info: this.authData
|
||||
};
|
||||
this.instanceForm.reset({
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this._close();
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.editingMode) {
|
||||
const operMessageForEdit = new OperateInfo();
|
||||
operMessageForEdit.name = 'DISTRIBUTION.UPDATE_INSTANCE';
|
||||
operMessageForEdit.data.id = this.model.id;
|
||||
operMessageForEdit.state = OperationState.progressing;
|
||||
operMessageForEdit.data.name = this.model.name;
|
||||
this.operationService.publishInfo(operMessageForEdit);
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
const instance: Instance = clone(this.originModelForEdit);
|
||||
instance.vendor = this.model.vendor;
|
||||
instance.name = this.model.name;
|
||||
instance.endpoint = this.model.endpoint;
|
||||
instance.insecure = this.model.insecure;
|
||||
instance.enabled = this.model.enabled;
|
||||
instance.auth_mode = this.model.auth_mode;
|
||||
instance.description = this.model.description;
|
||||
if (instance.auth_mode !== AuthMode.NONE) {
|
||||
instance.auth_info = this.authData;
|
||||
} else {
|
||||
delete instance.auth_info;
|
||||
}
|
||||
this.distributionService.UpdateInstance({preheatInstanceName: this.model.name, instance: this.handleInstance(instance)
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.translate.get('DISTRIBUTION.UPDATE_SUCCESS').subscribe(msg => {
|
||||
operateChanges(operMessageForEdit, OperationState.success);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this._close();
|
||||
this.refresh.emit();
|
||||
},
|
||||
err => {
|
||||
const message = errorHandler(err);
|
||||
this.translate.get('DISTRIBUTION.UPDATE_FAILED').subscribe(msg => {
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
operateChanges(operMessageForEdit, OperationState.failure, msg);
|
||||
this.inlineAlert.showInlineError(msg + ': ' + errMsg);
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
});
|
||||
this.msgHandler.handleErrorPopupUnauthorized(err);
|
||||
submit() {
|
||||
if (this.editingMode) {
|
||||
const operMessageForEdit = new OperateInfo();
|
||||
operMessageForEdit.name = 'DISTRIBUTION.UPDATE_INSTANCE';
|
||||
operMessageForEdit.data.id = this.model.id;
|
||||
operMessageForEdit.state = OperationState.progressing;
|
||||
operMessageForEdit.data.name = this.model.name;
|
||||
this.operationService.publishInfo(operMessageForEdit);
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
const instance: Instance = clone(this.originModelForEdit);
|
||||
instance.vendor = this.model.vendor;
|
||||
instance.name = this.model.name;
|
||||
instance.endpoint = this.model.endpoint;
|
||||
instance.insecure = this.model.insecure;
|
||||
instance.enabled = this.model.enabled;
|
||||
instance.auth_mode = this.model.auth_mode;
|
||||
instance.description = this.model.description;
|
||||
if (instance.auth_mode !== AuthMode.NONE) {
|
||||
instance.auth_info = this.authData;
|
||||
} else {
|
||||
delete instance.auth_info;
|
||||
}
|
||||
this.distributionService
|
||||
.UpdateInstance({
|
||||
preheatInstanceName: this.model.name,
|
||||
instance: this.handleInstance(instance),
|
||||
})
|
||||
.subscribe(
|
||||
response => {
|
||||
this.translate
|
||||
.get('DISTRIBUTION.UPDATE_SUCCESS')
|
||||
.subscribe(msg => {
|
||||
operateChanges(
|
||||
operMessageForEdit,
|
||||
OperationState.success
|
||||
);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this._close();
|
||||
this.refresh.emit();
|
||||
},
|
||||
err => {
|
||||
const message = errorHandler(err);
|
||||
this.translate
|
||||
.get('DISTRIBUTION.UPDATE_FAILED')
|
||||
.subscribe(msg => {
|
||||
this.translate
|
||||
.get(message)
|
||||
.subscribe(errMsg => {
|
||||
operateChanges(
|
||||
operMessageForEdit,
|
||||
OperationState.failure,
|
||||
msg
|
||||
);
|
||||
this.inlineAlert.showInlineError(
|
||||
msg + ': ' + errMsg
|
||||
);
|
||||
this.saveBtnState =
|
||||
ClrLoadingState.ERROR;
|
||||
});
|
||||
});
|
||||
this.msgHandler.handleErrorPopupUnauthorized(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.CREATE_INSTANCE';
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = this.model.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
if (this.model.auth_mode !== AuthMode.NONE) {
|
||||
this.model.auth_info = this.authData;
|
||||
} else {
|
||||
delete this.model.auth_info;
|
||||
}
|
||||
// set insure property to true or false
|
||||
this.model.insecure = !!this.model.insecure;
|
||||
this.distributionService
|
||||
.CreateInstance({ instance: this.model })
|
||||
.subscribe(
|
||||
response => {
|
||||
this.translate
|
||||
.get('DISTRIBUTION.CREATE_SUCCESS')
|
||||
.subscribe(msg => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.success
|
||||
);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this._close();
|
||||
this.refresh.emit();
|
||||
},
|
||||
err => {
|
||||
const message = errorHandler(err);
|
||||
this.translate
|
||||
.get('DISTRIBUTION.CREATE_FAILED')
|
||||
.subscribe(msg => {
|
||||
this.translate
|
||||
.get(message)
|
||||
.subscribe(errMsg => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.failure,
|
||||
msg
|
||||
);
|
||||
this.inlineAlert.showInlineError(
|
||||
msg + ': ' + errMsg
|
||||
);
|
||||
this.saveBtnState =
|
||||
ClrLoadingState.ERROR;
|
||||
});
|
||||
});
|
||||
this.msgHandler.handleErrorPopupUnauthorized(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const operMessage = new OperateInfo();
|
||||
operMessage.name = 'DISTRIBUTION.CREATE_INSTANCE';
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = this.model.name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
if (this.model.auth_mode !== AuthMode.NONE) {
|
||||
this.model.auth_info = this.authData;
|
||||
} else {
|
||||
delete this.model.auth_info;
|
||||
}
|
||||
// set insure property to true or false
|
||||
this.model.insecure = !!this.model.insecure;
|
||||
this.distributionService.CreateInstance({instance: this.model}).subscribe(
|
||||
response => {
|
||||
this.translate.get('DISTRIBUTION.CREATE_SUCCESS').subscribe(msg => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
this.msgHandler.info(msg);
|
||||
});
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
this._close();
|
||||
this.refresh.emit();
|
||||
},
|
||||
err => {
|
||||
const message = errorHandler(err);
|
||||
this.translate.get('DISTRIBUTION.CREATE_FAILED').subscribe(msg => {
|
||||
this.translate.get(message).subscribe(errMsg => {
|
||||
operateChanges(operMessage, OperationState.failure, msg);
|
||||
this.inlineAlert.showInlineError(msg + ': ' + errMsg);
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
});
|
||||
this.msgHandler.handleErrorPopupUnauthorized(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
openSetupModal(editingMode: boolean, data?: Instance): void {
|
||||
this.editingMode = editingMode;
|
||||
this._open();
|
||||
if (editingMode) {
|
||||
this.model = clone(data);
|
||||
this.originModelForEdit = clone(data);
|
||||
// set insure property to true or false
|
||||
this.originModelForEdit.insecure = !!data.insecure;
|
||||
this.model.insecure = !!data.insecure;
|
||||
this.authData = this.model.auth_info || {};
|
||||
} else {
|
||||
this.reset();
|
||||
if (this.providers && this.providers.length) {
|
||||
this.providers.forEach(item => {
|
||||
if (item.id === DEFAULT_PROVIDER) {
|
||||
this.model.vendor = item.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasChangesForEdit(): boolean {
|
||||
if ( this.editingMode) {
|
||||
if ( this.model.vendor !== this.originModelForEdit.vendor) {
|
||||
return true;
|
||||
}
|
||||
if ( this.model.name !== this.originModelForEdit.name) {
|
||||
return true;
|
||||
}
|
||||
if ( this.model.description !== this.originModelForEdit.description) {
|
||||
return true;
|
||||
}
|
||||
if ( this.model.endpoint !== this.originModelForEdit.endpoint) {
|
||||
return true;
|
||||
}
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if ( this.model.enabled != this.originModelForEdit.enabled) {
|
||||
return true;
|
||||
}
|
||||
// tslint:disable-next-line:triple-equals
|
||||
if ( this.model.insecure != this.originModelForEdit.insecure) {
|
||||
return true;
|
||||
}
|
||||
if (this.model.auth_mode !== this.originModelForEdit.auth_mode) {
|
||||
return true;
|
||||
} else {
|
||||
if (this.model.auth_mode === AuthMode.BASIC) {
|
||||
if (this.originModelForEdit.auth_info['username'] !== this.authData['username']) {
|
||||
return true;
|
||||
}
|
||||
if (this.originModelForEdit.auth_info['password'] !== this.authData['password']) {
|
||||
return true;
|
||||
}
|
||||
openSetupModal(editingMode: boolean, data?: Instance): void {
|
||||
this.editingMode = editingMode;
|
||||
this._open();
|
||||
if (editingMode) {
|
||||
this.model = clone(data);
|
||||
this.originModelForEdit = clone(data);
|
||||
// set insure property to true or false
|
||||
this.originModelForEdit.insecure = !!data.insecure;
|
||||
this.model.insecure = !!data.insecure;
|
||||
this.authData = this.model.auth_info || {};
|
||||
} else {
|
||||
this.reset();
|
||||
if (this.providers && this.providers.length) {
|
||||
this.providers.forEach(item => {
|
||||
if (item.id === DEFAULT_PROVIDER) {
|
||||
this.model.vendor = item.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.model.auth_mode === AuthMode.OAUTH) {
|
||||
if (this.originModelForEdit.auth_info['token'] !== this.authData['token']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onTestEndpoint() {
|
||||
this.onTesting = true;
|
||||
this.checkBtnState = ClrLoadingState.LOADING;
|
||||
const instance: Instance = clone(this.model);
|
||||
instance.id = 0;
|
||||
this.distributionService.PingInstances({
|
||||
instance: this.handleInstance(instance)
|
||||
}).pipe(finalize(() => this.onTesting = false))
|
||||
.subscribe(res => {
|
||||
this.checkBtnState = ClrLoadingState.SUCCESS;
|
||||
this.inlineAlert.showInlineSuccess({
|
||||
message: "SCANNER.TEST_PASS"
|
||||
});
|
||||
}, error => {
|
||||
this.inlineAlert.showInlineError('P2P_PROVIDER.TEST_FAILED');
|
||||
this.checkBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
}
|
||||
handleInstance(instance: FrontInstance): Instance {
|
||||
if (instance) {
|
||||
const copyOne: FrontInstance = clone(instance);
|
||||
delete copyOne.hasCheckHealth;
|
||||
delete copyOne.pingStatus;
|
||||
return copyOne;
|
||||
hasChangesForEdit(): boolean {
|
||||
if (this.editingMode) {
|
||||
if (this.model.vendor !== this.originModelForEdit.vendor) {
|
||||
return true;
|
||||
}
|
||||
if (this.model.name !== this.originModelForEdit.name) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
this.model.description !== this.originModelForEdit.description
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.model.endpoint !== this.originModelForEdit.endpoint) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (this.model.enabled != this.originModelForEdit.enabled) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (this.model.insecure != this.originModelForEdit.insecure) {
|
||||
return true;
|
||||
}
|
||||
if (this.model.auth_mode !== this.originModelForEdit.auth_mode) {
|
||||
return true;
|
||||
} else {
|
||||
if (this.model.auth_mode === AuthMode.BASIC) {
|
||||
if (
|
||||
this.originModelForEdit.auth_info['username'] !==
|
||||
this.authData['username']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
this.originModelForEdit.auth_info['password'] !==
|
||||
this.authData['password']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.model.auth_mode === AuthMode.OAUTH) {
|
||||
if (
|
||||
this.originModelForEdit.auth_info['token'] !==
|
||||
this.authData['token']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onTestEndpoint() {
|
||||
this.onTesting = true;
|
||||
this.checkBtnState = ClrLoadingState.LOADING;
|
||||
const instance: Instance = clone(this.model);
|
||||
instance.id = 0;
|
||||
this.distributionService
|
||||
.PingInstances({
|
||||
instance: this.handleInstance(instance),
|
||||
})
|
||||
.pipe(finalize(() => (this.onTesting = false)))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.checkBtnState = ClrLoadingState.SUCCESS;
|
||||
this.inlineAlert.showInlineSuccess({
|
||||
message: 'SCANNER.TEST_PASS',
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.inlineAlert.showInlineError(
|
||||
'P2P_PROVIDER.TEST_FAILED'
|
||||
);
|
||||
this.checkBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
);
|
||||
}
|
||||
handleInstance(instance: FrontInstance): Instance {
|
||||
if (instance) {
|
||||
const copyOne: FrontInstance = clone(instance);
|
||||
delete copyOne.hasCheckHealth;
|
||||
delete copyOne.pingStatus;
|
||||
return copyOne;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,20 @@ import { NgModule } from '@angular/core';
|
|||
import { DistributionInstancesComponent } from './distribution-instances/distribution-instances.component';
|
||||
import { DistributionSetupModalComponent } from './distribution-setup-modal/distribution-setup-modal.component';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'instances',
|
||||
component: DistributionInstancesComponent
|
||||
},
|
||||
{ path: '', redirectTo: 'instances', pathMatch: 'full' },
|
||||
{
|
||||
path: 'instances',
|
||||
component: DistributionInstancesComponent,
|
||||
},
|
||||
{ path: '', redirectTo: 'instances', pathMatch: 'full' },
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
declarations: [
|
||||
DistributionSetupModalComponent,
|
||||
DistributionInstancesComponent
|
||||
],
|
||||
imports: [SharedModule, RouterModule.forChild(routes)],
|
||||
declarations: [
|
||||
DistributionSetupModalComponent,
|
||||
DistributionInstancesComponent,
|
||||
],
|
||||
})
|
||||
export class DistributionModule {}
|
||||
|
|
|
@ -1,17 +1,27 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="custom-h2 gc-title">{{'CONFIG.GC' | translate }}<span *ngIf="inProgress" class="spinner spinner-inline ml-1 v-mid"></span></h2>
|
||||
<h2 class="custom-h2 gc-title">
|
||||
{{ 'CONFIG.GC' | translate
|
||||
}}<span
|
||||
*ngIf="inProgress"
|
||||
class="spinner spinner-inline ml-1 v-mid"></span>
|
||||
</h2>
|
||||
<clr-tabs>
|
||||
<clr-tab *ngIf="hasAdminRole">
|
||||
<button id="config-gc" clrTabLink>{{'CONFIG.GC' | translate }}</button>
|
||||
<button id="config-gc" clrTabLink>
|
||||
{{ 'CONFIG.GC' | translate }}
|
||||
</button>
|
||||
<ng-template [(clrIfActive)]="gcActive">
|
||||
<clr-tab-content id="gc" *ngIf="hasAdminRole">
|
||||
<gc-config (loadingGcStatus)="getGcStatus($event)"></gc-config>
|
||||
<gc-config
|
||||
(loadingGcStatus)="getGcStatus($event)"></gc-config>
|
||||
</clr-tab-content>
|
||||
</ng-template>
|
||||
</clr-tab>
|
||||
<clr-tab *ngIf="hasAdminRole">
|
||||
<button id="gc-log" clrTabLink>{{'CONFIG.HISTORY' | translate }}</button>
|
||||
<button id="gc-log" clrTabLink>
|
||||
{{ 'CONFIG.HISTORY' | translate }}
|
||||
</button>
|
||||
<ng-template [(clrIfActive)]="historyActive">
|
||||
<clr-tab-content id="history" *ngIf="hasAdminRole">
|
||||
<gc-history></gc-history>
|
||||
|
|
|
@ -1,39 +1,35 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { SessionService } from "../../../shared/services/session.service";
|
||||
import { SessionService } from '../../../shared/services/session.service';
|
||||
import { GcPageComponent } from './gc-page.component';
|
||||
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||
|
||||
describe('GcPageComponent', () => {
|
||||
let component: GcPageComponent;
|
||||
let fixture: ComponentFixture<GcPageComponent>;
|
||||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
}
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GcPageComponent],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: SessionService, useValue: fakeSessionService }
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
let component: GcPageComponent;
|
||||
let fixture: ComponentFixture<GcPageComponent>;
|
||||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
},
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GcPageComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [SharedTestingModule],
|
||||
providers: [
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GcPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GcPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { SessionService } from "../../../shared/services/session.service";
|
||||
import { Component } from '@angular/core';
|
||||
import { SessionService } from '../../../shared/services/session.service';
|
||||
|
||||
@Component({
|
||||
selector: "app-gc-page",
|
||||
templateUrl: "./gc-page.component.html",
|
||||
styleUrls: ["./gc-page.component.scss"]
|
||||
selector: 'app-gc-page',
|
||||
templateUrl: './gc-page.component.html',
|
||||
styleUrls: ['./gc-page.component.scss'],
|
||||
})
|
||||
export class GcPageComponent implements OnInit {
|
||||
inProgress: boolean = true;
|
||||
constructor(private session: SessionService) {}
|
||||
export class GcPageComponent {
|
||||
inProgress: boolean = true;
|
||||
constructor(private session: SessionService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
public get hasAdminRole(): boolean {
|
||||
return (
|
||||
this.session.getCurrentUser() &&
|
||||
this.session.getCurrentUser().has_admin_role
|
||||
);
|
||||
}
|
||||
public get hasAdminRole(): boolean {
|
||||
return (
|
||||
this.session.getCurrentUser() &&
|
||||
this.session.getCurrentUser().has_admin_role
|
||||
);
|
||||
}
|
||||
|
||||
getGcStatus(status: boolean) {
|
||||
this.inProgress = status;
|
||||
}
|
||||
getGcStatus(status: boolean) {
|
||||
this.inProgress = status;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { GcPageComponent } from "./gc-page.component";
|
||||
import { GcComponent } from "./gc/gc.component";
|
||||
import { GcHistoryComponent } from "./gc/gc-history/gc-history.component";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { GcPageComponent } from './gc-page.component';
|
||||
import { GcComponent } from './gc/gc.component';
|
||||
import { GcHistoryComponent } from './gc/gc-history/gc-history.component';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: GcPageComponent
|
||||
}
|
||||
component: GcPageComponent,
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
declarations: [
|
||||
GcPageComponent,
|
||||
GcComponent,
|
||||
GcHistoryComponent
|
||||
],
|
||||
imports: [SharedModule, RouterModule.forChild(routes)],
|
||||
declarations: [GcPageComponent, GcComponent, GcHistoryComponent],
|
||||
})
|
||||
export class GcModule {}
|
||||
|
|
|
@ -1,35 +1,64 @@
|
|||
<h5 class="history-header" id="history-header">{{'GC.JOB_HISTORY' | translate}}</h5>
|
||||
<h5 class="history-header" id="history-header">
|
||||
{{ 'GC.JOB_HISTORY' | translate }}
|
||||
</h5>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="getJobs(true, $event)">
|
||||
<clr-dg-column [clrDgField]="'id'">{{'GC.JOB_ID' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'GC.TRIGGER_TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'TAG_RETENTION.DRY_RUN' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="'update_time'">{{'UPDATE_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'LOGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]='job'>
|
||||
<clr-dg-cell>{{job.id }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{(job.schedule?.type ? 'SCHEDULE.' + job.schedule?.type.toUpperCase() : '') | translate }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{isDryRun(job?.job_parameters) | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.job_status.toUpperCase() | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.creation_time | harborDatetime:'medium'}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{job.update_time | harborDatetime:'medium'}}</clr-dg-cell>
|
||||
<clr-dg-column [clrDgField]="'id'">{{
|
||||
'GC.JOB_ID' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'GC.TRIGGER_TYPE' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'TAG_RETENTION.DRY_RUN' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'STATUS' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'CREATION_TIME' | translate }}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="'update_time'">{{
|
||||
'UPDATE_TIME' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'LOGS' | translate }}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]="job">
|
||||
<clr-dg-cell>{{ job.id }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
(job.schedule?.type
|
||||
? 'SCHEDULE.' + job.schedule?.type.toUpperCase()
|
||||
: ''
|
||||
) | translate
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
isDryRun(job?.job_parameters) | translate
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
job.job_status.toUpperCase() | translate
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
job.creation_time | harborDatetime: 'medium'
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
job.update_time | harborDatetime: 'medium'
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<a rel='noopener noreferrer' target="_blank"
|
||||
[href]="getLogLink(job.id)">
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
[href]="getLogLink(job.id)">
|
||||
<clr-icon shape="list"></clr-icon>
|
||||
</a>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="page" [clrDgTotalItems]="total">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span *ngIf="total">{{pagination.firstItem + 1}}
|
||||
- {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
|
||||
{{total}} {{'DESTINATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination
|
||||
#pagination
|
||||
[clrDgPageSize]="pageSize"
|
||||
[(clrDgPage)]="page"
|
||||
[clrDgTotalItems]="total">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15, 25, 50]">{{
|
||||
'PAGINATION.PAGE_SIZE' | translate
|
||||
}}</clr-dg-page-size>
|
||||
<span *ngIf="total"
|
||||
>{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }}
|
||||
{{ 'DESTINATION.OF' | translate }}</span
|
||||
>
|
||||
{{ total }} {{ 'DESTINATION.ITEMS' | translate }}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
|
|
@ -1,99 +1,107 @@
|
|||
import {
|
||||
ComponentFixture,
|
||||
ComponentFixtureAutoDetect, fakeAsync,
|
||||
TestBed, tick,
|
||||
ComponentFixture,
|
||||
ComponentFixtureAutoDetect,
|
||||
fakeAsync,
|
||||
TestBed,
|
||||
tick,
|
||||
} from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { GcHistoryComponent } from './gc-history.component';
|
||||
import { SharedTestingModule } from "../../../../../shared/shared.module";
|
||||
import { GCHistory } from "../../../../../../../ng-swagger-gen/models/gchistory";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { Registry } from "../../../../../../../ng-swagger-gen/models/registry";
|
||||
import { GcService } from "../../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { CURRENT_BASE_HREF } from "../../../../../shared/units/utils";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { SharedTestingModule } from '../../../../../shared/shared.module';
|
||||
import { GCHistory } from '../../../../../../../ng-swagger-gen/models/gchistory';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { Registry } from '../../../../../../../ng-swagger-gen/models/registry';
|
||||
import { GcService } from '../../../../../../../ng-swagger-gen/services/gc.service';
|
||||
import { CURRENT_BASE_HREF } from '../../../../../shared/units/utils';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
describe('GcHistoryComponent', () => {
|
||||
let component: GcHistoryComponent;
|
||||
let fixture: ComponentFixture<GcHistoryComponent>;
|
||||
const mockJobs: GCHistory[] = [
|
||||
{
|
||||
id: 1,
|
||||
job_name: 'test',
|
||||
job_kind: 'manual',
|
||||
schedule: null,
|
||||
job_status: 'pending',
|
||||
job_parameters: '{"dry_run":true}',
|
||||
creation_time: null,
|
||||
update_time: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
job_name: 'test',
|
||||
job_kind: 'manual',
|
||||
schedule: null,
|
||||
job_status: 'finished',
|
||||
job_parameters: '{"dry_run":true}',
|
||||
creation_time: null,
|
||||
update_time: null,
|
||||
}
|
||||
];
|
||||
const fakedGcService = {
|
||||
count: 0,
|
||||
getGCHistoryResponse() {
|
||||
if (this.count === 0) {
|
||||
this.count += 1;
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({'x-total-count': [mockJobs[0]].length.toString()}),
|
||||
body: [mockJobs[0]]
|
||||
let component: GcHistoryComponent;
|
||||
let fixture: ComponentFixture<GcHistoryComponent>;
|
||||
const mockJobs: GCHistory[] = [
|
||||
{
|
||||
id: 1,
|
||||
job_name: 'test',
|
||||
job_kind: 'manual',
|
||||
schedule: null,
|
||||
job_status: 'pending',
|
||||
job_parameters: '{"dry_run":true}',
|
||||
creation_time: null,
|
||||
update_time: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
job_name: 'test',
|
||||
job_kind: 'manual',
|
||||
schedule: null,
|
||||
job_status: 'finished',
|
||||
job_parameters: '{"dry_run":true}',
|
||||
creation_time: null,
|
||||
update_time: null,
|
||||
},
|
||||
];
|
||||
const fakedGcService = {
|
||||
count: 0,
|
||||
getGCHistoryResponse() {
|
||||
if (this.count === 0) {
|
||||
this.count += 1;
|
||||
const response: HttpResponse<Array<Registry>> =
|
||||
new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({
|
||||
'x-total-count': [mockJobs[0]].length.toString(),
|
||||
}),
|
||||
body: [mockJobs[0]],
|
||||
});
|
||||
return of(response).pipe(delay(0));
|
||||
} else {
|
||||
this.count += 1;
|
||||
const response: HttpResponse<Array<Registry>> =
|
||||
new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({
|
||||
'x-total-count': [mockJobs[1]].length.toString(),
|
||||
}),
|
||||
body: [mockJobs[1]],
|
||||
});
|
||||
return of(response).pipe(delay(0));
|
||||
}
|
||||
},
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GcHistoryComponent],
|
||||
imports: [SharedTestingModule],
|
||||
providers: [
|
||||
{ provide: GcService, useValue: fakedGcService },
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true },
|
||||
],
|
||||
});
|
||||
return of(response).pipe(delay(0));
|
||||
} else {
|
||||
this.count += 1;
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({'x-total-count': [mockJobs[1]].length.toString()}),
|
||||
body: [mockJobs[1]]
|
||||
});
|
||||
return of(response).pipe(delay(0));
|
||||
}
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GcHistoryComponent],
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
],
|
||||
providers: [
|
||||
{provide: GcService, useValue: fakedGcService},
|
||||
// open auto detect
|
||||
{provide: ComponentFixtureAutoDetect, useValue: true}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GcHistoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
afterEach(() => {
|
||||
if (component && component.timerDelay) {
|
||||
component.timerDelay.unsubscribe();
|
||||
component.timerDelay = null;
|
||||
}
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should retry getting jobs', fakeAsync(() => {
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.jobs[0].job_status).toEqual('finished');
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GcHistoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
afterEach(() => {
|
||||
if (component && component.timerDelay) {
|
||||
component.timerDelay.unsubscribe();
|
||||
component.timerDelay = null;
|
||||
}
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should retry getting jobs', fakeAsync(() => {
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.jobs[0].job_status).toEqual('finished');
|
||||
});
|
||||
}));
|
||||
it('should return right log link', () => {
|
||||
expect(component.getLogLink('1')).toEqual(
|
||||
`${CURRENT_BASE_HREF}/system/gc/1/log`
|
||||
);
|
||||
});
|
||||
}));
|
||||
it('should return right log link', () => {
|
||||
expect(component.getLogLink('1')).toEqual(`${CURRENT_BASE_HREF}/system/gc/1/log`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,132 +1,143 @@
|
|||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ErrorHandler } from "../../../../../shared/units/error-handler";
|
||||
import { Subscription, timer } from "rxjs";
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { ErrorHandler } from '../../../../../shared/units/error-handler';
|
||||
import { Subscription, timer } from 'rxjs';
|
||||
import { REFRESH_TIME_DIFFERENCE } from '../../../../../shared/entities/shared.const';
|
||||
import { GcService } from "../../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { GcService } from '../../../../../../../ng-swagger-gen/services/gc.service';
|
||||
import {
|
||||
CURRENT_BASE_HREF,
|
||||
getPageSizeFromLocalStorage,
|
||||
getSortingString,
|
||||
PageSizeMapKeys, setPageSizeToLocalStorage
|
||||
} from "../../../../../shared/units/utils";
|
||||
import { ClrDatagridStateInterface } from "@clr/angular";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { GCHistory } from "../../../../../../../ng-swagger-gen/models/gchistory";
|
||||
CURRENT_BASE_HREF,
|
||||
getPageSizeFromLocalStorage,
|
||||
getSortingString,
|
||||
PageSizeMapKeys,
|
||||
setPageSizeToLocalStorage,
|
||||
} from '../../../../../shared/units/utils';
|
||||
import { ClrDatagridStateInterface } from '@clr/angular';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { GCHistory } from '../../../../../../../ng-swagger-gen/models/gchistory';
|
||||
|
||||
const JOB_STATUS = {
|
||||
PENDING: "Pending",
|
||||
RUNNING: "Running"
|
||||
PENDING: 'Pending',
|
||||
RUNNING: 'Running',
|
||||
};
|
||||
const YES: string = 'TAG_RETENTION.YES';
|
||||
const NO: string = 'TAG_RETENTION.NO';
|
||||
|
||||
@Component({
|
||||
selector: 'gc-history',
|
||||
templateUrl: './gc-history.component.html',
|
||||
styleUrls: ['./gc-history.component.scss']
|
||||
selector: 'gc-history',
|
||||
templateUrl: './gc-history.component.html',
|
||||
styleUrls: ['./gc-history.component.scss'],
|
||||
})
|
||||
export class GcHistoryComponent implements OnInit, OnDestroy {
|
||||
jobs: Array<GCHistory> = [];
|
||||
loading: boolean = true;
|
||||
timerDelay: Subscription;
|
||||
pageSize: number = getPageSizeFromLocalStorage(PageSizeMapKeys.GC_HISTORY_COMPONENT);
|
||||
page: number = 1;
|
||||
total: number = 0;
|
||||
state: ClrDatagridStateInterface;
|
||||
constructor(
|
||||
private gcService: GcService,
|
||||
private errorHandler: ErrorHandler
|
||||
) {
|
||||
}
|
||||
export class GcHistoryComponent implements OnDestroy {
|
||||
jobs: Array<GCHistory> = [];
|
||||
loading: boolean = true;
|
||||
timerDelay: Subscription;
|
||||
pageSize: number = getPageSizeFromLocalStorage(
|
||||
PageSizeMapKeys.GC_HISTORY_COMPONENT
|
||||
);
|
||||
page: number = 1;
|
||||
total: number = 0;
|
||||
state: ClrDatagridStateInterface;
|
||||
constructor(
|
||||
private gcService: GcService,
|
||||
private errorHandler: ErrorHandler
|
||||
) {}
|
||||
refresh() {
|
||||
this.page = 1;
|
||||
this.total = 0;
|
||||
this.getJobs(true);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.page = 1;
|
||||
this.total = 0;
|
||||
this.getJobs(true);
|
||||
}
|
||||
|
||||
getJobs(withLoading: boolean, state?: ClrDatagridStateInterface) {
|
||||
if (state) {
|
||||
this.state = state;
|
||||
}
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
setPageSizeToLocalStorage(PageSizeMapKeys.GC_HISTORY_COMPONENT, this.pageSize);
|
||||
}
|
||||
let q: string;
|
||||
if (state && state.filters && state.filters.length) {
|
||||
q = encodeURIComponent(`${state.filters[0].property}=~${state.filters[0].value}`);
|
||||
}
|
||||
let sort: string;
|
||||
if (state && state.sort && state.sort.by) {
|
||||
sort = getSortingString(state);
|
||||
}
|
||||
if (withLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.gcService.getGCHistoryResponse({
|
||||
page: this.page,
|
||||
pageSize: this.pageSize,
|
||||
q: q,
|
||||
sort: sort
|
||||
}).pipe(finalize(() => this.loading = false))
|
||||
.subscribe(res => {
|
||||
// Get total count
|
||||
if (res.headers) {
|
||||
const xHeader: string = res.headers.get("X-Total-Count");
|
||||
if (xHeader) {
|
||||
this.total = parseInt(xHeader, 0);
|
||||
}
|
||||
this.jobs = res.body;
|
||||
getJobs(withLoading: boolean, state?: ClrDatagridStateInterface) {
|
||||
if (state) {
|
||||
this.state = state;
|
||||
}
|
||||
// to avoid some jobs not finished.
|
||||
if (!this.timerDelay) {
|
||||
this.timerDelay = timer(REFRESH_TIME_DIFFERENCE, REFRESH_TIME_DIFFERENCE).subscribe(() => {
|
||||
let count: number = 0;
|
||||
this.jobs.forEach(job => {
|
||||
if (
|
||||
job.job_status === JOB_STATUS.PENDING ||
|
||||
job.job_status === JOB_STATUS.RUNNING
|
||||
) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
if (count > 0) {
|
||||
this.getJobs(false, this.state);
|
||||
} else {
|
||||
this.timerDelay.unsubscribe();
|
||||
this.timerDelay = null;
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
setPageSizeToLocalStorage(
|
||||
PageSizeMapKeys.GC_HISTORY_COMPONENT,
|
||||
this.pageSize
|
||||
);
|
||||
}
|
||||
let q: string;
|
||||
if (state && state.filters && state.filters.length) {
|
||||
q = encodeURIComponent(
|
||||
`${state.filters[0].property}=~${state.filters[0].value}`
|
||||
);
|
||||
}
|
||||
let sort: string;
|
||||
if (state && state.sort && state.sort.by) {
|
||||
sort = getSortingString(state);
|
||||
}
|
||||
if (withLoading) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.gcService
|
||||
.getGCHistoryResponse({
|
||||
page: this.page,
|
||||
pageSize: this.pageSize,
|
||||
q: q,
|
||||
sort: sort,
|
||||
})
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe(
|
||||
res => {
|
||||
// Get total count
|
||||
if (res.headers) {
|
||||
const xHeader: string =
|
||||
res.headers.get('X-Total-Count');
|
||||
if (xHeader) {
|
||||
this.total = parseInt(xHeader, 0);
|
||||
}
|
||||
this.jobs = res.body;
|
||||
}
|
||||
// to avoid some jobs not finished.
|
||||
if (!this.timerDelay) {
|
||||
this.timerDelay = timer(
|
||||
REFRESH_TIME_DIFFERENCE,
|
||||
REFRESH_TIME_DIFFERENCE
|
||||
).subscribe(() => {
|
||||
let count: number = 0;
|
||||
this.jobs.forEach(job => {
|
||||
if (
|
||||
job.job_status === JOB_STATUS.PENDING ||
|
||||
job.job_status === JOB_STATUS.RUNNING
|
||||
) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
if (count > 0) {
|
||||
this.getJobs(false, this.state);
|
||||
} else {
|
||||
this.timerDelay.unsubscribe();
|
||||
this.timerDelay = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
isDryRun(param: string): string {
|
||||
if (param) {
|
||||
const paramObj: any = JSON.parse(param);
|
||||
if (paramObj && paramObj.dry_run) {
|
||||
return YES;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
isDryRun(param: string): string {
|
||||
if (param) {
|
||||
const paramObj: any = JSON.parse(param);
|
||||
if (paramObj && paramObj.dry_run) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.timerDelay) {
|
||||
this.timerDelay.unsubscribe();
|
||||
this.timerDelay = null;
|
||||
ngOnDestroy() {
|
||||
if (this.timerDelay) {
|
||||
this.timerDelay.unsubscribe();
|
||||
this.timerDelay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLogLink(id): string {
|
||||
return `${CURRENT_BASE_HREF}/system/gc/${id}/log`;
|
||||
}
|
||||
|
||||
getLogLink(id): string {
|
||||
return `${CURRENT_BASE_HREF}/system/gc/${id}/log`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<div class="cron-selection">
|
||||
<cron-selection [labelCurrent]="getLabelCurrent" #CronScheduleComponent [labelEdit]='getLabelCurrent'
|
||||
[originCron]='originCron' (inputvalue)="saveGcSchedule($event)"></cron-selection>
|
||||
<cron-selection
|
||||
[labelCurrent]="getLabelCurrent"
|
||||
#CronScheduleComponent
|
||||
[labelEdit]="getLabelCurrent"
|
||||
[originCron]="originCron"
|
||||
(inputvalue)="saveGcSchedule($event)"></cron-selection>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-2 flex-200"></div>
|
||||
<div class="clr-col">
|
||||
<span class="explain">{{'GC.EXPLAIN' | translate}}</span>
|
||||
<span class="explain">{{ 'GC.EXPLAIN' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
|
@ -13,21 +17,36 @@
|
|||
<div class="clr-col">
|
||||
<clr-toggle-container class="mt-05">
|
||||
<clr-toggle-wrapper>
|
||||
<input type="checkbox" clrToggle name="delete_untagged" id="delete_untagged"
|
||||
[(ngModel)]="shouldDeleteUntagged"/>
|
||||
<label class="font-weight-400" for="delete_untagged">{{'GC.DELETE_UNTAGGED' | translate}}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
clrToggle
|
||||
name="delete_untagged"
|
||||
id="delete_untagged"
|
||||
[(ngModel)]="shouldDeleteUntagged" />
|
||||
<label class="font-weight-400" for="delete_untagged">{{
|
||||
'GC.DELETE_UNTAGGED' | translate
|
||||
}}</label>
|
||||
</clr-toggle-wrapper>
|
||||
</clr-toggle-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-2 flex-200">
|
||||
<button id="gc-now" class="btn btn-primary gc-start-btn" (click)="gcNow()"
|
||||
[disabled]="disableGC">{{'GC.GC_NOW' | translate}}</button>
|
||||
<button
|
||||
id="gc-now"
|
||||
class="btn btn-primary gc-start-btn"
|
||||
(click)="gcNow()"
|
||||
[disabled]="disableGC">
|
||||
{{ 'GC.GC_NOW' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="clr-col">
|
||||
<button id="gc-dry-run" class="btn btn-outline gc-start-btn" (click)="dryRun()"
|
||||
[disabled]="dryRunOnGoing">{{'TAG_RETENTION.WHAT_IF_RUN' | translate}}</button>
|
||||
<button
|
||||
id="gc-dry-run"
|
||||
class="btn btn-outline gc-start-btn"
|
||||
(click)="dryRun()"
|
||||
[disabled]="dryRunOnGoing">
|
||||
{{ 'TAG_RETENTION.WHAT_IF_RUN' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,72 +2,86 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
import { GcComponent } from './gc.component';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { CronScheduleComponent } from '../../../../shared/components/cron-schedule';
|
||||
import { CronTooltipComponent } from "../../../../shared/components/cron-schedule";
|
||||
import { CronTooltipComponent } from '../../../../shared/components/cron-schedule';
|
||||
import { of } from 'rxjs';
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { GcService } from "../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { ScheduleType } from "../../../../shared/entities/shared.const";
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
import { GcService } from '../../../../../../ng-swagger-gen/services/gc.service';
|
||||
import { ScheduleType } from '../../../../shared/entities/shared.const';
|
||||
|
||||
describe('GcComponent', () => {
|
||||
let component: GcComponent;
|
||||
let fixture: ComponentFixture<GcComponent>;
|
||||
let gcRepoService: GcService;
|
||||
let mockSchedule = [];
|
||||
const fakedErrorHandler = {
|
||||
error(error) {
|
||||
return error;
|
||||
},
|
||||
info(info) {
|
||||
return info;
|
||||
}
|
||||
};
|
||||
let spySchedule: jasmine.Spy;
|
||||
let spyGcNow: jasmine.Spy;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
declarations: [ GcComponent, CronScheduleComponent, CronTooltipComponent],
|
||||
providers: [
|
||||
{ provide: ErrorHandler, useValue: fakedErrorHandler },
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
let component: GcComponent;
|
||||
let fixture: ComponentFixture<GcComponent>;
|
||||
let gcRepoService: GcService;
|
||||
let mockSchedule = [];
|
||||
const fakedErrorHandler = {
|
||||
error(error) {
|
||||
return error;
|
||||
},
|
||||
info(info) {
|
||||
return info;
|
||||
},
|
||||
};
|
||||
let spySchedule: jasmine.Spy;
|
||||
let spyGcNow: jasmine.Spy;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [
|
||||
GcComponent,
|
||||
CronScheduleComponent,
|
||||
CronTooltipComponent,
|
||||
],
|
||||
providers: [{ provide: ErrorHandler, useValue: fakedErrorHandler }],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GcComponent);
|
||||
component = fixture.componentInstance;
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GcComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
gcRepoService = fixture.debugElement.injector.get(GcService);
|
||||
spySchedule = spyOn(gcRepoService, "getGCSchedule").and.returnValues(of(mockSchedule as any));
|
||||
spyGcNow = spyOn(gcRepoService, "createGCSchedule").and.returnValues(of(null));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should get schedule and job', () => {
|
||||
expect(spySchedule.calls.count()).toEqual(1);
|
||||
});
|
||||
it('should trigger gcNow', () => {
|
||||
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('#gc-now');
|
||||
ele.click();
|
||||
fixture.detectChanges();
|
||||
expect(spyGcNow.calls.count()).toEqual(1);
|
||||
});
|
||||
it('should trigger dry run', () => {
|
||||
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('#gc-dry-run');
|
||||
ele.click();
|
||||
fixture.detectChanges();
|
||||
expect(spyGcNow.calls.count()).toEqual(1);
|
||||
});
|
||||
it('getScheduleType function should work', () => {
|
||||
expect(GcComponent.getScheduleType).toBeTruthy();
|
||||
expect(GcComponent.getScheduleType(null)).toEqual(ScheduleType.NONE);
|
||||
expect(GcComponent.getScheduleType('0 0 0 0 0 0')).toEqual(ScheduleType.CUSTOM);
|
||||
expect(GcComponent.getScheduleType('0 0 * * * *')).toEqual(ScheduleType.HOURLY);
|
||||
expect(GcComponent.getScheduleType('0 0 0 * * *')).toEqual(ScheduleType.DAILY);
|
||||
expect(GcComponent.getScheduleType('0 0 0 * * 0')).toEqual(ScheduleType.WEEKLY);
|
||||
});
|
||||
gcRepoService = fixture.debugElement.injector.get(GcService);
|
||||
spySchedule = spyOn(gcRepoService, 'getGCSchedule').and.returnValues(
|
||||
of(mockSchedule as any)
|
||||
);
|
||||
spyGcNow = spyOn(gcRepoService, 'createGCSchedule').and.returnValues(
|
||||
of(null)
|
||||
);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should get schedule and job', () => {
|
||||
expect(spySchedule.calls.count()).toEqual(1);
|
||||
});
|
||||
it('should trigger gcNow', () => {
|
||||
const ele: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#gc-now');
|
||||
ele.click();
|
||||
fixture.detectChanges();
|
||||
expect(spyGcNow.calls.count()).toEqual(1);
|
||||
});
|
||||
it('should trigger dry run', () => {
|
||||
const ele: HTMLButtonElement =
|
||||
fixture.nativeElement.querySelector('#gc-dry-run');
|
||||
ele.click();
|
||||
fixture.detectChanges();
|
||||
expect(spyGcNow.calls.count()).toEqual(1);
|
||||
});
|
||||
it('getScheduleType function should work', () => {
|
||||
expect(GcComponent.getScheduleType).toBeTruthy();
|
||||
expect(GcComponent.getScheduleType(null)).toEqual(ScheduleType.NONE);
|
||||
expect(GcComponent.getScheduleType('0 0 0 0 0 0')).toEqual(
|
||||
ScheduleType.CUSTOM
|
||||
);
|
||||
expect(GcComponent.getScheduleType('0 0 * * * *')).toEqual(
|
||||
ScheduleType.HOURLY
|
||||
);
|
||||
expect(GcComponent.getScheduleType('0 0 0 * * *')).toEqual(
|
||||
ScheduleType.DAILY
|
||||
);
|
||||
expect(GcComponent.getScheduleType('0 0 0 * * 0')).toEqual(
|
||||
ScheduleType.WEEKLY
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,192 +1,209 @@
|
|||
import {
|
||||
Component,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
OnInit
|
||||
} from "@angular/core";
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { CronScheduleComponent } from "../../../../shared/components/cron-schedule";
|
||||
Component,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { CronScheduleComponent } from '../../../../shared/components/cron-schedule';
|
||||
import { OriginCron } from '../../../../shared/services';
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { GcService } from "../../../../../../ng-swagger-gen/services/gc.service";
|
||||
import { GCHistory } from "../../../../../../ng-swagger-gen/models/gchistory";
|
||||
import { ScheduleType } from "../../../../shared/entities/shared.const";
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { GcService } from '../../../../../../ng-swagger-gen/services/gc.service';
|
||||
import { GCHistory } from '../../../../../../ng-swagger-gen/models/gchistory';
|
||||
import { ScheduleType } from '../../../../shared/entities/shared.const';
|
||||
|
||||
const ONE_MINUTE = 60000;
|
||||
|
||||
@Component({
|
||||
selector: "gc-config",
|
||||
templateUrl: "./gc.component.html",
|
||||
styleUrls: ["./gc.component.scss"]
|
||||
selector: 'gc-config',
|
||||
templateUrl: './gc.component.html',
|
||||
styleUrls: ['./gc.component.scss'],
|
||||
})
|
||||
export class GcComponent implements OnInit {
|
||||
originCron: OriginCron;
|
||||
disableGC: boolean = false;
|
||||
getLabelCurrent = 'GC.CURRENT_SCHEDULE';
|
||||
@Output() loadingGcStatus = new EventEmitter<boolean>();
|
||||
@ViewChild(CronScheduleComponent)
|
||||
CronScheduleComponent: CronScheduleComponent;
|
||||
shouldDeleteUntagged: boolean;
|
||||
dryRunOnGoing: boolean = false;
|
||||
originCron: OriginCron;
|
||||
disableGC: boolean = false;
|
||||
getLabelCurrent = 'GC.CURRENT_SCHEDULE';
|
||||
@Output() loadingGcStatus = new EventEmitter<boolean>();
|
||||
@ViewChild(CronScheduleComponent)
|
||||
CronScheduleComponent: CronScheduleComponent;
|
||||
shouldDeleteUntagged: boolean;
|
||||
dryRunOnGoing: boolean = false;
|
||||
|
||||
constructor(
|
||||
private gcService: GcService,
|
||||
private errorHandler: ErrorHandler,
|
||||
) {
|
||||
}
|
||||
constructor(
|
||||
private gcService: GcService,
|
||||
private errorHandler: ErrorHandler
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.getCurrentSchedule();
|
||||
}
|
||||
|
||||
getCurrentSchedule() {
|
||||
this.loadingGcStatus.emit(true);
|
||||
this.gcService.getGCSchedule()
|
||||
.pipe(finalize(() => {
|
||||
this.loadingGcStatus.emit(false);
|
||||
}))
|
||||
.subscribe(schedule => {
|
||||
this.initSchedule(schedule);
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
private initSchedule(gcHistory: GCHistory) {
|
||||
if (gcHistory && gcHistory.schedule) {
|
||||
this.originCron = {
|
||||
type: gcHistory.schedule.type,
|
||||
cron: gcHistory.schedule.cron
|
||||
};
|
||||
} else {
|
||||
this.originCron = {
|
||||
type: ScheduleType.NONE,
|
||||
cron: ''
|
||||
};
|
||||
ngOnInit() {
|
||||
this.getCurrentSchedule();
|
||||
}
|
||||
if (gcHistory && gcHistory.job_parameters) {
|
||||
this.shouldDeleteUntagged = JSON.parse(gcHistory.job_parameters).delete_untagged;
|
||||
} else {
|
||||
this.shouldDeleteUntagged = false;
|
||||
|
||||
getCurrentSchedule() {
|
||||
this.loadingGcStatus.emit(true);
|
||||
this.gcService
|
||||
.getGCSchedule()
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loadingGcStatus.emit(false);
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
schedule => {
|
||||
this.initSchedule(schedule);
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
gcNow(): void {
|
||||
this.disableGC = true;
|
||||
setTimeout(() => {
|
||||
this.enableGc();
|
||||
}, ONE_MINUTE);
|
||||
|
||||
this.gcService.createGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false
|
||||
},
|
||||
schedule: {
|
||||
type: ScheduleType.MANUAL
|
||||
private initSchedule(gcHistory: GCHistory) {
|
||||
if (gcHistory && gcHistory.schedule) {
|
||||
this.originCron = {
|
||||
type: gcHistory.schedule.type,
|
||||
cron: gcHistory.schedule.cron,
|
||||
};
|
||||
} else {
|
||||
this.originCron = {
|
||||
type: ScheduleType.NONE,
|
||||
cron: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.errorHandler.info("GC.MSG_SUCCESS");
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
dryRun() {
|
||||
this.dryRunOnGoing = true;
|
||||
this.gcService.createGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: true
|
||||
},
|
||||
schedule: {
|
||||
type: ScheduleType.MANUAL
|
||||
if (gcHistory && gcHistory.job_parameters) {
|
||||
this.shouldDeleteUntagged = JSON.parse(
|
||||
gcHistory.job_parameters
|
||||
).delete_untagged;
|
||||
} else {
|
||||
this.shouldDeleteUntagged = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.pipe(finalize(() => this.dryRunOnGoing = false))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.errorHandler.info("GC.DRY_RUN_SUCCESS");
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private enableGc() {
|
||||
this.disableGC = false;
|
||||
}
|
||||
|
||||
saveGcSchedule(cron: string) {
|
||||
if (this.originCron && this.originCron.type !== ScheduleType.NONE) {// no schedule, then create
|
||||
this.gcService.createGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false
|
||||
},
|
||||
schedule: {
|
||||
type: GcComponent.getScheduleType(cron),
|
||||
cron: cron
|
||||
}
|
||||
}
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.errorHandler.info("GC.MSG_SCHEDULE_RESET");
|
||||
this.CronScheduleComponent.resetSchedule();
|
||||
this.getCurrentSchedule(); // refresh schedule
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.gcService.updateGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false
|
||||
},
|
||||
schedule: {
|
||||
type: GcComponent.getScheduleType(cron),
|
||||
cron: cron
|
||||
}
|
||||
}
|
||||
}).subscribe(
|
||||
response => {
|
||||
this.errorHandler.info("GC.MSG_SCHEDULE_RESET");
|
||||
this.CronScheduleComponent.resetSchedule();
|
||||
this.getCurrentSchedule(); // refresh schedule
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static getScheduleType(cron: string): 'Hourly' | 'Daily' | 'Weekly' | 'Custom' | 'Manual' | 'None' {
|
||||
if (cron) {
|
||||
if (cron === '0 0 * * * *') {
|
||||
return ScheduleType.HOURLY;
|
||||
}
|
||||
if (cron === '0 0 0 * * *') {
|
||||
return ScheduleType.DAILY;
|
||||
}
|
||||
if (cron === '0 0 0 * * 0') {
|
||||
return ScheduleType.WEEKLY;
|
||||
}
|
||||
return ScheduleType.CUSTOM;
|
||||
gcNow(): void {
|
||||
this.disableGC = true;
|
||||
setTimeout(() => {
|
||||
this.enableGc();
|
||||
}, ONE_MINUTE);
|
||||
|
||||
this.gcService
|
||||
.createGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false,
|
||||
},
|
||||
schedule: {
|
||||
type: ScheduleType.MANUAL,
|
||||
},
|
||||
},
|
||||
})
|
||||
.subscribe(
|
||||
response => {
|
||||
this.errorHandler.info('GC.MSG_SUCCESS');
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
dryRun() {
|
||||
this.dryRunOnGoing = true;
|
||||
this.gcService
|
||||
.createGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: true,
|
||||
},
|
||||
schedule: {
|
||||
type: ScheduleType.MANUAL,
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(finalize(() => (this.dryRunOnGoing = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.errorHandler.info('GC.DRY_RUN_SUCCESS');
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private enableGc() {
|
||||
this.disableGC = false;
|
||||
}
|
||||
|
||||
saveGcSchedule(cron: string) {
|
||||
if (this.originCron && this.originCron.type !== ScheduleType.NONE) {
|
||||
// no schedule, then create
|
||||
this.gcService
|
||||
.createGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false,
|
||||
},
|
||||
schedule: {
|
||||
type: GcComponent.getScheduleType(cron),
|
||||
cron: cron,
|
||||
},
|
||||
},
|
||||
})
|
||||
.subscribe(
|
||||
response => {
|
||||
this.errorHandler.info('GC.MSG_SCHEDULE_RESET');
|
||||
this.CronScheduleComponent.resetSchedule();
|
||||
this.getCurrentSchedule(); // refresh schedule
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.gcService
|
||||
.updateGCSchedule({
|
||||
schedule: {
|
||||
parameters: {
|
||||
delete_untagged: this.shouldDeleteUntagged,
|
||||
dry_run: false,
|
||||
},
|
||||
schedule: {
|
||||
type: GcComponent.getScheduleType(cron),
|
||||
cron: cron,
|
||||
},
|
||||
},
|
||||
})
|
||||
.subscribe(
|
||||
response => {
|
||||
this.errorHandler.info('GC.MSG_SCHEDULE_RESET');
|
||||
this.CronScheduleComponent.resetSchedule();
|
||||
this.getCurrentSchedule(); // refresh schedule
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static getScheduleType(
|
||||
cron: string
|
||||
): 'Hourly' | 'Daily' | 'Weekly' | 'Custom' | 'Manual' | 'None' {
|
||||
if (cron) {
|
||||
if (cron === '0 0 * * * *') {
|
||||
return ScheduleType.HOURLY;
|
||||
}
|
||||
if (cron === '0 0 0 * * *') {
|
||||
return ScheduleType.DAILY;
|
||||
}
|
||||
if (cron === '0 0 0 * * 0') {
|
||||
return ScheduleType.WEEKLY;
|
||||
}
|
||||
return ScheduleType.CUSTOM;
|
||||
}
|
||||
return ScheduleType.NONE;
|
||||
}
|
||||
return ScheduleType.NONE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,77 @@
|
|||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalClosable]="false">
|
||||
<h3 class="modal-title" *ngIf="mode === 'create' && isLdapMode">{{'GROUP.IMPORT_LDAP_GROUP' | translate}}</h3>
|
||||
<h3 class="modal-title" *ngIf="mode === 'create' && isHttpAuthMode">{{'GROUP.IMPORT_HTTP_GROUP' | translate}}</h3>
|
||||
<h3 class="modal-title" *ngIf="mode === 'create' && isOidcMode">{{'GROUP.IMPORT_OIDC_GROUP' | translate}}</h3>
|
||||
<h3 class="modal-title" *ngIf="mode !== 'create'">{{'GROUP.EDIT' | translate}}</h3>
|
||||
<clr-modal
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalStaticBackdrop]="true"
|
||||
[clrModalClosable]="false">
|
||||
<h3 class="modal-title" *ngIf="mode === 'create' && isLdapMode">
|
||||
{{ 'GROUP.IMPORT_LDAP_GROUP' | translate }}
|
||||
</h3>
|
||||
<h3 class="modal-title" *ngIf="mode === 'create' && isHttpAuthMode">
|
||||
{{ 'GROUP.IMPORT_HTTP_GROUP' | translate }}
|
||||
</h3>
|
||||
<h3 class="modal-title" *ngIf="mode === 'create' && isOidcMode">
|
||||
{{ 'GROUP.IMPORT_OIDC_GROUP' | translate }}
|
||||
</h3>
|
||||
<h3 class="modal-title" *ngIf="mode !== 'create'">
|
||||
{{ 'GROUP.EDIT' | translate }}
|
||||
</h3>
|
||||
|
||||
<div class="modal-body">
|
||||
<form class="clr-form clr-form-horizontal" #groupForm="ngForm">
|
||||
<section>
|
||||
<clr-input-container *ngIf="isLdapMode">
|
||||
<label for="ldap_group_dn" class="required">{{ 'GROUP.GROUP_DN' | translate}}</label>
|
||||
<input clrInput type="text" id="ldap_group_dn" name="ldap_group_dn" required
|
||||
[disabled]="mode !== 'create'" [(ngModel)]="group.ldap_group_dn" #groupDN="ngModel" />
|
||||
<clr-control-error>{{dnTooltip | translate}}</clr-control-error>
|
||||
<label for="ldap_group_dn" class="required">{{
|
||||
'GROUP.GROUP_DN' | translate
|
||||
}}</label>
|
||||
<input
|
||||
clrInput
|
||||
type="text"
|
||||
id="ldap_group_dn"
|
||||
name="ldap_group_dn"
|
||||
required
|
||||
[disabled]="mode !== 'create'"
|
||||
[(ngModel)]="group.ldap_group_dn"
|
||||
#groupDN="ngModel" />
|
||||
<clr-control-error>{{
|
||||
dnTooltip | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
<div class="clr-form-control" *ngIf="isLdapMode">
|
||||
<label class="clr-control-label" for="type">{{'GROUP.TYPE' | translate}}</label>
|
||||
<label class="padding-left-6" id="type">{{'GROUP.LDAP_TYPE' | translate}}</label>
|
||||
<label class="clr-control-label" for="type">{{
|
||||
'GROUP.TYPE' | translate
|
||||
}}</label>
|
||||
<label class="padding-left-6" id="type">{{
|
||||
'GROUP.LDAP_TYPE' | translate
|
||||
}}</label>
|
||||
</div>
|
||||
<clr-input-container>
|
||||
<label for="group_name" class="required">{{ 'GROUP.NAME' | translate}}</label>
|
||||
<input clrInput type="text" id="group_name" name="group_name" required
|
||||
[(ngModel)]="group.group_name" #groupName="ngModel" />
|
||||
<clr-control-error>{{dnTooltip | translate}}</clr-control-error>
|
||||
<label for="group_name" class="required">{{
|
||||
'GROUP.NAME' | translate
|
||||
}}</label>
|
||||
<input
|
||||
clrInput
|
||||
type="text"
|
||||
id="group_name"
|
||||
name="group_name"
|
||||
required
|
||||
[(ngModel)]="group.group_name"
|
||||
#groupName="ngModel" />
|
||||
<clr-control-error>{{
|
||||
dnTooltip | translate
|
||||
}}</clr-control-error>
|
||||
</clr-input-container>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline"
|
||||
(click)="close()">{{'BUTTON.CANCEL' | translate | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isFormValid"
|
||||
(click)="save()">{{'BUTTON.SAVE' | translate | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="close()">
|
||||
{{ 'BUTTON.CANCEL' | translate | translate }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!isFormValid"
|
||||
(click)="save()">
|
||||
{{ 'BUTTON.SAVE' | translate | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
</clr-modal>
|
||||
|
|
|
@ -1,60 +1,59 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { MessageHandlerService } from "../../../../shared/services/message-handler.service";
|
||||
import { SessionService } from "../../../../shared/services/session.service";
|
||||
import { AppConfigService } from "../../../../services/app-config.service";
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { SessionService } from '../../../../shared/services/session.service';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
import { AddGroupModalComponent } from './add-group-modal.component';
|
||||
import { UsergroupService } from "../../../../../../ng-swagger-gen/services/usergroup.service";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { UsergroupService } from '../../../../../../ng-swagger-gen/services/usergroup.service';
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
|
||||
describe('AddGroupModalComponent', () => {
|
||||
let component: AddGroupModalComponent;
|
||||
let fixture: ComponentFixture<AddGroupModalComponent>;
|
||||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
}
|
||||
};
|
||||
let fakeGroupService = null;
|
||||
let fakeAppConfigService = {
|
||||
isLdapMode: function () {
|
||||
return true;
|
||||
},
|
||||
isHttpAuthMode: function () {
|
||||
return false;
|
||||
},
|
||||
isOidcMode: function () {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let fakeMessageHandlerService = null;
|
||||
let component: AddGroupModalComponent;
|
||||
let fixture: ComponentFixture<AddGroupModalComponent>;
|
||||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
},
|
||||
};
|
||||
let fakeGroupService = null;
|
||||
let fakeAppConfigService = {
|
||||
isLdapMode: function () {
|
||||
return true;
|
||||
},
|
||||
isHttpAuthMode: function () {
|
||||
return false;
|
||||
},
|
||||
isOidcMode: function () {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
let fakeMessageHandlerService = null;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AddGroupModalComponent],
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
providers: [
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: UsergroupService, useValue: fakeGroupService },
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AddGroupModalComponent],
|
||||
imports: [SharedTestingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: fakeMessageHandlerService,
|
||||
},
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: UsergroupService, useValue: fakeGroupService },
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddGroupModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.open();
|
||||
fixture.autoDetectChanges();
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddGroupModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.open();
|
||||
fixture.autoDetectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,123 +1,135 @@
|
|||
import { finalize } from 'rxjs/operators';
|
||||
import { Component, OnInit, EventEmitter, Output, OnDestroy, ViewChild } from "@angular/core";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { MessageHandlerService } from "../../../../shared/services/message-handler.service";
|
||||
import { SessionService } from "../../../../shared/services/session.service";
|
||||
import { AppConfigService } from "../../../../services/app-config.service";
|
||||
import { GroupType } from "../../../../shared/entities/shared.const";
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
EventEmitter,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { SessionService } from '../../../../shared/services/session.service';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
import { GroupType } from '../../../../shared/entities/shared.const';
|
||||
import { UserGroup } from 'ng-swagger-gen/models/user-group';
|
||||
import { UsergroupService } from "../../../../../../ng-swagger-gen/services/usergroup.service";
|
||||
import { UsergroupService } from '../../../../../../ng-swagger-gen/services/usergroup.service';
|
||||
|
||||
@Component({
|
||||
selector: "hbr-add-group-modal",
|
||||
templateUrl: "./add-group-modal.component.html",
|
||||
styleUrls: ["./add-group-modal.component.scss"]
|
||||
selector: 'hbr-add-group-modal',
|
||||
templateUrl: './add-group-modal.component.html',
|
||||
styleUrls: ['./add-group-modal.component.scss'],
|
||||
})
|
||||
export class AddGroupModalComponent implements OnInit, OnDestroy {
|
||||
opened = false;
|
||||
mode = "create";
|
||||
dnTooltip = 'TOOLTIP.ITEM_REQUIRED';
|
||||
export class AddGroupModalComponent implements OnInit {
|
||||
opened = false;
|
||||
mode = 'create';
|
||||
dnTooltip = 'TOOLTIP.ITEM_REQUIRED';
|
||||
|
||||
group: UserGroup;
|
||||
@ViewChild('groupForm', { static: true })
|
||||
groupForm: NgForm;
|
||||
group: UserGroup;
|
||||
@ViewChild('groupForm', { static: true })
|
||||
groupForm: NgForm;
|
||||
|
||||
submitted = false;
|
||||
submitted = false;
|
||||
|
||||
@Output() dataChange = new EventEmitter();
|
||||
@Output() dataChange = new EventEmitter();
|
||||
|
||||
isLdapMode: boolean;
|
||||
isHttpAuthMode: boolean;
|
||||
isOidcMode: boolean;
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private appConfigService: AppConfigService,
|
||||
private groupService: UsergroupService,
|
||||
) { }
|
||||
isLdapMode: boolean;
|
||||
isHttpAuthMode: boolean;
|
||||
isOidcMode: boolean;
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private appConfigService: AppConfigService,
|
||||
private groupService: UsergroupService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.appConfigService.isLdapMode()) {
|
||||
this.isLdapMode = true;
|
||||
ngOnInit() {
|
||||
if (this.appConfigService.isLdapMode()) {
|
||||
this.isLdapMode = true;
|
||||
}
|
||||
if (this.appConfigService.isHttpAuthMode()) {
|
||||
this.isHttpAuthMode = true;
|
||||
}
|
||||
if (this.appConfigService.isOidcMode()) {
|
||||
this.isOidcMode = true;
|
||||
}
|
||||
this.group = {
|
||||
group_type: this.isLdapMode
|
||||
? GroupType.LDAP_TYPE
|
||||
: this.isHttpAuthMode
|
||||
? GroupType.HTTP_TYPE
|
||||
: GroupType.OIDC_TYPE,
|
||||
};
|
||||
}
|
||||
if (this.appConfigService.isHttpAuthMode()) {
|
||||
this.isHttpAuthMode = true;
|
||||
|
||||
public get isFormValid(): boolean {
|
||||
return this.groupForm.valid;
|
||||
}
|
||||
if (this.appConfigService.isOidcMode()) {
|
||||
this.isOidcMode = true;
|
||||
|
||||
public open(group?: UserGroup, editMode: boolean = false): void {
|
||||
this.resetGroup();
|
||||
if (editMode) {
|
||||
this.mode = 'edit';
|
||||
Object.assign(this.group, group);
|
||||
} else {
|
||||
this.mode = 'create';
|
||||
}
|
||||
this.opened = true;
|
||||
}
|
||||
this.group = {
|
||||
group_type: this.isLdapMode ? GroupType.LDAP_TYPE : this.isHttpAuthMode ? GroupType.HTTP_TYPE : GroupType.OIDC_TYPE
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
ngOnDestroy() { }
|
||||
|
||||
public get isFormValid(): boolean {
|
||||
return this.groupForm.valid;
|
||||
}
|
||||
|
||||
public open(group?: UserGroup, editMode: boolean = false): void {
|
||||
this.resetGroup();
|
||||
if (editMode) {
|
||||
this.mode = "edit";
|
||||
Object.assign(this.group, group);
|
||||
} else {
|
||||
this.mode = "create";
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
this.resetGroup();
|
||||
}
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
this.resetGroup();
|
||||
}
|
||||
|
||||
save(): void {
|
||||
if (this.mode === "create") {
|
||||
this.createGroup();
|
||||
} else {
|
||||
this.editGroup();
|
||||
save(): void {
|
||||
if (this.mode === 'create') {
|
||||
this.createGroup();
|
||||
} else {
|
||||
this.editGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createGroup() {
|
||||
let groupCopy = Object.assign({}, this.group);
|
||||
this.groupService.createUserGroup({
|
||||
usergroup: groupCopy
|
||||
}).pipe(
|
||||
finalize(() => this.close()))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.msgHandler.showSuccess("GROUP.ADD_GROUP_SUCCESS");
|
||||
this.dataChange.emit();
|
||||
},
|
||||
error => this.msgHandler.handleError(error)
|
||||
);
|
||||
}
|
||||
createGroup() {
|
||||
let groupCopy = Object.assign({}, this.group);
|
||||
this.groupService
|
||||
.createUserGroup({
|
||||
usergroup: groupCopy,
|
||||
})
|
||||
.pipe(finalize(() => this.close()))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.msgHandler.showSuccess('GROUP.ADD_GROUP_SUCCESS');
|
||||
this.dataChange.emit();
|
||||
},
|
||||
error => this.msgHandler.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
editGroup() {
|
||||
let groupCopy = Object.assign({}, this.group);
|
||||
this.groupService
|
||||
.updateUserGroup({
|
||||
groupId: groupCopy.id,
|
||||
usergroup: groupCopy
|
||||
}).pipe(
|
||||
finalize(() => this.close()))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.msgHandler.showSuccess("GROUP.EDIT_GROUP_SUCCESS");
|
||||
this.dataChange.emit();
|
||||
},
|
||||
error => this.msgHandler.handleError(error)
|
||||
);
|
||||
}
|
||||
editGroup() {
|
||||
let groupCopy = Object.assign({}, this.group);
|
||||
this.groupService
|
||||
.updateUserGroup({
|
||||
groupId: groupCopy.id,
|
||||
usergroup: groupCopy,
|
||||
})
|
||||
.pipe(finalize(() => this.close()))
|
||||
.subscribe(
|
||||
res => {
|
||||
this.msgHandler.showSuccess('GROUP.EDIT_GROUP_SUCCESS');
|
||||
this.dataChange.emit();
|
||||
},
|
||||
error => this.msgHandler.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
resetGroup() {
|
||||
this.group = {
|
||||
group_type: this.isLdapMode ? GroupType.LDAP_TYPE : this.isHttpAuthMode ? GroupType.HTTP_TYPE : GroupType.OIDC_TYPE
|
||||
};
|
||||
this.groupForm.reset();
|
||||
}
|
||||
resetGroup() {
|
||||
this.group = {
|
||||
group_type: this.isLdapMode
|
||||
? GroupType.LDAP_TYPE
|
||||
: this.isHttpAuthMode
|
||||
? GroupType.HTTP_TYPE
|
||||
: GroupType.OIDC_TYPE,
|
||||
};
|
||||
this.groupForm.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,93 @@
|
|||
<div class="row relative">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="custom-h2">{{'GROUP.GROUP' | translate}}</h2>
|
||||
<div class="action-panel-pos rightPos">
|
||||
<hbr-filter [withDivider]="true" class="filter-pos" filterPlaceholder='group name' (filterEvt)="doFilter($event)" [currentValue]="currentTerm"></hbr-filter>
|
||||
<span class="refresh-btn">
|
||||
<clr-icon shape="refresh" [hidden]="loading" ng-disabled="loading" (click)="refresh()"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="loading === false"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<clr-datagrid (clrDgRefresh)="loadData($event)" [(clrDgSelected)]="selectedGroups" [clrDgLoading]="loading">
|
||||
<clr-dg-action-bar >
|
||||
<button type="button" class="btn btn-secondary" (click)="addGroup()" [disabled]="!canAddGroup">
|
||||
<clr-icon shape="plus" size="15"></clr-icon> {{'GROUP.ADD' | translate}}</button>
|
||||
<button type="button" class="btn btn-secondary" (click)="editGroup()" [disabled]="!canEditGroup">
|
||||
<clr-icon shape="pencil" size="15"></clr-icon> {{'GROUP.EDIT' | translate}}</button>
|
||||
<button type="button" class="btn btn-secondary" (click)="openDeleteConfirmationDialog()" [disabled]="!canDeleteGroup">
|
||||
<clr-icon shape="times" size="15"></clr-icon> {{'GROUP.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
|
||||
<clr-dg-column>{{'GROUP.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'GROUP.TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="isLdapMode">{{'GROUP.DN' | translate}}</clr-dg-column>
|
||||
|
||||
<clr-dg-row *ngFor="let group of groups" [clrDgItem]="group">
|
||||
<clr-dg-cell>{{group.group_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{groupToSring(group.group_type) | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="isLdapMode">{{group.ldap_group_dn}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalCount">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span *ngIf="totalCount">
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'GROUP.OF' | translate}}
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="custom-h2">{{ 'GROUP.GROUP' | translate }}</h2>
|
||||
<div class="action-panel-pos rightPos">
|
||||
<hbr-filter
|
||||
[withDivider]="true"
|
||||
class="filter-pos"
|
||||
filterPlaceholder="group name"
|
||||
(filterEvt)="doFilter($event)"
|
||||
[currentValue]="currentTerm"></hbr-filter>
|
||||
<span class="refresh-btn">
|
||||
<clr-icon
|
||||
shape="refresh"
|
||||
[hidden]="loading"
|
||||
ng-disabled="loading"
|
||||
(click)="refresh()"></clr-icon>
|
||||
<span
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="loading === false"></span>
|
||||
</span>
|
||||
{{totalCount}} {{'GROUP.ITEMS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<div>
|
||||
<clr-datagrid
|
||||
(clrDgRefresh)="loadData($event)"
|
||||
[(clrDgSelected)]="selectedGroups"
|
||||
[clrDgLoading]="loading">
|
||||
<clr-dg-action-bar>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="addGroup()"
|
||||
[disabled]="!canAddGroup">
|
||||
<clr-icon shape="plus" size="15"></clr-icon> {{
|
||||
'GROUP.ADD' | translate
|
||||
}}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="editGroup()"
|
||||
[disabled]="!canEditGroup">
|
||||
<clr-icon shape="pencil" size="15"></clr-icon> {{
|
||||
'GROUP.EDIT' | translate
|
||||
}}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="openDeleteConfirmationDialog()"
|
||||
[disabled]="!canDeleteGroup">
|
||||
<clr-icon shape="times" size="15"></clr-icon> {{
|
||||
'GROUP.DELETE' | translate
|
||||
}}
|
||||
</button>
|
||||
</clr-dg-action-bar>
|
||||
|
||||
<clr-dg-column>{{ 'GROUP.NAME' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'GROUP.TYPE' | translate }}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="isLdapMode">{{
|
||||
'GROUP.DN' | translate
|
||||
}}</clr-dg-column>
|
||||
|
||||
<clr-dg-row *ngFor="let group of groups" [clrDgItem]="group">
|
||||
<clr-dg-cell>{{ group.group_name }}</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
groupToSring(group.group_type) | translate
|
||||
}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="isLdapMode">{{
|
||||
group.ldap_group_dn
|
||||
}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination
|
||||
#pagination
|
||||
[clrDgPageSize]="pageSize"
|
||||
[(clrDgPage)]="currentPage"
|
||||
[clrDgTotalItems]="totalCount">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15, 25, 50]">{{
|
||||
'PAGINATION.PAGE_SIZE' | translate
|
||||
}}</clr-dg-page-size>
|
||||
<span *ngIf="totalCount">
|
||||
{{ pagination.firstItem + 1 }} -
|
||||
{{ pagination.lastItem + 1 }}
|
||||
{{ 'GROUP.OF' | translate }}
|
||||
</span>
|
||||
{{ totalCount }} {{ 'GROUP.ITEMS' | translate }}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<hbr-add-group-modal (dataChange)="refresh()"></hbr-add-group-modal>
|
||||
</div>
|
||||
<hbr-add-group-modal (dataChange)="refresh()"></hbr-add-group-modal>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,80 +1,87 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { GroupComponent } from './group.component';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { SessionService } from "../../../shared/services/session.service";
|
||||
import { of } from "rxjs";
|
||||
import { SessionService } from '../../../shared/services/session.service';
|
||||
import { of } from 'rxjs';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { AppConfigService } from '../../../services/app-config.service';
|
||||
import { OperationService } from "../../../shared/components/operation/operation.service";
|
||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { UsergroupService } from "../../../../../ng-swagger-gen/services/usergroup.service";
|
||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { UserGroup } from "../../../../../ng-swagger-gen/models/user-group";
|
||||
import { OperationService } from '../../../shared/components/operation/operation.service';
|
||||
import { ConfirmationDialogService } from '../../global-confirmation-dialog/confirmation-dialog.service';
|
||||
import { UsergroupService } from '../../../../../ng-swagger-gen/services/usergroup.service';
|
||||
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { UserGroup } from '../../../../../ng-swagger-gen/models/user-group';
|
||||
|
||||
describe('GroupComponent', () => {
|
||||
let component: GroupComponent;
|
||||
let fixture: ComponentFixture<GroupComponent>;
|
||||
let fakeMessageHandlerService = null;
|
||||
let fakeOperationService = null;
|
||||
let fakeGroupService = {
|
||||
listUserGroupsResponse: function () {
|
||||
const res: HttpResponse<Array<UserGroup>> = new HttpResponse<Array<UserGroup>>({
|
||||
headers: new HttpHeaders({'x-total-count': '3'}),
|
||||
body: [{
|
||||
group_name: ''
|
||||
}, {
|
||||
group_name: 'abc'
|
||||
}]
|
||||
});
|
||||
return of(res).pipe(delay(0));
|
||||
}
|
||||
};
|
||||
let fakeConfirmationDialogService = {
|
||||
confirmationConfirm$: of({
|
||||
state: 1,
|
||||
source: 2
|
||||
})
|
||||
};
|
||||
let fakeSessionService = {
|
||||
currentUser: {
|
||||
has_admin_role: true
|
||||
}
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
isLdapMode: function () {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
let component: GroupComponent;
|
||||
let fixture: ComponentFixture<GroupComponent>;
|
||||
let fakeMessageHandlerService = null;
|
||||
let fakeOperationService = null;
|
||||
let fakeGroupService = {
|
||||
listUserGroupsResponse: function () {
|
||||
const res: HttpResponse<Array<UserGroup>> = new HttpResponse<
|
||||
Array<UserGroup>
|
||||
>({
|
||||
headers: new HttpHeaders({ 'x-total-count': '3' }),
|
||||
body: [
|
||||
{
|
||||
group_name: '',
|
||||
},
|
||||
{
|
||||
group_name: 'abc',
|
||||
},
|
||||
],
|
||||
});
|
||||
return of(res).pipe(delay(0));
|
||||
},
|
||||
};
|
||||
let fakeConfirmationDialogService = {
|
||||
confirmationConfirm$: of({
|
||||
state: 1,
|
||||
source: 2,
|
||||
}),
|
||||
};
|
||||
let fakeSessionService = {
|
||||
currentUser: {
|
||||
has_admin_role: true,
|
||||
},
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
isLdapMode: function () {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GroupComponent],
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
providers: [
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{ provide: OperationService, useValue: fakeOperationService },
|
||||
{ provide: UsergroupService, useValue: fakeGroupService },
|
||||
{ provide: ConfirmationDialogService, useValue: fakeConfirmationDialogService },
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService }
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GroupComponent],
|
||||
imports: [SharedTestingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: fakeMessageHandlerService,
|
||||
},
|
||||
{ provide: OperationService, useValue: fakeOperationService },
|
||||
{ provide: UsergroupService, useValue: fakeGroupService },
|
||||
{
|
||||
provide: ConfirmationDialogService,
|
||||
useValue: fakeConfirmationDialogService,
|
||||
},
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GroupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GroupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,261 +1,313 @@
|
|||
import { of, Subscription, forkJoin } from "rxjs";
|
||||
import { flatMap, catchError, finalize, debounceTime, switchMap, filter } from "rxjs/operators";
|
||||
import { SessionService } from "../../../shared/services/session.service";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from "@angular/core";
|
||||
import { AddGroupModalComponent } from "./add-group-modal/add-group-modal.component";
|
||||
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
|
||||
import { throwError as observableThrowError } from "rxjs";
|
||||
import { AppConfigService } from '../../../services/app-config.service';
|
||||
import { OperationService } from "../../../shared/components/operation/operation.service";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../../shared/components/operation/operate";
|
||||
import { of, Subscription, forkJoin } from 'rxjs';
|
||||
import {
|
||||
ConfirmationButtons,
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
GroupType
|
||||
} from "../../../shared/entities/shared.const";
|
||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { errorHandler } from "../../../shared/units/shared.utils";
|
||||
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
||||
import { ClrDatagridStateInterface } from "@clr/angular";
|
||||
import { getPageSizeFromLocalStorage, PageSizeMapKeys, setPageSizeToLocalStorage } from "../../../shared/units/utils";
|
||||
import { UsergroupService } from "../../../../../ng-swagger-gen/services/usergroup.service";
|
||||
import { UserGroup } from "../../../../../ng-swagger-gen/models/user-group";
|
||||
import { FilterComponent } from "../../../shared/components/filter/filter.component";
|
||||
flatMap,
|
||||
catchError,
|
||||
finalize,
|
||||
debounceTime,
|
||||
switchMap,
|
||||
filter,
|
||||
} from 'rxjs/operators';
|
||||
import { SessionService } from '../../../shared/services/session.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { AddGroupModalComponent } from './add-group-modal/add-group-modal.component';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { throwError as observableThrowError } from 'rxjs';
|
||||
import { AppConfigService } from '../../../services/app-config.service';
|
||||
import { OperationService } from '../../../shared/components/operation/operation.service';
|
||||
import {
|
||||
operateChanges,
|
||||
OperateInfo,
|
||||
OperationState,
|
||||
} from '../../../shared/components/operation/operate';
|
||||
import {
|
||||
ConfirmationButtons,
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
GroupType,
|
||||
} from '../../../shared/entities/shared.const';
|
||||
import { ConfirmationDialogService } from '../../global-confirmation-dialog/confirmation-dialog.service';
|
||||
import { errorHandler } from '../../../shared/units/shared.utils';
|
||||
import { ConfirmationMessage } from '../../global-confirmation-dialog/confirmation-message';
|
||||
import { ClrDatagridStateInterface } from '@clr/angular';
|
||||
import {
|
||||
getPageSizeFromLocalStorage,
|
||||
PageSizeMapKeys,
|
||||
setPageSizeToLocalStorage,
|
||||
} from '../../../shared/units/utils';
|
||||
import { UsergroupService } from '../../../../../ng-swagger-gen/services/usergroup.service';
|
||||
import { UserGroup } from '../../../../../ng-swagger-gen/models/user-group';
|
||||
import { FilterComponent } from '../../../shared/components/filter/filter.component';
|
||||
|
||||
@Component({
|
||||
selector: "app-group",
|
||||
templateUrl: "./group.component.html",
|
||||
styleUrls: ["./group.component.scss"]
|
||||
selector: 'app-group',
|
||||
templateUrl: './group.component.html',
|
||||
styleUrls: ['./group.component.scss'],
|
||||
})
|
||||
export class GroupComponent implements OnInit, OnDestroy {
|
||||
loading = true;
|
||||
groups: UserGroup[] = [];
|
||||
currentPage: number = 1;
|
||||
totalCount: number = 0;
|
||||
pageSize: number = getPageSizeFromLocalStorage(PageSizeMapKeys.SYSTEM_GROUP_COMPONENT);
|
||||
selectedGroups: UserGroup[] = [];
|
||||
currentTerm = "";
|
||||
delSub: Subscription;
|
||||
batchOps = 'idle';
|
||||
isLdapMode: boolean;
|
||||
loading = true;
|
||||
groups: UserGroup[] = [];
|
||||
currentPage: number = 1;
|
||||
totalCount: number = 0;
|
||||
pageSize: number = getPageSizeFromLocalStorage(
|
||||
PageSizeMapKeys.SYSTEM_GROUP_COMPONENT
|
||||
);
|
||||
selectedGroups: UserGroup[] = [];
|
||||
currentTerm = '';
|
||||
delSub: Subscription;
|
||||
batchOps = 'idle';
|
||||
isLdapMode: boolean;
|
||||
|
||||
@ViewChild(AddGroupModalComponent) newGroupModal: AddGroupModalComponent;
|
||||
searchSub: Subscription;
|
||||
@ViewChild(FilterComponent, {static: true})
|
||||
filterComponent: FilterComponent;
|
||||
constructor(
|
||||
private operationService: OperationService,
|
||||
private translate: TranslateService,
|
||||
private operateDialogService: ConfirmationDialogService,
|
||||
private groupService: UsergroupService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private session: SessionService,
|
||||
private translateService: TranslateService,
|
||||
private appConfigService: AppConfigService
|
||||
) { }
|
||||
@ViewChild(AddGroupModalComponent) newGroupModal: AddGroupModalComponent;
|
||||
searchSub: Subscription;
|
||||
@ViewChild(FilterComponent, { static: true })
|
||||
filterComponent: FilterComponent;
|
||||
constructor(
|
||||
private operationService: OperationService,
|
||||
private translate: TranslateService,
|
||||
private operateDialogService: ConfirmationDialogService,
|
||||
private groupService: UsergroupService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private session: SessionService,
|
||||
private translateService: TranslateService,
|
||||
private appConfigService: AppConfigService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.appConfigService.isLdapMode()) {
|
||||
this.isLdapMode = true;
|
||||
}
|
||||
this.delSub = this.operateDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (
|
||||
message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.PROJECT_MEMBER
|
||||
) {
|
||||
if (this.batchOps === 'delete') {
|
||||
this.deleteGroups();
|
||||
}
|
||||
ngOnInit() {
|
||||
if (this.appConfigService.isLdapMode()) {
|
||||
this.isLdapMode = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!this.searchSub) {
|
||||
this.searchSub = this.filterComponent.filterTerms.pipe(
|
||||
filter(groupName => !!groupName),
|
||||
debounceTime(500),
|
||||
switchMap(groupName => {
|
||||
this.currentPage = 1;
|
||||
this.selectedGroups = [];
|
||||
this.loading = true;
|
||||
return this.groupService.listUserGroupsResponse({
|
||||
groupName: groupName,
|
||||
pageSize: this.pageSize,
|
||||
page: this.currentPage
|
||||
})
|
||||
.pipe(finalize(() => {
|
||||
this.loading = false;
|
||||
}));
|
||||
})).subscribe(response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'), 10
|
||||
this.delSub = this.operateDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (
|
||||
message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.PROJECT_MEMBER
|
||||
) {
|
||||
if (this.batchOps === 'delete') {
|
||||
this.deleteGroups();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
this.groups = response.body as UserGroup[];
|
||||
}, error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.delSub.unsubscribe();
|
||||
if (this.searchSub) {
|
||||
this.searchSub.unsubscribe();
|
||||
this.searchSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.currentPage = 1;
|
||||
this.selectedGroups = [];
|
||||
this.currentTerm = '';
|
||||
this.filterComponent.currentValue = '';
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData(state?: ClrDatagridStateInterface): void {
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
setPageSizeToLocalStorage(PageSizeMapKeys.SYSTEM_GROUP_COMPONENT, this.pageSize);
|
||||
}
|
||||
this.loading = true;
|
||||
if (this.currentTerm) {
|
||||
this.groupService.searchUserGroupsResponse({
|
||||
groupname: encodeURIComponent(this.currentTerm),
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize
|
||||
})
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'), 10
|
||||
if (!this.searchSub) {
|
||||
this.searchSub = this.filterComponent.filterTerms
|
||||
.pipe(
|
||||
filter(groupName => !!groupName),
|
||||
debounceTime(500),
|
||||
switchMap(groupName => {
|
||||
this.currentPage = 1;
|
||||
this.selectedGroups = [];
|
||||
this.loading = true;
|
||||
return this.groupService
|
||||
.listUserGroupsResponse({
|
||||
groupName: groupName,
|
||||
pageSize: this.pageSize,
|
||||
page: this.currentPage,
|
||||
})
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
);
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'),
|
||||
10
|
||||
);
|
||||
this.groups = response.body as UserGroup[];
|
||||
},
|
||||
error => {
|
||||
this.msgHandler.handleError(error);
|
||||
}
|
||||
);
|
||||
this.groups = response.body as UserGroup[];
|
||||
},
|
||||
err => {
|
||||
this.msgHandler.error(err);
|
||||
});
|
||||
} else {
|
||||
this.groupService.listUserGroupsResponse({
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize
|
||||
})
|
||||
.pipe(finalize(() => this.loading = false))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'), 10
|
||||
);
|
||||
this.groups = response.body as UserGroup[];
|
||||
},
|
||||
err => {
|
||||
this.msgHandler.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addGroup(): void {
|
||||
this.newGroupModal.open();
|
||||
}
|
||||
|
||||
editGroup(): void {
|
||||
this.newGroupModal.open(this.selectedGroups[0], true);
|
||||
}
|
||||
|
||||
openDeleteConfirmationDialog(): void {
|
||||
// open delete modal
|
||||
this.batchOps = 'delete';
|
||||
let nameArr: string[] = [];
|
||||
if (this.selectedGroups.length > 0) {
|
||||
this.selectedGroups.forEach(group => {
|
||||
nameArr.push(group.group_name);
|
||||
});
|
||||
// batchInfo.id = group.id;
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
"GROUP.DELETION_TITLE",
|
||||
"GROUP.DELETION_SUMMARY",
|
||||
nameArr.join(","),
|
||||
this.selectedGroups,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.operateDialogService.openComfirmDialog(deletionMessage);
|
||||
ngOnDestroy(): void {
|
||||
this.delSub.unsubscribe();
|
||||
if (this.searchSub) {
|
||||
this.searchSub.unsubscribe();
|
||||
this.searchSub = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteGroups() {
|
||||
let obs = this.selectedGroups.map(group => {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.DELETE_GROUP';
|
||||
operMessage.data.id = group.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = group.group_name;
|
||||
|
||||
this.operationService.publishInfo(operMessage);
|
||||
return this.groupService
|
||||
.deleteUserGroup({
|
||||
groupId: group.id
|
||||
})
|
||||
.pipe(flatMap(response => {
|
||||
return this.translate.get("BATCH.DELETED_SUCCESS").pipe(flatMap(res => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
return of(res);
|
||||
}));
|
||||
}))
|
||||
.pipe(catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translateService.get(message).subscribe(res =>
|
||||
operateChanges(operMessage, OperationState.failure, res)
|
||||
);
|
||||
return observableThrowError(error);
|
||||
}));
|
||||
});
|
||||
|
||||
forkJoin(obs).subscribe(
|
||||
res => {
|
||||
refresh(): void {
|
||||
this.currentPage = 1;
|
||||
this.selectedGroups = [];
|
||||
this.batchOps = 'idle';
|
||||
this.currentTerm = '';
|
||||
this.filterComponent.currentValue = '';
|
||||
this.loadData();
|
||||
},
|
||||
err => this.msgHandler.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
groupToSring(type: number) {
|
||||
if (type === GroupType.LDAP_TYPE) {
|
||||
return 'GROUP.LDAP_TYPE';
|
||||
} else if (type === GroupType.HTTP_TYPE) {
|
||||
return 'GROUP.HTTP_TYPE';
|
||||
} else if (type === GroupType.OIDC_TYPE) {
|
||||
return 'GROUP.OIDC_TYPE';
|
||||
} else {
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
doFilter(groupName: string): void {
|
||||
if (!groupName) {
|
||||
this.currentTerm = groupName;
|
||||
this.loadData();
|
||||
loadData(state?: ClrDatagridStateInterface): void {
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
setPageSizeToLocalStorage(
|
||||
PageSizeMapKeys.SYSTEM_GROUP_COMPONENT,
|
||||
this.pageSize
|
||||
);
|
||||
}
|
||||
this.loading = true;
|
||||
if (this.currentTerm) {
|
||||
this.groupService
|
||||
.searchUserGroupsResponse({
|
||||
groupname: encodeURIComponent(this.currentTerm),
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'),
|
||||
10
|
||||
);
|
||||
this.groups = response.body as UserGroup[];
|
||||
},
|
||||
err => {
|
||||
this.msgHandler.error(err);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.groupService
|
||||
.listUserGroupsResponse({
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.totalCount = Number.parseInt(
|
||||
response.headers.get('x-total-count'),
|
||||
10
|
||||
);
|
||||
this.groups = response.body as UserGroup[];
|
||||
},
|
||||
err => {
|
||||
this.msgHandler.error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
get canAddGroup(): boolean {
|
||||
return this.session.currentUser.has_admin_role;
|
||||
}
|
||||
|
||||
get canEditGroup(): boolean {
|
||||
return (
|
||||
this.selectedGroups.length === 1 &&
|
||||
this.session.currentUser.has_admin_role && this.isLdapMode
|
||||
);
|
||||
}
|
||||
get canDeleteGroup(): boolean {
|
||||
return (
|
||||
this.selectedGroups.length >= 1 &&
|
||||
this.session.currentUser.has_admin_role
|
||||
);
|
||||
}
|
||||
addGroup(): void {
|
||||
this.newGroupModal.open();
|
||||
}
|
||||
|
||||
editGroup(): void {
|
||||
this.newGroupModal.open(this.selectedGroups[0], true);
|
||||
}
|
||||
|
||||
openDeleteConfirmationDialog(): void {
|
||||
// open delete modal
|
||||
this.batchOps = 'delete';
|
||||
let nameArr: string[] = [];
|
||||
if (this.selectedGroups.length > 0) {
|
||||
this.selectedGroups.forEach(group => {
|
||||
nameArr.push(group.group_name);
|
||||
});
|
||||
// batchInfo.id = group.id;
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
'GROUP.DELETION_TITLE',
|
||||
'GROUP.DELETION_SUMMARY',
|
||||
nameArr.join(','),
|
||||
this.selectedGroups,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.operateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
deleteGroups() {
|
||||
let obs = this.selectedGroups.map(group => {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.DELETE_GROUP';
|
||||
operMessage.data.id = group.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = group.group_name;
|
||||
|
||||
this.operationService.publishInfo(operMessage);
|
||||
return this.groupService
|
||||
.deleteUserGroup({
|
||||
groupId: group.id,
|
||||
})
|
||||
.pipe(
|
||||
flatMap(response => {
|
||||
return this.translate.get('BATCH.DELETED_SUCCESS').pipe(
|
||||
flatMap(res => {
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.success
|
||||
);
|
||||
return of(res);
|
||||
})
|
||||
);
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
catchError(error => {
|
||||
const message = errorHandler(error);
|
||||
this.translateService
|
||||
.get(message)
|
||||
.subscribe(res =>
|
||||
operateChanges(
|
||||
operMessage,
|
||||
OperationState.failure,
|
||||
res
|
||||
)
|
||||
);
|
||||
return observableThrowError(error);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
forkJoin(obs).subscribe(
|
||||
res => {
|
||||
this.selectedGroups = [];
|
||||
this.batchOps = 'idle';
|
||||
this.loadData();
|
||||
},
|
||||
err => this.msgHandler.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
groupToSring(type: number) {
|
||||
if (type === GroupType.LDAP_TYPE) {
|
||||
return 'GROUP.LDAP_TYPE';
|
||||
} else if (type === GroupType.HTTP_TYPE) {
|
||||
return 'GROUP.HTTP_TYPE';
|
||||
} else if (type === GroupType.OIDC_TYPE) {
|
||||
return 'GROUP.OIDC_TYPE';
|
||||
} else {
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
doFilter(groupName: string): void {
|
||||
if (!groupName) {
|
||||
this.currentTerm = groupName;
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
get canAddGroup(): boolean {
|
||||
return this.session.currentUser.has_admin_role;
|
||||
}
|
||||
|
||||
get canEditGroup(): boolean {
|
||||
return (
|
||||
this.selectedGroups.length === 1 &&
|
||||
this.session.currentUser.has_admin_role &&
|
||||
this.isLdapMode
|
||||
);
|
||||
}
|
||||
get canDeleteGroup(): boolean {
|
||||
return (
|
||||
this.selectedGroups.length >= 1 &&
|
||||
this.session.currentUser.has_admin_role
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,16 @@ import { NgModule } from '@angular/core';
|
|||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { GroupComponent } from './group.component';
|
||||
import { AddGroupModalComponent } from './add-group-modal/add-group-modal.component';
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: GroupComponent,
|
||||
}
|
||||
{
|
||||
path: '',
|
||||
component: GroupComponent,
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes),
|
||||
],
|
||||
declarations: [
|
||||
GroupComponent,
|
||||
AddGroupModalComponent
|
||||
]
|
||||
imports: [SharedModule, RouterModule.forChild(routes)],
|
||||
declarations: [GroupComponent, AddGroupModalComponent],
|
||||
})
|
||||
export class GroupModule { }
|
||||
export class GroupModule {}
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
<h2 class="custom-h2" sub-header-title>{{'SIDE_NAV.SYSTEM_MGMT.INTERROGATION_SERVICES' | translate}}</h2>
|
||||
<h2 class="custom-h2" sub-header-title>
|
||||
{{ 'SIDE_NAV.SYSTEM_MGMT.INTERROGATION_SERVICES' | translate }}
|
||||
</h2>
|
||||
<nav class="mt-1">
|
||||
<ul class="nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="scanners" routerLinkActive="active">{{'SCANNER.SCANNERS' | translate }}</a>
|
||||
<a
|
||||
class="nav-link"
|
||||
routerLink="scanners"
|
||||
routerLinkActive="active"
|
||||
>{{ 'SCANNER.SCANNERS' | translate }}</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="vulnerability" routerLinkActive="active">{{'CONFIG.VULNERABILITY' | translate }}</a>
|
||||
<a
|
||||
class="nav-link"
|
||||
routerLink="vulnerability"
|
||||
routerLinkActive="active"
|
||||
>{{ 'CONFIG.VULNERABILITY' | translate }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
|
||||
|
|
|
@ -1,32 +1,28 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { InterrogationServicesComponent } from './interrogation-services.component';
|
||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
describe('InterrogationServicesComponent', () => {
|
||||
let component: InterrogationServicesComponent;
|
||||
let fixture: ComponentFixture<InterrogationServicesComponent>;
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
],
|
||||
declarations: [ InterrogationServicesComponent ],
|
||||
providers: [TranslateService],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
let component: InterrogationServicesComponent;
|
||||
let fixture: ComponentFixture<InterrogationServicesComponent>;
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [InterrogationServicesComponent],
|
||||
providers: [TranslateService],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InterrogationServicesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InterrogationServicesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-interrogation-services',
|
||||
templateUrl: './interrogation-services.component.html',
|
||||
styleUrls: ['./interrogation-services.component.scss']
|
||||
selector: 'app-interrogation-services',
|
||||
templateUrl: './interrogation-services.component.html',
|
||||
styleUrls: ['./interrogation-services.component.scss'],
|
||||
})
|
||||
export class InterrogationServicesComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
export class InterrogationServicesComponent {
|
||||
constructor() {}
|
||||
}
|
||||
|
|
|
@ -12,18 +12,20 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from "@angular/core";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { NewScannerModalComponent } from "./scanner/new-scanner-modal/new-scanner-modal.component";
|
||||
import { ScannerMetadataComponent } from "./scanner/scanner-metadata/scanner-metadata.component";
|
||||
import { NewScannerFormComponent } from "./scanner/new-scanner-form/new-scanner-form.component";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { ConfigurationScannerComponent } from "./scanner/config-scanner.component";
|
||||
import { VulnerabilityConfigComponent } from "./vulnerability/vulnerability-config.component";
|
||||
import { InterrogationServicesComponent } from "./interrogation-services.component";
|
||||
import { ScanAllRepoService } from "./vulnerability/scanAll.service";
|
||||
import { ScanApiDefaultRepository, ScanApiRepository } from "./vulnerability/scanAll.api.repository";
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { NewScannerModalComponent } from './scanner/new-scanner-modal/new-scanner-modal.component';
|
||||
import { ScannerMetadataComponent } from './scanner/scanner-metadata/scanner-metadata.component';
|
||||
import { NewScannerFormComponent } from './scanner/new-scanner-form/new-scanner-form.component';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ConfigurationScannerComponent } from './scanner/config-scanner.component';
|
||||
import { VulnerabilityConfigComponent } from './vulnerability/vulnerability-config.component';
|
||||
import { InterrogationServicesComponent } from './interrogation-services.component';
|
||||
import { ScanAllRepoService } from './vulnerability/scanAll.service';
|
||||
import {
|
||||
ScanApiDefaultRepository,
|
||||
ScanApiRepository,
|
||||
} from './vulnerability/scanAll.api.repository';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -32,37 +34,33 @@ const routes: Routes = [
|
|||
children: [
|
||||
{
|
||||
path: 'scanners',
|
||||
component: ConfigurationScannerComponent
|
||||
component: ConfigurationScannerComponent,
|
||||
},
|
||||
{
|
||||
path: 'vulnerability',
|
||||
component: VulnerabilityConfigComponent
|
||||
component: VulnerabilityConfigComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'scanners',
|
||||
pathMatch: 'full'
|
||||
pathMatch: 'full',
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule.forChild(routes),
|
||||
],
|
||||
imports: [SharedModule, RouterModule.forChild(routes)],
|
||||
declarations: [
|
||||
NewScannerModalComponent,
|
||||
NewScannerFormComponent,
|
||||
ScannerMetadataComponent,
|
||||
ConfigurationScannerComponent,
|
||||
InterrogationServicesComponent,
|
||||
VulnerabilityConfigComponent
|
||||
VulnerabilityConfigComponent,
|
||||
],
|
||||
providers: [
|
||||
ScanAllRepoService,
|
||||
{provide: ScanApiRepository, useClass: ScanApiDefaultRepository },
|
||||
]
|
||||
{ provide: ScanApiRepository, useClass: ScanApiDefaultRepository },
|
||||
],
|
||||
})
|
||||
export class InterrogationServicesModule {
|
||||
}
|
||||
export class InterrogationServicesModule {}
|
||||
|
|
|
@ -1,51 +1,123 @@
|
|||
<div class="row">
|
||||
<div>
|
||||
<h4 class="mt-1">{{'SCANNER.IMAGE_SCANNERS' | translate }}
|
||||
<h4 class="mt-1">
|
||||
{{ 'SCANNER.IMAGE_SCANNERS' | translate }}
|
||||
<clr-signpost>
|
||||
<clr-signpost-content *clrIfOpen>
|
||||
<a rel='noopener noreferrer' class="doc" target="_blank"
|
||||
href="{{scannerDocUrl}}">{{'SCANNER.VIEW_DOC' | translate }}</a>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
class="doc"
|
||||
target="_blank"
|
||||
href="{{ scannerDocUrl }}"
|
||||
>{{ 'SCANNER.VIEW_DOC' | translate }}</a
|
||||
>
|
||||
</clr-signpost-content>
|
||||
</clr-signpost>
|
||||
</h4>
|
||||
<clr-datagrid (clrDgRefresh)="getScanners($event)" [clrDgLoading]="onGoing" [(clrDgSingleSelected)]="selectedRow">
|
||||
<clr-datagrid
|
||||
(clrDgRefresh)="getScanners($event)"
|
||||
[clrDgLoading]="onGoing"
|
||||
[(clrDgSingleSelected)]="selectedRow">
|
||||
<clr-dg-action-bar>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-7">
|
||||
<button type="button" class="btn btn-secondary" (click)="addNewScanner()">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="addNewScanner()">
|
||||
<clr-icon shape="plus" size="16"></clr-icon>
|
||||
{{'SCANNER.NEW_SCANNER' | translate}}
|
||||
{{ 'SCANNER.NEW_SCANNER' | translate }}
|
||||
</button>
|
||||
<button id="set-default" [disabled]="!(selectedRow && !selectedRow.is_default && !selectedRow.disabled)"
|
||||
class="btn btn-secondary"
|
||||
(click)="setAsDefault()">{{'SCANNER.SET_AS_DEFAULT' | translate}}</button>
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="btn btn-link" clrDropdownTrigger>
|
||||
<span id="action-scanner">{{'MEMBER.ACTION' | translate}}<clr-icon class="clr-icon" shape="caret down"></clr-icon></span>
|
||||
<button
|
||||
id="set-default"
|
||||
[disabled]="
|
||||
!(
|
||||
selectedRow &&
|
||||
!selectedRow.is_default &&
|
||||
!selectedRow.disabled
|
||||
)
|
||||
"
|
||||
class="btn btn-secondary"
|
||||
(click)="setAsDefault()">
|
||||
{{ 'SCANNER.SET_AS_DEFAULT' | translate }}
|
||||
</button>
|
||||
<clr-dropdown
|
||||
[clrCloseMenuOnItemClick]="false"
|
||||
class="btn btn-link"
|
||||
clrDropdownTrigger>
|
||||
<span id="action-scanner"
|
||||
>{{ 'MEMBER.ACTION' | translate
|
||||
}}<clr-icon
|
||||
class="clr-icon"
|
||||
shape="caret down"></clr-icon
|
||||
></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button clrDropdownItem
|
||||
(click)="editScanner()"
|
||||
class="btn btn-secondary" [disabled]="!selectedRow">
|
||||
<clr-icon class="margin-top-0" size="16" shape="pencil"></clr-icon>
|
||||
<span class="margin-left-10">{{'BUTTON.EDIT' | translate}}</span>
|
||||
<button
|
||||
clrDropdownItem
|
||||
(click)="editScanner()"
|
||||
class="btn btn-secondary"
|
||||
[disabled]="!selectedRow">
|
||||
<clr-icon
|
||||
class="margin-top-0"
|
||||
size="16"
|
||||
shape="pencil"></clr-icon>
|
||||
<span class="margin-left-10">{{
|
||||
'BUTTON.EDIT' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
<button clrDropdownItem
|
||||
(click)="changeStat()"
|
||||
[disabled]="!(selectedRow && !selectedRow.is_default)">
|
||||
<span *ngIf="selectedRow && selectedRow.disabled">
|
||||
<clr-icon class="margin-top-2" size="16" shape="success-standard"></clr-icon>
|
||||
<span class="margin-left-10">{{'WEBHOOK.ENABLED_BUTTON' | translate}}</span>
|
||||
<button
|
||||
clrDropdownItem
|
||||
(click)="changeStat()"
|
||||
[disabled]="
|
||||
!(
|
||||
selectedRow &&
|
||||
!selectedRow.is_default
|
||||
)
|
||||
">
|
||||
<span
|
||||
*ngIf="
|
||||
selectedRow && selectedRow.disabled
|
||||
">
|
||||
<clr-icon
|
||||
class="margin-top-2"
|
||||
size="16"
|
||||
shape="success-standard"></clr-icon>
|
||||
<span class="margin-left-10">{{
|
||||
'WEBHOOK.ENABLED_BUTTON' | translate
|
||||
}}</span>
|
||||
</span>
|
||||
<span *ngIf="!(selectedRow && selectedRow.disabled)">
|
||||
<clr-icon class="margin-top-2" size="16" shape="ban"></clr-icon>
|
||||
<span class="margin-left-10">{{'WEBHOOK.DISABLED_BUTTON' | translate}}</span>
|
||||
<span
|
||||
*ngIf="
|
||||
!(
|
||||
selectedRow &&
|
||||
selectedRow.disabled
|
||||
)
|
||||
">
|
||||
<clr-icon
|
||||
class="margin-top-2"
|
||||
size="16"
|
||||
shape="ban"></clr-icon>
|
||||
<span class="margin-left-10">{{
|
||||
'WEBHOOK.DISABLED_BUTTON'
|
||||
| translate
|
||||
}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button clrDropdownItem
|
||||
(click)="deleteScanners()"
|
||||
class="btn btn-secondary" [disabled]="!selectedRow">
|
||||
<clr-icon class="margin-top-0" size="16" shape="times"></clr-icon>
|
||||
<span id="delete-scanner-action" class="margin-left-10">{{'BUTTON.DELETE' | translate}}</span>
|
||||
<button
|
||||
clrDropdownItem
|
||||
(click)="deleteScanners()"
|
||||
class="btn btn-secondary"
|
||||
[disabled]="!selectedRow">
|
||||
<clr-icon
|
||||
class="margin-top-0"
|
||||
size="16"
|
||||
shape="times"></clr-icon>
|
||||
<span
|
||||
id="delete-scanner-action"
|
||||
class="margin-left-10"
|
||||
>{{ 'BUTTON.DELETE' | translate }}</span
|
||||
>
|
||||
</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
|
@ -53,53 +125,96 @@
|
|||
<div class="clr-col-5">
|
||||
<div class="action-head-pos">
|
||||
<span (click)="refresh()" class="refresh-btn">
|
||||
<clr-icon shape="refresh" [hidden]="onGoing"></clr-icon>
|
||||
<clr-icon
|
||||
shape="refresh"
|
||||
[hidden]="onGoing"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column class="width-240" [clrDgField]="'name'">{{'SCANNER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column class="width-240" [clrDgField]="'url'">{{'SCANNER.ENDPOINT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.HEALTH' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.ENABLED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'SCANNER.AUTH' | translate}}</clr-dg-column>
|
||||
<clr-dg-column class="width-240" [clrDgField]="'name'">{{
|
||||
'SCANNER.NAME' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column class="width-240" [clrDgField]="'url'">{{
|
||||
'SCANNER.ENDPOINT' | translate
|
||||
}}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'SCANNER.HEALTH' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'SCANNER.ENABLED' | translate }}</clr-dg-column>
|
||||
<clr-dg-column>{{ 'SCANNER.AUTH' | translate }}</clr-dg-column>
|
||||
<clr-dg-placeholder>
|
||||
{{'SCANNER.NO_SCANNER' | translate}}
|
||||
{{ 'SCANNER.NO_SCANNER' | translate }}
|
||||
</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let scanner of scanners" [clrDgItem]="scanner">
|
||||
<clr-dg-cell class="position-relative">
|
||||
<span>{{scanner.name}}</span>
|
||||
<span *ngIf="scanner.is_default" class="label label-info ml-1">{{'SCANNER.DEFAULT' | translate}}</span>
|
||||
<span>{{ scanner.name }}</span>
|
||||
<span
|
||||
*ngIf="scanner.is_default"
|
||||
class="label label-info ml-1"
|
||||
>{{ 'SCANNER.DEFAULT' | translate }}</span
|
||||
>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.url}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{ scanner.url }}</clr-dg-cell>
|
||||
<clr-dg-cell class="position-relative">
|
||||
<span *ngIf="scanner.loadingMetadata;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>
|
||||
<span
|
||||
*ngIf="scanner.loadingMetadata; else elseBlockLoading"
|
||||
class="spinner spinner-inline ml-2"></span>
|
||||
<ng-template #elseBlockLoading>
|
||||
<span *ngIf="scanner.metadata;else elseBlock" class="label label-success">{{'SCANNER.HEALTHY' | translate}}</span>
|
||||
<span
|
||||
*ngIf="scanner.metadata; else elseBlock"
|
||||
class="label label-success"
|
||||
>{{ 'SCANNER.HEALTHY' | translate }}</span
|
||||
>
|
||||
<ng-template #elseBlock>
|
||||
<span class="label label-danger">{{'SCANNER.UNHEALTHY' | translate}}</span>
|
||||
<span class="label label-danger">{{
|
||||
'SCANNER.UNHEALTHY' | translate
|
||||
}}</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div *ngIf="!scanner.disabled" class="icon-wrap">
|
||||
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
|
||||
<span class="margin-left-5px">{{'WEBHOOK.ENABLED' | translate}}</span>
|
||||
<clr-icon
|
||||
shape="check-circle"
|
||||
size="20"
|
||||
class="is-success enabled-icon"></clr-icon>
|
||||
<span class="margin-left-5px">{{
|
||||
'WEBHOOK.ENABLED' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
<div *ngIf="scanner.disabled" class="icon-wrap">
|
||||
<clr-icon shape="exclamation-triangle" size="20" class="is-warning"></clr-icon>
|
||||
<span class="margin-left-5px">{{'WEBHOOK.DISABLED' | translate}}</span>
|
||||
<clr-icon
|
||||
shape="exclamation-triangle"
|
||||
size="20"
|
||||
class="is-warning"></clr-icon>
|
||||
<span class="margin-left-5px">{{
|
||||
'WEBHOOK.DISABLED' | translate
|
||||
}}</span>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{scanner.auth?scanner.auth:'None'}}</clr-dg-cell>
|
||||
<scanner-metadata *clrIfExpanded [uid]="scanner.uuid" ngProjectAs="clr-dg-row-detail"></scanner-metadata>
|
||||
<clr-dg-cell>{{
|
||||
scanner.auth ? scanner.auth : 'None'
|
||||
}}</clr-dg-cell>
|
||||
<scanner-metadata
|
||||
*clrIfExpanded
|
||||
[uid]="scanner.uuid"
|
||||
ngProjectAs="clr-dg-row-detail"></scanner-metadata>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="page" [clrDgTotalItems]="total">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
<span *ngIf="total">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
|
||||
{{total}} {{'DESTINATION.ITEMS' | translate}}
|
||||
<clr-dg-pagination
|
||||
#pagination
|
||||
[clrDgPageSize]="pageSize"
|
||||
[(clrDgPage)]="page"
|
||||
[clrDgTotalItems]="total">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15, 25, 50]">{{
|
||||
'PAGINATION.PAGE_SIZE' | translate
|
||||
}}</clr-dg-page-size>
|
||||
<span *ngIf="total"
|
||||
>{{ pagination.firstItem + 1 }} -
|
||||
{{ pagination.lastItem + 1 }}
|
||||
{{ 'DESTINATION.OF' | translate }}</span
|
||||
>
|
||||
{{ total }} {{ 'DESTINATION.ITEMS' | translate }}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
|
|
@ -1,96 +1,108 @@
|
|||
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
|
||||
import { of } from "rxjs";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { ConfigurationScannerComponent } from "./config-scanner.component";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { ScannerMetadataComponent } from "./scanner-metadata/scanner-metadata.component";
|
||||
import { NewScannerModalComponent } from "./new-scanner-modal/new-scanner-modal.component";
|
||||
import { NewScannerFormComponent } from "./new-scanner-form/new-scanner-form.component";
|
||||
import { ScannerService } from "../../../../../../ng-swagger-gen/services/scanner.service";
|
||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||
import { Registry } from "../../../../../../ng-swagger-gen/models/registry";
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import {
|
||||
ComponentFixture,
|
||||
ComponentFixtureAutoDetect,
|
||||
TestBed,
|
||||
} from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { ConfigurationScannerComponent } from './config-scanner.component';
|
||||
import { SharedTestingModule } from '../../../../shared/shared.module';
|
||||
import { ScannerMetadataComponent } from './scanner-metadata/scanner-metadata.component';
|
||||
import { NewScannerModalComponent } from './new-scanner-modal/new-scanner-modal.component';
|
||||
import { NewScannerFormComponent } from './new-scanner-form/new-scanner-form.component';
|
||||
import { ScannerService } from '../../../../../../ng-swagger-gen/services/scanner.service';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { Registry } from '../../../../../../ng-swagger-gen/models/registry';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
|
||||
describe('ConfigurationScannerComponent', () => {
|
||||
let mockScannerMetadata = {
|
||||
scanner: {
|
||||
name: 'test1',
|
||||
vendor: 'trivy',
|
||||
version: '1.0.1',
|
||||
},
|
||||
capabilities: [{
|
||||
consumes_mime_types: ['consumes_mime_types'],
|
||||
produces_mime_types: ['consumes_mime_types']
|
||||
}]
|
||||
};
|
||||
let mockScanner1 = {
|
||||
name: 'test1',
|
||||
description: 'just a sample',
|
||||
version: '1.0.0',
|
||||
url: 'http://168.0.0.1'
|
||||
};
|
||||
let component: ConfigurationScannerComponent;
|
||||
let fixture: ComponentFixture<ConfigurationScannerComponent>;
|
||||
let fakedConfigScannerService = {
|
||||
getScannerMetadata() {
|
||||
return of(mockScannerMetadata).pipe(delay(10));
|
||||
},
|
||||
listScannersResponse() {
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
|
||||
headers: new HttpHeaders({'x-total-count': [mockScanner1].length.toString()}),
|
||||
body: [mockScanner1]
|
||||
});
|
||||
return of(response).pipe(delay(10));
|
||||
},
|
||||
updateScanner() {
|
||||
return of(true);
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
],
|
||||
declarations: [
|
||||
ConfigurationScannerComponent,
|
||||
ScannerMetadataComponent,
|
||||
NewScannerModalComponent,
|
||||
NewScannerFormComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: ScannerService, useValue: fakedConfigScannerService },
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
]
|
||||
let mockScannerMetadata = {
|
||||
scanner: {
|
||||
name: 'test1',
|
||||
vendor: 'trivy',
|
||||
version: '1.0.1',
|
||||
},
|
||||
capabilities: [
|
||||
{
|
||||
consumes_mime_types: ['consumes_mime_types'],
|
||||
produces_mime_types: ['consumes_mime_types'],
|
||||
},
|
||||
],
|
||||
};
|
||||
let mockScanner1 = {
|
||||
name: 'test1',
|
||||
description: 'just a sample',
|
||||
version: '1.0.0',
|
||||
url: 'http://168.0.0.1',
|
||||
};
|
||||
let component: ConfigurationScannerComponent;
|
||||
let fixture: ComponentFixture<ConfigurationScannerComponent>;
|
||||
let fakedConfigScannerService = {
|
||||
getScannerMetadata() {
|
||||
return of(mockScannerMetadata).pipe(delay(10));
|
||||
},
|
||||
listScannersResponse() {
|
||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<
|
||||
Array<Registry>
|
||||
>({
|
||||
headers: new HttpHeaders({
|
||||
'x-total-count': [mockScanner1].length.toString(),
|
||||
}),
|
||||
body: [mockScanner1],
|
||||
});
|
||||
return of(response).pipe(delay(10));
|
||||
},
|
||||
updateScanner() {
|
||||
return of(true);
|
||||
},
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [
|
||||
ConfigurationScannerComponent,
|
||||
ScannerMetadataComponent,
|
||||
NewScannerModalComponent,
|
||||
NewScannerFormComponent,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ScannerService,
|
||||
useValue: fakedConfigScannerService,
|
||||
},
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfigurationScannerComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.newScannerDialog.saveBtnState = ClrLoadingState.LOADING;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should create', async () => {
|
||||
await fixture.whenStable();
|
||||
expect(component).toBeTruthy();
|
||||
expect(component.scanners.length).toBe(1);
|
||||
});
|
||||
it('should be clickable', () => {
|
||||
component.selectedRow = mockScanner1;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
let el: HTMLElement = fixture.nativeElement.querySelector('#set-default');
|
||||
expect(el.getAttribute('disable')).toBeFalsy();
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfigurationScannerComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.newScannerDialog.saveBtnState = ClrLoadingState.LOADING;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should create', async () => {
|
||||
await fixture.whenStable();
|
||||
expect(component).toBeTruthy();
|
||||
expect(component.scanners.length).toBe(1);
|
||||
});
|
||||
it('should be clickable', () => {
|
||||
component.selectedRow = mockScanner1;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
let el: HTMLElement =
|
||||
fixture.nativeElement.querySelector('#set-default');
|
||||
expect(el.getAttribute('disable')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
it('edit a scanner', () => {
|
||||
component.selectedRow = mockScanner1;
|
||||
component.editScanner();
|
||||
expect(component.newScannerDialog.opened).toBeTruthy();
|
||||
fixture.detectChanges();
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = 'test456';
|
||||
fixture.nativeElement.querySelector('#button-save').click();
|
||||
fixture.detectChanges();
|
||||
expect(component.newScannerDialog.opened).toBeFalsy();
|
||||
});
|
||||
});
|
||||
it('edit a scanner', () => {
|
||||
component.selectedRow = mockScanner1;
|
||||
component.editScanner();
|
||||
expect(component.newScannerDialog.opened).toBeTruthy();
|
||||
fixture.detectChanges();
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = 'test456';
|
||||
fixture.nativeElement.querySelector('#button-save').click();
|
||||
fixture.detectChanges();
|
||||
expect(component.newScannerDialog.opened).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,27 +1,34 @@
|
|||
import { Component, ViewChild, OnInit, OnDestroy } from "@angular/core";
|
||||
import { Scanner, SCANNERS_DOC } from "./scanner";
|
||||
import { NewScannerModalComponent } from "./new-scanner-modal/new-scanner-modal.component";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { MessageHandlerService } from "../../../../shared/services/message-handler.service";
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Scanner, SCANNERS_DOC } from './scanner';
|
||||
import { NewScannerModalComponent } from './new-scanner-modal/new-scanner-modal.component';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import {
|
||||
clone,
|
||||
getPageSizeFromLocalStorage,
|
||||
getSortingString,
|
||||
PageSizeMapKeys,
|
||||
setPageSizeToLocalStorage
|
||||
} from "../../../../shared/units/utils";
|
||||
import { ConfirmationDialogService } from "../../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../shared/entities/shared.const";
|
||||
import { ConfirmationMessage } from "../../../global-confirmation-dialog/confirmation-message";
|
||||
import { ScannerService } from "../../../../../../ng-swagger-gen/services/scanner.service";
|
||||
import { ClrDatagridStateInterface } from "@clr/angular";
|
||||
import { ScannerRegistrationReq } from "../../../../../../ng-swagger-gen/models/scanner-registration-req";
|
||||
setPageSizeToLocalStorage,
|
||||
} from '../../../../shared/units/utils';
|
||||
import { ConfirmationDialogService } from '../../../global-confirmation-dialog/confirmation-dialog.service';
|
||||
import {
|
||||
ConfirmationButtons,
|
||||
ConfirmationState,
|
||||
ConfirmationTargets,
|
||||
} from '../../../../shared/entities/shared.const';
|
||||
import { ConfirmationMessage } from '../../../global-confirmation-dialog/confirmation-message';
|
||||
import { ScannerService } from '../../../../../../ng-swagger-gen/services/scanner.service';
|
||||
import { ClrDatagridStateInterface } from '@clr/angular';
|
||||
import { ScannerRegistrationReq } from '../../../../../../ng-swagger-gen/models/scanner-registration-req';
|
||||
|
||||
@Component({
|
||||
selector: 'config-scanner',
|
||||
templateUrl: "config-scanner.component.html",
|
||||
styleUrls: ['./config-scanner.component.scss', '../../config/config.component.scss']
|
||||
templateUrl: 'config-scanner.component.html',
|
||||
styleUrls: [
|
||||
'./config-scanner.component.scss',
|
||||
'../../config/config.component.scss',
|
||||
],
|
||||
})
|
||||
export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
||||
scanners: Scanner[] = [];
|
||||
|
@ -32,32 +39,45 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
|||
deletionSubscription: any;
|
||||
scannerDocUrl: string = SCANNERS_DOC;
|
||||
page: number = 1;
|
||||
pageSize: number = getPageSizeFromLocalStorage(PageSizeMapKeys.SYSTEM_SCANNER_COMPONENT);
|
||||
pageSize: number = getPageSizeFromLocalStorage(
|
||||
PageSizeMapKeys.SYSTEM_SCANNER_COMPONENT
|
||||
);
|
||||
total: number = 0;
|
||||
state: ClrDatagridStateInterface;
|
||||
constructor(
|
||||
private configScannerService: ScannerService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private deletionDialogService: ConfirmationDialogService
|
||||
) {}
|
||||
ngOnInit() {
|
||||
if (!this.deletionSubscription) {
|
||||
this.deletionSubscription = this.deletionDialogService.confirmationConfirm$.subscribe(confirmed => {
|
||||
if (confirmed &&
|
||||
confirmed.source === ConfirmationTargets.SCANNER &&
|
||||
confirmed.state === ConfirmationState.CONFIRMED) {
|
||||
this.configScannerService.deleteScanner({
|
||||
registrationId: confirmed.data[0].uuid
|
||||
})
|
||||
.subscribe(response => {
|
||||
this.msgHandler.showSuccess("SCANNER.DELETE_SUCCESS");
|
||||
this.refresh();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
this.deletionSubscription =
|
||||
this.deletionDialogService.confirmationConfirm$.subscribe(
|
||||
confirmed => {
|
||||
if (
|
||||
confirmed &&
|
||||
confirmed.source === ConfirmationTargets.SCANNER &&
|
||||
confirmed.state === ConfirmationState.CONFIRMED
|
||||
) {
|
||||
this.configScannerService
|
||||
.deleteScanner({
|
||||
registrationId: confirmed.data[0].uuid,
|
||||
})
|
||||
.subscribe(
|
||||
response => {
|
||||
this.msgHandler.showSuccess(
|
||||
'SCANNER.DELETE_SUCCESS'
|
||||
);
|
||||
this.refresh();
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
|
@ -76,54 +96,75 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
|||
this.state = state;
|
||||
if (state && state.page) {
|
||||
this.pageSize = state.page.size;
|
||||
setPageSizeToLocalStorage(PageSizeMapKeys.SYSTEM_SCANNER_COMPONENT, this.pageSize);
|
||||
setPageSizeToLocalStorage(
|
||||
PageSizeMapKeys.SYSTEM_SCANNER_COMPONENT,
|
||||
this.pageSize
|
||||
);
|
||||
}
|
||||
let q: string;
|
||||
if (state && state.filters && state.filters.length) {
|
||||
q = encodeURIComponent(`${state.filters[0].property}=~${state.filters[0].value}`);
|
||||
q = encodeURIComponent(
|
||||
`${state.filters[0].property}=~${state.filters[0].value}`
|
||||
);
|
||||
}
|
||||
let sort: string;
|
||||
if (state && state.sort && state.sort.by) {
|
||||
sort = getSortingString(state);
|
||||
} else { // sort by creation_time desc by default
|
||||
sort = getSortingString(state);
|
||||
} else {
|
||||
// sort by creation_time desc by default
|
||||
sort = `-creation_time`;
|
||||
}
|
||||
this.onGoing = true;
|
||||
this.configScannerService.listScannersResponse({
|
||||
page: this.page,
|
||||
pageSize: this.pageSize,
|
||||
q: q,
|
||||
sort: sort
|
||||
})
|
||||
.pipe(finalize(() => this.onGoing = false))
|
||||
.subscribe(response => {
|
||||
// Get total count
|
||||
if (response.headers) {
|
||||
let xHeader: string = response.headers.get("X-Total-Count");
|
||||
if (xHeader) {
|
||||
this.total = parseInt(xHeader, 0);
|
||||
this.configScannerService
|
||||
.listScannersResponse({
|
||||
page: this.page,
|
||||
pageSize: this.pageSize,
|
||||
q: q,
|
||||
sort: sort,
|
||||
})
|
||||
.pipe(finalize(() => (this.onGoing = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
// Get total count
|
||||
if (response.headers) {
|
||||
let xHeader: string =
|
||||
response.headers.get('X-Total-Count');
|
||||
if (xHeader) {
|
||||
this.total = parseInt(xHeader, 0);
|
||||
}
|
||||
}
|
||||
this.scanners = response.body || [];
|
||||
this.getMetadataForAll();
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
this.scanners = response.body || [];
|
||||
this.getMetadataForAll();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
);
|
||||
}
|
||||
getMetadataForAll() {
|
||||
if (this.scanners && this.scanners.length > 0) {
|
||||
this.scanners.forEach((scanner, index) => {
|
||||
if (scanner.uuid ) {
|
||||
if (scanner.uuid) {
|
||||
this.scanners[index].loadingMetadata = true;
|
||||
this.configScannerService.getScannerMetadata({
|
||||
registrationId: scanner.uuid
|
||||
})
|
||||
.pipe(finalize(() => this.scanners[index].loadingMetadata = false))
|
||||
.subscribe(response => {
|
||||
this.scanners[index].metadata = response;
|
||||
}, error => {
|
||||
this.scanners[index].metadata = null;
|
||||
});
|
||||
this.configScannerService
|
||||
.getScannerMetadata({
|
||||
registrationId: scanner.uuid,
|
||||
})
|
||||
.pipe(
|
||||
finalize(
|
||||
() =>
|
||||
(this.scanners[index].loadingMetadata =
|
||||
false)
|
||||
)
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.scanners[index].metadata = response;
|
||||
},
|
||||
error => {
|
||||
this.scanners[index].metadata = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -141,40 +182,48 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
|||
if (this.selectedRow) {
|
||||
let scanner: ScannerRegistrationReq = clone(this.selectedRow);
|
||||
scanner.disabled = !scanner.disabled;
|
||||
this.configScannerService.updateScanner({
|
||||
registrationId: this.selectedRow.uuid,
|
||||
registration: scanner
|
||||
})
|
||||
.subscribe(response => {
|
||||
this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS");
|
||||
this.refresh();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
this.configScannerService
|
||||
.updateScanner({
|
||||
registrationId: this.selectedRow.uuid,
|
||||
registration: scanner,
|
||||
})
|
||||
.subscribe(
|
||||
response => {
|
||||
this.msgHandler.showSuccess('SCANNER.UPDATE_SUCCESS');
|
||||
this.refresh();
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
setAsDefault() {
|
||||
if (this.selectedRow) {
|
||||
this.configScannerService.setScannerAsDefault({
|
||||
registrationId: this.selectedRow.uuid,
|
||||
payload: {
|
||||
is_default: true
|
||||
}
|
||||
})
|
||||
.subscribe(response => {
|
||||
this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS");
|
||||
this.refresh();
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
this.configScannerService
|
||||
.setScannerAsDefault({
|
||||
registrationId: this.selectedRow.uuid,
|
||||
payload: {
|
||||
is_default: true,
|
||||
},
|
||||
})
|
||||
.subscribe(
|
||||
response => {
|
||||
this.msgHandler.showSuccess('SCANNER.UPDATE_SUCCESS');
|
||||
this.refresh();
|
||||
},
|
||||
error => {
|
||||
this.errorHandler.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
deleteScanners() {
|
||||
if (this.selectedRow) {
|
||||
// Confirm deletion
|
||||
let msg: ConfirmationMessage = new ConfirmationMessage(
|
||||
"SCANNER.CONFIRM_DELETION",
|
||||
"SCANNER.DELETION_SUMMARY",
|
||||
'SCANNER.CONFIRM_DELETION',
|
||||
'SCANNER.DELETION_SUMMARY',
|
||||
this.selectedRow.name,
|
||||
[this.selectedRow],
|
||||
ConfirmationTargets.SCANNER,
|
||||
|
@ -194,31 +243,36 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy {
|
|||
resetValue['useInner'] = this.selectedRow.use_internal_addr;
|
||||
if (this.selectedRow.auth === 'Basic') {
|
||||
resetValue['auth'] = 'Basic';
|
||||
let username: string = this.selectedRow.access_credential.split(":")[0];
|
||||
let password: string = this.selectedRow.access_credential.split(":")[1];
|
||||
let username: string =
|
||||
this.selectedRow.access_credential.split(':')[0];
|
||||
let password: string =
|
||||
this.selectedRow.access_credential.split(':')[1];
|
||||
resetValue['accessCredential'] = {
|
||||
username: username,
|
||||
password: password
|
||||
password: password,
|
||||
};
|
||||
} else if (this.selectedRow.auth === 'Bearer') {
|
||||
resetValue['auth'] = 'Bearer';
|
||||
resetValue['accessCredential'] = {
|
||||
token: this.selectedRow.access_credential
|
||||
token: this.selectedRow.access_credential,
|
||||
};
|
||||
} else if (this.selectedRow.auth === 'APIKey') {
|
||||
resetValue['auth'] = 'APIKey';
|
||||
resetValue['accessCredential'] = {
|
||||
apiKey: this.selectedRow.access_credential
|
||||
apiKey: this.selectedRow.access_credential,
|
||||
};
|
||||
} else {
|
||||
resetValue['auth'] = 'None';
|
||||
}
|
||||
this.newScannerDialog.newScannerFormComponent.newScannerForm.reset(resetValue);
|
||||
this.newScannerDialog.newScannerFormComponent.newScannerForm.reset(
|
||||
resetValue
|
||||
);
|
||||
this.newScannerDialog.isEdit = true;
|
||||
this.newScannerDialog.newScannerFormComponent.isEdit = true;
|
||||
this.newScannerDialog.uid = this.selectedRow.uuid;
|
||||
this.newScannerDialog.originValue = clone(resetValue);
|
||||
this.newScannerDialog.newScannerFormComponent.originValue = clone(resetValue);
|
||||
this.newScannerDialog.newScannerFormComponent.originValue =
|
||||
clone(resetValue);
|
||||
this.newScannerDialog.editScanner = clone(this.selectedRow);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,132 +1,248 @@
|
|||
<div>
|
||||
<form [formGroup]="newScannerForm" class="clr-form clr-form-horizontal">
|
||||
<form [formGroup]="newScannerForm" class="clr-form clr-form-horizontal">
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label">{{"SCANNER.NAME" | translate}}</label>
|
||||
<label class="required clr-control-label">{{
|
||||
'SCANNER.NAME' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!isNameValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input autocomplete="off" #name formControlName="name" class="clr-input width-280"
|
||||
type="text"
|
||||
id="scanner-name">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkOnGoing"></span>
|
||||
<input
|
||||
autocomplete="off"
|
||||
#name
|
||||
formControlName="name"
|
||||
class="clr-input width-280"
|
||||
type="text"
|
||||
id="scanner-name" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
<span
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="!checkOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isNameValid">
|
||||
<span id="name-error">{{nameTooltip | translate}}</span>
|
||||
<span id="name-error">{{ nameTooltip | translate }}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{"SCANNER.DESCRIPTION" | translate}}</label>
|
||||
<label class="clr-control-label">{{
|
||||
'SCANNER.DESCRIPTION' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container">
|
||||
<textarea autocomplete="off" formControlName="description" class="clr-textarea width-280" type="text"
|
||||
id="description">
|
||||
<textarea
|
||||
autocomplete="off"
|
||||
formControlName="description"
|
||||
class="clr-textarea width-280"
|
||||
type="text"
|
||||
id="description">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control">
|
||||
<label class="required clr-control-label">{{"SCANNER.ENDPOINT" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!isEndpointValid || showEndpointError">
|
||||
<label class="required clr-control-label">{{
|
||||
'SCANNER.ENDPOINT' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="!isEndpointValid || showEndpointError">
|
||||
<div class="clr-input-wrapper">
|
||||
<input (focus)="showEndpointError=false" (blur)="checkEndpointUrl()" #endpointUrl placeholder="http(s)://192.168.1.1" autocomplete="off" formControlName="url"
|
||||
class="clr-input width-280" type="text" id="scanner-endpoint">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="!checkEndpointOnGoing"></span>
|
||||
<input
|
||||
(focus)="showEndpointError = false"
|
||||
(blur)="checkEndpointUrl()"
|
||||
#endpointUrl
|
||||
placeholder="http(s)://192.168.1.1"
|
||||
autocomplete="off"
|
||||
formControlName="url"
|
||||
class="clr-input width-280"
|
||||
type="text"
|
||||
id="scanner-endpoint" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
<span
|
||||
class="spinner spinner-inline"
|
||||
[hidden]="!checkEndpointOnGoing"></span>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isEndpointValid || showEndpointError">
|
||||
<span id="endpoint-error">{{endpointTooltip | translate}}</span>
|
||||
<clr-control-error
|
||||
*ngIf="!isEndpointValid || showEndpointError">
|
||||
<span id="endpoint-error">{{
|
||||
endpointTooltip | translate
|
||||
}}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{"SCANNER.AUTH" | translate}}</label>
|
||||
<label class="clr-control-label">{{
|
||||
'SCANNER.AUTH' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-select-wrapper">
|
||||
<select formControlName="auth" class="clr-select width-280" id="scanner-authorization">
|
||||
<option value="None">{{"SCANNER.NONE" | translate}}</option>
|
||||
<option value="Basic">{{"SCANNER.BASIC" | translate}}</option>
|
||||
<option value="Bearer">{{"SCANNER.BEARER" | translate}}</option>
|
||||
<option value="APIKey">{{"SCANNER.API_KEY" | translate}}</option>
|
||||
<select
|
||||
formControlName="auth"
|
||||
class="clr-select width-280"
|
||||
id="scanner-authorization">
|
||||
<option value="None">
|
||||
{{ 'SCANNER.NONE' | translate }}
|
||||
</option>
|
||||
<option value="Basic">
|
||||
{{ 'SCANNER.BASIC' | translate }}
|
||||
</option>
|
||||
<option value="Bearer">
|
||||
{{ 'SCANNER.BEARER' | translate }}
|
||||
</option>
|
||||
<option value="APIKey">
|
||||
{{ 'SCANNER.API_KEY' | translate }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container formGroupName="accessCredential">
|
||||
<div class="clr-form-control" *ngIf="auth==='Basic'">
|
||||
<label class="required clr-control-label">{{"SCANNER.USERNAME" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!isUserNameValid">
|
||||
<div class="clr-form-control" *ngIf="auth === 'Basic'">
|
||||
<label class="required clr-control-label">{{
|
||||
'SCANNER.USERNAME' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="!isUserNameValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="username" autocomplete="off"
|
||||
class="clr-input width-280" type="text" id="scanner-username">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<input
|
||||
formControlName="username"
|
||||
autocomplete="off"
|
||||
class="clr-input width-280"
|
||||
type="text"
|
||||
id="scanner-username" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isUserNameValid">
|
||||
{{"SCANNER.USERNAME_REQUIRED" | translate}}
|
||||
{{ 'SCANNER.USERNAME_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" *ngIf="auth==='Basic'">
|
||||
<label class="required clr-control-label">{{"SCANNER.PASSWORD" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!isPasswordValid">
|
||||
<div class="clr-form-control" *ngIf="auth === 'Basic'">
|
||||
<label class="required clr-control-label">{{
|
||||
'SCANNER.PASSWORD' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="!isPasswordValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="password" autocomplete="off"
|
||||
class="clr-input width-280" type="password" id="scanner-password">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<input
|
||||
formControlName="password"
|
||||
autocomplete="off"
|
||||
class="clr-input width-280"
|
||||
type="password"
|
||||
id="scanner-password" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isPasswordValid">
|
||||
<span id="pwd-error">{{"SCANNER.PASSWORD_REQUIRED" | translate}}</span>
|
||||
<span id="pwd-error">{{
|
||||
'SCANNER.PASSWORD_REQUIRED' | translate
|
||||
}}</span>
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" *ngIf="auth==='Bearer'">
|
||||
<label class="required clr-control-label">{{"SCANNER.TOKEN" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!isTokenValid">
|
||||
<div class="clr-form-control" *ngIf="auth === 'Bearer'">
|
||||
<label class="required clr-control-label">{{
|
||||
'SCANNER.TOKEN' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="!isTokenValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="token" autocomplete="off"
|
||||
class="clr-input width-280" type="text" id="scanner-token">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<input
|
||||
formControlName="token"
|
||||
autocomplete="off"
|
||||
class="clr-input width-280"
|
||||
type="text"
|
||||
id="scanner-token" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isTokenValid">
|
||||
{{"SCANNER.TOKEN_REQUIRED" | translate}}
|
||||
{{ 'SCANNER.TOKEN_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-form-control" *ngIf="auth==='APIKey'">
|
||||
<label class="required clr-control-label">{{"SCANNER.API_KEY" | translate}}</label>
|
||||
<div class="clr-control-container" [class.clr-error]="!isApiKeyValid">
|
||||
<div class="clr-form-control" *ngIf="auth === 'APIKey'">
|
||||
<label class="required clr-control-label">{{
|
||||
'SCANNER.API_KEY' | translate
|
||||
}}</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="!isApiKeyValid">
|
||||
<div class="clr-input-wrapper">
|
||||
<input formControlName="apiKey" autocomplete="off"
|
||||
class="clr-input width-280" type="text" id="scanner-apiKey">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
<input
|
||||
formControlName="apiKey"
|
||||
autocomplete="off"
|
||||
class="clr-input width-280"
|
||||
type="text"
|
||||
id="scanner-apiKey" />
|
||||
<clr-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
</div>
|
||||
<clr-control-error *ngIf="!isApiKeyValid">
|
||||
{{"SCANNER.API_KEY_REQUIRED" | translate}}
|
||||
{{ 'SCANNER.API_KEY_REQUIRED' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{"SCANNER.OPTIONS" | translate}}</label>
|
||||
<label class="clr-control-label">{{
|
||||
'SCANNER.OPTIONS' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container padding-top-3">
|
||||
<clr-checkbox-wrapper>
|
||||
<input name="scanner-skipCertVerify" clrCheckbox formControlName="skipCertVerify"
|
||||
type="checkbox" id="scanner-skipCertVerify">
|
||||
<label for="scanner-skipCertVerify">{{"SCANNER.SKIP" | translate}}
|
||||
<input
|
||||
name="scanner-skipCertVerify"
|
||||
clrCheckbox
|
||||
formControlName="skipCertVerify"
|
||||
type="checkbox"
|
||||
id="scanner-skipCertVerify" />
|
||||
<label for="scanner-skipCertVerify"
|
||||
>{{ 'SCANNER.SKIP' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon class="color-57" clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen>
|
||||
{{'SCANNER.SKIP_CERT_VERIFY' | translate}}
|
||||
<clr-icon
|
||||
class="color-57"
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-left"
|
||||
clrSize="md"
|
||||
*clrIfOpen>
|
||||
{{ 'SCANNER.SKIP_CERT_VERIFY' | translate }}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
</clr-checkbox-wrapper>
|
||||
<clr-checkbox-wrapper>
|
||||
<input name="scanner-use-inner" clrCheckbox formControlName="useInner"
|
||||
type="checkbox" id="scanner-use-inner">
|
||||
<label for="scanner-use-inner">{{"SCANNER.USE_INNER" | translate}}
|
||||
<input
|
||||
name="scanner-use-inner"
|
||||
clrCheckbox
|
||||
formControlName="useInner"
|
||||
type="checkbox"
|
||||
id="scanner-use-inner" />
|
||||
<label for="scanner-use-inner"
|
||||
>{{ 'SCANNER.USE_INNER' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon class="color-57" clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-left" clrSize="md" *clrIfOpen>
|
||||
{{"SCANNER.USE_INNER_TIP" | translate}}
|
||||
<clr-icon
|
||||
class="color-57"
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="top-left"
|
||||
clrSize="md"
|
||||
*clrIfOpen>
|
||||
{{ 'SCANNER.USE_INNER_TIP' | translate }}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
|
|
|
@ -1,121 +1,134 @@
|
|||
import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { NewScannerFormComponent } from "./new-scanner-form.component";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { SharedTestingModule } from "../../../../../shared/shared.module";
|
||||
import { of } from "rxjs";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service";
|
||||
import {
|
||||
ComponentFixture,
|
||||
ComponentFixtureAutoDetect,
|
||||
fakeAsync,
|
||||
TestBed,
|
||||
tick,
|
||||
} from '@angular/core/testing';
|
||||
import { NewScannerFormComponent } from './new-scanner-form.component';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ClarityModule } from '@clr/angular';
|
||||
import { SharedTestingModule } from '../../../../../shared/shared.module';
|
||||
import { of } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { ScannerService } from '../../../../../../../ng-swagger-gen/services/scanner.service';
|
||||
|
||||
describe('NewScannerFormComponent', () => {
|
||||
let mockScanner1 = {
|
||||
name: 'test1',
|
||||
description: 'just a sample',
|
||||
version: '1.0.0',
|
||||
url: 'http://168.0.0.1'
|
||||
};
|
||||
let component: NewScannerFormComponent;
|
||||
let fixture: ComponentFixture<NewScannerFormComponent>;
|
||||
let fakedConfigScannerService = {
|
||||
listScanners() {
|
||||
return of([mockScanner1]).pipe(delay(500));
|
||||
},
|
||||
getScannersByEndpointUrl() {
|
||||
return of([mockScanner1]).pipe(delay(500));
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
],
|
||||
declarations: [ NewScannerFormComponent ],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
TranslateService,
|
||||
{ provide: ScannerService, useValue: fakedConfigScannerService },
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
]
|
||||
let mockScanner1 = {
|
||||
name: 'test1',
|
||||
description: 'just a sample',
|
||||
version: '1.0.0',
|
||||
url: 'http://168.0.0.1',
|
||||
};
|
||||
let component: NewScannerFormComponent;
|
||||
let fixture: ComponentFixture<NewScannerFormComponent>;
|
||||
let fakedConfigScannerService = {
|
||||
listScanners() {
|
||||
return of([mockScanner1]).pipe(delay(500));
|
||||
},
|
||||
getScannersByEndpointUrl() {
|
||||
return of([mockScanner1]).pipe(delay(500));
|
||||
},
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
BrowserAnimationsModule,
|
||||
ClarityModule,
|
||||
],
|
||||
declarations: [NewScannerFormComponent],
|
||||
providers: [
|
||||
FormBuilder,
|
||||
TranslateService,
|
||||
{
|
||||
provide: ScannerService,
|
||||
useValue: fakedConfigScannerService,
|
||||
},
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewScannerFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should creat', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should show "name is required"', () => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('name should be existed', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test1";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
tick(20000);
|
||||
const el = fixture.nativeElement.querySelector('#name-error');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(el).toBeTruthy();
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewScannerFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
it('name should be valid', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test2";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
tick(20000);
|
||||
const el = fixture.nativeElement.querySelector('#name-error');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(el).toBeFalsy();
|
||||
it('should creat', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
it('should show "name is required"', () => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = '';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
let el = fixture.nativeElement.querySelector('clr-control-error');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('name should be existed', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = 'test1';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
tick(20000);
|
||||
const el = fixture.nativeElement.querySelector('#name-error');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
it('name should be valid', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = 'test2';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
nameInput.blur();
|
||||
nameInput.dispatchEvent(new Event('blur'));
|
||||
tick(20000);
|
||||
const el = fixture.nativeElement.querySelector('#name-error');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(el).toBeFalsy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('endpoint url should be valid', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test2";
|
||||
let urlInput = fixture.nativeElement.querySelector('#scanner-endpoint');
|
||||
urlInput.value = "http://168.0.0.2";
|
||||
urlInput.dispatchEvent(new Event('input'));
|
||||
urlInput.blur();
|
||||
urlInput.dispatchEvent(new Event('blur'));
|
||||
tick(20000);
|
||||
const el = fixture.nativeElement.querySelector('#endpoint-error');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(el).toBeFalsy();
|
||||
});
|
||||
}));
|
||||
it('endpoint url should be valid', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = 'test2';
|
||||
let urlInput = fixture.nativeElement.querySelector('#scanner-endpoint');
|
||||
urlInput.value = 'http://168.0.0.2';
|
||||
urlInput.dispatchEvent(new Event('input'));
|
||||
urlInput.blur();
|
||||
urlInput.dispatchEvent(new Event('blur'));
|
||||
tick(20000);
|
||||
const el = fixture.nativeElement.querySelector('#endpoint-error');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(el).toBeFalsy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('auth should be valid', () => {
|
||||
let authInput = fixture.nativeElement.querySelector('#scanner-authorization');
|
||||
authInput.value = "Basic";
|
||||
authInput.dispatchEvent(new Event('change'));
|
||||
let usernameInput = fixture.nativeElement.querySelector('#scanner-username');
|
||||
let passwordInput = fixture.nativeElement.querySelector('#scanner-password');
|
||||
expect(usernameInput).toBeTruthy();
|
||||
expect(passwordInput).toBeTruthy();
|
||||
usernameInput.value = "user";
|
||||
passwordInput.value = "12345";
|
||||
usernameInput.dispatchEvent(new Event('input'));
|
||||
passwordInput.dispatchEvent(new Event('input'));
|
||||
let el = fixture.nativeElement.querySelector('#pwd-error');
|
||||
expect(el).toBeFalsy();
|
||||
});
|
||||
it('auth should be valid', () => {
|
||||
let authInput = fixture.nativeElement.querySelector(
|
||||
'#scanner-authorization'
|
||||
);
|
||||
authInput.value = 'Basic';
|
||||
authInput.dispatchEvent(new Event('change'));
|
||||
let usernameInput =
|
||||
fixture.nativeElement.querySelector('#scanner-username');
|
||||
let passwordInput =
|
||||
fixture.nativeElement.querySelector('#scanner-password');
|
||||
expect(usernameInput).toBeTruthy();
|
||||
expect(passwordInput).toBeTruthy();
|
||||
usernameInput.value = 'user';
|
||||
passwordInput.value = '12345';
|
||||
usernameInput.dispatchEvent(new Event('input'));
|
||||
passwordInput.dispatchEvent(new Event('input'));
|
||||
let el = fixture.nativeElement.querySelector('#pwd-error');
|
||||
expect(el).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,38 +3,43 @@ import {
|
|||
Component,
|
||||
ElementRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { fromEvent } from "rxjs";
|
||||
import { debounceTime, distinctUntilChanged, filter, finalize, map, switchMap } from "rxjs/operators";
|
||||
import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service";
|
||||
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { fromEvent } from 'rxjs';
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
finalize,
|
||||
map,
|
||||
switchMap,
|
||||
} from 'rxjs/operators';
|
||||
import { ScannerService } from '../../../../../../../ng-swagger-gen/services/scanner.service';
|
||||
|
||||
@Component({
|
||||
selector: 'new-scanner-form',
|
||||
templateUrl: 'new-scanner-form.component.html',
|
||||
styleUrls: ['new-scanner-form.component.scss']
|
||||
styleUrls: ['new-scanner-form.component.scss'],
|
||||
})
|
||||
export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
export class NewScannerFormComponent implements AfterViewInit, OnDestroy {
|
||||
checkOnGoing: boolean = false;
|
||||
newScannerForm: FormGroup = this.fb.group({
|
||||
name: this.fb.control("",
|
||||
[Validators.required]),
|
||||
description: this.fb.control(""),
|
||||
url: this.fb.control("",
|
||||
[Validators.required,
|
||||
Validators.pattern(/^http[s]?:\/\//)]),
|
||||
auth: this.fb.control(""),
|
||||
name: this.fb.control('', [Validators.required]),
|
||||
description: this.fb.control(''),
|
||||
url: this.fb.control('', [
|
||||
Validators.required,
|
||||
Validators.pattern(/^http[s]?:\/\//),
|
||||
]),
|
||||
auth: this.fb.control(''),
|
||||
accessCredential: this.fb.group({
|
||||
username: this.fb.control("", Validators.required),
|
||||
password: this.fb.control("", Validators.required),
|
||||
token: this.fb.control("", Validators.required),
|
||||
apiKey: this.fb.control("", Validators.required)
|
||||
username: this.fb.control('', Validators.required),
|
||||
password: this.fb.control('', Validators.required),
|
||||
token: this.fb.control('', Validators.required),
|
||||
apiKey: this.fb.control('', Validators.required),
|
||||
}),
|
||||
skipCertVerify: this.fb.control(false),
|
||||
useInner: this.fb.control(false)
|
||||
useInner: this.fb.control(false),
|
||||
});
|
||||
checkNameSubscribe: any;
|
||||
checkEndpointUrlSubscribe: any;
|
||||
|
@ -48,75 +53,118 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
|||
isEdit: boolean;
|
||||
@ViewChild('name') scannerName: ElementRef;
|
||||
@ViewChild('endpointUrl') scannerEndpointUrl: ElementRef;
|
||||
constructor(private fb: FormBuilder, private scannerService: ScannerService) {
|
||||
}
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private scannerService: ScannerService
|
||||
) {}
|
||||
ngAfterViewInit(): void {
|
||||
if (!this.checkNameSubscribe) {
|
||||
this.checkNameSubscribe = fromEvent(this.scannerName.nativeElement, 'input').pipe(
|
||||
map((e: any) => e.target.value),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
if (this.isEdit && this.originValue && this.originValue.name === name) {
|
||||
return false;
|
||||
}
|
||||
return this.newScannerForm.get('name').valid && name.length > 0;
|
||||
}),
|
||||
switchMap((name) => {
|
||||
this.isNameExisting = false;
|
||||
this.checkOnGoing = true;
|
||||
return this.scannerService.listScanners({
|
||||
q: encodeURIComponent(`name=${name}`)
|
||||
})
|
||||
.pipe(finalize(() => this.checkOnGoing = false));
|
||||
})).subscribe(response => {
|
||||
if (response && response.length > 0) {
|
||||
response.forEach(s => {
|
||||
if (s.name === this.newScannerForm.get('name').value) {
|
||||
this.isNameExisting = true;
|
||||
return;
|
||||
this.checkNameSubscribe = fromEvent(
|
||||
this.scannerName.nativeElement,
|
||||
'input'
|
||||
)
|
||||
.pipe(
|
||||
map((e: any) => e.target.value),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter(name => {
|
||||
if (
|
||||
this.isEdit &&
|
||||
this.originValue &&
|
||||
this.originValue.name === name
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, error => {
|
||||
this.isNameExisting = false;
|
||||
});
|
||||
return (
|
||||
this.newScannerForm.get('name').valid &&
|
||||
name.length > 0
|
||||
);
|
||||
}),
|
||||
switchMap(name => {
|
||||
this.isNameExisting = false;
|
||||
this.checkOnGoing = true;
|
||||
return this.scannerService
|
||||
.listScanners({
|
||||
q: encodeURIComponent(`name=${name}`),
|
||||
})
|
||||
.pipe(finalize(() => (this.checkOnGoing = false)));
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
if (response && response.length > 0) {
|
||||
response.forEach(s => {
|
||||
if (
|
||||
s.name ===
|
||||
this.newScannerForm.get('name').value
|
||||
) {
|
||||
this.isNameExisting = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.isNameExisting = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!this.checkEndpointUrlSubscribe) {
|
||||
this.checkEndpointUrlSubscribe = fromEvent(this.scannerEndpointUrl.nativeElement, 'input').pipe(
|
||||
map((e: any) => e.target.value),
|
||||
debounceTime(800),
|
||||
distinctUntilChanged(),
|
||||
filter(endpointUrl => {
|
||||
if (this.isEdit && this.originValue && this.originValue.url === endpointUrl) {
|
||||
return false;
|
||||
}
|
||||
return this.newScannerForm.get('url').valid && endpointUrl.length > 6;
|
||||
}),
|
||||
switchMap((endpointUrl) => {
|
||||
this.isEndpointUrlExisting = false;
|
||||
this.checkEndpointOnGoing = true;
|
||||
return this.scannerService.listScanners({
|
||||
q: encodeURIComponent(`url=${endpointUrl}`)
|
||||
})
|
||||
.pipe(finalize(() => this.checkEndpointOnGoing = false));
|
||||
})).subscribe(response => {
|
||||
if (response && response.length > 0) {
|
||||
response.forEach(s => {
|
||||
if (s.url === this.newScannerForm.get('url').value) {
|
||||
this.isEndpointUrlExisting = true;
|
||||
return;
|
||||
this.checkEndpointUrlSubscribe = fromEvent(
|
||||
this.scannerEndpointUrl.nativeElement,
|
||||
'input'
|
||||
)
|
||||
.pipe(
|
||||
map((e: any) => e.target.value),
|
||||
debounceTime(800),
|
||||
distinctUntilChanged(),
|
||||
filter(endpointUrl => {
|
||||
if (
|
||||
this.isEdit &&
|
||||
this.originValue &&
|
||||
this.originValue.url === endpointUrl
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, error => {
|
||||
this.isEndpointUrlExisting = false;
|
||||
});
|
||||
return (
|
||||
this.newScannerForm.get('url').valid &&
|
||||
endpointUrl.length > 6
|
||||
);
|
||||
}),
|
||||
switchMap(endpointUrl => {
|
||||
this.isEndpointUrlExisting = false;
|
||||
this.checkEndpointOnGoing = true;
|
||||
return this.scannerService
|
||||
.listScanners({
|
||||
q: encodeURIComponent(`url=${endpointUrl}`),
|
||||
})
|
||||
.pipe(
|
||||
finalize(
|
||||
() => (this.checkEndpointOnGoing = false)
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
if (response && response.length > 0) {
|
||||
response.forEach(s => {
|
||||
if (
|
||||
s.url ===
|
||||
this.newScannerForm.get('url').value
|
||||
) {
|
||||
this.isEndpointUrlExisting = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.isEndpointUrlExisting = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.checkNameSubscribe) {
|
||||
this.checkNameSubscribe.unsubscribe();
|
||||
|
@ -128,7 +176,12 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
|||
}
|
||||
}
|
||||
get isNameValid(): boolean {
|
||||
if (!(this.newScannerForm.get('name').dirty || this.newScannerForm.get('name').touched)) {
|
||||
if (
|
||||
!(
|
||||
this.newScannerForm.get('name').dirty ||
|
||||
this.newScannerForm.get('name').touched
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.checkOnGoing) {
|
||||
|
@ -138,18 +191,29 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
|||
this.nameTooltip = 'SCANNER.NAME_EXISTS';
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerForm.get('name').errors && this.newScannerForm.get('name').errors.required) {
|
||||
if (
|
||||
this.newScannerForm.get('name').errors &&
|
||||
this.newScannerForm.get('name').errors.required
|
||||
) {
|
||||
this.nameTooltip = 'SCANNER.NAME_REQUIRED';
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerForm.get('name').errors && this.newScannerForm.get('name').errors.pattern) {
|
||||
if (
|
||||
this.newScannerForm.get('name').errors &&
|
||||
this.newScannerForm.get('name').errors.pattern
|
||||
) {
|
||||
this.nameTooltip = 'SCANNER.NAME_REX';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
get isEndpointValid(): boolean {
|
||||
if (!(this.newScannerForm.get('url').dirty || this.newScannerForm.get('url').touched)) {
|
||||
if (
|
||||
!(
|
||||
this.newScannerForm.get('url').dirty ||
|
||||
this.newScannerForm.get('url').touched
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.checkEndpointOnGoing) {
|
||||
|
@ -159,20 +223,29 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
|||
this.endpointTooltip = 'SCANNER.ENDPOINT_EXISTS';
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerForm.get('url').errors && this.newScannerForm.get('url').errors.required) {
|
||||
if (
|
||||
this.newScannerForm.get('url').errors &&
|
||||
this.newScannerForm.get('url').errors.required
|
||||
) {
|
||||
this.endpointTooltip = 'SCANNER.ENDPOINT_REQUIRED';
|
||||
return false;
|
||||
}
|
||||
// skip here, validate when onblur
|
||||
if (this.newScannerForm.get('url').errors && this.newScannerForm.get('url').errors.pattern) {
|
||||
return true;
|
||||
if (
|
||||
this.newScannerForm.get('url').errors &&
|
||||
this.newScannerForm.get('url').errors.pattern
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// validate endpointUrl when onblur
|
||||
checkEndpointUrl() {
|
||||
if (this.newScannerForm.get('url').errors && this.newScannerForm.get('url').errors.pattern) {
|
||||
this.endpointTooltip = "SCANNER.ILLEGAL_ENDPOINT";
|
||||
if (
|
||||
this.newScannerForm.get('url').errors &&
|
||||
this.newScannerForm.get('url').errors.pattern
|
||||
) {
|
||||
this.endpointTooltip = 'SCANNER.ILLEGAL_ENDPOINT';
|
||||
this.showEndpointError = true;
|
||||
}
|
||||
}
|
||||
|
@ -180,23 +253,39 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro
|
|||
return this.newScannerForm.get('auth').value;
|
||||
}
|
||||
get isUserNameValid(): boolean {
|
||||
return !(this.newScannerForm.get('accessCredential').get('username').invalid
|
||||
&& (this.newScannerForm.get('accessCredential').get('username').dirty
|
||||
|| this.newScannerForm.get('accessCredential').get('username').touched));
|
||||
return !(
|
||||
this.newScannerForm.get('accessCredential').get('username')
|
||||
.invalid &&
|
||||
(this.newScannerForm.get('accessCredential').get('username')
|
||||
.dirty ||
|
||||
this.newScannerForm.get('accessCredential').get('username')
|
||||
.touched)
|
||||
);
|
||||
}
|
||||
get isPasswordValid(): boolean {
|
||||
return !(this.newScannerForm.get('accessCredential').get('password').invalid
|
||||
&& (this.newScannerForm.get('accessCredential').get('password').dirty
|
||||
|| this.newScannerForm.get('accessCredential').get('password').touched));
|
||||
return !(
|
||||
this.newScannerForm.get('accessCredential').get('password')
|
||||
.invalid &&
|
||||
(this.newScannerForm.get('accessCredential').get('password')
|
||||
.dirty ||
|
||||
this.newScannerForm.get('accessCredential').get('password')
|
||||
.touched)
|
||||
);
|
||||
}
|
||||
get isTokenValid(): boolean {
|
||||
return !(this.newScannerForm.get('accessCredential').get('token').invalid
|
||||
&& (this.newScannerForm.get('accessCredential').get('token').dirty
|
||||
|| this.newScannerForm.get('accessCredential').get('token').touched));
|
||||
return !(
|
||||
this.newScannerForm.get('accessCredential').get('token').invalid &&
|
||||
(this.newScannerForm.get('accessCredential').get('token').dirty ||
|
||||
this.newScannerForm.get('accessCredential').get('token')
|
||||
.touched)
|
||||
);
|
||||
}
|
||||
get isApiKeyValid(): boolean {
|
||||
return !(this.newScannerForm.get('accessCredential').get('apiKey').invalid
|
||||
&& (this.newScannerForm.get('accessCredential').get('apiKey').dirty
|
||||
|| this.newScannerForm.get('accessCredential').get('apiKey').touched));
|
||||
return !(
|
||||
this.newScannerForm.get('accessCredential').get('apiKey').invalid &&
|
||||
(this.newScannerForm.get('accessCredential').get('apiKey').dirty ||
|
||||
this.newScannerForm.get('accessCredential').get('apiKey')
|
||||
.touched)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,53 @@
|
|||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalClosable]="false">
|
||||
<h3 *ngIf="!isEdit" class="modal-title">{{'SCANNER.ADD_SCANNER' | translate}}</h3>
|
||||
<h3 *ngIf="isEdit" class="modal-title">{{'SCANNER.EDIT_SCANNER' | translate}}</h3>
|
||||
<clr-modal
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalStaticBackdrop]="true"
|
||||
[clrModalClosable]="false">
|
||||
<h3 *ngIf="!isEdit" class="modal-title">
|
||||
{{ 'SCANNER.ADD_SCANNER' | translate }}
|
||||
</h3>
|
||||
<h3 *ngIf="isEdit" class="modal-title">
|
||||
{{ 'SCANNER.EDIT_SCANNER' | translate }}
|
||||
</h3>
|
||||
<div class="modal-body body-format">
|
||||
<inline-alert class="modal-title"></inline-alert>
|
||||
<new-scanner-form></new-scanner-form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="button-test" type="button" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="!canTestEndpoint">{{'SCANNER.TEST_CONNECTION' | translate}}</button>
|
||||
<button id="button-cancel" type="button" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button id="button-add" *ngIf="!isEdit" type="button" [clrLoading]="saveBtnState" class="btn btn-primary" [disabled]="!valid" (click)="create()">{{'BUTTON.ADD' | translate}}</button>
|
||||
<button id="button-save" *ngIf="isEdit" type="button" [clrLoading]="saveBtnState" class="btn btn-primary" [disabled]="!validForSaving" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button
|
||||
id="button-test"
|
||||
type="button"
|
||||
[clrLoading]="checkBtnState"
|
||||
class="btn btn-outline"
|
||||
(click)="onTestEndpoint()"
|
||||
[disabled]="!canTestEndpoint">
|
||||
{{ 'SCANNER.TEST_CONNECTION' | translate }}
|
||||
</button>
|
||||
<button
|
||||
id="button-cancel"
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
(click)="close()">
|
||||
{{ 'BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
id="button-add"
|
||||
*ngIf="!isEdit"
|
||||
type="button"
|
||||
[clrLoading]="saveBtnState"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!valid"
|
||||
(click)="create()">
|
||||
{{ 'BUTTON.ADD' | translate }}
|
||||
</button>
|
||||
<button
|
||||
id="button-save"
|
||||
*ngIf="isEdit"
|
||||
type="button"
|
||||
[clrLoading]="saveBtnState"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!validForSaving"
|
||||
(click)="save()">
|
||||
{{ 'BUTTON.SAVE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
|
|
@ -1,158 +1,187 @@
|
|||
import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import { NewScannerModalComponent } from "./new-scanner-modal.component";
|
||||
import { MessageHandlerService } from "../../../../../shared/services/message-handler.service";
|
||||
import { NewScannerFormComponent } from "../new-scanner-form/new-scanner-form.component";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { of, Subscription } from "rxjs";
|
||||
import { delay } from "rxjs/operators";
|
||||
import { SharedTestingModule } from "../../../../../shared/shared.module";
|
||||
import { Scanner } from "../scanner";
|
||||
import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service";
|
||||
import {
|
||||
ComponentFixture,
|
||||
ComponentFixtureAutoDetect,
|
||||
fakeAsync,
|
||||
TestBed,
|
||||
tick,
|
||||
} from '@angular/core/testing';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
import { NewScannerModalComponent } from './new-scanner-modal.component';
|
||||
import { MessageHandlerService } from '../../../../../shared/services/message-handler.service';
|
||||
import { NewScannerFormComponent } from '../new-scanner-form/new-scanner-form.component';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { of, Subscription } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
import { SharedTestingModule } from '../../../../../shared/shared.module';
|
||||
import { Scanner } from '../scanner';
|
||||
import { ScannerService } from '../../../../../../../ng-swagger-gen/services/scanner.service';
|
||||
|
||||
describe('NewScannerModalComponent', () => {
|
||||
let component: NewScannerModalComponent;
|
||||
let fixture: ComponentFixture<NewScannerModalComponent>;
|
||||
let component: NewScannerModalComponent;
|
||||
let fixture: ComponentFixture<NewScannerModalComponent>;
|
||||
|
||||
let mockScanner1: Scanner = {
|
||||
name: 'test1',
|
||||
description: 'just a sample',
|
||||
url: 'http://168.0.0.1',
|
||||
auth: "",
|
||||
};
|
||||
let fakedConfigScannerService = {
|
||||
listScanners() {
|
||||
return of([mockScanner1]);
|
||||
},
|
||||
pingScanner() {
|
||||
return of(true).pipe(delay(200));
|
||||
},
|
||||
createScanner() {
|
||||
return of(true).pipe(delay(200));
|
||||
},
|
||||
updateScanner() {
|
||||
return of(true).pipe(delay(200));
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
],
|
||||
declarations: [
|
||||
NewScannerFormComponent,
|
||||
NewScannerModalComponent,
|
||||
],
|
||||
providers: [
|
||||
MessageHandlerService,
|
||||
{ provide: ScannerService, useValue: fakedConfigScannerService },
|
||||
FormBuilder,
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
]
|
||||
let mockScanner1: Scanner = {
|
||||
name: 'test1',
|
||||
description: 'just a sample',
|
||||
url: 'http://168.0.0.1',
|
||||
auth: '',
|
||||
};
|
||||
let fakedConfigScannerService = {
|
||||
listScanners() {
|
||||
return of([mockScanner1]);
|
||||
},
|
||||
pingScanner() {
|
||||
return of(true).pipe(delay(200));
|
||||
},
|
||||
createScanner() {
|
||||
return of(true).pipe(delay(200));
|
||||
},
|
||||
updateScanner() {
|
||||
return of(true).pipe(delay(200));
|
||||
},
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SharedTestingModule],
|
||||
declarations: [NewScannerFormComponent, NewScannerModalComponent],
|
||||
providers: [
|
||||
MessageHandlerService,
|
||||
{
|
||||
provide: ScannerService,
|
||||
useValue: fakedConfigScannerService,
|
||||
},
|
||||
FormBuilder,
|
||||
// open auto detect
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewScannerModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.opened = true;
|
||||
component.newScannerFormComponent.checkNameSubscribe = new Subscription();
|
||||
component.newScannerFormComponent.checkEndpointUrlSubscribe = new Subscription();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
afterEach(() => {
|
||||
if (component && component.newScannerFormComponent && component.newScannerFormComponent.checkNameSubscribe) {
|
||||
component.newScannerFormComponent.checkNameSubscribe.unsubscribe();
|
||||
component.newScannerFormComponent.checkNameSubscribe = null;
|
||||
}
|
||||
if (component && component.newScannerFormComponent && component.newScannerFormComponent.checkEndpointUrlSubscribe) {
|
||||
component.newScannerFormComponent.checkEndpointUrlSubscribe.unsubscribe();
|
||||
component.newScannerFormComponent.checkEndpointUrlSubscribe = null;
|
||||
}
|
||||
});
|
||||
it('should creat', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should be add mode', () => {
|
||||
component.isEdit = false;
|
||||
fixture.detectChanges();
|
||||
let el = fixture.nativeElement.querySelector('#button-add');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('should be edit mode', fakeAsync(() => {
|
||||
component.isEdit = true;
|
||||
fixture.detectChanges();
|
||||
let el = fixture.nativeElement.querySelector('#button-save');
|
||||
expect(el).toBeTruthy();
|
||||
// set origin value
|
||||
component.originValue = mockScanner1;
|
||||
component.editScanner = {};
|
||||
// input same value to origin
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = "test2";
|
||||
fixture.nativeElement.querySelector('#description').value = "just a sample";
|
||||
fixture.nativeElement.querySelector('#scanner-endpoint').value = "http://168.0.0.1";
|
||||
fixture.nativeElement.querySelector('#scanner-authorization').value = "";
|
||||
fixture.nativeElement.querySelector('#scanner-name').dispatchEvent(new Event('input'));
|
||||
fixture.nativeElement.querySelector('#description').dispatchEvent(new Event('input'));
|
||||
fixture.nativeElement.querySelector('#scanner-endpoint').dispatchEvent(new Event('input'));
|
||||
fixture.nativeElement.querySelector('#scanner-authorization').dispatchEvent(new Event('input'));
|
||||
// save button should not be disabled
|
||||
expect(component.validForSaving).toBeTruthy();
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = "test3";
|
||||
fixture.nativeElement.querySelector('#scanner-name').dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
expect(component.validForSaving).toBeTruthy();
|
||||
el.click();
|
||||
el.dispatchEvent(new Event('click'));
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.opened).toBeFalsy();
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewScannerModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.opened = true;
|
||||
component.newScannerFormComponent.checkNameSubscribe =
|
||||
new Subscription();
|
||||
component.newScannerFormComponent.checkEndpointUrlSubscribe =
|
||||
new Subscription();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
it('test connection button should not be disabled', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = "test2";
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
let urlInput = fixture.nativeElement.querySelector('#scanner-endpoint');
|
||||
urlInput.value = "http://168.0.0.1";
|
||||
urlInput.dispatchEvent(new Event('input'));
|
||||
expect(component.canTestEndpoint).toBeTruthy();
|
||||
let el = fixture.nativeElement.querySelector('#button-test');
|
||||
el.click();
|
||||
el.dispatchEvent(new Event('click'));
|
||||
expect(component.checkBtnState).toBe(ClrLoadingState.LOADING);
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.checkBtnState).toBe(ClrLoadingState.SUCCESS);
|
||||
afterEach(() => {
|
||||
if (
|
||||
component &&
|
||||
component.newScannerFormComponent &&
|
||||
component.newScannerFormComponent.checkNameSubscribe
|
||||
) {
|
||||
component.newScannerFormComponent.checkNameSubscribe.unsubscribe();
|
||||
component.newScannerFormComponent.checkNameSubscribe = null;
|
||||
}
|
||||
if (
|
||||
component &&
|
||||
component.newScannerFormComponent &&
|
||||
component.newScannerFormComponent.checkEndpointUrlSubscribe
|
||||
) {
|
||||
component.newScannerFormComponent.checkEndpointUrlSubscribe.unsubscribe();
|
||||
component.newScannerFormComponent.checkEndpointUrlSubscribe = null;
|
||||
}
|
||||
});
|
||||
}));
|
||||
it('add button should not be disabled', fakeAsync(() => {
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = "test2";
|
||||
fixture.nativeElement.querySelector('#scanner-endpoint').value = "http://168.0.0.1";
|
||||
let authInput = fixture.nativeElement.querySelector('#scanner-authorization');
|
||||
authInput.value = "Basic";
|
||||
authInput.dispatchEvent(new Event('change'));
|
||||
let usernameInput = fixture.nativeElement.querySelector('#scanner-username');
|
||||
let passwordInput = fixture.nativeElement.querySelector('#scanner-password');
|
||||
expect(usernameInput).toBeTruthy();
|
||||
expect(passwordInput).toBeTruthy();
|
||||
usernameInput.value = "user";
|
||||
passwordInput.value = "12345";
|
||||
usernameInput.dispatchEvent(new Event('input'));
|
||||
passwordInput.dispatchEvent(new Event('input'));
|
||||
let el = fixture.nativeElement.querySelector('#button-add');
|
||||
expect(component.valid).toBeFalsy();
|
||||
el.click();
|
||||
el.dispatchEvent(new Event('click'));
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.opened).toBeFalsy();
|
||||
it('should creat', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
it('should be add mode', () => {
|
||||
component.isEdit = false;
|
||||
fixture.detectChanges();
|
||||
let el = fixture.nativeElement.querySelector('#button-add');
|
||||
expect(el).toBeTruthy();
|
||||
});
|
||||
it('should be edit mode', fakeAsync(() => {
|
||||
component.isEdit = true;
|
||||
fixture.detectChanges();
|
||||
let el = fixture.nativeElement.querySelector('#button-save');
|
||||
expect(el).toBeTruthy();
|
||||
// set origin value
|
||||
component.originValue = mockScanner1;
|
||||
component.editScanner = {};
|
||||
// input same value to origin
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = 'test2';
|
||||
fixture.nativeElement.querySelector('#description').value =
|
||||
'just a sample';
|
||||
fixture.nativeElement.querySelector('#scanner-endpoint').value =
|
||||
'http://168.0.0.1';
|
||||
fixture.nativeElement.querySelector('#scanner-authorization').value =
|
||||
'';
|
||||
fixture.nativeElement
|
||||
.querySelector('#scanner-name')
|
||||
.dispatchEvent(new Event('input'));
|
||||
fixture.nativeElement
|
||||
.querySelector('#description')
|
||||
.dispatchEvent(new Event('input'));
|
||||
fixture.nativeElement
|
||||
.querySelector('#scanner-endpoint')
|
||||
.dispatchEvent(new Event('input'));
|
||||
fixture.nativeElement
|
||||
.querySelector('#scanner-authorization')
|
||||
.dispatchEvent(new Event('input'));
|
||||
// save button should not be disabled
|
||||
expect(component.validForSaving).toBeTruthy();
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = 'test3';
|
||||
fixture.nativeElement
|
||||
.querySelector('#scanner-name')
|
||||
.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
expect(component.validForSaving).toBeTruthy();
|
||||
el.click();
|
||||
el.dispatchEvent(new Event('click'));
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.opened).toBeFalsy();
|
||||
});
|
||||
}));
|
||||
it('test connection button should not be disabled', fakeAsync(() => {
|
||||
let nameInput = fixture.nativeElement.querySelector('#scanner-name');
|
||||
nameInput.value = 'test2';
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
let urlInput = fixture.nativeElement.querySelector('#scanner-endpoint');
|
||||
urlInput.value = 'http://168.0.0.1';
|
||||
urlInput.dispatchEvent(new Event('input'));
|
||||
expect(component.canTestEndpoint).toBeTruthy();
|
||||
let el = fixture.nativeElement.querySelector('#button-test');
|
||||
el.click();
|
||||
el.dispatchEvent(new Event('click'));
|
||||
expect(component.checkBtnState).toBe(ClrLoadingState.LOADING);
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.checkBtnState).toBe(ClrLoadingState.SUCCESS);
|
||||
});
|
||||
}));
|
||||
it('add button should not be disabled', fakeAsync(() => {
|
||||
fixture.nativeElement.querySelector('#scanner-name').value = 'test2';
|
||||
fixture.nativeElement.querySelector('#scanner-endpoint').value =
|
||||
'http://168.0.0.1';
|
||||
let authInput = fixture.nativeElement.querySelector(
|
||||
'#scanner-authorization'
|
||||
);
|
||||
authInput.value = 'Basic';
|
||||
authInput.dispatchEvent(new Event('change'));
|
||||
let usernameInput =
|
||||
fixture.nativeElement.querySelector('#scanner-username');
|
||||
let passwordInput =
|
||||
fixture.nativeElement.querySelector('#scanner-password');
|
||||
expect(usernameInput).toBeTruthy();
|
||||
expect(passwordInput).toBeTruthy();
|
||||
usernameInput.value = 'user';
|
||||
passwordInput.value = '12345';
|
||||
usernameInput.dispatchEvent(new Event('input'));
|
||||
passwordInput.dispatchEvent(new Event('input'));
|
||||
let el = fixture.nativeElement.querySelector('#button-add');
|
||||
expect(component.valid).toBeFalsy();
|
||||
el.click();
|
||||
el.dispatchEvent(new Event('click'));
|
||||
tick(10000);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.opened).toBeFalsy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { Component, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||
import { Scanner } from "../scanner";
|
||||
import { NewScannerFormComponent } from "../new-scanner-form/new-scanner-form.component";
|
||||
import { ClrLoadingState } from "@clr/angular";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { MessageHandlerService } from "../../../../../shared/services/message-handler.service";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { InlineAlertComponent } from "../../../../../shared/components/inline-alert/inline-alert.component";
|
||||
import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service";
|
||||
import { ScannerRegistrationReq } from "../../../../../../../ng-swagger-gen/models/scanner-registration-req";
|
||||
import { clone } from "../../../../../shared/units/utils";
|
||||
import { Scanner } from '../scanner';
|
||||
import { NewScannerFormComponent } from '../new-scanner-form/new-scanner-form.component';
|
||||
import { ClrLoadingState } from '@clr/angular';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { MessageHandlerService } from '../../../../../shared/services/message-handler.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { InlineAlertComponent } from '../../../../../shared/components/inline-alert/inline-alert.component';
|
||||
import { ScannerService } from '../../../../../../../ng-swagger-gen/services/scanner.service';
|
||||
import { ScannerRegistrationReq } from '../../../../../../../ng-swagger-gen/models/scanner-registration-req';
|
||||
import { clone } from '../../../../../shared/units/utils';
|
||||
|
||||
@Component({
|
||||
selector: "new-scanner-modal",
|
||||
templateUrl: "new-scanner-modal.component.html",
|
||||
styleUrls: ['../../../../../common.scss']
|
||||
selector: 'new-scanner-modal',
|
||||
templateUrl: 'new-scanner-modal.component.html',
|
||||
styleUrls: ['../../../../../common.scss'],
|
||||
})
|
||||
export class NewScannerModalComponent {
|
||||
testMap: any = {};
|
||||
opened: boolean = false;
|
||||
@Output() notify = new EventEmitter<Scanner>();
|
||||
@ViewChild(NewScannerFormComponent, {static: true})
|
||||
@ViewChild(NewScannerFormComponent, { static: true })
|
||||
newScannerFormComponent: NewScannerFormComponent;
|
||||
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
saveBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||
|
@ -33,7 +33,7 @@ export class NewScannerModalComponent {
|
|||
constructor(
|
||||
private configScannerService: ScannerService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private translate: TranslateService,
|
||||
private translate: TranslateService
|
||||
) {}
|
||||
open(): void {
|
||||
// reset
|
||||
|
@ -41,7 +41,7 @@ export class NewScannerModalComponent {
|
|||
this.inlineAlert.close();
|
||||
this.testMap = {};
|
||||
this.newScannerFormComponent.showEndpointError = false;
|
||||
this.newScannerFormComponent.newScannerForm.reset({auth: "None"});
|
||||
this.newScannerFormComponent.newScannerForm.reset({ auth: 'None' });
|
||||
}
|
||||
close(): void {
|
||||
this.opened = false;
|
||||
|
@ -49,17 +49,20 @@ export class NewScannerModalComponent {
|
|||
create(): void {
|
||||
this.onSaving = true;
|
||||
this.saveBtnState = ClrLoadingState.LOADING;
|
||||
const scanner: ScannerRegistrationReq = {name: "", url: ""};
|
||||
const scanner: ScannerRegistrationReq = { name: '', url: '' };
|
||||
const value = this.newScannerFormComponent.newScannerForm.value;
|
||||
scanner.name = value.name;
|
||||
scanner.description = value.description;
|
||||
scanner.url = value.url;
|
||||
if (value.auth === "None") {
|
||||
scanner.auth = "";
|
||||
} else if (value.auth === "Basic") {
|
||||
if (value.auth === 'None') {
|
||||
scanner.auth = '';
|
||||
} else if (value.auth === 'Basic') {
|
||||
scanner.auth = value.auth;
|
||||
scanner.access_credential = value.accessCredential.username + ":" + value.accessCredential.password;
|
||||
} else if (value.auth === "APIKey") {
|
||||
scanner.access_credential =
|
||||
value.accessCredential.username +
|
||||
':' +
|
||||
value.accessCredential.password;
|
||||
} else if (value.auth === 'APIKey') {
|
||||
scanner.auth = value.auth;
|
||||
scanner.access_credential = value.accessCredential.apiKey;
|
||||
} else {
|
||||
|
@ -68,49 +71,78 @@ export class NewScannerModalComponent {
|
|||
}
|
||||
scanner.skip_certVerify = !!value.skipCertVerify;
|
||||
scanner.use_internal_addr = !!value.useInner;
|
||||
this.configScannerService.createScanner({
|
||||
registration: scanner
|
||||
})
|
||||
.pipe(finalize(() => this.onSaving = false))
|
||||
.subscribe(response => {
|
||||
this.close();
|
||||
this.msgHandler.showSuccess("SCANNER.ADD_SUCCESS");
|
||||
this.notify.emit();
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
}, error => {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
this.configScannerService
|
||||
.createScanner({
|
||||
registration: scanner,
|
||||
})
|
||||
.pipe(finalize(() => (this.onSaving = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.close();
|
||||
this.msgHandler.showSuccess('SCANNER.ADD_SUCCESS');
|
||||
this.notify.emit();
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
},
|
||||
error => {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
);
|
||||
}
|
||||
get hasPassedTest(): boolean {
|
||||
return this.testMap[this.newScannerFormComponent.newScannerForm.get('url').value];
|
||||
return this.testMap[
|
||||
this.newScannerFormComponent.newScannerForm.get('url').value
|
||||
];
|
||||
}
|
||||
get canTestEndpoint(): boolean {
|
||||
if (this.newScannerFormComponent.newScannerForm.get('auth').value === "Basic") {
|
||||
return this.newScannerFormComponent.newScannerForm.get('accessCredential').get('username').valid
|
||||
&& this.newScannerFormComponent.newScannerForm.get('accessCredential').get('password').valid;
|
||||
if (
|
||||
this.newScannerFormComponent.newScannerForm.get('auth').value ===
|
||||
'Basic'
|
||||
) {
|
||||
return (
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('username').valid &&
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('password').valid
|
||||
);
|
||||
}
|
||||
if (this.newScannerFormComponent.newScannerForm.get('auth').value === "Bearer") {
|
||||
return this.newScannerFormComponent.newScannerForm.get('accessCredential').get('token').valid;
|
||||
if (
|
||||
this.newScannerFormComponent.newScannerForm.get('auth').value ===
|
||||
'Bearer'
|
||||
) {
|
||||
return this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('token').valid;
|
||||
}
|
||||
if (this.newScannerFormComponent.newScannerForm.get('auth').value === "APIKey") {
|
||||
return this.newScannerFormComponent.newScannerForm.get('accessCredential').get('apiKey').valid;
|
||||
if (
|
||||
this.newScannerFormComponent.newScannerForm.get('auth').value ===
|
||||
'APIKey'
|
||||
) {
|
||||
return this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('apiKey').valid;
|
||||
}
|
||||
return !this.onTesting
|
||||
&& this.newScannerFormComponent
|
||||
&& !this.newScannerFormComponent.checkOnGoing
|
||||
&& this.newScannerFormComponent.newScannerForm.get('name').valid
|
||||
&& !this.newScannerFormComponent.checkEndpointOnGoing
|
||||
&& this.newScannerFormComponent.newScannerForm.get('url').valid;
|
||||
return (
|
||||
!this.onTesting &&
|
||||
this.newScannerFormComponent &&
|
||||
!this.newScannerFormComponent.checkOnGoing &&
|
||||
this.newScannerFormComponent.newScannerForm.get('name').valid &&
|
||||
!this.newScannerFormComponent.checkEndpointOnGoing &&
|
||||
this.newScannerFormComponent.newScannerForm.get('url').valid
|
||||
);
|
||||
}
|
||||
get valid(): boolean {
|
||||
if (this.onSaving
|
||||
|| this.newScannerFormComponent.isNameExisting
|
||||
|| this.newScannerFormComponent.isEndpointUrlExisting
|
||||
|| this.onTesting
|
||||
|| !this.newScannerFormComponent
|
||||
|| this.newScannerFormComponent.checkOnGoing
|
||||
|| this.newScannerFormComponent.checkEndpointOnGoing) {
|
||||
if (
|
||||
this.onSaving ||
|
||||
this.newScannerFormComponent.isNameExisting ||
|
||||
this.newScannerFormComponent.isEndpointUrlExisting ||
|
||||
this.onTesting ||
|
||||
!this.newScannerFormComponent ||
|
||||
this.newScannerFormComponent.checkOnGoing ||
|
||||
this.newScannerFormComponent.checkEndpointOnGoing
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerFormComponent.newScannerForm.get('name').invalid) {
|
||||
|
@ -119,15 +151,34 @@ export class NewScannerModalComponent {
|
|||
if (this.newScannerFormComponent.newScannerForm.get('url').invalid) {
|
||||
return false;
|
||||
}
|
||||
if (this.newScannerFormComponent.newScannerForm.get('auth').value === "Basic") {
|
||||
return this.newScannerFormComponent.newScannerForm.get('accessCredential').get('username').valid
|
||||
&& this.newScannerFormComponent.newScannerForm.get('accessCredential').get('password').valid;
|
||||
if (
|
||||
this.newScannerFormComponent.newScannerForm.get('auth').value ===
|
||||
'Basic'
|
||||
) {
|
||||
return (
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('username').valid &&
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('password').valid
|
||||
);
|
||||
}
|
||||
if (this.newScannerFormComponent.newScannerForm.get('auth').value === "Bearer") {
|
||||
return this.newScannerFormComponent.newScannerForm.get('accessCredential').get('token').valid;
|
||||
if (
|
||||
this.newScannerFormComponent.newScannerForm.get('auth').value ===
|
||||
'Bearer'
|
||||
) {
|
||||
return this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('token').valid;
|
||||
}
|
||||
if (this.newScannerFormComponent.newScannerForm.get('auth').value === "APIKey") {
|
||||
return this.newScannerFormComponent.newScannerForm.get('accessCredential').get('apiKey').valid;
|
||||
if (
|
||||
this.newScannerFormComponent.newScannerForm.get('auth').value ===
|
||||
'APIKey'
|
||||
) {
|
||||
return this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('apiKey').valid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -135,62 +186,100 @@ export class NewScannerModalComponent {
|
|||
return this.valid && this.hasChange();
|
||||
}
|
||||
hasChange(): boolean {
|
||||
if (this.originValue.name !== this.newScannerFormComponent.newScannerForm.get('name').value) {
|
||||
if (
|
||||
this.originValue.name !==
|
||||
this.newScannerFormComponent.newScannerForm.get('name').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.description !== this.newScannerFormComponent.newScannerForm.get('description').value) {
|
||||
if (
|
||||
this.originValue.description !==
|
||||
this.newScannerFormComponent.newScannerForm.get('description').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.url !== this.newScannerFormComponent.newScannerForm.get('url').value) {
|
||||
if (
|
||||
this.originValue.url !==
|
||||
this.newScannerFormComponent.newScannerForm.get('url').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.auth !== this.newScannerFormComponent.newScannerForm.get('auth').value) {
|
||||
if (
|
||||
this.originValue.auth !==
|
||||
this.newScannerFormComponent.newScannerForm.get('auth').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.skipCertVerify !== this.newScannerFormComponent.newScannerForm.get('skipCertVerify').value) {
|
||||
if (
|
||||
this.originValue.skipCertVerify !==
|
||||
this.newScannerFormComponent.newScannerForm.get('skipCertVerify')
|
||||
.value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.useInner !== this.newScannerFormComponent.newScannerForm.get('useInner').value) {
|
||||
if (
|
||||
this.originValue.useInner !==
|
||||
this.newScannerFormComponent.newScannerForm.get('useInner').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.auth === "Basic") {
|
||||
if (this.originValue.accessCredential.username !==
|
||||
this.newScannerFormComponent.newScannerForm.get('accessCredential').get('username').value) {
|
||||
if (this.originValue.auth === 'Basic') {
|
||||
if (
|
||||
this.originValue.accessCredential.username !==
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('username').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (this.originValue.accessCredential.password !==
|
||||
this.newScannerFormComponent.newScannerForm.get('accessCredential').get('password').value) {
|
||||
if (
|
||||
this.originValue.accessCredential.password !==
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('password').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.originValue.auth === "Bearer") {
|
||||
if (this.originValue.accessCredential.token !==
|
||||
this.newScannerFormComponent.newScannerForm.get('accessCredential').get('token').value) {
|
||||
if (this.originValue.auth === 'Bearer') {
|
||||
if (
|
||||
this.originValue.accessCredential.token !==
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('token').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.originValue.auth === "APIKey") {
|
||||
if (this.originValue.accessCredential.apiKey !==
|
||||
this.newScannerFormComponent.newScannerForm.get('accessCredential').get('apiKey').value) {
|
||||
if (this.originValue.auth === 'APIKey') {
|
||||
if (
|
||||
this.originValue.accessCredential.apiKey !==
|
||||
this.newScannerFormComponent.newScannerForm
|
||||
.get('accessCredential')
|
||||
.get('apiKey').value
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
onTestEndpoint() {
|
||||
this.onTesting = true;
|
||||
this.checkBtnState = ClrLoadingState.LOADING;
|
||||
const scanner: ScannerRegistrationReq = {name: "", url: ""};
|
||||
const scanner: ScannerRegistrationReq = { name: '', url: '' };
|
||||
const value = this.newScannerFormComponent.newScannerForm.value;
|
||||
scanner.name = value.name;
|
||||
scanner.description = value.description;
|
||||
scanner.url = value.url;
|
||||
if (value.auth === "None") {
|
||||
scanner.auth = "";
|
||||
} else if (value.auth === "Basic") {
|
||||
if (value.auth === 'None') {
|
||||
scanner.auth = '';
|
||||
} else if (value.auth === 'Basic') {
|
||||
scanner.auth = value.auth;
|
||||
scanner.access_credential = value.accessCredential.username + ":" + value.accessCredential.password;
|
||||
} else if (value.auth === "APIKey") {
|
||||
scanner.access_credential =
|
||||
value.accessCredential.username +
|
||||
':' +
|
||||
value.accessCredential.password;
|
||||
} else if (value.auth === 'APIKey') {
|
||||
scanner.auth = value.auth;
|
||||
scanner.access_credential = value.accessCredential.apiKey;
|
||||
} else {
|
||||
|
@ -199,26 +288,39 @@ export class NewScannerModalComponent {
|
|||
}
|
||||
scanner.skip_certVerify = !!value.skipCertVerify;
|
||||
scanner.use_internal_addr = !!value.useInner;
|
||||
this.configScannerService.pingScanner({
|
||||
settings: scanner
|
||||
})
|
||||
.pipe(finalize(() => this.onTesting = false))
|
||||
.subscribe(response => {
|
||||
this.inlineAlert.showInlineSuccess({
|
||||
message: "SCANNER.TEST_PASS"
|
||||
});
|
||||
this.checkBtnState = ClrLoadingState.SUCCESS;
|
||||
this.testMap[this.newScannerFormComponent.newScannerForm.get('url').value] = true;
|
||||
}, error => {
|
||||
this.translate.get("SCANNER.TEST_FAILED",
|
||||
{
|
||||
name: this.newScannerFormComponent.newScannerForm.get('name').value,
|
||||
url: this.newScannerFormComponent.newScannerForm.get('url').value
|
||||
}).subscribe((res: string) => {
|
||||
this.inlineAlert.showInlineError(res);
|
||||
});
|
||||
this.checkBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
this.configScannerService
|
||||
.pingScanner({
|
||||
settings: scanner,
|
||||
})
|
||||
.pipe(finalize(() => (this.onTesting = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.inlineAlert.showInlineSuccess({
|
||||
message: 'SCANNER.TEST_PASS',
|
||||
});
|
||||
this.checkBtnState = ClrLoadingState.SUCCESS;
|
||||
this.testMap[
|
||||
this.newScannerFormComponent.newScannerForm.get(
|
||||
'url'
|
||||
).value
|
||||
] = true;
|
||||
},
|
||||
error => {
|
||||
this.translate
|
||||
.get('SCANNER.TEST_FAILED', {
|
||||
name: this.newScannerFormComponent.newScannerForm.get(
|
||||
'name'
|
||||
).value,
|
||||
url: this.newScannerFormComponent.newScannerForm.get(
|
||||
'url'
|
||||
).value,
|
||||
})
|
||||
.subscribe((res: string) => {
|
||||
this.inlineAlert.showInlineError(res);
|
||||
});
|
||||
this.checkBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
);
|
||||
}
|
||||
save() {
|
||||
this.onSaving = true;
|
||||
|
@ -227,12 +329,15 @@ export class NewScannerModalComponent {
|
|||
this.editScanner.name = value.name;
|
||||
this.editScanner.description = value.description;
|
||||
this.editScanner.url = value.url;
|
||||
if (value.auth === "None") {
|
||||
this.editScanner.auth = "";
|
||||
} else if (value.auth === "Basic") {
|
||||
if (value.auth === 'None') {
|
||||
this.editScanner.auth = '';
|
||||
} else if (value.auth === 'Basic') {
|
||||
this.editScanner.auth = value.auth;
|
||||
this.editScanner.access_credential = value.accessCredential.username + ":" + value.accessCredential.password;
|
||||
} else if (value.auth === "APIKey") {
|
||||
this.editScanner.access_credential =
|
||||
value.accessCredential.username +
|
||||
':' +
|
||||
value.accessCredential.password;
|
||||
} else if (value.auth === 'APIKey') {
|
||||
this.editScanner.auth = value.auth;
|
||||
this.editScanner.access_credential = value.accessCredential.apiKey;
|
||||
} else {
|
||||
|
@ -243,19 +348,23 @@ export class NewScannerModalComponent {
|
|||
this.editScanner.use_internal_addr = !!value.useInner;
|
||||
this.editScanner.uuid = this.uid;
|
||||
const scanner: ScannerRegistrationReq = clone(this.editScanner);
|
||||
this.configScannerService.updateScanner({
|
||||
registrationId: this.editScanner.uuid,
|
||||
registration: scanner
|
||||
})
|
||||
.pipe(finalize(() => this.onSaving = false))
|
||||
.subscribe(response => {
|
||||
this.close();
|
||||
this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS");
|
||||
this.notify.emit();
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
}, error => {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
});
|
||||
this.configScannerService
|
||||
.updateScanner({
|
||||
registrationId: this.editScanner.uuid,
|
||||
registration: scanner,
|
||||
})
|
||||
.pipe(finalize(() => (this.onSaving = false)))
|
||||
.subscribe(
|
||||
response => {
|
||||
this.close();
|
||||
this.msgHandler.showSuccess('SCANNER.UPDATE_SUCCESS');
|
||||
this.notify.emit();
|
||||
this.saveBtnState = ClrLoadingState.SUCCESS;
|
||||
},
|
||||
error => {
|
||||
this.inlineAlert.showInlineError(error);
|
||||
this.saveBtnState = ClrLoadingState.ERROR;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue