TSK-177 Add authentication and login page for taskana spring rest example

This commit is contained in:
Martin Rojas Miguel Angel 2018-05-14 17:27:06 +02:00 committed by Holger Hagen
parent 66bdaab7cf
commit 99b882f867
54 changed files with 721 additions and 272 deletions

View File

@ -1,19 +1,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>pro.taskana</groupId> <groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId> <artifactId>taskana-rest-parent</artifactId>
<version>0.1.4-SNAPSHOT</version> <version>0.1.4-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<description>This pom is parent to all taskana rest modules and serves the common build.</description> <description>This pom is parent to all taskana rest modules and serves the common build.</description>
<properties> <properties>
<java.version>1.8</java.version> <java.version>1.8</java.version>
</properties> </properties>
<modules> <modules>
<module>taskana-rest-spring</module> <module>taskana-rest-spring</module>
<module>taskana-rest-spring-example</module> <module>../web</module>
</modules> <module>taskana-rest-spring-example</module>
</modules>
</project> </project>

View File

@ -52,7 +52,16 @@
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>1.4.197</version> <version>1.4.197</version>
</dependency> </dependency>
<!--
Since taskana-web packs its content in /static, we do not have to unpack it again.
However, when any local change has to be done to that folder you have to copy
target/classes/static manually from taskana-web.
-->
<dependency>
<groupId>pro.taskana</groupId>
<artifactId>taskana-web</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Tests --> <!-- Tests -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -60,7 +69,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@ -69,4 +77,5 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -0,0 +1,12 @@
package pro.taskana.rest.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ViewController {
@RequestMapping({ "/administration*/**", "/workplace*/**", "/monitor*/**" })
public String index() {
return "forward:/index.html";
}
}

View File

