Skip to content
Snippets Groups Projects
Commit ad41c190 authored by Lukasz Lopatowski's avatar Lukasz Lopatowski
Browse files

Merge branch '221-improve-sorting-in-the-application-list-view' into 'release/1.6.4'

Resolve "Improve sorting in the application list view"

See merge request !32
parents e3468153 e74b9f98
Branches
Tags
3 merge requests!41Revert "Merge branch '221-improve-sorting-in-the-application-list-view' into 'release/1.6.4'",!39release/1.6.4 into develop,!32Resolve "Improve sorting in the application list view"
Showing
with 296 additions and 18 deletions
import {Injectable} from '@angular/core';
import {ApplicationBase} from '../../model/application-base';
import {HttpClient} from '@angular/common/http';
import {HttpClient, HttpParams} from '@angular/common/http';
import {AppConfigService} from '../../service';
import {BulkResponse} from '../../model/bulk-response';
import {Observable} from 'rxjs';
......@@ -89,5 +89,14 @@ export class AppdeploymentService {
return this.http.get(this.getUrl() + `app/csv/${id}`, {responseType: 'blob'})
}
public refreshStatesInBulkDeployment(id: number) : Observable<BulkDeployment> {
return this.http.get<BulkDeployment>(this.getUrl() + `refresh/${id}`)
}
public removeBulkDeployment(id: number, removeAll: boolean): Observable<void> {
let params = new HttpParams()
params = params.append('removeAll', removeAll)
return this.http.delete<void>(this.getUrl() + `${id}`, {params})
}
}
<app-bulk-list [header]="'BULK.APP.HEADER'" [bulks]="bulks" [mode]="mode"></app-bulk-list>
<app-bulk-list [header]="'BULK.APP.HEADER'"
[bulks]="bulks"
[mode]="mode"
(reloadBulks)="retrieveBulks()"
></app-bulk-list>
......@@ -20,6 +20,10 @@ export class BulkAppListComponent implements OnInit {
}
ngOnInit(): void {
this.retrieveBulks();
}
public retrieveBulks() {
if (this.authService.getRoles().find(value => value === 'ROLE_VL_MANAGER') !== undefined) {
this.deployService.getBulksAppDeploymentsOwner().subscribe(data => {
data = data.sort((a, b) => new Date(b.creationDate).getTime() - new Date(a.creationDate).getTime())
......
......@@ -103,7 +103,7 @@
<a (click)="getAppBulkDetails(bulk?.id)"> {{"BULK.APP.DOWNLOAD_CSV" | translate}}</a>
</li>
<li *ngIf="mode === bulkTypeApp">
<a (click)="modal.show()">{{ 'BULK.LIST.REMOVE' | translate }}</a>
<a (click)="modal.show(); setBulkToDelete(bulk)">{{ 'BULK.LIST.REMOVE' | translate }}</a>
</li>
</ul>
</span>
......@@ -130,9 +130,12 @@
<p-checkbox [(ngModel)]="removeAll" binary="true"></p-checkbox>
</div>
</div>
<div *ngIf="errorMessage !== ''" style="margin-top: 2rem; display: flex; justify-content: start; color: indianred">
<p>{{ 'BULK.REMOVE.ERROR' | translate}} {{errorMessage}}</p>
</div>
</div>
<div class="nmaas-modal-footer">
<button type="button" class="btn btn-secondary" (click)="modal.hide()">{{'APP_CHANGE_STATE_MODAL.CANCEL_BUTTON' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="false">{{'BULK.REMOVE.REMOVE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="removeBulkDeployment()" [disabled]="false">{{'BULK.REMOVE.REMOVE' | translate}}</button>
</div>
</nmaas-modal>
import {Component, Input, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {Component, EventEmitter, Input, Output, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {BulkDeployment} from '../../../model/bulk-deployment';
import {BulkType} from '../../../model/bulk-response';
import {SortableHeaderDirective} from '../../../service/sort-domain.directive';
......@@ -42,6 +42,12 @@ export class BulkListComponent {
public searchValue = '';
public removeAll = false;
public bulkToDelete;
public errorMessage = '';
@Output()
public reloadBulks = new EventEmitter<void>();
constructor(private appDeploy: AppdeploymentService,
private sanitizer: DomSanitizer) {
......@@ -147,4 +153,25 @@ export class BulkListComponent {
}
return null
}
public removeBulkDeployment() {
this.appDeploy.removeBulkDeployment(this.bulkToDelete.id, this.removeAll)
.subscribe({
next: (_) => {
this.reloadData()
this.modal.hide()
},
error: err => {
this.errorMessage = err.error.message
}
})
}
public setBulkToDelete(bulk: BulkDeployment) {
this.bulkToDelete = bulk
}
private reloadData() {
this.reloadBulks.emit()
}
}
......@@ -197,6 +197,8 @@
</div>
<div class="flex justify-content-end">
<button class="btn btn-primary mr-2" (click)="refreshStates()">{{'BULK.APP.REFRESH' | translate}}</button>
<button class="btn btn-primary" (click)="getAppBulkDetails(this.bulkId)">{{'BULK.APP.DOWNLOAD_CSV' | translate}}</button>
</div>
</div>
......@@ -233,9 +235,9 @@
<li *ngIf="response.type === 'APPLICATION' && response?.details['appInstanceId'] !== undefined">
<a [routerLink]="['/instances/', response?.details['appInstanceId']]">{{ 'BULK.LIST.MOVE_APP' | translate }}</a>
</li>
<li *ngIf="response.type === 'APPLICATION' && response.state !== 'COMPLETED'">
<!-- <li *ngIf="response.type === 'APPLICATION' && response.state !== 'COMPLETED'">
<a>{{ 'BULK.APP.CHECK_STATE' | translate }}</a>
</li>
</li> -->
</ul>
</span>
</td>
......
......@@ -115,4 +115,11 @@ export class BulkViewComponent implements OnInit, OnDestroy {
})
}
public refreshStates() {
this.deployService.refreshStatesInBulkDeployment(this.bulkId).subscribe( deply => {
this.bulk = deply;
console.log("Updated states of bulks")
})
}
}
......@@ -24,6 +24,9 @@ function compareAppsPopularity(a: ApplicationBase, b: ApplicationBase, stats: an
const bPop = stats[b.name] ? stats[b.name] : 0;
return (aPop - bPop) * -1; // desc
}
function compareAppsId(a: ApplicationBase, b: ApplicationBase): number {
return (a.id - b.id);
}
@Component({
selector: 'nmaas-applications-view',
......@@ -53,8 +56,8 @@ export class ApplicationsViewComponent implements OnInit, OnChanges {
public searchedAppName = '';
protected searchedTag = 'all';
public sortModeList = ['NONE', 'NAME', 'RATING', 'POPULAR'];
public sortMode = 'NONE';
public sortModeList = [ 'NAME', 'RATING', 'POPULAR', 'DATE'];
public sortMode = 'NAME';
private popStats: any = {};
......@@ -73,6 +76,7 @@ export class ApplicationsViewComponent implements OnInit, OnChanges {
this.popStats = data;
}
)
}
ngOnChanges(changes: SimpleChanges) {
......@@ -106,7 +110,7 @@ export class ApplicationsViewComponent implements OnInit, OnChanges {
}
this.applications = applications;
this.doSearch()
}
protected updateSelected() {
......@@ -165,6 +169,8 @@ export class ApplicationsViewComponent implements OnInit, OnChanges {
return [...apps].sort(compareAppsRating)
case 'POPULAR':
return [...apps].sort(popComp)
case 'DATE':
return [...apps].sort(compareAppsId)
default:
return apps
}
......
......@@ -51,16 +51,17 @@ import {DomainRolesDirective} from '../directive/domain-roles.directive';
import {SshKeysComponent} from './users/ssh-keys/ssh-keys.component';
import {NewSshKeyComponent} from './users/new-ssh-key/new-ssh-key.component';
import {ModalProvideSshKeyComponent} from './modal/modal-provide-ssh-key/modal-provide-ssh-key.component';
import { ContactComponent } from './contact/contact.component';
import {ContactComponent} from './contact/contact.component';
import {FormioModule} from '@formio/angular';
import { PreferencesComponent } from './users/preferences/preferences.component';
import {PreferencesComponent} from './users/preferences/preferences.component';
import {TooltipModule} from 'primeng/tooltip';
import {DropdownModule} from 'primeng/dropdown';
import {SortableHeaderDirective} from '../service/sort-domain.directive';
import {InputTextModule} from 'primeng/inputtext';
import { DomainNamespaceAnnotationsComponent } from './domain-namespace-annotations/domain-namespace-annotations.component';
import {DomainNamespaceAnnotationsComponent} from './domain-namespace-annotations/domain-namespace-annotations.component';
import { DEFAULT_PSM_OPTIONS } from 'angular-password-strength-meter/zxcvbn';
import {AccessTokensComponent} from './users/access-tokens/access-tokens.component';
@NgModule({
imports: [
......@@ -123,7 +124,8 @@ import { DEFAULT_PSM_OPTIONS } from 'angular-password-strength-meter/zxcvbn';
ContactComponent,
PreferencesComponent,
SortableHeaderDirective,
DomainNamespaceAnnotationsComponent
DomainNamespaceAnnotationsComponent,
AccessTokensComponent
],
providers: [
PasswordValidator,
......@@ -174,7 +176,8 @@ import { DEFAULT_PSM_OPTIONS } from 'angular-password-strength-meter/zxcvbn';
ModalProvideSshKeyComponent,
PreferencesComponent,
SortableHeaderDirective,
DomainNamespaceAnnotationsComponent
DomainNamespaceAnnotationsComponent,
AccessTokensComponent
]
})
export class SharedModule {
......
import {Observable} from 'rxjs';
import {AccessToken} from './access-token';
import {Injectable} from '@angular/core';
import {GenericDataService} from '../../../service/genericdata.service';
import {HttpClient} from '@angular/common/http';
import {AppConfigService} from '../../../service';
@Injectable({
providedIn: 'root'
})
export class AccessTokenService extends GenericDataService {
constructor(http: HttpClient, appConfig: AppConfigService) {
super(http, appConfig);
}
public getAll(): Observable<AccessToken[]> {
return this.http.get<AccessToken[]>(this.getUrl())
}
public invalidate(id: number): Observable<void> {
return this.http.put<void>(`${this.getUrl()}/${id}`, '')
}
public createToken(tokenName: string): Observable<AccessToken> {
return this.http.post<AccessToken>(this.getUrl(), tokenName)
}
private getUrl(): string {
return this.appConfig.getApiUrl() + '/tokens';
}
}
export class AccessToken {
public id: number
public name: string
public userId: number
public tokenValue: string
public valid: boolean
}
<div style="margin-bottom: 15px;" class="panel panel-default">
<div class="panel-heading">{{'TOKENS.HEADER' | translate}}</div>
<div class="panel-body">
<table class="table table-hover" aria-describedby="User access tokens table">
<thead>
<tr>
<th scope="col">{{'TOKENS.TABLE.ID' | translate}}</th>
<th scope="col">{{'TOKENS.TABLE.NAME' | translate}}</th>
<th scope="col">{{'TOKENS.TABLE.VALUE' | translate}}</th>
<th scope="col">{{'TOKENS.TABLE.VALID' | translate}}</th>
<th scope="col">{{'TOKENS.TABLE.ACTIONS' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let token of tokensList">
<td>{{token.id}}</td>
<td>{{token.name}}</td>
<td>{{token.tokenValue}}</td>
<td>{{token.valid}}</td>
<td>
<button type="button" class="btn btn-danger"
(click)="invalidate(token.id)">{{'TOKENS.BUTTON_INVALIDATE' | translate}}</button>
</td>
</tr>
<tr *ngIf="tokensList.length === 0">
<td colspan="3" style="text-align: center">{{'TOKENS.NO_TOKENS' | translate}}</td>
</tr>
</tbody>
</table>
<div>
<button type="button" class="btn btn-success"
(click)="modal.show()">{{'TOKENS.NEW_TOKEN' | translate}}</button>
</div>
</div>
</div>
<nmaas-modal styleModal="info">
<div class="nmaas-modal-header">{{'TOKENS.MODAL.HEADER' | translate}}</div>
<div class="nmaas-modal-body" style="height: 60%; max-height: 80vh;overflow-y: auto;">
<form [formGroup]="requestForm" (ngSubmit)="createNewToken()">
<div class="form-group">
<label class="control-label" for="new-token-name">
{{'TOKENS.MODAL.NAME' | translate}}:
</label>
<input id="new-token-name" type="text" class="form-control" formControlName="name">
</div>
<div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger">
<div *ngIf="name.errors.required">{{'SSH_KEYS.MODAL.ERROR.NAME_REQUIRED' | translate}}</div>
<div *ngIf="name.errors.minlength">{{'SSH_KEYS.MODAL.ERROR.NAME_MINLENGTH' | translate}}</div>
<div *ngIf="name.errors.maxlength">{{'SSH_KEYS.MODAL.ERROR.NAME_MAXLENGTH' | translate}}</div>
</div>
<input type="submit" class="btn btn-success" value="{{'SSH_KEYS.MODAL.BUTTON_ADD' | translate}}"
[disabled]="!requestForm.valid">
<button type="button" class="btn btn-primary pull-right"
(click)="modal.hide()">{{'SSH_KEYS.MODAL.BUTTON_CANCEL' | translate}}</button>
</form>
</div>
</nmaas-modal>
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {AccessTokensComponent} from './access-tokens.component';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {TranslateFakeLoader, TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {ReactiveFormsModule} from '@angular/forms';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
describe('AccessTokensComponent', () => {
let component: AccessTokensComponent;
let fixture: ComponentFixture<AccessTokensComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AccessTokensComponent],
imports: [
HttpClientTestingModule,
ReactiveFormsModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateFakeLoader
}
}),
],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
})
.compileComponents();
fixture = TestBed.createComponent(AccessTokensComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnInit, ViewChild} from '@angular/core';
import {Observable} from 'rxjs';
import {AccessToken} from './access-token';
import {AccessTokenService} from './access-token.service';
import {ModalComponent} from '../../modal';
import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms';
@Component({
selector: 'app-access-tokens',
templateUrl: './access-tokens.component.html',
styleUrls: ['./access-tokens.component.css']
})
export class AccessTokensComponent implements OnInit {
public tokens: Observable<AccessToken[]> = undefined;
public tokensList: AccessToken[] = [];
public requestForm: UntypedFormGroup = undefined;
public newTokenName = '';
@ViewChild(ModalComponent, {static: true})
public readonly modal: ModalComponent;
constructor(private tokenService: AccessTokenService,
private formBuilder: UntypedFormBuilder) {
}
ngOnInit() {
this.requestForm = this.formBuilder.group({
name: ['', [Validators.required, Validators.minLength(1), Validators.maxLength(16)]],
})
this.tokens = this.tokenService.getAll();
this.getData();
}
getData() {
this.tokensList = [];
this.tokens.subscribe(
data => this.tokensList.push(...data),
error => console.error(error)
)
}
invalidate(id: number) {
this.tokenService.invalidate(id).subscribe(
(_) => this.getData(),
error => console.error(error)
);
}
public createNewToken() {
this.tokenService.createToken(this.requestForm.value.name.trim()).subscribe({
next: val => {
this.tokensList.push(val)
this.requestForm.reset();
this.modal.hide();
},
error: err => console.warn(err)
})
}
get name() {
return this.requestForm.get('name');
}
}
<div style="margin-bottom: 120px;" class="panel panel-default">
<div style="margin-bottom: 15px;" class="panel panel-default">
<div class="panel-heading">{{'SSH_KEYS.HEADER' | translate}}</div>
<div class="panel-body">
<div class="flex justify-content-end mb-4">
......
<div id="welcome-container" class="container-fluid" style="margin-bottom: 20px;">
<div class="center-block center text-center"><a routerLink="/" data-toggle="tooltip" data-placement="right"
title="Network Management as a Service homepage"><img src="assets/images/logo.png" alt="logo"
style="margin: 5px" /></a>
title="Network Management as a Service homepage"><img src="assets/images/logo-small.png" alt="logo" width="280px"
style="margin-top: 30px" /></a>
</div>
<div class="card card-body col-lg-offset-2 col-md-offset-2 col-sm-offset-2
col-lg-8 col-md-8 col-sm-8 col-xs-12" style="margin-top: 15px; font-size: 14px;">
......
......@@ -34,5 +34,6 @@
[allowedModes]="[ComponentMode.PROFILVIEW, ComponentMode.EDIT]">
</nmaas-userprivileges>
<nmaas-ssh-keys></nmaas-ssh-keys>
<app-access-tokens></app-access-tokens>
</div>
</div>
......@@ -10,6 +10,14 @@ html, body {
.flex-column{
flex-direction: column;
}
.banner{
background-color : #424242;
color: #E8E8E8;
padding: 12px;
justify-self: center;
display: grid;
width: 100%;
}
.button{
text-decoration: none;
font-weight: bold;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment