mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-05 06:21:22 +01:00
Add permission check to CVE export (#17267)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
04fa3853c9
commit
7e7ae7ea1b
@ -102,7 +102,7 @@
|
||||
<clr-dropdown class="width-tag-label">
|
||||
<div class="label-text">
|
||||
<div
|
||||
class="dropdown-toggle"
|
||||
class="dropdown-toggle labels"
|
||||
clrDropdownTrigger>
|
||||
<ng-container
|
||||
*ngFor="
|
||||
|
@ -78,3 +78,8 @@
|
||||
.input-width {
|
||||
width: 310px;
|
||||
}
|
||||
|
||||
.labels {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { Label } from 'ng-swagger-gen/models/label';
|
||||
import { LabelService } from 'ng-swagger-gen/services/label.service';
|
||||
import { forkJoin, Observable } from 'rxjs';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { Project } from 'src/app/base/project/project';
|
||||
import { NgForm } from '@angular/forms';
|
||||
@ -13,8 +17,8 @@ import {
|
||||
EventService,
|
||||
HarborEvent,
|
||||
} from '../../../../../services/event-service/event.service';
|
||||
import { LabelService } from 'src/app/shared/services/label.service';
|
||||
|
||||
const PAGE_SIZE: number = 100;
|
||||
const SUPPORTED_MIME_TYPE: string =
|
||||
'application/vnd.security.vulnerability.report; version=1.1';
|
||||
@Component({
|
||||
@ -23,6 +27,7 @@ const SUPPORTED_MIME_TYPE: string =
|
||||
styleUrls: ['./export-cve.component.scss'],
|
||||
})
|
||||
export class ExportCveComponent {
|
||||
@Output() triggerExportSuccess = new EventEmitter<void>();
|
||||
selectedProjects: Project[] = [];
|
||||
opened: boolean = false;
|
||||
loading: boolean = false;
|
||||
@ -92,6 +97,7 @@ export class ExportCveComponent {
|
||||
)
|
||||
.subscribe(
|
||||
res => {
|
||||
this.triggerExportSuccess.emit();
|
||||
this.msgHandler.showSuccess(
|
||||
'CVE_EXPORT.TRIGGER_EXPORT_SUCCESS'
|
||||
);
|
||||
@ -107,7 +113,7 @@ export class ExportCveComponent {
|
||||
isSelected(l: Label): boolean {
|
||||
let flag: boolean = false;
|
||||
this.selectedLabels.forEach(item => {
|
||||
if (item.name === l.name) {
|
||||
if (item.id === l.id) {
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
@ -116,7 +122,7 @@ export class ExportCveComponent {
|
||||
selectOrUnselect(l: Label) {
|
||||
if (this.isSelected(l)) {
|
||||
this.selectedLabels = this.selectedLabels.filter(
|
||||
item => item.name !== l.name
|
||||
item => item.id !== l.id
|
||||
);
|
||||
} else {
|
||||
this.selectedLabels.push(l);
|
||||
@ -144,54 +150,12 @@ export class ExportCveComponent {
|
||||
// get all global labels
|
||||
this.loadingAllLabels = true;
|
||||
this.labelService
|
||||
.ListLabelsResponse({
|
||||
pageSize: PAGE_SIZE,
|
||||
page: 1,
|
||||
scope: 'g',
|
||||
})
|
||||
.getAllGlobalAndSpecificProjectLabels(
|
||||
this.selectedProjects[0].project_id
|
||||
)
|
||||
.pipe(finalize(() => (this.loadingAllLabels = false)))
|
||||
.subscribe(res => {
|
||||
if (res.headers) {
|
||||
const xHeader: string = res.headers.get('X-Total-Count');
|
||||
const totalCount = parseInt(xHeader, 0);
|
||||
let arr = res.body || [];
|
||||
if (totalCount <= 100) {
|
||||
// already gotten all global labels
|
||||
if (arr && arr.length) {
|
||||
arr.forEach(data => {
|
||||
this.allLabels.push(data);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// get all the global labels in specified times
|
||||
const times: number = Math.ceil(totalCount / PAGE_SIZE);
|
||||
const observableList: Observable<Label[]>[] = [];
|
||||
for (let i = 2; i <= times; i++) {
|
||||
observableList.push(
|
||||
this.labelService.ListLabels({
|
||||
page: i,
|
||||
pageSize: PAGE_SIZE,
|
||||
scope: 'g',
|
||||
})
|
||||
);
|
||||
}
|
||||
this.loadingAllLabels = true;
|
||||
forkJoin(observableList)
|
||||
.pipe(
|
||||
finalize(() => (this.loadingAllLabels = false))
|
||||
)
|
||||
.subscribe(response => {
|
||||
if (response && response.length) {
|
||||
response.forEach(item => {
|
||||
arr = arr.concat(item);
|
||||
});
|
||||
arr.forEach(data => {
|
||||
this.allLabels.push(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.allLabels = res;
|
||||
});
|
||||
}
|
||||
handleBrace(originStr: string): string {
|
||||
|
@ -1,7 +1,8 @@
|
||||
<clr-datagrid
|
||||
(clrDgRefresh)="clrLoad($event)"
|
||||
[clrDgLoading]="loading"
|
||||
[(clrDgSelected)]="selectedRow">
|
||||
[(clrDgSelected)]="selectedRow"
|
||||
(clrDgSelectedChange)="selectionChanged()">
|
||||
<clr-dg-action-bar>
|
||||
<button
|
||||
type="button"
|
||||
@ -23,11 +24,14 @@
|
||||
down"></clr-icon
|
||||
></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button clrDropdownItem (click)="exportCVE()">
|
||||
<button
|
||||
[disabled]="!hasPermission || !canClickExport"
|
||||
[clrLoading]="checkingPermission"
|
||||
clrDropdownItem
|
||||
(click)="exportCVE()">
|
||||
<clr-icon shape="export" size="16"></clr-icon>
|
||||
<span id="export-cve">{{
|
||||
getExportButtonText()
|
||||
| translate: { number: selectedRow?.length }
|
||||
'CVE_EXPORT.EXPORT_SOME_PROJECTS' | translate
|
||||
}}</span>
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
@ -102,4 +106,4 @@
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
<export-cve></export-cve>
|
||||
<export-cve (triggerExportSuccess)="triggerExportSuccess()"></export-cve>
|
||||
|
@ -11,16 +11,21 @@
|
||||
// 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 { Subscription, forkJoin, of } from 'rxjs';
|
||||
import { forkJoin, Observable, of, Subscription } from 'rxjs';
|
||||
import {
|
||||
Component,
|
||||
Output,
|
||||
OnDestroy,
|
||||
EventEmitter,
|
||||
OnDestroy,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ProjectService, State } from '../../../../shared/services';
|
||||
import {
|
||||
ProjectService,
|
||||
State,
|
||||
UserPermissionService,
|
||||
USERSTATICPERMISSION,
|
||||
} from '../../../../shared/services';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SessionService } from '../../../../shared/services/session.service';
|
||||
import { StatisticHandler } from '../statictics/statistic-handler.service';
|
||||
@ -28,7 +33,7 @@ import { MessageHandlerService } from '../../../../shared/services/message-handl
|
||||
import { SearchTriggerService } from '../../../../shared/components/global-search/search-trigger.service';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
import { Project } from '../../../project/project';
|
||||
import { map, catchError, finalize } from 'rxjs/operators';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import {
|
||||
calculatePage,
|
||||
getPageSizeFromLocalStorage,
|
||||
@ -55,6 +60,8 @@ import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
import { ConfirmationMessage } from '../../../global-confirmation-dialog/confirmation-message';
|
||||
import { ExportCveComponent } from './export-cve/export-cve.component';
|
||||
|
||||
const MAX_PROJECTS_NUM: number = 1;
|
||||
const INTERVAL: number = 30000;
|
||||
@Component({
|
||||
selector: 'list-project',
|
||||
templateUrl: 'list-project.component.html',
|
||||
@ -83,6 +90,9 @@ export class ListProjectComponent implements OnDestroy {
|
||||
state: ClrDatagridStateInterface;
|
||||
@ViewChild(ExportCveComponent)
|
||||
exportCveComponent: ExportCveComponent;
|
||||
hasPermission: boolean = false;
|
||||
checkingPermission: boolean = false;
|
||||
canClickExport: boolean = true;
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private appConfigService: AppConfigService,
|
||||
@ -94,7 +104,8 @@ export class ListProjectComponent implements OnDestroy {
|
||||
private translate: TranslateService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private operationService: OperationService,
|
||||
private translateService: TranslateService
|
||||
private translateService: TranslateService,
|
||||
private permissionService: UserPermissionService
|
||||
) {
|
||||
this.subscription =
|
||||
deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
@ -382,11 +393,36 @@ export class ListProjectComponent implements OnDestroy {
|
||||
exportCVE() {
|
||||
this.exportCveComponent.open(this.selectedRow);
|
||||
}
|
||||
|
||||
getExportButtonText(): string {
|
||||
if (this.selectedRow?.length) {
|
||||
return `CVE_EXPORT.EXPORT_SOME_PROJECTS`;
|
||||
selectionChanged() {
|
||||
this.hasPermission = false;
|
||||
if (
|
||||
this.selectedRow?.length &&
|
||||
this.selectedRow?.length <= MAX_PROJECTS_NUM
|
||||
) {
|
||||
const obs: Observable<boolean>[] = [];
|
||||
this.selectedRow.forEach(item => {
|
||||
obs.push(
|
||||
this.permissionService.getPermission(
|
||||
item.project_id,
|
||||
USERSTATICPERMISSION.EXPORT_CVE.KEY,
|
||||
USERSTATICPERMISSION.EXPORT_CVE.VALUE.CREATE
|
||||
)
|
||||
);
|
||||
});
|
||||
this.checkingPermission = true;
|
||||
forkJoin(obs)
|
||||
.pipe(finalize(() => (this.checkingPermission = false)))
|
||||
.subscribe(res => {
|
||||
if (res?.length) {
|
||||
this.hasPermission = res.every(item => item);
|
||||
}
|
||||
});
|
||||
}
|
||||
return 'CVE_EXPORT.EXPORT_ALL_PROJECTS';
|
||||
}
|
||||
triggerExportSuccess() {
|
||||
this.canClickExport = false;
|
||||
setTimeout(() => {
|
||||
this.canClickExport = true;
|
||||
}, INTERVAL);
|
||||
}
|
||||
}
|
||||
|
@ -281,31 +281,37 @@
|
||||
class="dropdown clr-select-wrapper"
|
||||
formArrayName="value">
|
||||
<clr-dropdown class="width-tag-label">
|
||||
<button
|
||||
type="button"
|
||||
class="width-100 dropdown-toggle btn btn-link statistic-data label-text"
|
||||
<div
|
||||
class="width-100 label-text"
|
||||
clrDropdownTrigger>
|
||||
<ng-template
|
||||
ngFor
|
||||
let-label
|
||||
[ngForOf]="filter.value.value"
|
||||
let-m="index">
|
||||
<hbr-label-piece
|
||||
*ngIf="m < 1"
|
||||
[hasIcon]="false"
|
||||
[label]="getLabel(label)"
|
||||
[labelWidth]="
|
||||
84
|
||||
"></hbr-label-piece>
|
||||
</ng-template>
|
||||
<span
|
||||
class="ellipsis color-white-dark"
|
||||
*ngIf="
|
||||
filter.value.value.length >
|
||||
1
|
||||
"
|
||||
>···</span
|
||||
>
|
||||
<div class="label-container">
|
||||
<ng-template
|
||||
ngFor
|
||||
let-label
|
||||
[ngForOf]="
|
||||
filter.value.value
|
||||
"
|
||||
let-m="index">
|
||||
<hbr-label-piece
|
||||
class="label-piece"
|
||||
*ngIf="m < 1"
|
||||
[hasIcon]="false"
|
||||
[label]="
|
||||
getLabel(label)
|
||||
"
|
||||
[labelWidth]="
|
||||
84
|
||||
"></hbr-label-piece>
|
||||
</ng-template>
|
||||
<span
|
||||
class="ellipsis color-white-dark"
|
||||
*ngIf="
|
||||
filter.value.value
|
||||
.length > 1
|
||||
"
|
||||
>···</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
*ngFor="
|
||||
let label1 of filter.value
|
||||
@ -333,7 +339,7 @@
|
||||
}}"
|
||||
placeholder="select labels" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<clr-dropdown-menu
|
||||
[ngStyle]="{ 'max-height.px': 230 }"
|
||||
class="right-align"
|
||||
|
@ -213,18 +213,12 @@ clr-modal {
|
||||
}
|
||||
|
||||
.label-text {
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
position: relative;
|
||||
height: 1.2rem;
|
||||
margin: 0 !important;
|
||||
line-height: 1rem;
|
||||
text-align: left;
|
||||
padding-left: 6px;
|
||||
outline: none;
|
||||
border-bottom: 1px solid rgb(154 154 154);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
border-bottom: 0.05rem solid;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
@ -311,3 +305,13 @@ clr-modal {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.label-piece {
|
||||
display: flex;
|
||||
left: .25rem;
|
||||
}
|
||||
|
||||
.label-container{
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -8,9 +8,15 @@ import {
|
||||
} from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { errorHandler } from '../shared/units/shared.utils';
|
||||
|
||||
export const SAFE_METHODS: string[] = ['GET', 'HEAD', 'OPTIONS', 'TRACE'];
|
||||
|
||||
enum INVALID_CSRF_TOKEN {
|
||||
CODE = 403,
|
||||
MESSAGE = 'CSRF token invalid',
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@ -75,7 +81,10 @@ export class InterceptHttpService implements HttpInterceptor {
|
||||
})
|
||||
);
|
||||
}
|
||||
if (error.status === 403) {
|
||||
if (
|
||||
error.status === INVALID_CSRF_TOKEN.CODE &&
|
||||
errorHandler(error) === INVALID_CSRF_TOKEN.MESSAGE
|
||||
) {
|
||||
const csrfToken = localStorage.getItem('__csrf');
|
||||
if (csrfToken) {
|
||||
request = request.clone({
|
||||
|
@ -58,6 +58,7 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
timeout;
|
||||
refreshExportJobSub: Subscription;
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private operationService: OperationService,
|
||||
@ -66,29 +67,34 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
private event: EventService,
|
||||
private msgHandler: MessageHandlerService
|
||||
) {
|
||||
this.event.subscribe(HarborEvent.REFRESH_EXPORT_JOBS, () => {
|
||||
if (this.animationState === 'out') {
|
||||
this._newMessageCount += 1;
|
||||
}
|
||||
this.refreshExportJobs();
|
||||
});
|
||||
|
||||
this.batchInfoSubscription = operationService.operationInfo$.subscribe(
|
||||
data => {
|
||||
if (this.animationState === 'out') {
|
||||
this._newMessageCount += 1;
|
||||
}
|
||||
if (data) {
|
||||
if (this.resultLists.length >= MAX_NUMBER) {
|
||||
this.resultLists.splice(
|
||||
MAX_NUMBER - 1,
|
||||
this.resultLists.length + 1 - MAX_NUMBER
|
||||
);
|
||||
if (!this.refreshExportJobSub) {
|
||||
this.refreshExportJobSub = this.event.subscribe(
|
||||
HarborEvent.REFRESH_EXPORT_JOBS,
|
||||
() => {
|
||||
if (this.animationState === 'out') {
|
||||
this._newMessageCount += 1;
|
||||
}
|
||||
this.resultLists.unshift(data);
|
||||
this.refreshExportJobs();
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
if (!this.batchInfoSubscription) {
|
||||
this.batchInfoSubscription =
|
||||
operationService.operationInfo$.subscribe(data => {
|
||||
if (this.animationState === 'out') {
|
||||
this._newMessageCount += 1;
|
||||
}
|
||||
if (data) {
|
||||
if (this.resultLists.length >= MAX_NUMBER) {
|
||||
this.resultLists.splice(
|
||||
MAX_NUMBER - 1,
|
||||
this.resultLists.length + 1 - MAX_NUMBER
|
||||
);
|
||||
}
|
||||
this.resultLists.unshift(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getNewMessageCountStr(): string {
|
||||
@ -184,6 +190,7 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy(): void {
|
||||
if (this.batchInfoSubscription) {
|
||||
this.batchInfoSubscription.unsubscribe();
|
||||
this.batchInfoSubscription = null;
|
||||
}
|
||||
if (this._timeoutInterval) {
|
||||
clearInterval(this._timeoutInterval);
|
||||
@ -193,6 +200,10 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
if (this.refreshExportJobSub) {
|
||||
this.refreshExportJobSub.unsubscribe();
|
||||
this.refreshExportJobSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
toggleTitle(errorSpan: any) {
|
||||
@ -273,7 +284,7 @@ export class OperationComponent implements OnInit, OnDestroy {
|
||||
hasFile: item.file_present,
|
||||
name: `${FILE_NAME_PREFIX}${new HarborDatetimePipe().transform(
|
||||
item.start_time,
|
||||
'yyyyMMddHHss'
|
||||
'yyyyMMddHHmmss'
|
||||
)}`,
|
||||
id: item.id,
|
||||
errorInf:
|
||||
|
@ -3,13 +3,21 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { RequestQueryParams } from './RequestQueryParams';
|
||||
import { Label } from './interface';
|
||||
import { LabelService as GeneratedLabelService } from 'ng-swagger-gen/services/label.service';
|
||||
import { Label as GeneratedLabel } from 'ng-swagger-gen/models/label';
|
||||
import {
|
||||
buildHttpRequestOptions,
|
||||
CURRENT_BASE_HREF,
|
||||
V1_BASE_HREF,
|
||||
HTTP_JSON_OPTIONS,
|
||||
} from '../units/utils';
|
||||
import { Observable, throwError as observableThrowError } from 'rxjs';
|
||||
import {
|
||||
forkJoin,
|
||||
mergeMap,
|
||||
Observable,
|
||||
of,
|
||||
throwError as observableThrowError,
|
||||
} from 'rxjs';
|
||||
|
||||
export abstract class LabelService {
|
||||
abstract getGLabels(
|
||||
@ -63,15 +71,22 @@ export abstract class LabelService {
|
||||
version: string,
|
||||
label: Label
|
||||
): Observable<any>;
|
||||
}
|
||||
|
||||
abstract getAllGlobalAndSpecificProjectLabels(
|
||||
projectId: number
|
||||
): Observable<GeneratedLabel[]>;
|
||||
}
|
||||
const PAGE_SIZE: number = 100;
|
||||
@Injectable()
|
||||
export class LabelDefaultService extends LabelService {
|
||||
labelUrl: string;
|
||||
chartUrl: string;
|
||||
chartLabelUrl: string;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private labelService: GeneratedLabelService
|
||||
) {
|
||||
super();
|
||||
this.labelUrl = CURRENT_BASE_HREF + '/labels';
|
||||
this.chartUrl = V1_BASE_HREF + '/chartrepo';
|
||||
@ -235,4 +250,119 @@ export class LabelDefaultService extends LabelService {
|
||||
HTTP_JSON_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
getAllGlobalAndSpecificProjectLabels(
|
||||
projectId: number
|
||||
): Observable<GeneratedLabel[]> {
|
||||
return new Observable<GeneratedLabel[]>(observer => {
|
||||
// get all project labels
|
||||
forkJoin([
|
||||
this._gelAllLabelsForGlobalOrProject(true, null),
|
||||
this._gelAllLabelsForGlobalOrProject(false, projectId),
|
||||
]).subscribe({
|
||||
next: results => {
|
||||
observer.next([].concat.apply([], results));
|
||||
observer.complete();
|
||||
},
|
||||
error: err => {
|
||||
observer.error(err);
|
||||
observer.complete();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _gelAllLabelsForGlobalOrProject(
|
||||
isGlobal: boolean,
|
||||
projectId: number
|
||||
): Observable<GeneratedLabel[]> {
|
||||
if (isGlobal) {
|
||||
return this.labelService
|
||||
.ListLabelsResponse({
|
||||
pageSize: PAGE_SIZE,
|
||||
page: 1,
|
||||
scope: 'g',
|
||||
})
|
||||
.pipe(
|
||||
mergeMap(res => {
|
||||
if (res.headers) {
|
||||
const xHeader: string =
|
||||
res.headers.get('X-Total-Count');
|
||||
const totalCount = parseInt(xHeader, 0);
|
||||
if (totalCount <= PAGE_SIZE) {
|
||||
return of(res.body);
|
||||
} else {
|
||||
// get all the project labels in specified times
|
||||
const times: number = Math.ceil(
|
||||
totalCount / PAGE_SIZE
|
||||
);
|
||||
const observableList: Observable<
|
||||
GeneratedLabel[]
|
||||
>[] = [];
|
||||
for (let i = 2; i <= times; i++) {
|
||||
observableList.push(
|
||||
this.labelService.ListLabels({
|
||||
page: i,
|
||||
pageSize: PAGE_SIZE,
|
||||
scope: 'g',
|
||||
})
|
||||
);
|
||||
}
|
||||
return new Observable<GeneratedLabel[]>(ob => {
|
||||
forkJoin(observableList).subscribe(
|
||||
labels => {
|
||||
ob.next(
|
||||
[].concat.apply([], labels)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
return this.labelService
|
||||
.ListLabelsResponse({
|
||||
pageSize: PAGE_SIZE,
|
||||
page: 1,
|
||||
scope: 'p',
|
||||
projectId: projectId,
|
||||
})
|
||||
.pipe(
|
||||
mergeMap(res => {
|
||||
if (res.headers) {
|
||||
const xHeader: string =
|
||||
res.headers.get('X-Total-Count');
|
||||
const totalCount = parseInt(xHeader, 0);
|
||||
if (totalCount <= PAGE_SIZE) {
|
||||
return of(res.body);
|
||||
} else {
|
||||
// get all the project labels in specified times
|
||||
const times: number = Math.ceil(
|
||||
totalCount / PAGE_SIZE
|
||||
);
|
||||
const observableList: Observable<
|
||||
GeneratedLabel[]
|
||||
>[] = [];
|
||||
for (let i = 2; i <= times; i++) {
|
||||
observableList.push(
|
||||
this.labelService.ListLabels({
|
||||
page: i,
|
||||
pageSize: PAGE_SIZE,
|
||||
scope: 'p',
|
||||
projectId: projectId,
|
||||
})
|
||||
);
|
||||
}
|
||||
return new Observable<GeneratedLabel[]>(ob => {
|
||||
forkJoin(observableList).subscribe(labels => {
|
||||
ob.next([].concat.apply([], labels));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -202,4 +202,12 @@ export const USERSTATICPERMISSION = {
|
||||
DELETE: 'delete',
|
||||
},
|
||||
},
|
||||
EXPORT_CVE: {
|
||||
KEY: 'export-cve',
|
||||
VALUE: {
|
||||
READ: 'read',
|
||||
CREATE: 'create',
|
||||
LIST: 'list',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -332,3 +332,9 @@ hbr-copy-input {
|
||||
.select-all-for-dropdown {
|
||||
color: $select-all-for-dropdown-color !important;
|
||||
}
|
||||
|
||||
hbr-create-edit-rule {
|
||||
.label-text {
|
||||
border-bottom-color: $normal-border-color !important;
|
||||
}
|
||||
}
|
||||
|
@ -44,4 +44,5 @@ $input-autofill-color: #eaedf0;
|
||||
$pull-command-icon-color: #4aaed9;
|
||||
$pull-command-icon-hover-color: #007CBB;
|
||||
$select-all-for-dropdown-color: #4aaed9;
|
||||
$normal-border-color: #acbac3;
|
||||
@import "./common.scss";
|
||||
|
@ -45,4 +45,5 @@ $input-autofill-color: #000;
|
||||
$pull-command-icon-color: #007CBB;
|
||||
$pull-command-icon-hover-color: #4aaed9;
|
||||
$select-all-for-dropdown-color: #0072a3;
|
||||
$normal-border-color: #6a7a81;
|
||||
@import "./common.scss";
|
||||
|
@ -1757,8 +1757,7 @@
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
"EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs",
|
||||
"ALL_PROJECTS": "All projects",
|
||||
"EXPORT_TITLE": "Export CVE",
|
||||
"EXPORT_SUBTITLE": "Set exporting conditions",
|
||||
|
@ -1757,8 +1757,7 @@
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
"EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs",
|
||||
"ALL_PROJECTS": "All projects",
|
||||
"EXPORT_TITLE": "Export CVE",
|
||||
"EXPORT_SUBTITLE": "Set exporting conditions",
|
||||
|
@ -1756,8 +1756,7 @@
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
"EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs",
|
||||
"ALL_PROJECTS": "All projects",
|
||||
"EXPORT_TITLE": "Export CVE",
|
||||
"EXPORT_SUBTITLE": "Set exporting conditions",
|
||||
|
@ -1726,8 +1726,7 @@
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
"EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs",
|
||||
"ALL_PROJECTS": "All projects",
|
||||
"EXPORT_TITLE": "Export CVE",
|
||||
"EXPORT_SUBTITLE": "Set exporting conditions",
|
||||
|
@ -1753,8 +1753,7 @@
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
"EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs",
|
||||
"ALL_PROJECTS": "All projects",
|
||||
"EXPORT_TITLE": "Export CVE",
|
||||
"EXPORT_SUBTITLE": "Set exporting conditions",
|
||||
|
@ -1757,8 +1757,7 @@
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
"EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs",
|
||||
"ALL_PROJECTS": "All projects",
|
||||
"EXPORT_TITLE": "Export CVE",
|
||||
"EXPORT_SUBTITLE": "Set exporting conditions",
|
||||
|
@ -1755,8 +1755,7 @@
|
||||
"NO_PURGE_RECORDS": "未发现任何清理记录!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "导出 CVEs - {{number}} 个项目",
|
||||
"EXPORT_ALL_PROJECTS": "导出 CVEs - 全部项目",
|
||||
"EXPORT_SOME_PROJECTS": "导出 CVEs",
|
||||
"ALL_PROJECTS": "全部项目",
|
||||
"EXPORT_TITLE": "导出 CVE",
|
||||
"EXPORT_SUBTITLE": "设置导出条件",
|
||||
|
@ -1748,8 +1748,7 @@
|
||||
"NO_PURGE_RECORDS": "We couldn't find any purge histories!"
|
||||
},
|
||||
"CVE_EXPORT": {
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs - {{number}} project(s)",
|
||||
"EXPORT_ALL_PROJECTS": "Export CVEs - All projects",
|
||||
"EXPORT_SOME_PROJECTS": "Export CVEs",
|
||||
"ALL_PROJECTS": "All projects",
|
||||
"EXPORT_TITLE": "Export CVE",
|
||||
"EXPORT_SUBTITLE": "Set exporting conditions",
|
||||
|
Loading…
Reference in New Issue
Block a user