@ -0,0 +1,25 @@
package pro.taskana.rest.models;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -10,13 +10,14 @@ import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule; import javax.security.auth.spi.LoginModule;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import pro.taskana.security.GroupPrincipal; import pro.taskana.security.GroupPrincipal;
import pro.taskana.security.UserPrincipal; import pro.taskana.security.UserPrincipal;
/** /**
* TODO. * TODO.
*/ */
public class SampleLoginModule implements LoginModule { public class SampleLoginModule extends UsernamePasswordAuthenticationFilter implements LoginModule {
private NameCallback nameCallback; private NameCallback nameCallback;
@ -24,6 +25,7 @@ public class SampleLoginModule implements LoginModule {
private Subject subject; private Subject subject;
@Override @Override
public boolean abort() throws LoginException { public boolean abort() throws LoginException {
return true; return true;

View File

@ -1,5 +1,6 @@
package pro.taskana.rest.security; package pro.taskana.rest.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -11,13 +12,16 @@ import org.springframework.security.authentication.jaas.JaasAuthenticationProvid
import org.springframework.security.authentication.jaas.JaasNameCallbackHandler; import org.springframework.security.authentication.jaas.JaasNameCallbackHandler;
import org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler; import org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter; import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@ -28,26 +32,43 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
@EnableWebSecurity @EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${devMode}")
private boolean devMode;
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.csrf() http.csrf()
.disable() .disable()
.authenticationProvider(jaasAuthProvider()) .authenticationProvider(jaasAuthProvider())
.authorizeRequests() .authorizeRequests()
.antMatchers(HttpMethod.GET, "/**") .antMatchers(HttpMethod.GET, "/**")
.authenticated() .authenticated()
.and() .and()
.httpBasic() .httpBasic()
.and() .and()
.addFilter(new JaasApiIntegrationFilter()); .addFilter(new JaasApiIntegrationFilter());
if (devMode) {
return;
}
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
} }
@Bean @Bean
public JaasAuthenticationProvider jaasAuthProvider() { public JaasAuthenticationProvider jaasAuthProvider() {
JaasAuthenticationProvider authenticationProvider = new JaasAuthenticationProvider(); JaasAuthenticationProvider authenticationProvider = new JaasAuthenticationProvider();
authenticationProvider.setAuthorityGranters(new AuthorityGranter[] {new SampleRoleGranter()}); authenticationProvider.setAuthorityGranters(new AuthorityGranter[]{new SampleRoleGranter()});
authenticationProvider.setCallbackHandlers(new JaasAuthenticationCallbackHandler[] { authenticationProvider.setCallbackHandlers(new JaasAuthenticationCallbackHandler[]{
new JaasNameCallbackHandler(), new JaasPasswordCallbackHandler()}); new JaasNameCallbackHandler(), new JaasPasswordCallbackHandler()});
authenticationProvider.setLoginContextName("taskana"); authenticationProvider.setLoginContextName("taskana");
authenticationProvider.setLoginConfig(new ClassPathResource("pss_jaas.config")); authenticationProvider.setLoginConfig(new ClassPathResource("pss_jaas.config"));
return authenticationProvider; return authenticationProvider;
@ -72,6 +93,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
config.addAllowedOrigin("*"); config.addAllowedOrigin("*");
config.addAllowedHeader("*"); config.addAllowedHeader("*");
config.addAllowedMethod("*"); config.addAllowedMethod("*");
config.addAllowedMethod("POST");
source.registerCorsConfiguration("/**", config); source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0); bean.setOrder(0);

View File

@ -4,3 +4,4 @@ datasource.url=jdbc:h2:mem:taskana;IGNORECASE=TRUE;LOCK_MODE=0;INIT=CREATE SCHEM
datasource.driverClassName=org.h2.Driver datasource.driverClassName=org.h2.Driver
datasource.username=sa datasource.username=sa
datasource.password=sa datasource.password=sa
devMode=false

View File

@ -0,0 +1,27 @@
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Custom Login Page</title>
</head>
<body>
<div>
<form name="f" th:action="@{/login}" method="post">
<fieldset>
<legend>Please Login</legend>
<div th:if="${param.error}" class="alert alert-error">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-success">
You have been logged out.
</div>
<label for="username">Username</label>
<input type="text" id="username" name="username"/>
<label for="password">Password</label>
<input type="password" id="password" name="password"/>
<div class="form-actions">
<button type="submit" class="btn">Log in</button>
</div>
</fieldset>
</form>
</div>
</body>
</html>

View File

@ -39,7 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import pro.taskana.rest.resource.ClassificationSummaryResource; import pro.taskana.rest.resource.ClassificationSummaryResource;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class) @Import(RestConfiguration.class)
public class ClassificationControllerIntTest { public class ClassificationControllerIntTest {

View File

@ -43,7 +43,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import pro.taskana.rest.resource.TaskSummaryResource; import pro.taskana.rest.resource.TaskSummaryResource;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class) @Import(RestConfiguration.class)
public class TaskControllerIntTest { public class TaskControllerIntTest {

View File

@ -32,7 +32,7 @@ import pro.taskana.TaskanaRole;
import pro.taskana.rest.resource.TaskanaUserInfoResource; import pro.taskana.rest.resource.TaskanaUserInfoResource;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class) @Import(RestConfiguration.class)
public class TaskanaEngineControllerIntTest { public class TaskanaEngineControllerIntTest {

View File

@ -40,7 +40,7 @@ import pro.taskana.rest.resource.DistributionTargetResource;
import pro.taskana.rest.resource.WorkbasketSummaryResource; import pro.taskana.rest.resource.WorkbasketSummaryResource;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class) @Import(RestConfiguration.class)
public class WorkbasketControllerIntTest { public class WorkbasketControllerIntTest {

32
web/package-lock.json generated
View File

@ -1221,7 +1221,17 @@
"boom": { "boom": {
"version": "2.10.1", "version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
"requires": {
"hoek": "2.16.3"
},
"dependencies": {
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
}
}
}, },
"bootstrap": { "bootstrap": {
"version": "3.3.7", "version": "3.3.7",
@ -5168,7 +5178,15 @@
"requires": { "requires": {
"boom": "2.10.1", "boom": "2.10.1",
"cryptiles": "2.0.5", "cryptiles": "2.0.5",
"hoek": "2.16.3",
"sntp": "1.0.9" "sntp": "1.0.9"
},
"dependencies": {
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
}
} }
}, },
"he": { "he": {
@ -10004,7 +10022,17 @@
"sntp": { "sntp": {
"version": "1.0.9", "version": "1.0.9",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
"requires": {
"hoek": "2.16.3"
},
"dependencies": {
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
}
}
}, },
"socket.io": { "socket.io": {
"version": "2.0.4", "version": "2.0.4",

View File

@ -41,7 +41,7 @@ const routes: Routes = [
{ {
path: 'classifications', path: 'classifications',
component: MasterAndDetailComponent, component: MasterAndDetailComponent,
canActivate: [DomainGuard], canActivate: [DomainGuard],
children: [ children: [
{ {
path: '', path: '',

View File

@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { Ng2AutoCompleteModule } from 'ng2-auto-complete'; import { Ng2AutoCompleteModule } from 'ng2-auto-complete';
import { HttpClientModule } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { AlertModule } from 'ngx-bootstrap'; import { AlertModule } from 'ngx-bootstrap';
import { SharedModule } from 'app/shared/shared.module'; import { SharedModule } from 'app/shared/shared.module';
@ -45,11 +44,10 @@ const MODULES = [
CommonModule, CommonModule,
FormsModule, FormsModule,
Ng2AutoCompleteModule, Ng2AutoCompleteModule,
HttpClientModule,
AngularSvgIconModule, AngularSvgIconModule,
AlertModule, AlertModule,
SharedModule, SharedModule,
AdministrationRoutingModule, AdministrationRoutingModule
]; ];
const DECLARATIONS = [ const DECLARATIONS = [

View File

@ -4,23 +4,19 @@ import { Injectable } from '@angular/core';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { ReplaySubject } from 'rxjs/ReplaySubject'; import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable() @Injectable()
export class ClassificationCategoriesService { export class ClassificationCategoriesService {
private url = environment.taskanaRestUrl + '/v1/classification-categories'; private url = environment.taskanaRestUrl + '/v1/classification-categories';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private dataObs$ = new ReplaySubject<Array<string>>(1); private dataObs$ = new ReplaySubject<Array<string>>(1);
constructor(private httpClient: HttpClient) { } constructor(
private httpClient: HttpClient) { }
getCategories(forceRefresh = false): Observable<Array<string>> { getCategories(forceRefresh = false): Observable<Array<string>> {
if (!this.dataObs$.observers.length || forceRefresh) { if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<Array<string>>(this.url, this.httpOptions).subscribe( this.httpClient.get<Array<string>>(this.url).subscribe(
data => this.dataObs$.next(data), data => this.dataObs$.next(data),
error => { error => {
this.dataObs$.error(error); this.dataObs$.error(error);

View File

@ -11,21 +11,16 @@ import { TaskanaDate } from 'app/shared/util/taskana.date';
export class ClassificationDefinitionService { export class ClassificationDefinitionService {
url = environment.taskanaRestUrl + '/v1/classificationdefinitions'; url = environment.taskanaRestUrl + '/v1/classificationdefinitions';
constructor(
httpOptions = { private httpClient: HttpClient,
headers: new HttpHeaders({ private alertService: AlertService
'Content-Type': 'application/json', ) {
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient, private alertService: AlertService) {
} }
// GET // GET
exportClassifications(domain: string) { exportClassifications(domain: string) {
domain = (domain === '' ? '' : '?domain=' + domain); domain = (domain === '' ? '' : '?domain=' + domain);
this.httpClient.get<ClassificationDefinition[]>(this.url + domain, this.httpOptions) this.httpClient.get<ClassificationDefinition[]>(this.url + domain)
.subscribe( .subscribe(
response => saveAs(new Blob([JSON.stringify(response)], { type: 'text/plain;charset=utf-8' }), response => saveAs(new Blob([JSON.stringify(response)], { type: 'text/plain;charset=utf-8' }),
'Classifications_' + TaskanaDate.getDate() + '.json') 'Classifications_' + TaskanaDate.getDate() + '.json')
@ -36,7 +31,7 @@ export class ClassificationDefinitionService {
// TODO handle error // TODO handle error
importClassifications(classifications: any) { importClassifications(classifications: any) {
this.httpClient.post(this.url + '/import', this.httpClient.post(this.url + '/import',
JSON.parse(classifications), this.httpOptions).subscribe( JSON.parse(classifications)).subscribe(
classificationsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')), classificationsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')),
error => this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'Import was not successful')) error => this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'Import was not successful'))
); );

View File

@ -9,22 +9,17 @@ import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable() @Injectable()
export class ClassificationTypesService { export class ClassificationTypesService {
private url = environment.taskanaRestUrl + '/v1/classification-types'; private url = environment.taskanaRestUrl + '/v1/classification-types';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private classificationTypeSelectedValue = 'TASK'; private classificationTypeSelectedValue = 'TASK';
private classificationTypeSelected = new BehaviorSubject<string>(this.classificationTypeSelectedValue); private classificationTypeSelected = new BehaviorSubject<string>(this.classificationTypeSelectedValue);
private dataObs$ = new ReplaySubject<Array<string>>(1); private dataObs$ = new ReplaySubject<Array<string>>(1);
constructor(private httpClient: HttpClient) { } constructor(private httpClient: HttpClient
) { }
getClassificationTypes(forceRefresh = false): Observable<Array<string>> { getClassificationTypes(forceRefresh = false): Observable<Array<string>> {
if (!this.dataObs$.observers.length || forceRefresh) { if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<Array<string>>(this.url, this.httpOptions).subscribe( this.httpClient.get<Array<string>>(this.url).subscribe(
data => this.dataObs$.next(data), data => this.dataObs$.next(data),
error => { error => {
this.dataObs$.error(error); this.dataObs$.error(error);

View File

@ -22,13 +22,6 @@ export class ClassificationsService {
private classificationSelected = new Subject<string>(); private classificationSelected = new Subject<string>();
private classificationSaved = new Subject<number>(); private classificationSaved = new Subject<number>();
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private classificationTypes: Array<string>; private classificationTypes: Array<string>;
constructor( constructor(
@ -42,8 +35,7 @@ export class ClassificationsService {
return this.domainService.getSelectedDomain().mergeMap(domain => { return this.domainService.getSelectedDomain().mergeMap(domain => {
const classificationTypes = this.classificationTypeService.getSelectedClassificationType(); const classificationTypes = this.classificationTypeService.getSelectedClassificationType();
return this.getClassificationObservable(this.httpClient.get<ClassificationResource>( return this.getClassificationObservable(this.httpClient.get<ClassificationResource>(
`${environment.taskanaRestUrl}/v1/classifications/?domain=${domain}`, `${environment.taskanaRestUrl}/v1/classifications/?domain=${domain}`));
this.httpOptions));
}).do(() => { }).do(() => {
this.domainService.domainChangedComplete(); this.domainService.domainChangedComplete();
@ -52,7 +44,7 @@ export class ClassificationsService {
// GET // GET
getClassification(id: string): Observable<ClassificationDefinition> { getClassification(id: string): Observable<ClassificationDefinition> {
return this.httpClient.get<ClassificationDefinition>(`${environment.taskanaRestUrl}/v1/classifications/${id}`, this.httpOptions) return this.httpClient.get<ClassificationDefinition>(`${environment.taskanaRestUrl}/v1/classifications/${id}`)
.do((classification: ClassificationDefinition) => { .do((classification: ClassificationDefinition) => {
if (classification) { if (classification) {
this.classificationTypeService.selectClassificationType(classification.type); this.classificationTypeService.selectClassificationType(classification.type);
@ -63,18 +55,17 @@ export class ClassificationsService {
// POST // POST
postClassification(classification: Classification): Observable<Classification> { postClassification(classification: Classification): Observable<Classification> {
return this.httpClient.post<Classification>(`${environment.taskanaRestUrl}/v1/classifications`, classification, return this.httpClient.post<Classification>(`${environment.taskanaRestUrl}/v1/classifications`, classification);
this.httpOptions);
} }
// PUT // PUT
putClassification(url: string, classification: Classification): Observable<Classification> { putClassification(url: string, classification: Classification): Observable<Classification> {
return this.httpClient.put<Classification>(url, classification, this.httpOptions); return this.httpClient.put<Classification>(url, classification);
} }
// DELETE // DELETE
deleteClassification(url: string): Observable<string> { deleteClassification(url: string): Observable<string> {
return this.httpClient.delete<string>(url, this.httpOptions); return this.httpClient.delete<string>(url);
} }
// #region "Service extras" // #region "Service extras"

View File

@ -1,37 +1,29 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import {environment} from 'app/../environments/environment'; import { environment } from 'app/../environments/environment';
import {saveAs} from 'file-saver/FileSaver'; import { saveAs } from 'file-saver/FileSaver';
import {AlertService} from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import {WorkbasketDefinition} from 'app/models/workbasket-definition'; import { WorkbasketDefinition } from 'app/models/workbasket-definition';
import {AlertModel, AlertType} from 'app/models/alert'; import { AlertModel, AlertType } from 'app/models/alert';
import {TaskanaDate} from 'app/shared/util/taskana.date'; import { TaskanaDate } from 'app/shared/util/taskana.date';
import {ErrorModel} from 'app/models/modal-error'; import { ErrorModel } from 'app/models/modal-error';
import {ErrorModalService} from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
@Injectable() @Injectable()
export class WorkbasketDefinitionService { export class WorkbasketDefinitionService {
url: string = environment.taskanaRestUrl + '/v1/workbasketdefinitions'; url: string = environment.taskanaRestUrl + '/v1/workbasketdefinitions';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient, private alertService: AlertService, constructor(private httpClient: HttpClient, private alertService: AlertService,
private errorModalService: ErrorModalService) { private errorModalService: ErrorModalService) {
} }
// GET // GET
exportWorkbaskets(domain: string) { exportWorkbaskets(domain: string) {
domain = (domain === '' ? '' : '?domain=' + domain); domain = (domain === '' ? '' : '?domain=' + domain);
this.httpClient.get<WorkbasketDefinition[]>(this.url + domain, this.httpOptions).subscribe( this.httpClient.get<WorkbasketDefinition[]>(this.url + domain).subscribe(
response => { response => {
saveAs(new Blob([JSON.stringify(response)], {type: 'text/plain;charset=utf-8'}), saveAs(new Blob([JSON.stringify(response)], { type: 'text/plain;charset=utf-8' }),
'Workbaskets_' + TaskanaDate.getDate() + '.json'); 'Workbaskets_' + TaskanaDate.getDate() + '.json');
} }
); );
@ -40,10 +32,10 @@ export class WorkbasketDefinitionService {
// POST // POST
importWorkbasketDefinitions(workbasketDefinitions: any) { importWorkbasketDefinitions(workbasketDefinitions: any) {
this.httpClient.post(environment.taskanaRestUrl + '/v1/workbasketdefinitions/import', this.httpClient.post(environment.taskanaRestUrl + '/v1/workbasketdefinitions/import',
JSON.parse(workbasketDefinitions), this.httpOptions).subscribe( JSON.parse(workbasketDefinitions)).subscribe(
workbasketsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')), workbasketsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')),
error => this.errorModalService.triggerError(new ErrorModel( error => this.errorModalService.triggerError(new ErrorModel(
`There was an error importing workbaskets`, error.message)) `There was an error importing workbaskets`, error.message))
); );
} }
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
import { environment } from 'app/../environments/environment'; import { environment } from 'app/../environments/environment';
@ -43,12 +43,6 @@ export class WorkbasketService {
// Domain // Domain
readonly DOMAIN = 'domain'; readonly DOMAIN = 'domain';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
page = 1; page = 1;
pageSize = 9; pageSize = 9;
@ -86,7 +80,7 @@ export class WorkbasketService {
`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters( `${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(
sortBy, order, name, sortBy, order, name,
nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission, nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission,
!allPages ? this.page : undefined, !allPages ? this.pageSize : undefined, domain)}`, this.httpOptions) !allPages ? this.page : undefined, !allPages ? this.pageSize : undefined, domain)}`)
.do(workbaskets => { .do(workbaskets => {
return workbaskets; return workbaskets;
}); });
@ -96,51 +90,50 @@ export class WorkbasketService {
} }
// GET // GET
getWorkBasket(id: string): Observable<Workbasket> { getWorkBasket(id: string): Observable<Workbasket> {
return this.httpClient.get<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets/${id}`, this.httpOptions); return this.httpClient.get<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets/${id}`);
} }
// POST // POST
createWorkbasket(workbasket: Workbasket): Observable<Workbasket> { createWorkbasket(workbasket: Workbasket): Observable<Workbasket> {
return this.httpClient return this.httpClient
.post<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets`, workbasket, this.httpOptions); .post<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets`, workbasket);
} }
// PUT // PUT
updateWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> { updateWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> {
return this.httpClient return this.httpClient
.put<Workbasket>(url, workbasket, this.httpOptions) .put<Workbasket>(url, workbasket)
.catch(this.handleError); .catch(this.handleError);
} }
// DELETE // DELETE
deleteWorkbasket(url: string) { deleteWorkbasket(url: string) {
return this.httpClient.delete(url, this.httpOptions); return this.httpClient.delete(url);
} }
// GET // GET
getWorkBasketAccessItems(url: string): Observable<WorkbasketAccessItemsResource> { getWorkBasketAccessItems(url: string): Observable<WorkbasketAccessItemsResource> {
return this.httpClient.get<WorkbasketAccessItemsResource>(url, this.httpOptions); return this.httpClient.get<WorkbasketAccessItemsResource>(url);
} }
// POST // POST
createWorkBasketAccessItem(url: string, workbasketAccessItem: WorkbasketAccessItems): Observable<WorkbasketAccessItems> { createWorkBasketAccessItem(url: string, workbasketAccessItem: WorkbasketAccessItems): Observable<WorkbasketAccessItems> {
return this.httpClient.post<WorkbasketAccessItems>(url, workbasketAccessItem, this.httpOptions); return this.httpClient.post<WorkbasketAccessItems>(url, workbasketAccessItem);
} }
// PUT // PUT
updateWorkBasketAccessItem(url: string, workbasketAccessItem: Array<WorkbasketAccessItems>): Observable<string> { updateWorkBasketAccessItem(url: string, workbasketAccessItem: Array<WorkbasketAccessItems>): Observable<string> {
return this.httpClient.put<string>(url, return this.httpClient.put<string>(url,
workbasketAccessItem, workbasketAccessItem);
this.httpOptions);
} }
// GET // GET
getWorkBasketsDistributionTargets(url: string): Observable<WorkbasketDistributionTargetsResource> { getWorkBasketsDistributionTargets(url: string): Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.get<WorkbasketDistributionTargetsResource>(url, this.httpOptions); return this.httpClient.get<WorkbasketDistributionTargetsResource>(url);
} }
// PUT // PUT
updateWorkBasketsDistributionTargets(url: string, distributionTargetsIds: Array<string>): updateWorkBasketsDistributionTargets(url: string, distributionTargetsIds: Array<string>):
Observable<WorkbasketDistributionTargetsResource> { Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.put<WorkbasketDistributionTargetsResource>(url, distributionTargetsIds, this.httpOptions); return this.httpClient.put<WorkbasketDistributionTargetsResource>(url, distributionTargetsIds);
} }
// DELETE // DELETE
removeDistributionTarget(url: string) { removeDistributionTarget(url: string) {
return this.httpClient.delete<string>(url, this.httpOptions); return this.httpClient.delete<string>(url);
} }
@ -214,5 +207,6 @@ export class WorkbasketService {
return Observable.throw(errMsg); return Observable.throw(errMsg);
} }
// #endregion // #endregion
} }

View File

@ -1,6 +1,6 @@
<div class="container-scrollable"> <div class="container-scrollable">
<taskana-spinner [isRunning]="requestInProgress" ></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" ></taskana-spinner>
<taskana-no-access *ngIf="!requestInProgress && (!hasPermission && !workbasket || !workbasket && selectedId)"></taskana-no-access> <taskana-no-access *ngIf="!requestInProgress && (!workbasket || !workbasket && selectedId)"></taskana-no-access>
<div id="workbasket-details" *ngIf="workbasket && !requestInProgress"> <div id="workbasket-details" *ngIf="workbasket && !requestInProgress">
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
<li *ngIf="showDetail" class="visible-xs visible-sm hidden"> <li *ngIf="showDetail" class="visible-xs visible-sm hidden">

View File

@ -20,7 +20,6 @@ import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'; import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service';
import { PermissionService } from 'app/services/permission/permission.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service'; import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
@ -79,7 +78,7 @@ describe('WorkbasketDetailsComponent', () => {
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent, declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent, IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe], DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, PermissionService, ErrorModalService, RequestInProgressService, providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService,
AlertService, SavingWorkbasketService, { AlertService, SavingWorkbasketService, {
provide: DomainService, provide: DomainService,
useClass: DomainServiceMock useClass: DomainServiceMock
@ -92,7 +91,6 @@ describe('WorkbasketDetailsComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketDetailsComponent); fixture = TestBed.createComponent(WorkbasketDetailsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.hasPermission = false;
debugElement = fixture.debugElement.nativeElement; debugElement = fixture.debugElement.nativeElement;
router = TestBed.get(Router) router = TestBed.get(Router)
fixture.detectChanges(); fixture.detectChanges();
@ -129,16 +127,10 @@ describe('WorkbasketDetailsComponent', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should has created taskana-no-access if workbasket is not defined and hasPermission is false', () => {
expect(component.workbasket).toBeUndefined();
component.hasPermission = false;
fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();
});
it('should has created taskana-workbasket-details if workbasket is defined and taskana-no-access should dissapear', () => { it('should has created taskana-workbasket-details if workbasket is defined and taskana-no-access should dissapear', () => {
expect(component.workbasket).toBeUndefined(); expect(component.workbasket).toBeUndefined();
component.hasPermission = false;
fixture.detectChanges(); fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy(); expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();

View File

@ -11,7 +11,6 @@ import { ACTION } from 'app/models/action';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service' import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service'
import { PermissionService } from 'app/services/permission/permission.service';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service' import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'
import { DomainService } from 'app/services/domain/domain.service'; import { DomainService } from 'app/services/domain/domain.service';
@ -28,7 +27,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
workbasketCopy: Workbasket; workbasketCopy: Workbasket;
selectedId: string = undefined; selectedId: string = undefined;
showDetail = false; showDetail = false;
hasPermission = true;
requestInProgress = false; requestInProgress = false;
action: string; action: string;
@ -43,7 +41,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private masterAndDetailService: MasterAndDetailService, private masterAndDetailService: MasterAndDetailService,
private permissionService: PermissionService,
private domainService: DomainService) { } private domainService: DomainService) { }
@ -80,14 +77,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => { this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => {
this.showDetail = showDetail; this.showDetail = showDetail;
}); });
this.permissionSubscription = this.permissionService.hasPermission().subscribe(permission => {
this.hasPermission = permission;
if (!this.hasPermission) {
this.requestInProgress = false;
}
})
} }
backClicked(): void { backClicked(): void {

View File

@ -2,13 +2,17 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard';
const appRoutes: Routes = [ const appRoutes: Routes = [
{ {
canActivate: [BusinessAdminGuard],
path: 'administration', path: 'administration',
loadChildren: './administration/administration.module#AdministrationModule', loadChildren: './administration/administration.module#AdministrationModule',
}, },
{ {
canActivate: [MonitorGuard],
path: 'monitor', path: 'monitor',
loadChildren: './monitor/monitor.module#MonitorModule', loadChildren: './monitor/monitor.module#MonitorModule',
}, },

View File

@ -19,8 +19,6 @@ import { SharedModule } from 'app/shared/shared.module';
* Services * Services
*/ */
import { HttpClientInterceptor } from 'app/services/httpClientInterceptor/http-client-interceptor.service';
import { PermissionService } from 'app/services/permission/permission.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { OrientationService } from 'app/services/orientation/orientation.service'; import { OrientationService } from 'app/services/orientation/orientation.service';
@ -32,19 +30,22 @@ import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-
import { TreeService } from 'app/services/tree/tree.service'; import { TreeService } from 'app/services/tree/tree.service';
import { TitlesService } from 'app/services/titles/titles.service'; import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { WindowRefService } from 'app/services/window/window.service';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
/** /**
* Components * Components
*/ */
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { NavBarComponent } from 'app/components/nav-bar/nav-bar.component'; import { NavBarComponent } from 'app/components/nav-bar/nav-bar.component';
import { UserInformationComponent } from 'app/components/user-information/user-information.component';
/** /**
* Guards * Guards
*/ */
import { DomainGuard } from './guards/domain-guard'; import { DomainGuard } from './guards/domain-guard';
import { BusinessAdminGuard } from './guards/business-admin-guard';
import { MonitorGuard } from './guards/monitor-guard';
import { APP_BASE_HREF } from '@angular/common'; import { APP_BASE_HREF } from '@angular/common';
@ -64,29 +65,31 @@ const MODULES = [
const DECLARATIONS = [ const DECLARATIONS = [
AppComponent, AppComponent,
NavBarComponent NavBarComponent,
UserInformationComponent
]; ];
export function startupServiceFactory(startupService: StartupService): Function { export function startupServiceFactory(startupService: StartupService): () => Promise<any> {
return () => startupService.load(); return (): Promise<any> => {
return startupService.load();
};
} }
@NgModule({ @NgModule({
declarations: DECLARATIONS, declarations: DECLARATIONS,
imports: MODULES, imports: MODULES,
providers: [ providers: [
WindowRefService,
{ provide: APP_BASE_HREF, useValue: '/' }, { provide: APP_BASE_HREF, useValue: '/' },
DomainService, DomainService,
{
provide: HTTP_INTERCEPTORS,
useClass: HttpClientInterceptor,
multi: true
},
ErrorModalService, ErrorModalService,
RequestInProgressService, RequestInProgressService,
OrientationService, OrientationService,
SelectedRouteService, SelectedRouteService,
DomainGuard, DomainGuard,
BusinessAdminGuard,
MonitorGuard,
StartupService, StartupService,
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
@ -95,11 +98,11 @@ export function startupServiceFactory(startupService: StartupService): Function
multi: true multi: true
}, },
AlertService, AlertService,
PermissionService,
MasterAndDetailService, MasterAndDetailService,
TreeService, TreeService,
TitlesService, TitlesService,
CustomFieldsService CustomFieldsService,
TaskanaEngineService
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -3,7 +3,7 @@
<div class="pull-left col-sm-3 col-md-4"> <div class="pull-left col-sm-3 col-md-4">
<button type="button" *ngIf="!showNavbar" class="btn btn-default navbar-toggle show pull-left" (click)="toogleNavBar();" <button type="button" *ngIf="!showNavbar" class="btn btn-default navbar-toggle show pull-left" (click)="toogleNavBar();"
aria-expanded="true" aria-controls="navbar"> aria-expanded="true" aria-controls="navbar">
<span class="glyphicon glyphicon-arrow-right white"></span> <span class="glyphicon glyphicon glyphicon-th-list white"></span>
</button> </button>
<span>&nbsp;</span> <span>&nbsp;</span>
</div> </div>
@ -13,7 +13,7 @@
<h2 class="navbar-brand no-margin"> {{title}}</h2> <h2 class="navbar-brand no-margin"> {{title}}</h2>
</ul> </ul>
</div> </div>
<div *ngIf="selectedRoute.indexOf('workbaskets') !== -1 || selectedRoute.indexOf('classifications') !== -1"class="pull-right domain-form"> <div *ngIf="selectedRoute.indexOf('workbaskets') !== -1 || selectedRoute.indexOf('classifications') !== -1" class="pull-right domain-form">
<div class="dropdown clearfix btn-group"> <div class="dropdown clearfix btn-group">
<label class="control-label hidden-xs">Working on </label> <label class="control-label hidden-xs">Working on </label>
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
@ -36,23 +36,25 @@
<ul class="nav"> <ul class="nav">
<svg-icon class="logo white visible-xs" src="./assets/icons/logo.svg"></svg-icon> <svg-icon class="logo white visible-xs" src="./assets/icons/logo.svg"></svg-icon>
<h2 class="navbar-brand no-margin logo visible-xs"> {{title}}</h2> <h2 class="navbar-brand no-margin logo visible-xs"> {{title}}</h2>
<button type="button" *ngIf="showNavbar" class="btn btn-default navbar-toggle show pull-right" (click)="toogleNavBar();" <button type="button" class="btn btn-default logout navbar-toggle show pull-right" data-toggle="tooltip" title="logout" (click)="logout()" aria-expanded="true"
aria-expanded="true" aria-controls="navbar"> aria-controls="logout">
<span class="glyphicon glyphicon-arrow-left white"></span> <span class="glyphicon glyphicon-share white"></span>
</button> </button>
</ul> </ul>
</div> </div>
<div class="nav-content"> <div class="nav-content">
<div class="row menu"> <taskana-user-information></taskana-user-information>
<div *ngIf="administrationAccess" class="row menu">
<span (click)="toogleNavBar()" routerLink="administration/workbaskets" aria-controls="administration" routerLinkActive="active">Administration</span> <span (click)="toogleNavBar()" routerLink="administration/workbaskets" aria-controls="administration" routerLinkActive="active">Administration</span>
<div class="row submenu" [ngClass]="{'selected': selectedRoute.indexOf('workbaskets') !== -1 }"> <div class="row submenu" [ngClass]="{'selected': selectedRoute.indexOf('workbaskets') !== -1 }">
<span (click)="toogleNavBar()" class="col-xs-6" routerLink="administration/workbaskets" aria-controls="Workbaskets" routerLinkActive="active">Workbaskets</span> <span (click)="toogleNavBar()" class="col-xs-6" routerLink="administration/workbaskets" aria-controls="Workbaskets" routerLinkActive="active">Workbaskets</span>
</div> </div>
<div class="row submenu" [ngClass]="{'selected': selectedRoute.indexOf('classifications') !== -1}"> <div class="row submenu" [ngClass]="{'selected': selectedRoute.indexOf('classifications') !== -1}">
<span (click)="toogleNavBar()" class="col-xs-6" routerLink="administration/classifications" aria-controls="Classifications" routerLinkActive="active">Classifications</span> <span (click)="toogleNavBar()" class="col-xs-6" routerLink="administration/classifications" aria-controls="Classifications"
routerLinkActive="active">Classifications</span>
</div> </div>
</div> </div>
<div class="row menu" [ngClass]="{'selected': selectedRoute.indexOf('monitor') !== -1}"> <div *ngIf="monitorAccess" class="row menu" [ngClass]="{'selected': selectedRoute.indexOf('monitor') !== -1}">
<span (click)="toogleNavBar()" routerLink="{{monitorUrl}}" aria-controls="Monitor" routerLinkActive="active">Monitor</span> <span (click)="toogleNavBar()" routerLink="{{monitorUrl}}" aria-controls="Monitor" routerLinkActive="active">Monitor</span>
</div> </div>
<div class="row menu" [ngClass]="{'selected': selectedRoute.indexOf('workplace') !== -1 || selectedRoute === ''}"> <div class="row menu" [ngClass]="{'selected': selectedRoute.indexOf('workplace') !== -1 || selectedRoute === ''}">

View File

@ -10,6 +10,10 @@ $selected-border-color: #22a39f;
.navbar-toggle{ .navbar-toggle{
margin-right: 0px; margin-right: 0px;
}
button.navbar-toggle:hover > span{
color:$selected-border-color;
} }
ul.nav > p { ul.nav > p {
white-space: nowrap; white-space: nowrap;
@ -29,7 +33,6 @@ svg-icon.logo {
padding: 6px 5px 5px 5px; padding: 6px 5px 5px 5px;
} }
h2.navbar-brand{ h2.navbar-brand{
vertical-align: middle; vertical-align: middle;
text-decoration: none; text-decoration: none;
@ -67,8 +70,7 @@ h2.navbar-brand{
.nav-content{ .nav-content{
margin-top: 25px; margin-top: 5px;
margin-left: 8px;
} }
/* /*

View File

@ -3,16 +3,28 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from 'app/shared/shared.module';
import { Observable } from 'rxjs/Observable';
import { NavBarComponent } from './nav-bar.component'; import { NavBarComponent } from './nav-bar.component';
import { UserInformationComponent } from 'app/components/user-information/user-information.component';
import { SelectedRouteService } from 'app/services/selected-route/selected-route'; import { SelectedRouteService } from 'app/services/selected-route/selected-route';
import { DomainService } from 'app/services/domain/domain.service'; import { DomainService } from 'app/services/domain/domain.service';
import { DomainServiceMock } from 'app/services/domain/domain.service.mock'; import { DomainServiceMock } from 'app/services/domain/domain.service.mock';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from 'app/services/window/window.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { UserInfoModel } from 'app/models/user-info';
describe('NavBarComponent', () => { describe('NavBarComponent', () => {
let component: NavBarComponent; let component: NavBarComponent;
let fixture: ComponentFixture<NavBarComponent>; let fixture: ComponentFixture<NavBarComponent>;
let debugElement, navBar; let debugElement, navBar, taskanaEngineService;
const routes: Routes = [ const routes: Routes = [
{ path: 'classifications', component: NavBarComponent } { path: 'classifications', component: NavBarComponent }
@ -20,16 +32,23 @@ describe('NavBarComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [NavBarComponent], declarations: [NavBarComponent, UserInformationComponent],
imports: [ imports: [
AngularSvgIconModule, AngularSvgIconModule,
HttpClientModule, HttpClientModule,
RouterTestingModule.withRoutes(routes), RouterTestingModule.withRoutes(routes),
SharedModule
], ],
providers: [SelectedRouteService, { providers: [SelectedRouteService, {
provide: DomainService, provide: DomainService,
useClass: DomainServiceMock useClass: DomainServiceMock
}] },
BusinessAdminGuard,
MonitorGuard,
TaskanaEngineService,
WindowRefService,
ErrorModalService,
RequestInProgressService]
}) })
.compileComponents(); .compileComponents();
})); }));
@ -39,6 +58,8 @@ describe('NavBarComponent', () => {
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement; debugElement = fixture.debugElement.nativeElement;
navBar = fixture.debugElement.componentInstance; navBar = fixture.debugElement.componentInstance;
taskanaEngineService = TestBed.get(TaskanaEngineService);
spyOn(taskanaEngineService, 'getUserInformation').and.returnValue(Observable.of(new UserInfoModel));
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -4,7 +4,9 @@ import { SelectedRouteService } from 'app/services/selected-route/selected-route
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { trigger, state, style, transition, keyframes, animate } from '@angular/animations'; import { trigger, state, style, transition, keyframes, animate } from '@angular/animations';
import { DomainService } from 'app/services/domain/domain.service'; import { DomainService } from 'app/services/domain/domain.service';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard';
import { WindowRefService } from 'app/services/window/window.service';
@Component({ @Component({
selector: 'taskana-nav-bar', selector: 'taskana-nav-bar',
templateUrl: './nav-bar.component.html', templateUrl: './nav-bar.component.html',
@ -38,11 +40,17 @@ export class NavBarComponent implements OnInit, OnDestroy {
monitorUrl = './monitor'; monitorUrl = './monitor';
workplaceUrl = './workplace'; workplaceUrl = './workplace';
administrationAccess = false;
monitorAccess = false;
selectedRouteSubscription: Subscription; selectedRouteSubscription: Subscription;
constructor( constructor(
private selectedRouteService: SelectedRouteService, private selectedRouteService: SelectedRouteService,
private domainService: DomainService) { } private domainService: DomainService,
private businessAdminGuard: BusinessAdminGuard,
private monitorGuard: MonitorGuard,
private window: WindowRefService) { }
ngOnInit() { ngOnInit() {
this.selectedRouteSubscription = this.selectedRouteService.getSelectedRoute().subscribe((value: string) => { this.selectedRouteSubscription = this.selectedRouteService.getSelectedRoute().subscribe((value: string) => {
@ -56,6 +64,13 @@ export class NavBarComponent implements OnInit, OnDestroy {
this.domainService.getSelectedDomain().subscribe(domain => { this.domainService.getSelectedDomain().subscribe(domain => {
this.selectedDomain = domain; this.selectedDomain = domain;
}); });
this.businessAdminGuard.canActivate().subscribe(hasAccess => {
this.administrationAccess = hasAccess
});
this.monitorGuard.canActivate().subscribe(hasAccess => {
this.monitorAccess = hasAccess
});
} }
switchDomain(domain) { switchDomain(domain) {
@ -65,6 +80,12 @@ export class NavBarComponent implements OnInit, OnDestroy {
toogleNavBar() { toogleNavBar() {
this.showNavbar = !this.showNavbar; this.showNavbar = !this.showNavbar;
} }
logout() {
this.window.nativeWindow.location.href = environment.taskanaRestUrl + '/login?logout';
}
private setTitle(value: string = 'workbaskets') { private setTitle(value: string = 'workbaskets') {
if (value.indexOf('workbaskets') === 0 || value.indexOf('classifications') === 0) { if (value.indexOf('workbaskets') === 0 || value.indexOf('classifications') === 0) {
this.title = this.titleAdministration; this.title = this.titleAdministration;

View File

@ -0,0 +1,16 @@
<div class="row">
<div class="icon">
<div class="icon-wrap col-xs-offset-5">
<svg-icon class="blue big" src="./assets/icons/user.svg"></svg-icon>
</div>
</div>
<div class="user-info white">
<span>Logged as: {{userInformation.userId}}</span>
<button type="button" class="btn btn-default white pull-right transparent" (click)="toogleRoles();" aria-expanded="true" aria-controls="roles">
<span>Roles</span>
</button>
</div>
<div class="white pull-right roles col-xs-12" [@toggle]="showRoles" *ngIf="showRoles">
<span><i>{{roles}}</i></span>
</div>
</div>

View File

@ -0,0 +1,68 @@
.big {
width: 64px;
height: 64px;
}
.user-info {
margin-top:85px;
> span {
margin: 0 15px 0 15px;
font-size: 20px;
font-family: inherit;
font-weight: 500;
line-height: 1.1;
}
}
.icon, {
width: 100%;
position: relative;
& > .icon-wrap {
border-radius: 50%;
border: solid 3px #22a39f;
width: 74px;
height: 74px;
overflow: hidden;
background-color: #175263;
position: absolute;
-webkit-box-shadow: 0px 3px 3px #416b6a;
-moz-box-shadow: 0px 3px 3px #416b6a;
box-shadow: 0px 3px 3px #416b6a;
&> svg-icon {
border-radius: 50%;
overflow: hidden;
background-color: white;
position: absolute;
margin:2px
}
}
}
.icon:before { content: '';
position: absolute;
border-bottom: solid 3px #22a39f;
width: 100%;
height: 60px;
transform: translateY(-33%);
-webkit-box-shadow: 0px 3px 3px #416b6a;
-moz-box-shadow: 0px 3px 3px #416b6a;
box-shadow: 0px 3px 3px #416b6a;
}
button.transparent, {
border: none;
background-color: transparent;
&:hover{
border: none;
background-color: transparent;
color:#22a39f
}
&:focus{
border: none;
background-color: transparent;
outline: none;
color:white
}
}

View File

@ -0,0 +1,37 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { HttpModule } from '@angular/http';
import { UserInformationComponent } from './user-information.component';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { UserInfoModel } from 'app/models/user-info';
describe('UserInformationComponent', () => {
let component: UserInformationComponent;
let fixture: ComponentFixture<UserInformationComponent>;
let taskanaEngineService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AngularSvgIconModule,
HttpClientModule, HttpModule],
declarations: [UserInformationComponent],
providers: [TaskanaEngineService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UserInformationComponent);
component = fixture.componentInstance;
taskanaEngineService = TestBed.get(TaskanaEngineService);
spyOn(taskanaEngineService, 'getUserInformation').and.returnValue(Observable.of(new UserInfoModel));
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,40 @@
import { Component, OnInit } from '@angular/core';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { UserInfoModel } from 'app/models/user-info';
import { trigger, transition, animate, keyframes, style } from '@angular/animations';
@Component({
selector: 'taskana-user-information',
templateUrl: './user-information.component.html',
styleUrls: ['./user-information.component.scss'],
animations: [
trigger('toggle', [
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 1, height: '10px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0, height: '10px' }),
style({ opacity: 0, height: '0px' })])))
]
)],
})
export class UserInformationComponent implements OnInit {
userInformation: UserInfoModel;
roles = '';
showRoles = false;
constructor(private taskanaEngineService: TaskanaEngineService) { }
ngOnInit() {
this.taskanaEngineService.getUserInformation().subscribe(userInfo => {
this.userInformation = userInfo;
this.roles = '[' + userInfo.roles.join(',') + ']';
})
}
toogleRoles() {
this.showRoles = !this.showRoles;
}
}

View File

@ -0,0 +1,37 @@
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
if (userInfo.roles.length === 0) {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'ADMIN') {
return true;
}
});
if (adminRole) {
return true;
}
return this.navigateToWorplace();
}).catch(() => {
return Observable.of(this.navigateToWorplace())
});
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
return false
}
}

View File

@ -0,0 +1,38 @@
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
@Injectable()
export class BusinessAdminGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
if (userInfo.roles.length === 0) {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'BUSINESS_ADMIN' || role === 'ADMIN' ) {
return true;
}
});
if (adminRole) {
return true;
}
return this.navigateToWorplace();
}).catch(() => {
return Observable.of(this.navigateToWorplace())
});
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
return false
}
}

View File

@ -0,0 +1,38 @@
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
@Injectable()
export class MonitorGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
if (userInfo.roles.length === 0) {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'MONITOR' || role === 'ADMIN' ) {
return true;
}
});
if (adminRole) {
return true;
}
return this.navigateToWorplace();
}).catch(() => {
return Observable.of(this.navigateToWorplace())
});
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
return false
}
}

View File

@ -0,0 +1,8 @@
export class UserInfoModel {
constructor(
public userId: string = undefined,
public groupIds: Array<string> = [],
public roles: Array<string> = []) { };
}

View File

@ -7,29 +7,22 @@ import { WorkbasketCounter } from 'app/monitor/models/workbasket-counter';
@Injectable() @Injectable()
export class RestConnectorService { export class RestConnectorService {
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/hal+json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient) { constructor(private httpClient: HttpClient) {
} }
getTaskStatistics(): Observable<State[]> { getTaskStatistics(): Observable<State[]> {
return this.httpClient.get<Array<State>>(environment.taskanaRestUrl + '/v1/monitor/countByState?states=READY,CLAIMED,COMPLETED', return this.httpClient.get<Array<State>>(environment.taskanaRestUrl + '/v1/monitor/countByState?states=READY,CLAIMED,COMPLETED')
this.httpOptions)
} }
getWorkbasketStatistics(): Observable<WorkbasketCounter> { getWorkbasketStatistics(): Observable<WorkbasketCounter> {
return this.httpClient.get<WorkbasketCounter>(environment.taskanaRestUrl return this.httpClient.get<WorkbasketCounter>(environment.taskanaRestUrl
+ '/v1/monitor/taskcountByWorkbasketDaysAndState?daysInPast=5&states=READY,CLAIMED,COMPLETED', + '/v1/monitor/taskcountByWorkbasketDaysAndState?daysInPast=5&states=READY,CLAIMED,COMPLETED')
this.httpOptions)
} }
getTaskStatusReport(): Observable<Object> { getTaskStatusReport(): Observable<Object> {
return this.httpClient.get(environment.taskanaRestUrl + '/v1/monitor/taskStatusReport', this.httpOptions) return this.httpClient.get(environment.taskanaRestUrl + '/v1/monitor/taskStatusReport')
} }
} }

View File

@ -13,12 +13,6 @@ export class DomainService {
url = environment.taskanaRestUrl + '/v1/domains'; url = environment.taskanaRestUrl + '/v1/domains';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private domainSelectedValue; private domainSelectedValue;
private domainSelected = new BehaviorSubject<string>(''); private domainSelected = new BehaviorSubject<string>('');
private domainSwitched = new Subject<string>(); private domainSwitched = new Subject<string>();
@ -33,7 +27,7 @@ export class DomainService {
// GET // GET
getDomains(forceRefresh = false): Observable<string[]> { getDomains(forceRefresh = false): Observable<string[]> {
if (!this.dataObs$.observers.length || forceRefresh) { if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<string[]>(this.url, this.httpOptions).subscribe( this.httpClient.get<string[]>(this.url).subscribe(
domains => { domains => {
this.dataObs$.next(domains); this.dataObs$.next(domains);
if (!this.domainSelectedValue && domains && domains.length > 0) { if (!this.domainSelectedValue && domains && domains.length > 0) {

View File

@ -1,15 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { PermissionService } from './permission.service';
describe('PermissionService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [PermissionService]
});
});
it('should be created', inject([PermissionService], (service: PermissionService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -1,18 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class PermissionService {
private permission = new BehaviorSubject<boolean>(true);
setPermission(permission: boolean) {
this.permission.next(permission);
}
hasPermission(): Observable<boolean> {
return this.permission.asObservable();
}
}

View File

@ -2,20 +2,23 @@ import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { CanActivate } from '@angular/router'; import { CanActivate } from '@angular/router';
import { environment } from 'app/../environments/environment'; import { environment } from 'app/../environments/environment';
import { Injectable } from '@angular/core'; import { Injectable, Inject } from '@angular/core';
import { TitlesService } from 'app/services/titles/titles.service'; import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
@Injectable() @Injectable()
export class StartupService { export class StartupService {
constructor( constructor(
private httpClient: HttpClient, private httpClient: HttpClient,
private titlesService: TitlesService, private titlesService: TitlesService,
private customFieldsService: CustomFieldsService) { } private customFieldsService: CustomFieldsService) { }
load(): Promise<any> { load(): Promise<any> {
return this.loadEnvironment(); return this.loadEnvironment();
} }
private loadEnvironment() { private loadEnvironment() {
return this.httpClient.get<any>('environments/data-sources/environment-information.json').map(jsonFile => { return this.httpClient.get<any>('environments/data-sources/environment-information.json').map(jsonFile => {
if (jsonFile) { if (jsonFile) {

View File

@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { environment } from 'environments/environment';
import { UserInfoModel } from 'app/models/user-info';
import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable()
export class TaskanaEngineService {
private dataObs$ = new ReplaySubject<UserInfoModel>(1);
constructor(
private httpClient: HttpClient
) { }
// GET
getUserInformation(forceRefresh = false): Observable<UserInfoModel> {
if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<UserInfoModel>(`${environment.taskanaRestUrl}/v1/current-user-info`).subscribe(
data => this.dataObs$.next(data),
error => {
this.dataObs$.error(error);
this.dataObs$ = new ReplaySubject(1);
}
);
}
return this.dataObs$;
}
}

View File

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
// This interface is optional, showing how you can add strong typings for custom globals.
// Just use "Window" as the type if you don't have custom global stuff
export interface IWindowService extends Window {
__custom_global_stuff: string;
}
function getWindow (): any {
return window;
}
@Injectable()
export class WindowRefService {
get nativeWindow (): IWindowService {
return getWindow();
}
}

View File

@ -3,7 +3,6 @@ import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { HttpClientInterceptor } from './http-client-interceptor.service'; import { HttpClientInterceptor } from './http-client-interceptor.service';
import { PermissionService } from 'app/services/permission/permission.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
@ -11,7 +10,7 @@ describe('HttpExtensionService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientModule, HttpModule], imports: [HttpClientModule, HttpModule],
providers: [HttpClientInterceptor, PermissionService, ErrorModalService, RequestInProgressService] providers: [HttpClientInterceptor, ErrorModalService, RequestInProgressService]
}); });
}); });

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/throw';
@ -7,28 +7,31 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ErrorModel } from 'app/models/modal-error'; import { ErrorModel } from 'app/models/modal-error';
import { PermissionService } from 'app/services/permission/permission.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { environment } from 'environments/environment';
@Injectable() @Injectable()
export class HttpClientInterceptor implements HttpInterceptor { export class HttpClientInterceptor implements HttpInterceptor {
constructor( constructor(
private permissionService: PermissionService,
private errorModalService: ErrorModalService, private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService) { private requestInProgressService: RequestInProgressService) {
} }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({ headers: req.headers.set('Content-Type', 'application/hal+json') });
if (!environment.production) {
req = req.clone({ headers: req.headers.set('Authorization', 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x') });
}
return next.handle(req).do(event => { return next.handle(req).do(event => {
this.permissionService.setPermission(true);
}, err => { }, err => {
this.requestInProgressService.setRequestInProgress(false); this.requestInProgressService.setRequestInProgress(false);
if (err instanceof HttpErrorResponse && (err.status === 401 || err.status === 403)) { if (err instanceof HttpErrorResponse && (err.status === 401 || err.status === 403)) {
this.permissionService.setPermission(false) this.errorModalService.triggerError(
new ErrorModel('You have no access to this resource ', err));
} else if (err instanceof HttpErrorResponse && (err.status === 404) && err.url.indexOf('environment-information.json')) { } else if (err instanceof HttpErrorResponse && (err.status === 404) && err.url.indexOf('environment-information.json')) {
// ignore this error message // ignore this error message
} else { } else {

View File

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { AlertModule } from 'ngx-bootstrap'; import { AlertModule } from 'ngx-bootstrap';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@ -22,6 +22,7 @@ import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbas
import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number'; import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number';
import { OrderBy } from './pipes/orderBy/orderBy'; import { OrderBy } from './pipes/orderBy/orderBy';
import { MapToIterable } from './pipes/mapToIterable/mapToIterable'; import { MapToIterable } from './pipes/mapToIterable/mapToIterable';
import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service';
const MODULES = [ const MODULES = [
@ -51,7 +52,14 @@ const DECLARATIONS = [
@NgModule({ @NgModule({
declarations: DECLARATIONS, declarations: DECLARATIONS,
imports: MODULES, imports: MODULES,
exports: DECLARATIONS exports: DECLARATIONS,
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: HttpClientInterceptor,
multi: true
}
]
}) })
export class SharedModule { export class SharedModule {
} }

View File

@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { environment } from 'environments/environment';
@Injectable()
export class CustomHttpClientInterceptor implements HttpInterceptor {
constructor() {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!environment.production) {
req = req.clone({ headers: req.headers.set('Authorization', 'Basic YWRtaW46YWRtaW4=') });
}
return next.handle(req);
}
}

View File

@ -8,35 +8,27 @@ import { environment } from 'app/../environments/environment';
export class TaskService { export class TaskService {
url = environment.taskanaRestUrl + '/v1/tasks'; url = environment.taskanaRestUrl + '/v1/tasks';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/hal+json',
'Authorization': 'Basic YWRtaW46YWRtaW4=',
'user': 'user_1_1'
})
};
constructor(private httpClient: HttpClient) { constructor(private httpClient: HttpClient) {
} }
findTaskWithWorkbaskets(basketKey: string): Observable<Task[]> { findTaskWithWorkbaskets(basketKey: string): Observable<Task[]> {
return this.httpClient.get<Task[]>(this.url + '?workbasketId=' + basketKey, this.httpOptions); return this.httpClient.get<Task[]>(this.url + '?workbasketId=' + basketKey);
} }
getTask(id: string): Observable<Task> { getTask(id: string): Observable<Task> {
return this.httpClient.get<Task>(this.url + '/' + id, this.httpOptions); return this.httpClient.get<Task>(this.url + '/' + id);
} }
completeTask(id: string): Observable<Task> { completeTask(id: string): Observable<Task> {
return this.httpClient.post<Task>(this.url + '/' + id + '/complete', '', this.httpOptions); return this.httpClient.post<Task>(this.url + '/' + id + '/complete', '');
} }
claimTask(id: string): Observable<Task> { claimTask(id: string): Observable<Task> {
return this.httpClient.post<Task>(this.url + '/' + id + '/claim', 'test', this.httpOptions); return this.httpClient.post<Task>(this.url + '/' + id + '/claim', 'test');
} }
transferTask(taskId: string, workbasketKey: string): Observable<Task> { transferTask(taskId: string, workbasketKey: string): Observable<Task> {
return this.httpClient.post<Task>(this.url + '/' + taskId return this.httpClient.post<Task>(this.url + '/' + taskId
+ '/transfer/' + workbasketKey, '', this.httpOptions); + '/transfer/' + workbasketKey, '');
} }
} }

View File

@ -1,25 +1,17 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {Workbasket} from 'app/models/workbasket'; import { Workbasket } from 'app/models/workbasket';
import {environment} from 'app/../environments/environment'; import { environment } from 'app/../environments/environment';
import {Observable} from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import {HttpClient, HttpHeaders} from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
@Injectable() @Injectable()
export class WorkbasketService { export class WorkbasketService {
url = environment.taskanaRestUrl + '/v1/workbaskets'; url = environment.taskanaRestUrl + '/v1/workbaskets';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/hal+json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient) { constructor(private httpClient: HttpClient) {
} }
getAllWorkBaskets(): Observable<Workbasket[]> { getAllWorkBaskets(): Observable<Workbasket[]> {
return this.httpClient.get<Workbasket[]>(this.url + '?required-permission=OPEN', this.httpOptions); return this.httpClient.get<Workbasket[]>(this.url + '?required-permission=OPEN');
} }
} }

View File

@ -2,10 +2,11 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { Ng2AutoCompleteModule } from 'ng2-auto-complete'; import { Ng2AutoCompleteModule } from 'ng2-auto-complete';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { WorkplaceRoutingModule } from './workplace-routing.module'; import { WorkplaceRoutingModule } from './workplace-routing.module';
import { AlertModule } from 'ngx-bootstrap'; import { AlertModule } from 'ngx-bootstrap';
import { SharedModule } from 'app/shared/shared.module';
import { WorkplaceComponent } from './workplace.component'; import { WorkplaceComponent } from './workplace.component';
import { SelectorComponent } from './workbasket-selector/workbasket-selector.component'; import { SelectorComponent } from './workbasket-selector/workbasket-selector.component';
@ -19,6 +20,8 @@ import { OrderTasksByPipe } from './util/orderTasksBy.pipe';
import { DataService } from './services/data.service'; import { DataService } from './services/data.service';
import { TaskService } from './services/task.service'; import { TaskService } from './services/task.service';
import { WorkbasketService } from './services/workbasket.service'; import { WorkbasketService } from './services/workbasket.service';
import { CustomHttpClientInterceptor } from './services/custom-http-interceptor/custom-http-interceptor.service';
const MODULES = [ const MODULES = [
CommonModule, CommonModule,
@ -27,7 +30,8 @@ const MODULES = [
HttpClientModule, HttpClientModule,
AngularSvgIconModule, AngularSvgIconModule,
WorkplaceRoutingModule, WorkplaceRoutingModule,
AlertModule AlertModule,
SharedModule
]; ];
const DECLARATIONS = [ const DECLARATIONS = [
@ -46,7 +50,12 @@ const DECLARATIONS = [
providers: [ providers: [
DataService, DataService,
TaskService, TaskService,
WorkbasketService WorkbasketService,
{
provide: HTTP_INTERCEPTORS,
useClass: CustomHttpClientInterceptor,
multi: true
},
] ]
}) })
export class WorkplaceModule { export class WorkplaceModule {

View File

@ -3,4 +3,5 @@ $blue: #2e9eca;
$green: #246972; $green: #246972;
$grey: grey; $grey: grey;
$brown: #f0ad4e; $brown: #f0ad4e;
$invalid: #a94442; $invalid: #a94442;
$aquamarine: #22a39f;

View File

@ -77,6 +77,13 @@
} }
} }
.blue-green{
color: $blue-green;
& svg {
fill: $blue-green;
}
}
.green { .green {
color: $green; color: $green;
& svg { & svg {
@ -112,6 +119,13 @@
} }
} }
.aquamarine {
color: $aquamarine;
& svg {
fill: $aquamarine;
}
}
svg-icon.fa-fw > svg { svg-icon.fa-fw > svg {
text-align: center; text-align: center;
width: 1.25em; width: 1.25em;
@ -282,4 +296,5 @@ li.list-group-item:hover, {
color: #555; color: #555;
text-decoration: none; text-decoration: none;
background-color: #f5f5f5; background-color: #f5f5f5;
} }