autoupdate, permissions, operator, directive
-overworked login and logout -new directive 'appOsPerms' (former os-perms) -appOsPerms compares with groups in Operator -login observes operator for user-information (also serves as example on how to user observables and subjects) -operator observes datastore for groups (so the operators knows it's groups by creation or directly after an autoupdate)
This commit is contained in:
parent
2331ecd6b8
commit
30ac9c8e36
41
client/package-lock.json
generated
41
client/package-lock.json
generated
@ -4271,7 +4271,8 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@ -4292,12 +4293,14 @@
|
|||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@ -4312,17 +4315,20 @@
|
|||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@ -4439,7 +4445,8 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
@ -4451,6 +4458,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@ -4465,6 +4473,7 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@ -4472,12 +4481,14 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.1",
|
"safe-buffer": "^5.1.1",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
@ -4496,6 +4507,7 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
@ -4576,7 +4588,8 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@ -4588,6 +4601,7 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@ -4673,7 +4687,8 @@
|
|||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
@ -4709,6 +4724,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@ -4728,6 +4744,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -4771,12 +4788,14 @@
|
|||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { OpenslidesService } from 'app/core/services/openslides.service';
|
|
||||||
import { AutoupdateService } from 'app/core/services/autoupdate.service';
|
import { AutoupdateService } from 'app/core/services/autoupdate.service';
|
||||||
|
// import { AuthService } from 'app/core/services/auth.service';
|
||||||
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
// export class AppComponent implements OnInit {
|
||||||
|
export class AppComponent {
|
||||||
constructor(
|
constructor(
|
||||||
private openSlides: OpenslidesService,
|
private operator: OperatorService,
|
||||||
private autoupdate: AutoupdateService,
|
private autoupdate: AutoupdateService,
|
||||||
private translate: TranslateService
|
private translate: TranslateService
|
||||||
) {
|
) {
|
||||||
@ -23,8 +25,4 @@ export class AppComponent implements OnInit {
|
|||||||
// try to use the browser language if it is available. If not, uses english.
|
// try to use the browser language if it is available. If not, uses english.
|
||||||
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.openSlides.bootup();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import { AlertComponent } from './core/directives/alert/alert.component';
|
|||||||
//translation module. TODO: Potetially a SharedModule and own files
|
//translation module. TODO: Potetially a SharedModule and own files
|
||||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
import { PruningTranslationLoader } from './core/pruning-loader';
|
import { PruningTranslationLoader } from './core/pruning-loader';
|
||||||
|
import { OsPermsDirective } from './core/directives/os-perms.directive';
|
||||||
|
|
||||||
export function HttpLoaderFactory(http: HttpClient) {
|
export function HttpLoaderFactory(http: HttpClient) {
|
||||||
return new PruningTranslationLoader(http);
|
return new PruningTranslationLoader(http);
|
||||||
@ -60,7 +61,8 @@ library.add(fas);
|
|||||||
SiteComponent,
|
SiteComponent,
|
||||||
StartComponent,
|
StartComponent,
|
||||||
ProjectorContainerComponent,
|
ProjectorContainerComponent,
|
||||||
AlertComponent
|
AlertComponent,
|
||||||
|
OsPermsDirective
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
import { Injector } from '@angular/core';
|
import { Injector } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { DataStoreService } from 'app/core/services/DS.service';
|
import { DataStoreService } from 'app/core/services/DS.service';
|
||||||
// import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
// provides functions that might be used by a lot of components
|
// provides functions that might be used by a lot of components
|
||||||
export abstract class BaseComponent {
|
export abstract class BaseComponent {
|
||||||
protected injector: Injector;
|
protected injector: Injector;
|
||||||
protected dataStore: DataStoreService;
|
protected dataStore: DataStoreService;
|
||||||
// would die in every scope change. disabled for now
|
|
||||||
// protected _translateService: TranslateService;
|
|
||||||
private titleSuffix = ' - OpenSlides 3';
|
private titleSuffix = ' - OpenSlides 3';
|
||||||
|
|
||||||
constructor(protected titleService?: Title) {
|
constructor(protected titleService?: Title) {
|
||||||
// throws a warning even tho it is the new syntax. Ignored for now.
|
// throws a warning even tho it is the new syntax. Ignored for now.
|
||||||
this.injector = Injector.create([{ provide: DataStoreService, useClass: DataStoreService, deps: [] }]);
|
this.injector = Injector.create([{ provide: DataStoreService, useClass: DataStoreService, deps: [] }]);
|
||||||
// this._injector = Injector.create([{ provide: TranslateService, useClass: TranslateService, deps: [] }]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitle(prefix: string) {
|
setTitle(prefix: string) {
|
||||||
@ -29,11 +25,4 @@ export abstract class BaseComponent {
|
|||||||
}
|
}
|
||||||
return this.dataStore;
|
return this.dataStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get translate(): TranslateService {
|
|
||||||
// if (this._translateService == null) {
|
|
||||||
// this._translateService = this._injector.get(TranslateService);
|
|
||||||
// }
|
|
||||||
// return this._translateService;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { OsPermsDirective } from './os-perms.directive';
|
||||||
|
|
||||||
|
describe('OsPermsDirective', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new OsPermsDirective();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
69
client/src/app/core/directives/os-perms.directive.ts
Normal file
69
client/src/app/core/directives/os-perms.directive.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { Directive, Input, ElementRef, TemplateRef, ViewContainerRef, OnInit } from '@angular/core';
|
||||||
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
|
import { BaseComponent } from 'app/base.component';
|
||||||
|
import { Group } from 'app/core/models/users/group';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[appOsPerms]'
|
||||||
|
})
|
||||||
|
export class OsPermsDirective extends BaseComponent {
|
||||||
|
private userPermissions: string[];
|
||||||
|
private permissions;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private element: ElementRef,
|
||||||
|
private template: TemplateRef<any>,
|
||||||
|
private viewContainer: ViewContainerRef, //TODO private operator. OperatorService
|
||||||
|
private operator: OperatorService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.userPermissions = [];
|
||||||
|
|
||||||
|
// observe groups of operator, so the directive can actively react to changes
|
||||||
|
this.operator.getObservable().subscribe(content => {
|
||||||
|
console.log('os-perms did monitor changes in observer: ', content);
|
||||||
|
if (content instanceof Group && this.permissions !== '') {
|
||||||
|
console.log('content was a Group');
|
||||||
|
this.userPermissions = [...this.userPermissions, ...content.permissions];
|
||||||
|
this.updateView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set appOsPerms(value) {
|
||||||
|
this.permissions = value;
|
||||||
|
this.readUserPermissions();
|
||||||
|
this.updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readUserPermissions(): void {
|
||||||
|
const opGroups = this.operator.getGroups();
|
||||||
|
console.log('operator Groups: ', opGroups);
|
||||||
|
opGroups.forEach(group => {
|
||||||
|
this.userPermissions = [...this.userPermissions, ...group.permissions];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// hides or shows a contrainer
|
||||||
|
private updateView(): void {
|
||||||
|
if (this.checkPermissions()) {
|
||||||
|
// will just render the page normally
|
||||||
|
this.viewContainer.createEmbeddedView(this.template);
|
||||||
|
} else {
|
||||||
|
// will remove the content of the container
|
||||||
|
this.viewContainer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks for the required permission
|
||||||
|
private checkPermissions(): boolean {
|
||||||
|
let isPermitted = false;
|
||||||
|
if (this.userPermissions && this.permissions) {
|
||||||
|
this.permissions.forEach(perm => {
|
||||||
|
isPermitted = this.userPermissions.find(userPerm => userPerm === perm) ? true : false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return isPermitted;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of, BehaviorSubject } from 'rxjs';
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap, map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
import { ImproperlyConfiguredError } from 'app/core/exceptions';
|
||||||
import { BaseModel, ModelId } from 'app/core/models/baseModel';
|
import { BaseModel, ModelId } from 'app/core/models/baseModel';
|
||||||
@ -28,6 +28,8 @@ const httpOptions = {
|
|||||||
export class DataStoreService {
|
export class DataStoreService {
|
||||||
// needs to be static cause becauseusing dependency injection, services are unique for a scope.
|
// needs to be static cause becauseusing dependency injection, services are unique for a scope.
|
||||||
private static store: Storrage = {};
|
private static store: Storrage = {};
|
||||||
|
// observe datastore to enable dynamic changes in models and view
|
||||||
|
private static dataStoreSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ export class DataStoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: type for callback function
|
// TODO: type for callback function
|
||||||
// example: this.DS.filder(User, myUser => myUser.first_name === "Max")
|
// example: this.DS.filter(User, myUser => myUser.first_name === "Max")
|
||||||
filter(Type, callback): BaseModel[] {
|
filter(Type, callback): BaseModel[] {
|
||||||
let filterCollection = [];
|
let filterCollection = [];
|
||||||
const typeCollection = this.get(Type);
|
const typeCollection = this.get(Type);
|
||||||
@ -86,7 +88,7 @@ export class DataStoreService {
|
|||||||
DataStoreService.store[collectionString] = {};
|
DataStoreService.store[collectionString] = {};
|
||||||
}
|
}
|
||||||
DataStoreService.store[collectionString][model.id] = model;
|
DataStoreService.store[collectionString][model.id] = model;
|
||||||
// console.log('add model ', model, ' into Datastore');
|
this.setObservable(model);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,4 +134,12 @@ export class DataStoreService {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getObservable(): Observable<any> {
|
||||||
|
return DataStoreService.dataStoreSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setObservable(value) {
|
||||||
|
DataStoreService.dataStoreSubject.next(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,30 +2,23 @@ import { Injectable } from '@angular/core';
|
|||||||
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
import { OperatorService } from './operator.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
constructor(private authService: AuthService, private router: Router) {}
|
constructor(private authService: AuthService, private operator: OperatorService, private router: Router) {}
|
||||||
|
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
|
||||||
const url: string = state.url;
|
const url: string = state.url;
|
||||||
return this.checkLogin(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkLogin(url: string): boolean {
|
if (this.operator.id) {
|
||||||
//check if the user is already logged in.
|
|
||||||
//TODO: This is faked
|
|
||||||
if (this.authService.isLoggedIn) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// Store the attempted URL for redirecting
|
|
||||||
this.authService.redirectUrl = url;
|
this.authService.redirectUrl = url;
|
||||||
|
|
||||||
// Navigate to the login page with extras
|
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angul
|
|||||||
import { Observable, of, throwError } from 'rxjs';
|
import { Observable, of, throwError } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { User } from 'app/core/models/users/user';
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
|
|
||||||
const httpOptions = {
|
const httpOptions = {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@ -16,54 +16,27 @@ const httpOptions = {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
isLoggedIn: boolean;
|
|
||||||
|
|
||||||
// store the URL so we can redirect after logging in
|
|
||||||
redirectUrl: string;
|
redirectUrl: string;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient, private operator: OperatorService) {}
|
||||||
//check for the cookie in local storrage
|
|
||||||
//TODO checks for username now since django does not seem to return a cookie
|
|
||||||
if (localStorage.getItem('username')) {
|
|
||||||
this.isLoggedIn = true;
|
|
||||||
} else {
|
|
||||||
this.isLoggedIn = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the service by querying the server
|
|
||||||
// Not sure if this makes sense, since a service is not supposed to init()
|
|
||||||
init(): Observable<User | any> {
|
|
||||||
return this.http.get<User>('/users/whoami/', httpOptions).pipe(
|
|
||||||
tap(val => {
|
|
||||||
console.log('auth-init-whami : ', val);
|
|
||||||
}),
|
|
||||||
catchError(this.handleError())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//loggins a users. expects a user model
|
//loggins a users. expects a user model
|
||||||
login(username: string, password: string): Observable<User | any> {
|
login(username: string, password: string): Observable<any> {
|
||||||
const user: any = {
|
const user: any = {
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
return this.http.post<any>('/users/login/', user, httpOptions).pipe(
|
return this.http.post<any>('/users/login/', user, httpOptions).pipe(
|
||||||
tap(val => {
|
tap(resp => this.operator.storeUser(resp.user)),
|
||||||
localStorage.setItem('username', val.username);
|
|
||||||
this.isLoggedIn = true;
|
|
||||||
}),
|
|
||||||
catchError(this.handleError())
|
catchError(this.handleError())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//logout the user
|
//logout the user
|
||||||
//TODO not yet used
|
//TODO not yet used
|
||||||
logout(): Observable<User | any> {
|
logout(): Observable<any> {
|
||||||
this.isLoggedIn = false;
|
this.operator.clear();
|
||||||
localStorage.removeItem('username');
|
return this.http.post<any>('/users/logout/', {}, httpOptions);
|
||||||
|
|
||||||
return this.http.post<User>('/users/logout/', {}, httpOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//very generic error handling function.
|
//very generic error handling function.
|
||||||
|
@ -34,6 +34,11 @@ export class AutoupdateService extends BaseComponent {
|
|||||||
|
|
||||||
constructor(private websocketService: WebsocketService) {
|
constructor(private websocketService: WebsocketService) {
|
||||||
super();
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialte autpupdate Service
|
||||||
|
startAutoupdate(): void {
|
||||||
|
console.log('start autoupdate');
|
||||||
this.socket = this.websocketService.connect();
|
this.socket = this.websocketService.connect();
|
||||||
this.socket.subscribe(response => {
|
this.socket.subscribe(response => {
|
||||||
this.storeResponse(response);
|
this.storeResponse(response);
|
||||||
|
@ -3,22 +3,22 @@ import { Router } from '@angular/router';
|
|||||||
|
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
//it seems that this service is useless
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class OpenslidesService {
|
export class OpenslidesService {
|
||||||
constructor(private auth: AuthService, private router: Router) {}
|
constructor(private auth: AuthService, private router: Router) {}
|
||||||
|
|
||||||
bootup() {
|
// now in authService
|
||||||
// TODO Lock the interface..
|
// bootup() {
|
||||||
this.auth.init().subscribe(whoami => {
|
// // TODO Lock the interface..
|
||||||
console.log(whoami);
|
// this.auth.init().subscribe(whoami => {
|
||||||
if (!whoami.user && !whoami.guest_enabled) {
|
// console.log(whoami);
|
||||||
this.router.navigate(['/login']);
|
// if (!whoami.user && !whoami.guest_enabled) {
|
||||||
} else {
|
// this.router.navigate(['/login']);
|
||||||
// It's ok!
|
// } else {
|
||||||
}
|
// // It's ok!
|
||||||
});
|
// }
|
||||||
}
|
// });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
15
client/src/app/core/services/operator.service.spec.ts
Normal file
15
client/src/app/core/services/operator.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { OperatorService } from './operator.service';
|
||||||
|
|
||||||
|
describe('OperatorService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [OperatorService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([OperatorService], (service: OperatorService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
198
client/src/app/core/services/operator.service.ts
Normal file
198
client/src/app/core/services/operator.service.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable, of, BehaviorSubject } from 'rxjs';
|
||||||
|
import { HttpClient, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { tap, catchError, share } from 'rxjs/operators';
|
||||||
|
import { BaseComponent } from 'app/base.component';
|
||||||
|
import { Group } from 'app/core/models/users/group';
|
||||||
|
|
||||||
|
// TODO: Dry
|
||||||
|
const httpOptions = {
|
||||||
|
withCredentials: true,
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class OperatorService extends BaseComponent {
|
||||||
|
// default variables
|
||||||
|
about_me: string;
|
||||||
|
comment: string;
|
||||||
|
default_password: string;
|
||||||
|
email: string;
|
||||||
|
first_name: string;
|
||||||
|
groups_id: number[];
|
||||||
|
id: number;
|
||||||
|
is_active: boolean;
|
||||||
|
is_committee: boolean;
|
||||||
|
is_present: boolean;
|
||||||
|
last_email_send: string;
|
||||||
|
last_name: string;
|
||||||
|
number: string;
|
||||||
|
structure_level: string;
|
||||||
|
title: string;
|
||||||
|
username: string;
|
||||||
|
logged_in: boolean;
|
||||||
|
// subject
|
||||||
|
private operatorSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||||
|
// real groups, once they arrived in datastore
|
||||||
|
private groups: Group[] = new Array();
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// recreate old operator from localStorage.
|
||||||
|
if (localStorage.getItem('operator')) {
|
||||||
|
const oldOperator = JSON.parse(localStorage.getItem('operator'));
|
||||||
|
if (Object.keys(oldOperator).length > 0) {
|
||||||
|
this.storeUser(oldOperator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// observe the datastore now to avoid race conditions. Ensures to
|
||||||
|
// find the groups in time
|
||||||
|
this.observeDataStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// calls 'whoami' to find out the operator
|
||||||
|
public whoAmI(): Observable<any> {
|
||||||
|
return this.http.get<any>('/users/whoami/', httpOptions).pipe(
|
||||||
|
tap(whoami => {
|
||||||
|
if (whoami && whoami.user) {
|
||||||
|
this.storeUser(whoami.user);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(this.handleError())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public storeUser(user: any): void {
|
||||||
|
// store in file
|
||||||
|
this.about_me = user.about_me;
|
||||||
|
this.comment = user.comment;
|
||||||
|
this.default_password = user.default_password;
|
||||||
|
this.email = user.email;
|
||||||
|
this.first_name = user.first_name;
|
||||||
|
this.groups_id = user.groups_id;
|
||||||
|
this.id = user.id;
|
||||||
|
this.is_active = user.is_active;
|
||||||
|
this.is_committee = user.is_committee;
|
||||||
|
this.is_present = user.is_present;
|
||||||
|
this.last_email_send = user.last_email_send;
|
||||||
|
this.last_name = user.last_name;
|
||||||
|
this.number = user.number;
|
||||||
|
this.structure_level = user.structure_level;
|
||||||
|
this.title = user.title;
|
||||||
|
this.username = user.username;
|
||||||
|
// also store in localstorrage
|
||||||
|
this.updateLocalStorrage();
|
||||||
|
// update mode to inform observers
|
||||||
|
this.updateMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.about_me = null;
|
||||||
|
this.comment = null;
|
||||||
|
this.default_password = null;
|
||||||
|
this.email = null;
|
||||||
|
this.first_name = null;
|
||||||
|
this.groups_id = null;
|
||||||
|
this.id = null;
|
||||||
|
this.is_active = null;
|
||||||
|
this.is_committee = null;
|
||||||
|
this.is_present = null;
|
||||||
|
this.last_email_send = null;
|
||||||
|
this.last_name = null;
|
||||||
|
this.number = null;
|
||||||
|
this.structure_level = null;
|
||||||
|
this.title = null;
|
||||||
|
this.username = null;
|
||||||
|
this.updateMode();
|
||||||
|
localStorage.removeItem('operator');
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateLocalStorrage(): void {
|
||||||
|
localStorage.setItem('operator', JSON.stringify(this.getUpdateObject()));
|
||||||
|
console.log('update local storrage: groups: ', this.groups_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateMode(): void {
|
||||||
|
this.setObservable(this.getUpdateObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUpdateObject(): any {
|
||||||
|
return {
|
||||||
|
about_me: this.about_me,
|
||||||
|
comment: this.comment,
|
||||||
|
default_password: this.default_password,
|
||||||
|
email: this.email,
|
||||||
|
first_name: this.first_name,
|
||||||
|
groups_id: this.groups_id,
|
||||||
|
id: this.id,
|
||||||
|
is_active: this.is_active,
|
||||||
|
is_committee: this.is_committee,
|
||||||
|
is_present: this.is_present,
|
||||||
|
last_email_send: this.last_email_send,
|
||||||
|
last_name: this.last_name,
|
||||||
|
number: this.number,
|
||||||
|
structure_level: this.structure_level,
|
||||||
|
title: this.title,
|
||||||
|
username: this.username,
|
||||||
|
logged_in: this.logged_in
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// observe dataStore to set groups once they are there
|
||||||
|
// TODO logic to remove groups / user from certain groups
|
||||||
|
private observeDataStore(): void {
|
||||||
|
console.log('Operator observes DataStore');
|
||||||
|
this.DS.getObservable().subscribe(newModel => {
|
||||||
|
if (newModel instanceof Group) {
|
||||||
|
this.addGroup(newModel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// read out the Groups from the DataStore by the operators 'groups_id'
|
||||||
|
// requires that the DataStore has been setup (websocket.service)
|
||||||
|
// requires that the whoAmI did return a valid operator
|
||||||
|
public readGroupsFromStore(): void {
|
||||||
|
this.DS.filter(Group, myGroup => {
|
||||||
|
if (this.groups_id.includes(myGroup.id)) {
|
||||||
|
this.addGroup(myGroup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getObservable() {
|
||||||
|
return this.operatorSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setObservable(value) {
|
||||||
|
this.operatorSubject.next(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGroups() {
|
||||||
|
return this.groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the operator has the corresponding ID, set the group
|
||||||
|
private addGroup(newGroup: Group): void {
|
||||||
|
if (this.groups_id.includes(newGroup.id)) {
|
||||||
|
this.groups.push(newGroup);
|
||||||
|
// inform the observers about new groups (appOsPerms)
|
||||||
|
console.log('pushed a group into operator');
|
||||||
|
this.setObservable(newGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Dry
|
||||||
|
private handleError<T>() {
|
||||||
|
return (error: any): Observable<T> => {
|
||||||
|
console.error(error);
|
||||||
|
return of(error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -16,4 +16,12 @@
|
|||||||
|
|
||||||
<div class="app-content">
|
<div class="app-content">
|
||||||
Agenda Works
|
Agenda Works
|
||||||
|
<br/>
|
||||||
|
<div>
|
||||||
|
everyone should see this
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div *appOsPerms="['agenda.can_see']">
|
||||||
|
Only permitted users should see this
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
@ -5,6 +5,7 @@ import { MatSnackBar } from '@angular/material';
|
|||||||
|
|
||||||
import { BaseComponent } from 'app/base.component';
|
import { BaseComponent } from 'app/base.component';
|
||||||
import { AuthService } from 'app/core/services/auth.service';
|
import { AuthService } from 'app/core/services/auth.service';
|
||||||
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
@ -19,20 +20,22 @@ export class LoginComponent extends BaseComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private operator: OperatorService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private snackBar: MatSnackBar
|
private snackBar: MatSnackBar
|
||||||
) {
|
) {
|
||||||
super(titleService);
|
super(titleService);
|
||||||
this.setInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
//TODO translate
|
super.setTitle('Log In');
|
||||||
super.setTitle('Anmelden');
|
|
||||||
}
|
|
||||||
|
|
||||||
setInfo() {
|
// if there is stored login information, try to login directly.
|
||||||
this.info = 'Logged in? ' + (this.authService.isLoggedIn ? 'in' : 'out');
|
this.operator.getObservable().subscribe(user => {
|
||||||
|
if (user && user.id) {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openSnackBar(message: string) {
|
openSnackBar(message: string) {
|
||||||
@ -42,23 +45,16 @@ export class LoginComponent extends BaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Todo: This serves as a prototype and need enhancement,
|
// Todo: This serves as a prototype and need enhancement,
|
||||||
//like saving a "logged in state" and real checking the server
|
|
||||||
// if logIn was fine
|
// if logIn was fine
|
||||||
|
// like saving a "logged in state" and real checking the server
|
||||||
formLogin(): void {
|
formLogin(): void {
|
||||||
this.authService.login(this.username, this.password).subscribe(res => {
|
this.authService.login(this.username, this.password).subscribe(res => {
|
||||||
if (res.status === 400) {
|
if (res.status === 400) {
|
||||||
//TODO translate
|
//TODO translate
|
||||||
console.log('res: ', res);
|
|
||||||
this.openSnackBar(res.error.detail);
|
this.openSnackBar(res.error.detail);
|
||||||
} else {
|
} else {
|
||||||
// this.toastService.success('Logged in! :)');
|
if (res.user_id) {
|
||||||
this.setInfo();
|
|
||||||
if (this.authService.isLoggedIn) {
|
|
||||||
// Get the redirect URL from our auth service
|
|
||||||
// If no redirect has been set, use the default
|
|
||||||
const redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/';
|
const redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/';
|
||||||
|
|
||||||
// Redirect the user
|
|
||||||
this.router.navigate([redirect]);
|
this.router.navigate([redirect]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
<!-- User Menu -->
|
<!-- User Menu -->
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<!-- TODO use an operator service or get from whoami -->
|
<!-- Get the username from operator -->
|
||||||
UserName
|
{{username}}
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<mat-action-row>
|
<mat-action-row>
|
||||||
<button (click)='logOutButton()' mat-button>Logout</button>
|
<button (click)='logOutButton()' mat-button>Logout</button>
|
||||||
|
@ -3,6 +3,8 @@ import { Router } from '@angular/router';
|
|||||||
import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout';
|
import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout';
|
||||||
|
|
||||||
import { AuthService } from 'app/core/services/auth.service';
|
import { AuthService } from 'app/core/services/auth.service';
|
||||||
|
import { AutoupdateService } from 'app/core/services/autoupdate.service';
|
||||||
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -15,10 +17,13 @@ import { BaseComponent } from 'app/base.component';
|
|||||||
styleUrls: ['./site.component.css']
|
styleUrls: ['./site.component.css']
|
||||||
})
|
})
|
||||||
export class SiteComponent extends BaseComponent implements OnInit {
|
export class SiteComponent extends BaseComponent implements OnInit {
|
||||||
|
username = this.operator.username;
|
||||||
isMobile = false;
|
isMobile = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private autoupdateService: AutoupdateService,
|
||||||
|
private operator: OperatorService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
private translate: TranslateService
|
private translate: TranslateService
|
||||||
@ -39,7 +44,18 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
|||||||
|
|
||||||
//get a translation via code: use the translation service
|
//get a translation via code: use the translation service
|
||||||
this.translate.get('Motions').subscribe((res: string) => {
|
this.translate.get('Motions').subscribe((res: string) => {
|
||||||
console.log(res);
|
console.log('translation of motions in the target language: ' + res);
|
||||||
|
});
|
||||||
|
|
||||||
|
//start autoupdate if the user is logged in:
|
||||||
|
this.operator.whoAmI().subscribe(resp => {
|
||||||
|
if (resp.user) {
|
||||||
|
this.autoupdateService.startAutoupdate();
|
||||||
|
} else {
|
||||||
|
//if whoami is not sucsessfull, forward to login again
|
||||||
|
this.operator.clear();
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
<div class="app-content" translate>
|
<div class="app-content" translate>
|
||||||
<span>{{'Welcome to OpenSlides' | translate}}</span>
|
<span>{{'Welcome to OpenSlides' | translate}}</span>
|
||||||
<br/>
|
<br/>
|
||||||
<p translate [translateParams]="{user: 'Tim'}">Hello user</p>
|
<!-- <p translate [translateParams]="{user: 'Tim'}">Hello user</p> -->
|
||||||
|
<p>{{'Hello user' | translate:username}}</p>
|
||||||
<button type="button" (click)="DataStoreTest()">DataStoreTest</button>
|
<button type="button" (click)="DataStoreTest()">DataStoreTest</button>
|
||||||
<br/>
|
<br/>
|
||||||
<button type="button" (click)="TranslateTest()">Translate in console</button>
|
<button type="button" (click)="TranslateTest()">Translate in console</button>
|
||||||
|
@ -5,6 +5,7 @@ import { BaseComponent } from 'app/base.component';
|
|||||||
import { TranslateService } from '@ngx-translate/core'; //showcase
|
import { TranslateService } from '@ngx-translate/core'; //showcase
|
||||||
|
|
||||||
// for testing the DS and BaseModel
|
// for testing the DS and BaseModel
|
||||||
|
import { OperatorService } from 'app/core/services/operator.service';
|
||||||
import { User } from 'app/core/models/users/user';
|
import { User } from 'app/core/models/users/user';
|
||||||
import { Group } from 'app/core/models/users/group';
|
import { Group } from 'app/core/models/users/group';
|
||||||
|
|
||||||
@ -14,12 +15,15 @@ import { Group } from 'app/core/models/users/group';
|
|||||||
styleUrls: ['./start.component.css']
|
styleUrls: ['./start.component.css']
|
||||||
})
|
})
|
||||||
export class StartComponent extends BaseComponent implements OnInit {
|
export class StartComponent extends BaseComponent implements OnInit {
|
||||||
constructor(titleService: Title, private translate: TranslateService) {
|
//useage of translation with variables in code and view
|
||||||
|
username = { user: this.operator.username };
|
||||||
|
|
||||||
|
constructor(titleService: Title, private translate: TranslateService, private operator: OperatorService) {
|
||||||
super(titleService);
|
super(titleService);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
super.setTitle('Start page');
|
super.setTitle('Start page'); //TODO translate
|
||||||
}
|
}
|
||||||
|
|
||||||
//quick testing of some data store functions
|
//quick testing of some data store functions
|
||||||
|
35
package.json
35
package.json
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "openslides",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"prepublish": "bower install && gulp",
|
|
||||||
"karma": "karma start tests/karma/karma.conf.js",
|
|
||||||
"karma:watch": "karma start tests/karma/karma.conf.js --single-run=false"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"angular-mocks": "~1.5.11",
|
|
||||||
"bower": "^1.8.0",
|
|
||||||
"gulp": "~4.0.0",
|
|
||||||
"gulp-angular-gettext": "^2.2.0",
|
|
||||||
"gulp-angular-templatecache": "^2.0.0",
|
|
||||||
"gulp-concat": "^2.6.1",
|
|
||||||
"gulp-cssnano": "^2.1.2",
|
|
||||||
"gulp-if": "^2.0.2",
|
|
||||||
"gulp-inject-string": "^1.1.0",
|
|
||||||
"gulp-jshint": "^2.0.4",
|
|
||||||
"gulp-rename": "^1.2.2",
|
|
||||||
"gulp-sass": "^3.1.0",
|
|
||||||
"gulp-sourcemaps": "^2.6.0",
|
|
||||||
"gulp-uglify": "^2.1.2",
|
|
||||||
"jasmine-core": "^2.6.1",
|
|
||||||
"jshint": "^2.9.4",
|
|
||||||
"karma": "^1.5.0",
|
|
||||||
"karma-chrome-launcher": "^2.0.0",
|
|
||||||
"karma-jasmine": "^1.1.0",
|
|
||||||
"karma-phantomjs-launcher": "^1.0.4",
|
|
||||||
"main-bower-files": "^2.13.1",
|
|
||||||
"sprintf-js": "^1.0.3",
|
|
||||||
"through2": "^2.0.3",
|
|
||||||
"yargs": "^7.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user