diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..910a2a5f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "typescript.tsdk": "node_modules\\typescript\\lib"
+}
diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts
index fffe9c8a..e5343681 100644
--- a/src/app/admin/admin-routing.module.ts
+++ b/src/app/admin/admin-routing.module.ts
@@ -12,6 +12,9 @@ import { UserDetailComponent } from './users/user-detail/user-detail.component';
import { UserEditComponent } from './users/user-edit/user-edit.component';
import { UserListComponent } from './users/user-list/user-list.component';
import { UsersComponent } from './users/users.component';
+import { ApiKeyComponent } from './api-key/api-key.component';
+import { ApiKeyListComponent } from './api-key/api-key-list/api-key-list.component';
+import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.component';
const adminRoutes: Routes = [
@@ -44,6 +47,18 @@ const adminRoutes: Routes = [
},
],
},
+ {
+ path: 'api-key',
+ component: ApiKeyComponent,
+ children: [
+ { path: '', component: ApiKeyListComponent },
+ { path: 'new-api-key', component: ApiKeyEditComponent },
+ {
+ path: ':api-key-id/edit-api-key',
+ component: ApiKeyEditComponent,
+ },
+ ],
+ },
];
diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts
index c7c52faf..93210070 100644
--- a/src/app/admin/admin.module.ts
+++ b/src/app/admin/admin.module.ts
@@ -28,6 +28,10 @@ import { UsersComponent } from './users/users.component';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectSearchModule } from '@shared/components/mat-select-search/mat-select-search.module';
+import { ApiKeyComponent } from './api-key/api-key.component';
+import { ApiKeyListComponent } from './api-key/api-key-list/api-key-list.component';
+import { ApiKeyTableComponent } from './api-key/api-key-list/api-key-table/api-key-table.component';
+import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.component';
@NgModule({
declarations: [
@@ -46,6 +50,10 @@ import { MatSelectSearchModule } from '@shared/components/mat-select-search/mat-
OrganisationDetailComponent,
OrganisationEditComponent,
OrganisationListComponent,
+ ApiKeyComponent,
+ ApiKeyListComponent,
+ ApiKeyTableComponent,
+ ApiKeyEditComponent,
],
imports: [
AdminRoutingModule,
@@ -79,6 +87,10 @@ import { MatSelectSearchModule } from '@shared/components/mat-select-search/mat-
OrganisationDetailComponent,
OrganisationEditComponent,
OrganisationListComponent,
+ ApiKeyComponent,
+ ApiKeyListComponent,
+ ApiKeyTableComponent,
+ ApiKeyEditComponent,
],
})
export class AdminModule {}
diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.html b/src/app/admin/api-key/api-key-edit/api-key-edit.component.html
new file mode 100644
index 00000000..268d6972
--- /dev/null
+++ b/src/app/admin/api-key/api-key-edit/api-key-edit.component.html
@@ -0,0 +1,66 @@
+
+
+
diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.scss b/src/app/admin/api-key/api-key-edit/api-key-edit.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts
new file mode 100644
index 00000000..015f7852
--- /dev/null
+++ b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts
@@ -0,0 +1,106 @@
+import { Location } from '@angular/common';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { PermissionResponse } from '@app/admin/permission/permission.model';
+import { PermissionService } from '@app/admin/permission/permission.service';
+import { TranslateService } from '@ngx-translate/core';
+import { ErrorMessageService } from '@shared/error-message.service';
+import { BackButton } from '@shared/models/back-button.model';
+import { SharedVariableService } from '@shared/shared-variable/shared-variable.service';
+import { ApiKeyRequest } from '../api-key.model';
+import { ApiKeyService } from '../api-key.service';
+
+@Component({
+ selector: 'app-api-key-edit',
+ templateUrl: './api-key-edit.component.html',
+ styleUrls: ['./api-key-edit.component.scss'],
+})
+export class ApiKeyEditComponent implements OnInit {
+ apiKeyRequest = new ApiKeyRequest();
+ public backButton: BackButton = {
+ label: '',
+ routerLink: ['admin', 'api-key'],
+ };
+ public title = '';
+ public submitButton = '';
+ public errorMessage: string;
+ public errorMessages: string[];
+ public errorFields: string[];
+ public formFailedSubmit = false;
+ public permissions: PermissionResponse[] = [];
+ private organizationId: number;
+
+ constructor(
+ private translate: TranslateService,
+ private route: ActivatedRoute,
+ private location: Location,
+ private apiKeyService: ApiKeyService,
+ private permissionService: PermissionService,
+ private errorMessageService: ErrorMessageService,
+ private sharedVariableService: SharedVariableService
+ ) {
+ translate.use('da');
+ }
+
+ ngOnInit(): void {
+ this.getPermissions();
+ this.translate.use('da');
+ this.translate
+ .get(['NAV.API-KEY', 'FORM.EDIT-API-KEY', 'API-KEY.EDIT.SAVE'])
+ .subscribe((translations) => {
+ this.backButton.label = translations['NAV.API-KEY'];
+ this.title = translations['FORM.EDIT-API-KEY'];
+ this.submitButton = translations['API-KEY.EDIT.SAVE'];
+ });
+
+ this.organizationId = this.sharedVariableService.getSelectedOrganisationId();
+ }
+
+ private getPermissions() {
+ this.permissionService
+ .getPermissions(
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ this.organizationId
+ )
+ .subscribe(
+ (permissions) => {
+ this.permissions = permissions.data.filter(
+ (x) => x.organization?.id === this.organizationId
+ );
+ },
+ (error: HttpErrorResponse) => {
+ this.showError(error);
+ }
+ );
+ }
+
+ onSubmit(): void {
+ this.create();
+ }
+
+ private create(): void {
+ this.apiKeyService.create(this.apiKeyRequest).subscribe(
+ () => this.routeBack(),
+ (err) => this.showError(err)
+ );
+ }
+
+ public compare(o1: any, o2: any): boolean {
+ return o1 === o2;
+ }
+
+ private showError(err: HttpErrorResponse) {
+ const result = this.errorMessageService.handleErrorMessageWithFields(err);
+ this.errorFields = result.errorFields;
+ this.errorMessages = result.errorMessages;
+ }
+
+ routeBack(): void {
+ this.location.back();
+ }
+}
diff --git a/src/app/admin/api-key/api-key-list/api-key-list.component.html b/src/app/admin/api-key/api-key-list/api-key-list.component.html
new file mode 100644
index 00000000..8ecf306e
--- /dev/null
+++ b/src/app/admin/api-key/api-key-list/api-key-list.component.html
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/app/admin/api-key/api-key-list/api-key-list.component.scss b/src/app/admin/api-key/api-key-list/api-key-list.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/admin/api-key/api-key-list/api-key-list.component.ts b/src/app/admin/api-key/api-key-list/api-key-list.component.ts
new file mode 100644
index 00000000..f422eac1
--- /dev/null
+++ b/src/app/admin/api-key/api-key-list/api-key-list.component.ts
@@ -0,0 +1,28 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { Title } from '@angular/platform-browser';
+import { TranslateService } from '@ngx-translate/core';
+import { SharedVariableService } from '@shared/shared-variable/shared-variable.service';
+
+@Component({
+ selector: 'app-api-key-list',
+ templateUrl: './api-key-list.component.html',
+ styleUrls: ['./api-key-list.component.scss'],
+})
+export class ApiKeyListComponent implements OnInit {
+ @Input() organisationId: number;
+
+ constructor(
+ public translate: TranslateService,
+ private titleService: Title,
+ private globalService: SharedVariableService
+ ) {
+ translate.use('da');
+ }
+
+ ngOnInit(): void {
+ this.translate.get(['TITLE.API-KEY']).subscribe((translations) => {
+ this.titleService.setTitle(translations['TITLE.API-KEY']);
+ });
+ this.organisationId = this.globalService.getSelectedOrganisationId();
+ }
+}
diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html
new file mode 100644
index 00000000..e52194d0
--- /dev/null
+++ b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+ |
+ {{ 'API-KEY.NAME' | translate }}
+ |
+
+ {{ element.name }}
+ |
+
+
+
+
+
+ {{ 'API-KEY.PERMISSIONS' | translate }}
+ |
+
+
+
+ {{ pm.name }}
+
+
+
+ {{ 'NoUsersAdded' | translate }}
+ |
+
+
+
+
+
+ {{ 'API-KEY.KEY' | translate }}
+ |
+
+ {{ element.key }}
+ |
+
+
+
+
+ |
+
+ {{ 'API-KEY.TABLE-ROW.DELETE' | translate }}
+
+ |
+
+
+
+
+
+
+
+
diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.scss b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.ts b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.ts
new file mode 100644
index 00000000..208d9c8e
--- /dev/null
+++ b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.ts
@@ -0,0 +1,111 @@
+import { AfterViewInit, Component, Input, ViewChild } from '@angular/core';
+import { MatPaginator, PageEvent } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import { Router } from '@angular/router';
+import { environment } from '@environments/environment';
+import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service';
+import { MeService } from '@shared/services/me.service';
+import { merge, Observable, of } from 'rxjs';
+import { catchError, map, startWith, switchMap } from 'rxjs/operators';
+import { ApiKeyGetManyResponse, ApiKeyResponse } from '../../api-key.model';
+import { ApiKeyService } from '../../api-key.service';
+
+@Component({
+ selector: 'app-api-key-table',
+ templateUrl: './api-key-table.component.html',
+ styleUrls: ['./api-key-table.component.scss'],
+})
+export class ApiKeyTableComponent implements AfterViewInit {
+ @Input() organisationId: number;
+ displayedColumns: string[] = [
+ 'name',
+ 'permissions',
+ 'key',
+ 'menu',
+ ];
+ data: ApiKeyResponse[] = [];
+ isLoadingResults = true;
+ @ViewChild(MatPaginator) paginator: MatPaginator;
+ @ViewChild(MatSort) sort: MatSort;
+ resultsLength = 0;
+ public pageSize = environment.tablePageSize;
+
+ constructor(
+ private meService: MeService,
+ private router: Router,
+ private apiKeyService: ApiKeyService,
+ private deleteDialogService: DeleteDialogService
+ ) {}
+
+ ngAfterViewInit(): void {
+ // If the user changes the sort order, reset back to the first page.
+ this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
+
+ merge(this.sort.sortChange, this.paginator.page)
+ .pipe(
+ startWith({}),
+ switchMap(() => {
+ this.isLoadingResults = true;
+ return this.getApiKeysByOrganisationId(
+ this.sort.active,
+ this.sort.direction
+ );
+ }),
+ map((data) => {
+ // Flip flag to show that loading has finished.
+ this.isLoadingResults = false;
+ this.resultsLength = data.count;
+
+ return data.data;
+ }),
+ catchError(() => {
+ this.isLoadingResults = false;
+ return of([]);
+ })
+ )
+ .subscribe((data) => (this.data = data));
+ }
+
+ getApiKeysByOrganisationId(
+ orderByColumn: string,
+ orderByDirection: string
+ ): Observable {
+ return this.apiKeyService.getApiKeys(
+ this.paginator.pageSize,
+ this.paginator.pageIndex * this.paginator.pageSize,
+ orderByColumn,
+ orderByDirection,
+ null,
+ this.organisationId
+ );
+ }
+
+ canAccess(_element: ApiKeyResponse) {
+ return this.meService.hasAdminAccessInTargetOrganization(
+ this.organisationId
+ );
+ }
+
+ routeToPermissions(element: any) {
+ this.router.navigate(['admin/api-key', element.id]);
+ }
+
+ deleteApiKey(id: number) {
+ this.deleteDialogService.showSimpleDialog().subscribe((response) => {
+ if (response) {
+ this.apiKeyService.delete(id).subscribe((response) => {
+ if (response.ok && response.body.affected > 0) {
+ this.refresh();
+ }
+ });
+ }
+ });
+ }
+
+ private refresh() {
+ const pageEvent = new PageEvent();
+ pageEvent.pageIndex = this.paginator.pageIndex;
+ pageEvent.pageSize = this.paginator.pageSize;
+ this.paginator.page.emit(pageEvent);
+ }
+}
diff --git a/src/app/admin/api-key/api-key.component.ts b/src/app/admin/api-key/api-key.component.ts
new file mode 100644
index 00000000..97c6b2fe
--- /dev/null
+++ b/src/app/admin/api-key/api-key.component.ts
@@ -0,0 +1,11 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-api-key',
+ template: '',
+})
+export class ApiKeyComponent implements OnInit {
+ constructor() {}
+
+ ngOnInit(): void {}
+}
diff --git a/src/app/admin/api-key/api-key.model.ts b/src/app/admin/api-key/api-key.model.ts
new file mode 100644
index 00000000..050a799b
--- /dev/null
+++ b/src/app/admin/api-key/api-key.model.ts
@@ -0,0 +1,23 @@
+import { PermissionResponse } from '../permission/permission.model';
+
+export class ApiKeyRequest {
+ id: number;
+ name: string;
+ permissions?: PermissionResponse[];
+}
+
+export interface ApiKeyResponse {
+ id: number;
+ name: string;
+ key: string;
+ permissions?: PermissionResponse[];
+ createdBy: number;
+ updatedBy: number;
+ createdByName: string;
+ updatedByName: string;
+}
+
+export interface ApiKeyGetManyResponse {
+ data: ApiKeyResponse[];
+ count: number;
+}
diff --git a/src/app/admin/api-key/api-key.service.ts b/src/app/admin/api-key/api-key.service.ts
new file mode 100644
index 00000000..91a6dc2c
--- /dev/null
+++ b/src/app/admin/api-key/api-key.service.ts
@@ -0,0 +1,79 @@
+import { Injectable } from '@angular/core';
+import { RestService } from '@shared/services/rest.service';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { UserMinimalService } from '../users/user-minimal.service';
+import {
+ ApiKeyGetManyResponse,
+ ApiKeyRequest,
+ ApiKeyResponse,
+} from './api-key.model';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class ApiKeyService {
+ endpoint = 'api-key';
+ constructor(
+ private restService: RestService,
+ private userMinimalService: UserMinimalService
+ ) {}
+
+ create(body: ApiKeyRequest): Observable {
+ return this.restService.post(this.endpoint, body, {
+ observe: 'response',
+ });
+ }
+
+ get(id: number): Observable {
+ return this.restService.get(this.endpoint, {}, id).pipe(
+ map((response: ApiKeyResponse) => {
+ response.createdByName = this.userMinimalService.getUserNameFrom(
+ response.createdBy
+ );
+ response.updatedByName = this.userMinimalService.getUserNameFrom(
+ response.updatedBy
+ );
+ return response;
+ })
+ );
+ }
+
+ getApiKeys(
+ limit: number = 1000,
+ offset: number = 0,
+ orderByColumn?: string,
+ orderByDirection?: string,
+ userId?: number,
+ organisationId?: number
+ ): Observable {
+ if (userId) {
+ return this.restService.get(this.endpoint, {
+ limit,
+ offset,
+ orderOn: orderByColumn,
+ sort: orderByDirection,
+ userId,
+ });
+ } else if (organisationId) {
+ return this.restService.get(this.endpoint, {
+ limit,
+ offset,
+ orderOn: orderByColumn,
+ sort: orderByDirection,
+ organisationId,
+ });
+ } else {
+ return this.restService.get(this.endpoint, {
+ limit,
+ offset,
+ orderOn: orderByColumn,
+ sort: orderByDirection,
+ });
+ }
+ }
+
+ delete(id: number) {
+ return this.restService.delete(this.endpoint, id);
+ }
+}
diff --git a/src/app/admin/organisation/organisation.model.ts b/src/app/admin/organisation/organisation.model.ts
index d3961220..f07c0a52 100644
--- a/src/app/admin/organisation/organisation.model.ts
+++ b/src/app/admin/organisation/organisation.model.ts
@@ -1,5 +1,6 @@
import { Application } from '@applications/application.model';
import { PayloadDecoder } from '../../payload-decoder/payload-decoder.model';
+import { PermissionResponse } from '../permission/permission.model';
export class Organisation {
id?: number;
@@ -18,8 +19,7 @@ export interface OrganisationResponse {
payloadDecoders: PayloadDecoder[];
applications: Application[];
- // TODO: This.
- permissions: any[];
+ permissions: PermissionResponse[];
}
export interface OrganisationGetManyResponse {
@@ -30,4 +30,4 @@ export interface OrganisationGetManyResponse {
export interface OrganisationGetMinimalResponse {
data: Organisation[];
count: number;
-}
\ No newline at end of file
+}
diff --git a/src/app/admin/permission/permission-list/permission-list.component.ts b/src/app/admin/permission/permission-list/permission-list.component.ts
index cec1432d..7559e47a 100644
--- a/src/app/admin/permission/permission-list/permission-list.component.ts
+++ b/src/app/admin/permission/permission-list/permission-list.component.ts
@@ -49,7 +49,6 @@ export class PermissionListComponent implements OnInit, OnChanges {
}
deletePermission(id: number) {
- console.log("list")
this.permissionService.deletePermission(id).subscribe((response) => {
if (response.ok && response.body.affected > 0) {
this.getPermissions();
diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts
index 1c6916ae..a071e8ca 100644
--- a/src/app/dashboard/dashboard.component.ts
+++ b/src/app/dashboard/dashboard.component.ts
@@ -48,13 +48,13 @@ export class DashboardComponent implements OnInit {
} else {
const error = params['error'];
if (error) {
- if (error == "MESSAGE.KOMBIT-LOGIN-FAILED") {
+ if (error == "MESSAGE.KOMBIT-LOGIN-FAILED" || error == "MESSAGE.API-KEY-AUTH-FAILED") {
this.router.navigate(['/not-authorized'], { state: { message: this.kombitError, code: 401 } });
} if (error == "MESSAGE.USER-INACTIVE") {
this.router.navigate(['/not-authorized'], { state: { message: this.noAccess, code: 401 } });
} else {
this.router.navigate(['/not-authorized'], { state: { message: this.unauthorizedMessage, code: 401 } });
- }
+ }
}
}
await this.sharedVariableService.setUserInfo();
diff --git a/src/app/navbar/organisation-dropdown/organisation-dropdown.component.html b/src/app/navbar/organisation-dropdown/organisation-dropdown.component.html
index 566b42df..f6a9a123 100644
--- a/src/app/navbar/organisation-dropdown/organisation-dropdown.component.html
+++ b/src/app/navbar/organisation-dropdown/organisation-dropdown.component.html
@@ -40,4 +40,11 @@
-
\ No newline at end of file
+
+
+ {{'NAV.API-KEY' | translate}}
+
+
+
+
diff --git a/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts b/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts
index 3d73055b..97364003 100644
--- a/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts
+++ b/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts
@@ -3,7 +3,7 @@ import { Router } from '@angular/router';
import { Organisation } from '@app/admin/organisation/organisation.model';
import { PermissionType } from '@app/admin/permission/permission.model';
import { UserResponse } from '@app/admin/users/user.model';
-import { faExchangeAlt, faLayerGroup, faUsers, faIdBadge, faToolbox, faBurn } from '@fortawesome/free-solid-svg-icons';
+import { faExchangeAlt, faLayerGroup, faUsers, faIdBadge, faToolbox, faBurn, faKey } from '@fortawesome/free-solid-svg-icons';
import { TranslateService } from '@ngx-translate/core';
import { SharedVariableService } from '@shared/shared-variable/shared-variable.service';
@@ -24,6 +24,7 @@ export class OrganisationDropdownComponent implements OnInit {
faIdBadge = faIdBadge;
faToolbox = faToolbox;
faBurn = faBurn;
+ faKey = faKey;
constructor(
diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json
index 1c086680..2381c4c3 100644
--- a/src/assets/i18n/da.json
+++ b/src/assets/i18n/da.json
@@ -35,7 +35,8 @@
"DEVICE-MODEL": "Device model",
"BACK": "Tilbage",
"LOGOUT": "Log ud",
- "HELP": "Hjælp"
+ "HELP": "Hjælp",
+ "API-KEY": "API nøgler"
},
"TOPBAR":{
"SEARCH": {
@@ -165,7 +166,7 @@
"AUTHORIZATIONHEADER": "Authorization header",
"NO-AUTHORIZATIONHEADER": "Ingen Authorization header angivet",
"ADD-TO-OPENDATADK": "Send data til OpenDataDK",
- "OPENDATA-DK": "OpenDataDK",
+ "OPENDATA-DK": "OpenDataDK",
"NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu"
},
"MULTICAST": {
@@ -429,7 +430,8 @@
"EDIT-SIGFOX-GROUPS": "Redigér Sigfox grupper",
"EDIT-SIGFOX-GROUP": "Redigér Sigfox gruppe",
"EDIT-USERS": "Redigér bruger",
- "EDIT-DEVICE-PROFILE": "Redigér device profil"
+ "EDIT-DEVICE-PROFILE": "Redigér device profil",
+ "EDIT-API-KEY": "Redigér API nøgle"
},
"QUESTION": {
"CREATE-IOT-DEVICE": "IoT-enhed",
@@ -577,6 +579,9 @@
"SELECT-ENERGYLIMITATIONCLASS": "Vælg energibegrænsningsklassen (energyLimitationClass)",
"SELECT-SUPPORTEDPROTOCOL": "Vælg understøttede protokoller (supportedProtocol)",
"FIWARE-LINK-TEXT": "Denne data model er adopteret fra Fiware og følger ETSI standarden"
+ },
+ "PERMISSION": {
+ "SELECT-PERMISSION": "Vælg brugergruppe"
}
},
"QUESTION-LORA-GATEWAY": {
@@ -897,7 +902,8 @@
"MULTICAST": "OS2IoT - Multicast",
"BULKIMPORT": "OS2IoT - Bulk import",
"IOTDEVICE": "OS2IoT - IoT enhed",
- "FRONTPAGE": "OS2IoT - Forside"
+ "FRONTPAGE": "OS2IoT - Forside",
+ "API-KEY": "OS2IoT - API nøgler"
},
"PAGINATOR": {
@@ -915,5 +921,25 @@
"Forbidden resource": "Du har ikke rettigheder til at foretage denne handling",
"GENERIC_HTTP": "Generisk HTTP",
"LORAWAN": "LoRaWAN",
- "SIGFOX": "Sigfox"
+ "SIGFOX": "Sigfox",
+ "API-KEY": {
+ "NAME": "Navn",
+ "ORGANIZATION": "Organisation",
+ "PERMISSIONS": "Brugergrupper",
+ "KEY": "Nøgle",
+ "CREATE-NEW-API-KEY": "Opret ny nøgle",
+ "DETAIL": {},
+ "EDIT": {
+ "NAME": "Indtast nøglens navn",
+ "NAME-PLACEHOLDER": "Indtast nøglens navn",
+ "CANCEL": "Annuller",
+ "SAVE": "Gem nøgle",
+ "CREATE-API-KEY": "Opret nøgle"
+ },
+ "TABLE-ROW": {
+ "EDIT": "Redigér",
+ "DELETE": "Slet"
+ }
+ },
+ "NoUsersAdded": "Ingen brugergrupper er tilføjet"
}
diff --git a/tslint.json b/tslint.json
index 4f4dff57..b35a3d4e 100644
--- a/tslint.json
+++ b/tslint.json
@@ -129,7 +129,7 @@
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"variable-name": {
- "options": ["ban-keywords", "check-format", "allow-pascal-case"]
+ "options": ["ban-keywords", "check-format", "allow-pascal-case", "allow-leading-underscore"]
},
"whitespace": {
"options": [