CST-11048 FInished the angular implementation for the ldn inbox, needs cleanup

This commit is contained in:
Sondissimo
2023-09-26 11:12:19 +02:00
parent b040f9c2e7
commit 1c9fbd4629
16 changed files with 933 additions and 261 deletions

View File

@@ -4,6 +4,7 @@ import { I18nBreadcrumbResolver } from 'src/app/core/breadcrumbs/i18n-breadcrumb
import { LdnServicesOverviewComponent } from './ldn-services-directory/ldn-services-directory.component';
import { LdnServicesGuard } from './ldn-services-guard/ldn-services-guard.service';
import { LdnServiceNewComponent } from './ldn-service-new/ldn-service-new.component';
import { LdnServiceFormEditComponent } from './ldn-service-form-edit/ldn-service-form-edit.component';
@NgModule({
imports: [
@@ -22,6 +23,12 @@ import { LdnServiceNewComponent } from './ldn-service-new/ldn-service-new.compon
component: LdnServiceNewComponent,
data: { title: 'ldn-register-new-service.title', breadcrumbKey: 'ldn-register-new-service' }
},
{
path: 'edit/:serviceId',
resolve: { breadcrumb: I18nBreadcrumbResolver },
component: LdnServiceFormEditComponent,
data: { title: 'ldn-edit-service.title', breadcrumbKey: 'ldn-edit-service' }
},
]),
]
})

View File

@@ -5,6 +5,7 @@ import { LdnServicesOverviewComponent } from './ldn-services-directory/ldn-servi
import { SharedModule } from '../../shared/shared.module';
import { LdnServiceNewComponent } from './ldn-service-new/ldn-service-new.component';
import { LdnServiceFormComponent } from './ldn-service-form/ldn-service-form.component';
import { LdnServiceFormEditComponent } from './ldn-service-form-edit/ldn-service-form-edit.component';
@@ -18,6 +19,7 @@ import { LdnServiceFormComponent } from './ldn-service-form/ldn-service-form.com
LdnServicesOverviewComponent,
LdnServiceNewComponent,
LdnServiceFormComponent,
LdnServiceFormEditComponent,
]
})
export class AdminLdnServicesModule { }

View File

@@ -0,0 +1,117 @@
<form (ngSubmit)="submitForm()" [formGroup]="formModel">
<div class="form-group">
<!-- In the name section -->
<label for="name">{{ 'ldn-edit-service.form.label.name' | translate }}</label>
<input formControlName="name" id="name" name="name" placeholder="{{ 'ldn-edit-service.form.label.name' | translate }}" required
type="text">
</div>
<!-- In the description section -->
<div class="form-group">
<label for="description">{{ 'ldn-edit-service.form.label.description' | translate }}</label>
<input formControlName="description" id="description" name="description" placeholder="{{ 'ldn-edit-service.form.label.description' | translate }}"
required type="text">
</div>
<!-- In the url section -->
<div class="form-group">
<label for="url">{{ 'ldn-edit-service.form.label.url' | translate }}</label>
<input formControlName="url" id="url" name="url" placeholder="{{ 'ldn-edit-service.form.label.url' | translate }}" required type="text">
</div>
<!-- In the ldnUrl section -->
<div class="form-group">
<label for="ldnUrl">{{ 'ldn-edit-service.form.label.ldnUrl' | translate }}</label>
<input formControlName="ldnUrl" id="ldnUrl" name="ldnUrl" placeholder="{{ 'ldn-edit-service.form.label.ldnUrl' | translate }}"
required type="text">
</div>
<!-- In the Inbound Patterns section -->
<div *ngFor="let patternGroup of formModel.get('notifyServiceInboundPatterns')['controls']; let i = index" class="form-group"
formGroupName="notifyServiceInboundPatterns">
<ng-container [formGroupName]="i">
<label for="additionalInboundPattern{{i}}">{{ 'ldn-edit-service.form.label.inboundPattern' | translate }} {{ i + 1 }}</label>
<select #inboundPattern formControlName="pattern" id="additionalInboundPattern{{i}}"
name="additionalInboundPattern{{i}}" required>
<option value="">{{ 'ldn-edit-service.form.label.noInboundPatternSelected' | translate }}</option>
<option *ngFor="let pattern of inboundPatterns" [ngValue]="pattern.name">{{ pattern.name }}</option>
</select>
<div *ngIf="inboundPattern.value" class="form-group">
<label for="constraint{{i}}">{{ 'ldn-edit-service.form.label.selectedItemFilter' | translate }}</label>
<select formControlName="constraint" id="constraint{{i}}" name="constraint{{i}}">
<option value="">{{ 'ldn-edit-service.form.label.selectItemFilter' | translate }}</option>
<option *ngFor="let itemFilter of itemFilterList"
[value]="itemFilter.name">{{ itemFilter.name }}</option>
</select>
<div class="form-group">
<label for="automatic{{i}}">{{ 'ldn-edit-service.form.label.automatic' | translate }}</label>
<input type="checkbox" formControlName="automatic" id="automatic{{i}}" name="automatic{{i}}">
</div>
</div>
<button *ngIf="i > 0" (click)="removeInboundPattern(patternGroup)" class="btn btn-outline-dark">
<i class="fas fa-trash"></i>
</button>
</ng-container>
</div>
<span (click)="addInboundPattern()" class="add-pattern-link">{{ 'ldn-edit-service.form.label.addInboundPattern' | translate }}</span>
<!-- In the Outbound Patterns section -->
<div *ngFor="let patternGroup of formModel.get('notifyServiceOutboundPatterns')['controls']; let i = index" class="form-group"
formGroupName="notifyServiceOutboundPatterns">
<ng-container [formGroupName]="i">
<label for="additionalOutboundPattern{{i}}">{{ 'ldn-edit-service.form.label.outboundPattern' | translate }} {{ i + 1 }}</label>
<select #outboundPattern formControlName="pattern" id="additionalOutboundPattern{{i}}" name="additionalOutboundPattern{{i}}"
required>
<option value="">{{ 'ldn-edit-service.form.label.noOutboundPatternSelected' | translate }}</option>
<option *ngFor="let pattern of outboundPatterns" [ngValue]="pattern.name">{{ pattern.name }}</option>
</select>
<div *ngIf="outboundPattern.value" class="form-group">
<label for="constraint{{i}}">{{ 'ldn-edit-service.form.label.selectedItemFilter' | translate }}</label>
<select formControlName="constraint" id="constraint{{i}}" name="constraint{{i}}">
<option value="">{{ 'ldn-edit-service.form.label.selectItemFilter' | translate }}</option>
<option *ngFor="let itemFilter of itemFilterList"
[value]="itemFilter.name">{{ itemFilter.name }}</option>
</select>
</div>
<button *ngIf="i > 0" (click)="removeOutboundPattern(patternGroup)" class="btn btn-outline-dark">
<i class="fas fa-trash"></i>
</button>
</ng-container>
</div>
<span (click)="addOutboundPattern()" class="add-pattern-link">{{ 'ldn-edit-service.form.label.addOutboundPattern' | translate }}</span>
<button class="btn btn-primary" type="submit">{{ 'ldn-edit-service.form.label.submit' | translate }}</button>
</form>

View File

@@ -0,0 +1,51 @@
form {
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 auto;
max-width: 600px;
font-size: 14px;
}
.form-group input[type="text"],
.form-group select {
max-width: 100%;
width: 100%;
padding: 8px;
margin-bottom: 5px;
box-sizing: border-box;
font-size: 14px;
}
.description {
height: 9em;
width: 100%;
}
.form-group select {
position: relative;
z-index: 1;
}
.form-group select option {
font-weight: bold;
}
.add-pattern-link{
color: #0048ff;
cursor: pointer;
margin-left: 10px;
}
.remove-pattern-link{
color: #e34949;
cursor: pointer;
margin-left: 10px;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LdnServiceFormEditComponent } from './ldn-service-form-edit.component';
describe('LdnServiceFormEditComponent', () => {
let component: LdnServiceFormEditComponent;
let fixture: ComponentFixture<LdnServiceFormEditComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LdnServiceFormEditComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(LdnServiceFormEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,250 @@
import { Component, Input } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { LDN_SERVICE } from '../ldn-services-model/ldn-service.resource-type';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { LdnDirectoryService } from '../ldn-services-services/ldn-directory.service';
import { LdnServicesService } from '../ldn-services-data/ldn-services-data.service';
import { LdnServiceConstraint } from '../ldn-services-model/ldn-service-constraint.model';
import { notifyPatterns } from '../ldn-services-patterns/ldn-service-coar-patterns';
import { ActivatedRoute } from '@angular/router'; // Import ActivatedRoute and Params
@Component({
selector: 'ds-ldn-service-form-edit',
templateUrl: './ldn-service-form-edit.component.html',
styleUrls: ['./ldn-service-form-edit.component.scss']
})
export class LdnServiceFormEditComponent {
formModel: FormGroup;
showItemFilterDropdown = false;
public inboundPatterns: object[] = notifyPatterns;
public outboundPatterns: object[] = notifyPatterns;
public itemFilterList: LdnServiceConstraint[];
@Input() public name: string;
@Input() public description: string;
@Input() public url: string;
@Input() public ldnUrl: string;
@Input() public inboundPattern: string;
@Input() public outboundPattern: string;
@Input() public constraint: string;
@Input() public automatic: boolean;
@Input() public headerKey: string;
private serviceId: string;
constructor(
private ldnServicesService: LdnServicesService,
private ldnDirectoryService: LdnDirectoryService,
private formBuilder: FormBuilder,
private http: HttpClient,
private router: Router,
private route: ActivatedRoute
) {
this.formModel = this.formBuilder.group({
id: [''],
name: ['', Validators.required],
description: ['', Validators.required],
url: ['', Validators.required],
ldnUrl: ['', Validators.required],
inboundPattern: [''],
outboundPattern: [''],
constraintPattern: [''],
notifyServiceInboundPatterns: this.formBuilder.array([this.createInboundPatternFormGroup()]),
notifyServiceOutboundPatterns: this.formBuilder.array([this.createOutboundPatternFormGroup()]),
type: LDN_SERVICE.value,
});
}
ngOnInit(): void {
this.route.params.subscribe((params) => {
this.serviceId = params.serviceId;
if (this.serviceId) {
this.fetchServiceData(this.serviceId);
}
});
this.ldnDirectoryService.getItemFilters().subscribe((itemFilters) => {
console.log(itemFilters);
this.itemFilterList = itemFilters._embedded.itemfilters.map((filter: { id: string; }) => ({
name: filter.id
}));
console.log(this.itemFilterList);
});
}
fetchServiceData(serviceId: string): void {
const apiUrl = `http://localhost:8080/server/api/ldn/ldnservices/${serviceId}`;
this.http.get(apiUrl).subscribe(
(data: any) => {
console.log(data);
this.formModel.patchValue({
id: data.id,
name: data.name,
description: data.description,
url: data.url,
ldnUrl: data.ldnUrl,
notifyServiceInboundPatterns: data.notifyServiceInboundPatterns,
notifyServiceOutboundPatterns: data.notifyServiceOutboundPatterns
});
},
(error) => {
console.error('Error fetching service data:', error);
}
);
}
generatePatchOperations(): any[] {
const patchOperations: any[] = [];
if (this.formModel.get('name').dirty) {
patchOperations.push({
op: 'replace',
path: '/name',
value: this.formModel.get('name').value,
});
}
if (this.formModel.get('description').dirty) {
patchOperations.push({
op: 'replace',
path: '/description',
value: this.formModel.get('description').value,
});
}
if (this.formModel.get('ldnUrl').dirty) {
patchOperations.push({
op: 'replace',
path: '/ldnUrl',
value: this.formModel.get('ldnUrl').value,
});
}
if (this.formModel.get('url').dirty) {
patchOperations.push({
op: 'replace',
path: '/url',
value: this.formModel.get('url').value,
});
}
const inboundPatternsArray = this.formModel.get('notifyServiceInboundPatterns') as FormArray;
const inboundPatternsControls = inboundPatternsArray.controls;
if (inboundPatternsArray.dirty) {
const inboundPatternsValue = [];
for (let i = 0; i < inboundPatternsControls.length; i++) {
const patternGroup = inboundPatternsControls[i] as FormGroup;
const patternValue = patternGroup.value;
if (patternGroup.dirty) {
inboundPatternsValue.push(patternValue);
}
}
if (inboundPatternsValue.length > 0) {
patchOperations.push({
op: 'replace',
path: '/notifyServiceInboundPatterns',
value: inboundPatternsValue,
});
} else {
patchOperations.push({
op: 'remove',
path: '/notifyServiceInboundPatterns',
});
}
}
const outboundPatternsArray = this.formModel.get('notifyServiceOutboundPatterns') as FormArray;
const outboundPatternsControls = outboundPatternsArray.controls;
if (outboundPatternsArray.dirty) {
const outboundPatternsValue = [];
for (let i = 0; i < outboundPatternsControls.length; i++) {
const patternGroup = outboundPatternsControls[i] as FormGroup;
const patternValue = patternGroup.value;
if (patternGroup.dirty) {
outboundPatternsValue.push(patternValue);
}
}
if (outboundPatternsValue.length > 0) {
patchOperations.push({
op: 'replace',
path: '/notifyServiceOutboundPatterns',
value: outboundPatternsValue,
});
} else {
patchOperations.push({
op: 'remove',
path: '/notifyServiceOutboundPatterns',
});
}
}
return patchOperations;
}
submitForm() {
const apiUrl = `http://localhost:8080/server/api/ldn/ldnservices/${this.serviceId}`;
const patchOperations = this.generatePatchOperations();
this.http.patch(apiUrl, patchOperations).subscribe(
(response) => {
console.log('Service updated successfully:', response);
},
(error) => {
console.error('Error updating service:', error);
}
);
}
addInboundPattern() {
const notifyServiceInboundPatternsArray = this.formModel.get('notifyServiceInboundPatterns') as FormArray;
notifyServiceInboundPatternsArray.push(this.createInboundPatternFormGroup());
}
removeInboundPattern(patternGroup: FormGroup) {
const notifyServiceInboundPatternsArray = this.formModel.get('notifyServiceInboundPatterns') as FormArray;
notifyServiceInboundPatternsArray.removeAt(notifyServiceInboundPatternsArray.controls.indexOf(patternGroup));
}
addOutboundPattern() {
const notifyServiceOutboundPatternsArray = this.formModel.get('notifyServiceOutboundPatterns') as FormArray;
notifyServiceOutboundPatternsArray.push(this.createOutboundPatternFormGroup());
}
removeOutboundPattern(patternGroup: FormGroup) {
const notifyServiceOutboundPatternsArray = this.formModel.get('notifyServiceOutboundPatterns') as FormArray;
notifyServiceOutboundPatternsArray.removeAt(notifyServiceOutboundPatternsArray.controls.indexOf(patternGroup));
}
private sendBack() {
this.router.navigateByUrl('admin/ldn/services');
}
private createOutboundPatternFormGroup(): FormGroup {
return this.formBuilder.group({
pattern: '',
constraint: ''
});
}
private createInboundPatternFormGroup(): FormGroup {
return this.formBuilder.group({
pattern: '',
constraint: '',
automatic: ''
});
}
}

View File

@@ -1,96 +1,136 @@
<form (ngSubmit)="submitForm()" [formGroup]="formModel">
<div class="form-group">
<!-- In the name section -->
<label for="name">Name</label>
<input formControlName="name" id="name" name="name" placeholder="Please provide service name" required
<!-- In the name section -->
<div class="mb-2">
<label for="name">{{ 'ldn-new-service.form.label.name' | translate }}</label>
<input [placeholder]="'ldn-new-service.form.placeholder.name' | translate"
formControlName="name" id="name" name="name"
[class.invalid-field]="formModel.get('name').invalid && formModel.get('name').touched"
type="text">
</div>
<!-- In the description section -->
<div class="form-group">
<label for="description">Description</label>
<input formControlName="description" id="description" name="description" placeholder="Please provide a description regarding your service"
required type="text">
<div class="mb-2 d-flex flex-column">
<label for="description">{{ 'ldn-new-service.form.label.description' | translate }}</label>
<textarea [placeholder]="'ldn-new-service.form.placeholder.description' | translate"
formControlName="description" id="description" name="description"></textarea>
</div>
<!-- In the url section -->
<div class="form-group">
<label for="url">Service URL</label>
<input formControlName="url" id="url" name="url" placeholder="Please input the URL for users to check out more information about the service"
required type="text">
<div class="mb-2">
<label for="url">{{ 'ldn-new-service.form.label.url' | translate }}</label>
<input [placeholder]="'ldn-new-service.form.placeholder.url' | translate"
formControlName="url" id="url" name="url"
[class.invalid-field]="formModel.get('url').invalid && formModel.get('url').touched"
type="text">
</div>
<!-- In the ldnUrl section -->
<div class="form-group">
<label for="ldnUrl">LDN Inbox URL</label>
<input formControlName="ldnUrl" id="ldnUrl" name="ldnUrl" placeholder="Please specify the URL of the LDN Inbox"
required type="text">
<div class="mb-2">
<label for="ldnUrl">{{ 'ldn-new-service.form.label.ldnUrl' | translate }}</label>
<input [placeholder]="'ldn-new-service.form.placeholder.ldnUrl' | translate"
formControlName="ldnUrl" id="ldnUrl" name="ldnUrl"
[class.invalid-field]="formModel.get('ldnUrl').invalid && formModel.get('ldnUrl').touched"
type="text">
</div>
<!-- In the Inbound Patterns section -->
<div *ngFor="let patternGroup of formModel.get('notifyServiceInboundPatterns')['controls']; let i = index" class="form-group"
formGroupName="notifyServiceInboundPatterns">
<div *ngFor="let patternGroup of formModel.get('notifyServiceInboundPatterns')['controls']; let i = index"
class="mb-2 d-flex align-content-center" formGroupName="notifyServiceInboundPatterns">
<ng-container [formGroupName]="i">
<label for="additionalInboundPattern{{i}}">Inbound Pattern {{i + 1}}</label>
<div class="flex-grow-1">
<label for="additionalInboundPattern{{i}}">{{ 'ldn-new-service.form.label.inboundPattern' | translate }} {{ i + 1 }}</label>
<select #inboundPattern formControlName="pattern" id="additionalInboundPattern{{i}}"
name="additionalInboundPattern{{i}}" required>
<select #inboundPattern formControlName="pattern" id="additionalInboundPattern{{i}}"
name="additionalInboundPattern{{i}}" required>
<option disabled value="">Select an Additional Inbound Pattern</option>
<option *ngFor="let pattern of inboundPatterns" [ngValue]="pattern.name">{{ pattern.name }}</option>
<option value="">{{ 'ldn-new-service.form.label.placeholder.inboundPattern' | translate }}</option>
<option *ngFor="let pattern of inboundPatterns" [ngValue]="pattern.name">{{ pattern.name }}</option>
</select>
</select>
</div>
<div *ngIf="inboundPattern.value" class="form-group">
<ng-container *ngIf="inboundPattern.value">
<label for="constraint{{i}}">Selected Item Filter</label>
<div class="ml-2 flex-grow-1">
<label for="constraint{{i}}">{{ 'ldn-new-service.form.label.ItemFilter' | translate }}</label>
<select formControlName="constraint" id="constraint{{i}}" name="constraint{{i}}">
<option value="">{{ 'ldn-new-service.form.label.placeholder.selectedItemFilter' | translate }}</option>
<option *ngFor="let itemFilter of itemFilterList"
[value]="itemFilter.name">{{ itemFilter.name }}</option>
</select>
</div>
<div class="ml-2 d-flex flex-column align-content-center">
<label for="automatic{{i}}">{{ 'ldn-new-service.form.label.automatic' | translate }}</label>
<div class="d-flex flex-grow-1 align-content-center justify-content-center">
<input formControlName="automatic" id="automatic{{i}}" name="automatic{{i}}" type="checkbox">
</div>
</div>
</ng-container>
<div class="d-flex align-items-end justify-content-center">
<button (click)="removeInboundPattern(patternGroup)" *ngIf="i > 0" class="btn btn-outline-dark trash-button ml-2">
<i class="fas fa-trash"></i>
</button>
</div>
</ng-container>
</div>
<span (click)="addInboundPattern()"
class="add-pattern-link mb-2">{{ 'ldn-new-service.form.label.addPattern' | translate }}</span>
<!-- In the Outbound Patterns section -->
<div *ngFor="let patternGroup of formModel.get('notifyServiceOutboundPatterns')['controls']; let i = index"
class="mb-2 d-flex align-content-center" formGroupName="notifyServiceOutboundPatterns">
<ng-container [formGroupName]="i">
<div class="flex-grow-1">
<label for="additionalOutboundPattern{{i}}">{{ 'ldn-new-service.form.label.outboundPattern' | translate }} {{ i + 1 }}</label>
<select #outboundPattern formControlName="pattern" id="additionalOutboundPattern{{i}}"
name="additionalOutboundPattern{{i}}"
required>
<option value="">{{ 'ldn-new-service.form.label.placeholder.outboundPattern' | translate }}</option>
<option *ngFor="let pattern of outboundPatterns"
[ngValue]="pattern.name">{{ pattern.name }}</option>
</select>
</div>
<div *ngIf="outboundPattern.value" class="ml-2 flex-grow-1">
<label for="constraint{{i}}">{{ 'ldn-new-service.form.label.ItemFilter' | translate }}</label>
<select formControlName="constraint" id="constraint{{i}}" name="constraint{{i}}">
<option disabled value="">Select an Item Filter</option>
<option value="">{{ 'ldn-new-service.form.label.placeholder.selectedItemFilter' | translate }}</option>
<option *ngFor="let itemFilter of itemFilterList"
[value]="itemFilter.name">{{ itemFilter.name }}</option>
</select>
</div>
<span *ngIf="i > 0" (click)="removeInboundPattern(patternGroup)" class="remove-pattern-link">- Remove</span>
<div class="d-flex align-items-end justify-content-center">
<button (click)="removeOutboundPattern(patternGroup)" *ngIf="i > 0" class="btn btn-outline-dark trash-button ml-2">
<i class="fas fa-trash"></i>
</button>
</div>
</ng-container>
</div>
<span (click)="addInboundPattern()" class="add-pattern-link">+ Add more</span>
<!-- In the Outbound Patterns section -->
<div *ngFor="let patternGroup of formModel.get('notifyServiceOutboundPatterns')['controls']; let i = index" class="form-group"
formGroupName="notifyServiceOutboundPatterns">
<ng-container [formGroupName]="i">
<label for="additionalOutboundPattern{{i}}">Outbound Pattern {{i + 1}}</label>
<select formControlName="pattern" id="additionalOutboundPattern{{i}}" name="additionalOutboundPattern{{i}}"
required>
<option disabled value="">Select an Additional Outbound Pattern</option>
<option *ngFor="let pattern of outboundPatterns" [ngValue]="pattern.name">{{ pattern.name }}</option>
</select>
<span *ngIf="i > 0" (click)="removeOutboundPattern(patternGroup)" class="remove-pattern-link">- Remove</span>
</ng-container>
</div>
<span (click)="addOutboundPattern()" class="add-pattern-link">+ Add more</span>
<span (click)="addOutboundPattern()"
class="add-pattern-link mb-2">{{ 'ldn-new-service.form.label.addPattern' | translate }}
</span>
<button class="btn btn-primary" type="submit">Submit</button>
</form>

View File

@@ -1,41 +1,38 @@
form {
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 auto;
max-width: 600px;
max-width: 800px;
font-size: 14px;
margin-left: 300px;
& > * {
width: 100%;
}
}
.form-group input[type="text"],
.form-group select {
input[type="text"],
select {
max-width: 100%;
width: 100%;
padding: 8px;
margin-bottom: 5px;
box-sizing: border-box;
font-size: 14px;
}
.description {
height: 9em;
width: 100%;
}
.form-group select {
position: relative;
z-index: 1;
}
.form-group select option {
option:not(:first-child) {
font-weight: bold;
}
.trash-button {
width: 40px;
height: 40px;
}
textarea {
height: 200px;
resize: none;
}
.add-pattern-link{
color: #0048ff;
cursor: pointer;
@@ -47,5 +44,13 @@ form {
margin-left: 10px;
}
.status-checkbox {
margin-top: 5px;
}
.invalid-field {
border: 1px solid red;
background-color: #e89f9f;
color: #000000;
}

View File

@@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { LdnServicesService } from '../ldn-services-data/ldn-services-data.service';
@@ -17,14 +17,17 @@ import { LDN_SERVICE } from '../ldn-services-model/ldn-service.resource-type';
export class LdnServiceFormComponent implements OnInit {
formModel: FormGroup;
showItemFilterDropdown = false;
//showItemFilterDropdown = false;
public inboundPatterns: object[] = notifyPatterns;
public outboundPatterns: object[] = notifyPatterns;
public itemFilterList: LdnServiceConstraint[];
additionalOutboundPatterns: FormGroup[] = [];
additionalInboundPatterns: FormGroup[] = [];
//additionalOutboundPatterns: FormGroup[] = [];
//additionalInboundPatterns: FormGroup[] = [];
//@Input() public status: boolean;
@Input() public name: string;
@Input() public description: string;
@Input() public url: string;
@@ -36,6 +39,12 @@ export class LdnServiceFormComponent implements OnInit {
@Input() public headerKey: string;
/*
get notifyServiceInboundPatternsFormArray(): FormArray {
return this.formModel.get('notifyServiceInboundPatterns') as FormArray;
}
*/
constructor(
private ldnServicesService: LdnServicesService,
private ldnDirectoryService: LdnDirectoryService,
@@ -45,9 +54,10 @@ export class LdnServiceFormComponent implements OnInit {
) {
this.formModel = this.formBuilder.group({
//enabled: true,
id: [''],
name: ['', Validators.required],
description: ['', Validators.required],
description: [''],
url: ['', Validators.required],
ldnUrl: ['', Validators.required],
inboundPattern: [''],
@@ -65,12 +75,23 @@ export class LdnServiceFormComponent implements OnInit {
this.itemFilterList = itemFilters._embedded.itemfilters.map((filter: { id: string; }) => ({
name: filter.id
}));
console.log(this.itemFilterList);
});
}
submitForm() {
this.formModel.get('name').markAsTouched();
this.formModel.get('url').markAsTouched();
this.formModel.get('ldnUrl').markAsTouched();
const name = this.formModel.get('name').value;
const url = this.formModel.get('url').value;
const ldnUrl = this.formModel.get('ldnUrl').value;
if (!name || !url || !ldnUrl) {
return;
}
this.formModel.removeControl('inboundPattern');
this.formModel.removeControl('outboundPattern');
this.formModel.removeControl('constraintPattern');
@@ -78,7 +99,7 @@ export class LdnServiceFormComponent implements OnInit {
const apiUrl = 'http://localhost:8080/server/api/ldn/ldnservices';
this.http.post(apiUrl, this.formModel.value ).subscribe(
this.http.post(apiUrl, this.formModel.value).subscribe(
(response) => {
console.log('Service created successfully:', response);
this.formModel.reset();
@@ -90,16 +111,6 @@ export class LdnServiceFormComponent implements OnInit {
);
}
private validateForm(form: FormGroup): boolean {
let valid = true;
Object.keys(form.controls).forEach((key) => {
if (form.controls[key].invalid) {
form.controls[key].markAsDirty();
valid = false;
}
});
return valid;
}
private sendBack() {
this.router.navigateByUrl('admin/ldn/services');
@@ -136,7 +147,8 @@ export class LdnServiceFormComponent implements OnInit {
return this.formBuilder.group({
pattern: [''],
constraint: [''],
automatic: [true]
automatic: false
});
}
}

View File

@@ -1,85 +1,81 @@
<div class="container">
{{ldnServicesRD$ | async | json }}
<div class="d-flex">
<h2 class="flex-grow-1">{{'ldn-registered-services.title' | translate}}</h2>
</div>
<div class="d-flex justify-content-end">
<button *ngIf="ldnServicesBulkDeleteService.hasSelected()" class="btn btn-primary mr-2"
(click)="ldnServicesBulkDeleteService.clearAllServices()"><i
class="fas fa-undo pr-2"></i>{{'process.overview.delete.clear' | translate }}
</button>
<button *ngIf="ldnServicesBulkDeleteService.hasSelected()" class="btn btn-danger mr-2"
(click)="openDeleteModal(deleteModal)"><i
class="fas fa-trash pr-2"></i>{{'process.overview.delete' | translate: {count: ldnServicesBulkDeleteService.getAmountOfSelectedServices()} }}
</button>
<button class="btn btn-success" routerLink="/admin/ldn/services/new"><i
class="fas fa-plus pr-2"></i>{{'process.overview.new' | translate}}</button>
</div>
<ds-pagination *ngIf="(ldnServicesRD$ | async)?.payload?.totalElements > 0"
[paginationOptions]="pageConfig"
[pageInfoState]="(ldnServicesRD$ | async)?.payload"
[collectionSize]="(ldnServicesRD$ | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{'service.overview.table.name' | translate}}</th>
<th scope="col">{{'service.overview.table.description' | translate}}</th>
<th scope="col">{{'service.overview.table.status' | translate}}</th>
<th scope="col">{{'service.overview.table.actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let ldnService of (ldnServicesRD$ | async)?.payload?.page"
[class.table-danger]="ldnServicesBulkDeleteService.isToBeDeleted(ldnService.id)">
<td><a [routerLink]="['/ldn-services/', ldnService.id]">{{ldnService.id}}</a></td>
<td>{{ldnService.description}}</td>
<td>
<button class="btn btn-outline-danger"
(click)="ldnServicesBulkDeleteService.toggleDelete(ldnService.id)"><i
class="fas fa-trash"></i></button>
</td>
</tr>
</tbody>
</table>
{{ ldnServicesRD$ | async | json }}
<div class="d-flex">
<h2 class="flex-grow-1">{{ 'ldn-registered-services.title' | translate }}</h2>
</div>
</ds-pagination>
<div class="d-flex justify-content-end">
<button class="btn btn-success" routerLink="/admin/ldn/services/new"><i
class="fas fa-plus pr-2"></i>{{ 'process.overview.new' | translate }}</button>
</div>
<ds-pagination *ngIf="(ldnServicesRD$ | async)?.payload?.totalElements > 0"
[collectionSize]="(ldnServicesRD$ | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true"
[pageInfoState]="(ldnServicesRD$ | async)?.payload"
[paginationOptions]="pageConfig">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ 'service.overview.table.name' | translate }}</th>
<th scope="col">{{ 'service.overview.table.description' | translate }}</th>
<th scope="col">{{ 'service.overview.table.status' | translate }}</th>
<th scope="col">{{ 'service.overview.table.actions' | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let ldnService of servicesData">
<td>{{ ldnService.name }}</td>
<td>{{ ldnService.description }}</td>
<td>
<span [ngClass]="{ 'status-enabled': ldnService.status, 'status-disabled': !ldnService.status }"
class="status-indicator" (click)="toggleStatus(ldnService)"
[title]="ldnService.status ? ('ldn-service.overview.table.clickToDisable' | translate) : ('ldn-service.overview.table.clickToEnable' | translate)">
{{ ldnService.status ? ('ldn-service.overview.table.enabled' | translate) : ('ldn-service.overview.table.disabled' | translate) }}
</span>
</td>
<td>
<button (click)="selectServiceToDelete(ldnService.id)" class="btn btn-outline-danger">
<i class="fas fa-trash"></i>
</button>
<button [routerLink]="['/admin/ldn/services/edit/', ldnService.id]"
class="btn btn-outline-dark">
<i class="fas fa-edit"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
</div>
<ng-template #deleteModal>
<div>
<div>
<div class="modal-header">
<div>
<h4>{{'process.overview.delete.header' | translate }}</h4>
</div>
<button type="button" class="close"
(click)="closeModal()" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<div class="modal-header">
<div>
<h4>{{'service.overview.delete.header' | translate }}</h4>
</div>
<button (click)="closeModal()" aria-label="Close"
class="close" type="button">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div>
{{ 'service.overview.delete.body' | translate }}
</div>
<div class="mt-4">
<button (click)="closeModal()"
class="btn btn-primary mr-2">{{ 'service.detail.delete.cancel' | translate }}</button>
<button (click)="deleteSelected()" class="btn btn-danger"
id="delete-confirm">{{ 'service.overview.delete' | translate }}
</button>
</div>
</div>
</div>
<div class="modal-body">
<div *ngIf="!(ldnServicesBulkDeleteService.isProcessing$() |async)">{{'process.overview.delete.body' | translate: {count: ldnServicesBulkDeleteService.getAmountOfSelectedServices()} }}</div>
<div *ngIf="ldnServicesBulkDeleteService.isProcessing$() |async" class="alert alert-info">
<span class="spinner-border spinner-border-sm spinner-button" role="status" aria-hidden="true"></span>
<span> {{ 'process.overview.delete.processing' | translate: {count: ldnServicesBulkDeleteService.getAmountOfSelectedServices()} }}</span>
</div>
<div class="mt-4">
<button class="btn btn-primary mr-2" [disabled]="ldnServicesBulkDeleteService.isProcessing$() |async"
(click)="closeModal()">{{'process.detail.delete.cancel' | translate}}</button>
<button id="delete-confirm" class="btn btn-danger"
[disabled]="ldnServicesBulkDeleteService.isProcessing$() |async"
(click)="deleteSelected()">{{ 'process.overview.delete' | translate: {count: ldnServicesBulkDeleteService.getAmountOfSelectedServices()} }}
</button>
</div>
</div>
</div>
</ng-template>

View File

@@ -0,0 +1,29 @@
.status-indicator {
padding: 2.5px 25px 2.5px 25px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.5s;
}
.status-enabled {
background-color: #daf7a6;
color: #4f5359;
font-size: 85%;
font-weight: bold;
}
.status-enabled:hover {
background-color: #faa0a0;
}
.status-disabled {
background-color: #faa0a0;
color: #4f5359;
font-size: 85%;
font-weight: bold;
}
.status-disabled:hover {
background-color: #daf7a6;
}

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { LdnDirectoryService } from '../ldn-services-services/ldn-directory.service';
import { Observable, Subscription } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
@@ -10,96 +10,152 @@ import { switchMap } from 'rxjs/operators';
import { LdnServicesService } from 'src/app/admin/admin-ldn-services/ldn-services-data/ldn-services-data.service';
import { PaginationService } from 'src/app/core/pagination/pagination.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { LdnServicesBulkDeleteService } from 'src/app/admin/admin-ldn-services/ldn-services-services/ldn-service-bulk-delete.service';
import { hasValue } from '../../../shared/empty.util';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'ds-ldn-services-directory',
templateUrl: './ldn-services-directory.component.html',
styleUrls: ['./ldn-services-directory.component.scss'],
selector: 'ds-ldn-services-directory',
templateUrl: './ldn-services-directory.component.html',
styleUrls: ['./ldn-services-directory.component.scss'],
})
export class LdnServicesOverviewComponent implements OnInit, OnDestroy {
ldnServicesRD$: Observable<RemoteData<PaginatedList<LdnService>>>;
config: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 20
});
pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'po',
pageSize: 20
});
private modalRef: any;
isProcessingSub: Subscription;
selectedServiceId: number | null = null;
servicesData: any[] = [];
@ViewChild('deleteModal', {static: true}) deleteModal: TemplateRef<any>;
ldnServicesRD$: Observable<RemoteData<PaginatedList<LdnService>>>;
config: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 20
});
pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'po',
pageSize: 20
});
isProcessingSub: Subscription;
private modalRef: any;
constructor(
protected processLdnService: LdnServicesService,
protected paginationService: PaginationService,
protected modalService: NgbModal,
public ldnServicesBulkDeleteService: LdnServicesBulkDeleteService,
public ldnDirectoryService: LdnDirectoryService,
private http: HttpClient
) {}
ngOnInit(): void {
this.setLdnServices();
this.ldnDirectoryService.listLdnServices();
this.searchByLdnUrl();
}
setLdnServices() {
debugger;
this.ldnServicesRD$ = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe(
switchMap((config) => this.processLdnService.findAll(config, true, false))
);
console.log()
}
ngOnDestroy(): void {
this.paginationService.clearPagination(this.pageConfig.id);
if (hasValue(this.isProcessingSub)) {
this.isProcessingSub.unsubscribe();
constructor(
protected processLdnService: LdnServicesService,
protected paginationService: PaginationService,
protected modalService: NgbModal,
public ldnDirectoryService: LdnDirectoryService,
private http: HttpClient
) {
}
}
openDeleteModal(content) {
this.modalRef = this.modalService.open(content);
}
ngOnInit(): void {
/*this.ldnDirectoryService.listLdnServices();*/
this.findAllServices();
this.setLdnServices();
/*this.ldnServicesRD$.subscribe(data => {
console.log('searchByLdnUrl()', data);
});*/
closeModal() {
this.modalRef.close();
}
/*this.ldnServicesRD$.pipe(
tap(data => {
console.log('ldnServicesRD$ data:', data);
})
).subscribe(() => {
this.searchByLdnUrl();
});*/
}
findByLdnUrl(): Observable<any> {
const url = 'http://localhost:8080/server/api/ldn/ldnservices';
setLdnServices() {
this.ldnServicesRD$ = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe(
switchMap((config) => this.processLdnService.findAll(config, true, false))
);
console.log();
}
return this.http.get(url);
}
searchByLdnUrl(): void {
this.findByLdnUrl().subscribe(
(response) => {
console.log('Search results:', response);
},
(error) => {
console.error('Error:', error);
ngOnDestroy(): void {
this.paginationService.clearPagination(this.pageConfig.id);
if (hasValue(this.isProcessingSub)) {
this.isProcessingSub.unsubscribe();
}
);
}
deleteSelected() {
this.ldnServicesBulkDeleteService.deleteSelectedLdnServices();
if (hasValue(this.isProcessingSub)) {
this.isProcessingSub.unsubscribe();
}
this.isProcessingSub = this.ldnServicesBulkDeleteService.isProcessing$()
.subscribe((isProcessing) => {
if (!isProcessing) {
this.closeModal();
this.setLdnServices();
}
});
}
openDeleteModal(content) {
this.modalRef = this.modalService.open(content);
}
closeModal() {
this.modalRef.close();
}
findAllServices(): void {
this.retrieveAll().subscribe(
(response) => {
this.servicesData = response._embedded.ldnservices;
console.log('ServicesData =', this.servicesData);
},
(error) => {
console.error('Error:', error);
}
);
}
retrieveAll(): Observable<any> {
const url = 'http://localhost:8080/server/api/ldn/ldnservices';
return this.http.get(url);
}
deleteSelected() {
if (this.selectedServiceId !== null) {
const deleteUrl = `http://localhost:8080/server/api/ldn/ldnservices/${this.selectedServiceId}`;
this.http.delete(deleteUrl).subscribe(
() => {
this.closeModal();
this.findAllServices();
},
(error) => {
console.error('Error deleting service:', error);
}
);
}
}
selectServiceToDelete(serviceId: number) {
this.selectedServiceId = serviceId;
this.openDeleteModal(this.deleteModal);
}
toggleStatus(ldnService: any): void {
const newStatus = !ldnService.status;
const apiUrl = `http://localhost:8080/server/api/ldn/ldnservices/${ldnService.id}`;
const patchOperation = {
op: 'replace',
path: '/enabled',
value: newStatus,
};
this.http.patch(apiUrl, [patchOperation]).subscribe(
() => {
console.log('Status updated successfully.');
// After a successful update, fetch the data to refresh the view
this.fetchServiceData(ldnService.id);
},
(error) => {
console.error('Error updating status:', error);
}
);
}
fetchServiceData(serviceId: string): void {
const apiUrl = `http://localhost:8080/server/api/ldn/ldnservices/${serviceId}`;
this.http.get(apiUrl).subscribe(
(data: any) => {
console.log(data);
},
(error) => {
console.error('Error fetching service data:', error);
}
);
}
}

View File

@@ -6,4 +6,4 @@
*/
import { ResourceType } from '../../../core/shared/resource-type';
export const LDN_SERVICE = new ResourceType('notifyservice');
export const LDN_SERVICE = new ResourceType('ldnservices');

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Observable, tap } from 'rxjs';
import { LdnService } from '../ldn-services-model/ldn-services.model';
@Injectable({
@@ -16,7 +16,11 @@ export class LdnDirectoryService {
public listLdnServices(): Observable<LdnService[]> {
const endpoint = `${this.baseUrl}`;
return this.http.get<LdnService[]>(endpoint);
return this.http.get<LdnService[]>(endpoint).pipe(
tap(data => {
console.log('listLdnServices() Data:', data);
})
);
}
public getLdnServiceById(id: string): Observable<LdnService> {
@@ -44,8 +48,8 @@ export class LdnDirectoryService {
}
public getItemFilters(): Observable<any> {
const endpoint = `${this.itemFilterEndpoint}`;
return this.http.get(endpoint);
const itemFiltersEndpoint = `${this.itemFilterEndpoint}`;
return this.http.get(itemFiltersEndpoint);
}
}

View File

@@ -222,6 +222,18 @@ export class MenuResolver implements Resolve<boolean> {
text: 'menu.section.new_process',
link: '/processes/new'
} as LinkMenuItemModel,
},/* ldn_services */
{
id: 'ldn_services_new',
parentID: 'new',
active: false,
visible: isSiteAdmin,
model: {
type: MenuItemType.LINK,
text: 'menu.section.services_new',
link: '/admin/ldn/services/new'
} as LinkMenuItemModel,
icon: '',
},
];
const editSubMenuList = [
@@ -350,6 +362,19 @@ export class MenuResolver implements Resolve<boolean> {
icon: 'terminal',
index: 10
},
/* LDN Services */
{
id: 'ldn_services',
active: false,
visible: isSiteAdmin,
model: {
type: MenuItemType.LINK,
text: 'menu.section.services',
link: '/admin/ldn/services'
} as LinkMenuItemModel,
icon: 'inbox',
index: 14
},
{
id: 'health',
active: false,

View File

@@ -902,7 +902,7 @@
"coar-notify-support.message-moderation.content": "To ensure a secure and productive environment, all incoming LDN messages are moderated. If you are planning to exchange information with us, kindly reach out via our dedicated Feedback form. You can access the Feedback form by clicking <a href=\"info/feedback\">here</a>.",
"service.overview.delete.header": "Delete Service",
"ldn-registered-services.title": "Registered Services",
"ldn-registered-services.table.name":"Name",
@@ -912,14 +912,49 @@
"ldn-registered-services.new": "NEW",
"ldn-registered-services.new.breadcrumbs": "Registered Services",
"ldn-service.overview.table.enabled": "Enabled",
"ldn-service.overview.table.disabled": "Disabled",
"ldn-service.overview.table.clickToEnable": "Click to enable",
"ldn-service.overview.table.clickToDisable": "Click to disable",
"ldn-register-new-service.title": "Register a new service",
"ldn-register-new-service.name": "Name",
"ldn-register-new-service.url": "Service URL",
"ldn-register-new-service.ldn.inbox.url": "LDN InBox URL",
"ldn-register-new-service.inbound": "Inbound- Patterns supported by the service (i.e. messages that the service is able to receive and understand)",
"ldn-register-new-service.outbound": "Outbound- Patterns supported by the service (i.e. messages that the service is likely to generate and that should be processed by DSpace)",
"ldn-register-new-service.addmore": "+ Add more",
"ldn-new-service.form.label.name": "Name",
"ldn-new-service.form.label.description": "Description",
"ldn-new-service.form.label.url": "Service URL",
"ldn-new-service.form.label.ldnUrl": "LDN Inbox URL",
"ldn-new-service.form.placeholder.name": "Please provide service name",
"ldn-new-service.form.placeholder.description": "Please provide a description regarding your service",
"ldn-new-service.form.placeholder.url": "Please input the URL for users to check out more information about the service",
"ldn-new-service.form.placeholder.ldnUrl": "Please specify the URL of the LDN Inbox",
"ldn-new-service.form.label.inboundPattern": "Inbound Pattern",
"ldn-new-service.form.label.placeholder.inboundPattern": "No Inbound Pattern",
"ldn-new-service.form.label.placeholder.selectedItemFilter": "No Item Filter Selected",
"ldn-new-service.form.label.ItemFilter": "Item Filter",
"ldn-new-service.form.label.automatic": "Automatic",
"ldn-new-service.form.label.outboundPattern": "Outbound Pattern",
"ldn-new-service.form.label.placeholder.outboundPattern": "No Outbound Pattern",
"ldn-new-service.form.label.addPattern": "+ Add more",
"ldn-new-service.form.label.removeItemFilter": "Remove",
"ldn-register-new-service.breadcrumbs": "New Service",
"service.overview.delete.body": "Are you sure you want to delete this service?",
"service.overview.delete": "Delete service",
"ldn-edit-service.title": "Edit service",
"ldn-edit-service.form.label.name": "Name",
"ldn-edit-service.form.label.description": "Description",
"ldn-edit-service.form.label.url": "Service URL",
"ldn-edit-service.form.label.ldnUrl": "LDN Inbox URL",
"ldn-edit-service.form.label.inboundPattern": "Inbound Pattern",
"ldn-edit-service.form.label.noInboundPatternSelected": "No Inbound Pattern",
"ldn-edit-service.form.label.selectedItemFilter": "Selected Item Filter",
"ldn-edit-service.form.label.selectItemFilter": "No Item Filter",
"ldn-edit-service.form.label.automatic": "Automatic",
"ldn-edit-service.form.label.addInboundPattern": "+ Add more",
"ldn-edit-service.form.label.outboundPattern": "Outbound Pattern",
"ldn-edit-service.form.label.noOutboundPatternSelected": "No Outbound Pattern",
"ldn-edit-service.form.label.addOutboundPattern": "+ Add more",
"ldn-edit-service.form.label.submit": "Submit",
"ldn-edit-service.breadcrumbs": "Edit Service",
"ldn-register-new-service.notification.error.title": "Error",
"ldn-register-new-service.notification.error.content": "An error occurred while creating this process",
@@ -2942,6 +2977,8 @@
"menu.section.icon.notifications": "Notifications menu section",
"menu.section.icon.ldn_services": "LDN Services overview",
"menu.section.import": "Import",
"menu.section.import_batch": "Batch Import (ZIP)",
@@ -2980,6 +3017,10 @@
"menu.section.registries_metadata": "Metadata",
"menu.section.services": "LDN Services",
"menu.section.services_new": "LDN Service",
"menu.section.statistics": "Statistics",
"menu.section.statistics_task": "Statistics Task",
@@ -3454,6 +3495,20 @@
"process.bulk.delete.success": "{{count}} process(es) have been succesfully deleted",
"service.detail.delete.cancel": "Cancel",
"service.detail.delete.button": "Delete service",
"service.detail.delete.header": "Delete service",
"service.detail.delete.body": "Are you sure you want to delete the current service?",
"service.detail.delete.confirm": "Delete service",
"service.detail.delete.success": "The service was successfully deleted.",
"service.detail.delete.error": "Something went wrong when deleting the service",
"service.overview.table.id": "Services ID",
"service.overview.table.name": "Name",