| @@ -1,6 +1,6 @@ | |||
| { | |||
| "/api/v1": { | |||
| "target": "http://svr2021:8080", | |||
| "target": "https://svr2021.lawipac.com:8080", | |||
| "secure": false, | |||
| "pathRewrite": {"^/api/v1" : "/api/v1"} | |||
| } | |||
| @@ -21,7 +21,10 @@ | |||
| .appbar, | |||
| .k-appbar-sticky{ | |||
| z-index: 1000; | |||
| /* z-index: 1000; */ | |||
| } | |||
| .k-notification-group{ | |||
| z-index:1000; | |||
| } | |||
| #topBar .main-menu-item , #topBar .k-icon, #topBar .k-menu-item { | |||
| @@ -7,6 +7,7 @@ import {WebSocketService} from './websocket'; | |||
| import {Title} from '@angular/platform-browser'; | |||
| import {WsLoginEventModel} from './models/websocket/ws.login.event.model'; | |||
| import {SessionService} from './service/session.service'; | |||
| import {AppConfig} from './app.config'; | |||
| @Component({ | |||
| selector: 'app-root', | |||
| @@ -41,8 +42,6 @@ export class AppComponent implements OnInit , OnDestroy { | |||
| // tslint:disable-next-line:typedef | |||
| ngOnInit() { | |||
| this.listenToMenuEvent(); | |||
| // must be the last, as it may emit events | |||
| this.authService.AutoLogin(); | |||
| } | |||
| listenToMenuEvent(): void { | |||
| @@ -62,8 +61,12 @@ export class AppComponent implements OnInit , OnDestroy { | |||
| onWSLogin(e: WsLoginEventModel): void { | |||
| if ( e.T === 'logout' ) { // regardless where are they, logout means logout | |||
| if ( this.ss.isCurrentUser(e.Uid) ) { | |||
| if ( this.ss.isLoggedIn() && this.ss.isCurrentUser(e.Uid) ) { | |||
| if ( e.Sid === this.ss.SessionId ) { | |||
| this.ss.logoutAndClearLocalStorage(); | |||
| }else{ | |||
| this.ss.logout(); | |||
| } | |||
| } | |||
| }else{ // some one logged in from another browser tab | |||
| if ( e.Mid === this.ss.loggedIn.machineId) { // same browser | |||
| @@ -29,10 +29,12 @@ export class AppConfig { | |||
| try { | |||
| if ( location.href.includes('//localhost:4200/') ) { | |||
| AppConfig.config = AppConfig.debugConfig; | |||
| // AppConfig.config.Server = '/api/v1/'; | |||
| console.log('Using Debug Config:', AppConfig.config); | |||
| }else{ | |||
| const json = this.decode(el.innerText); | |||
| AppConfig.config = JSON.parse(json); | |||
| // AppConfig.config.Server = '/api/v1/'; | |||
| console.log('Using Production Config:', AppConfig.config); | |||
| } | |||
| resolve(); | |||
| @@ -14,32 +14,52 @@ export class AuthHttpInterceptor implements HttpInterceptor { | |||
| intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |||
| let h = req.headers; | |||
| if (this.ss.loggedIn.hasValidSession()) { | |||
| if (this.ss.SessionId !== '') { | |||
| h = h.set('Biukop-Session', this.ss.loggedIn.session); | |||
| } | |||
| if (this.ss.loggedIn.hasValidMachineId()) { | |||
| h = h.set('Biukop-Mid', this.ss.loggedIn.machineId); | |||
| if (this.ss.MachineId !== '') { | |||
| h = h.set('Biukop-Mid', this.ss.MachineId); | |||
| } | |||
| const authReq = req.clone({ | |||
| headers: h, | |||
| withCredentials: true | |||
| }); | |||
| const self = this; | |||
| return next.handle(authReq).pipe( | |||
| tap(event => { | |||
| // console.log(event); | |||
| if (event.type === HttpEventType.Response){ | |||
| const bs = event.headers.get('biukop-session'); | |||
| if (bs !== undefined){ | |||
| if ( this.ss.loggedIn.session !== bs ){ | |||
| this.ss.loggedIn.session = bs; | |||
| this.ss.saveSessionInfo(); | |||
| console.log('switch session:' , bs); | |||
| } | |||
| } | |||
| self.setSession(bs); | |||
| const mid = event.headers.get('biukop-mid'); | |||
| self.setMachineId(mid); | |||
| } | |||
| }) | |||
| ); | |||
| } | |||
| public setSession(bs: string): void { | |||
| // console.log('receive session:' , bs); | |||
| if (bs){ | |||
| if ( this.ss.loggedIn.session !== bs ){ | |||
| this.ss.loggedIn.session = bs; | |||
| this.ss.saveSessionInfo(); | |||
| console.log('switch session:' , bs); | |||
| } | |||
| } | |||
| } | |||
| public setMachineId(mid: string): void{ | |||
| // console.log('receive mid:' , mid); | |||
| if ( mid && mid !== '' ){ | |||
| if ( this.ss.MachineId !== mid ){ | |||
| this.ss.MachineId = mid; | |||
| console.log('switch machine id:' , mid); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -25,12 +25,10 @@ export class AuthComponent implements OnInit, OnDestroy{ | |||
| private router: Router, private notificationService: NotificationService) { } | |||
| ngOnInit(): void { | |||
| if ( this.ss.loggedIn.login ){ | |||
| this.ss.logout(); | |||
| } | |||
| this.ss.logout(); | |||
| // this.ss.logoutAndClearLocalStorage(); | |||
| this.loginSub = this.ss.loginSuccess.subscribe( | |||
| this.loginSub = this.ss.loginResult.subscribe( | |||
| responseData => { | |||
| // console.log(responseData); | |||
| this.onLogin(responseData); | |||
| @@ -1,27 +0,0 @@ | |||
| .k-form.none { | |||
| width: 400px; | |||
| } | |||
| div.parent { | |||
| display: table; | |||
| width: 100%; | |||
| height: 100vh; | |||
| opacity: 0.95; | |||
| background: url("../../assets/img/body_bg.jpg") no-repeat center center fixed; | |||
| background-size: cover; | |||
| } | |||
| .form_login { | |||
| margin-left: auto; | |||
| margin-right: auto; | |||
| margin-top: 10%; | |||
| max-width: 400px; | |||
| padding-top: 20px; | |||
| text-align: center; | |||
| vertical-align: middle; | |||
| padding-left: 20px; | |||
| padding-right: 20px; | |||
| padding-bottom: 20px; | |||
| box-shadow: 0 0 6px black; | |||
| border-radius: 5px; | |||
| } | |||
| @@ -1,61 +0,0 @@ | |||
| "use strict"; | |||
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | |||
| var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | |||
| if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | |||
| else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | |||
| return c > 3 && r && Object.defineProperty(target, key, r), r; | |||
| }; | |||
| exports.__esModule = true; | |||
| exports.AuthComponent = void 0; | |||
| var core_1 = require("@angular/core"); | |||
| var forms_1 = require("@angular/forms"); | |||
| var AuthComponent = /** @class */ (function () { | |||
| function AuthComponent(authService, rounter, notificationService) { | |||
| this.authService = authService; | |||
| this.rounter = rounter; | |||
| this.notificationService = notificationService; | |||
| this.userForm = new forms_1.FormGroup({ | |||
| password: new forms_1.FormControl('password', [forms_1.Validators.minLength(3), forms_1.Validators.maxLength(20)]), | |||
| email: new forms_1.FormControl('email@email.com', forms_1.Validators.email) | |||
| }); | |||
| } | |||
| AuthComponent.prototype.ngOnInit = function () { | |||
| var _this = this; | |||
| this.loginSub = this.authService.loginSuccess.subscribe(function (ok) { _this.onLogin(ok); }); | |||
| }; | |||
| AuthComponent.prototype.ngOnDestroy = function () { | |||
| this.loginSub.unsubscribe(); | |||
| }; | |||
| AuthComponent.prototype.submitForm = function () { | |||
| console.log(this.userForm); | |||
| this.loading = true; | |||
| this.authService.login(this.userForm.value.email, this.userForm.value.password); | |||
| }; | |||
| AuthComponent.prototype.onLogin = function (ok) { | |||
| this.loading = false; | |||
| console.log(" found login success " + ok); | |||
| if (ok) | |||
| this.rounter.navigate(["/dashboard"]); | |||
| else | |||
| this.show(); | |||
| }; | |||
| AuthComponent.prototype.show = function () { | |||
| this.notificationService.show({ | |||
| content: 'Your loggin is failed, please try again.', | |||
| cssClass: 'button-notification', | |||
| animation: { type: 'slide', duration: 400 }, | |||
| position: { horizontal: 'center', vertical: 'top' }, | |||
| type: { style: 'error', icon: true }, | |||
| hideAfter: 2000 | |||
| }); | |||
| }; | |||
| AuthComponent = __decorate([ | |||
| core_1.Component({ | |||
| selector: 'app-auth', | |||
| templateUrl: './auth.component.html', | |||
| styleUrls: ['./auth.component.scss'] | |||
| }) | |||
| ], AuthComponent); | |||
| return AuthComponent; | |||
| }()); | |||
| exports.AuthComponent = AuthComponent; | |||
| @@ -1,5 +1,8 @@ | |||
| <kendo-chart [transitions]="true"> | |||
| <ng-template kendoChartDonutCenterTemplate> | |||
| <h3>{{totalNumberOfLoans}}</h3> | |||
| </ng-template> | |||
| <kendo-chart-title text="Stages of Loans"></kendo-chart-title> | |||
| <kendo-chart-series> | |||
| <kendo-chart-series-item | |||
| @@ -1,9 +1,10 @@ | |||
| import { Component, OnInit } from '@angular/core'; | |||
| import {Observable} from 'rxjs'; | |||
| import {Observable, Subscriber} from 'rxjs'; | |||
| import {TypeOfLoansModel} from '../models/type-of-loans.model'; | |||
| import {HttpClient} from '@angular/common/http'; | |||
| import {AuthService} from '../service/auth.service'; | |||
| import {map} from 'rxjs/operators'; | |||
| import {equalsArray, randomNumber} from '../helper.function'; | |||
| @Component({ | |||
| selector: 'app-chart-type-of-loans', | |||
| @@ -12,16 +13,41 @@ import {map} from 'rxjs/operators'; | |||
| }) | |||
| export class ChartTypeOfLoansComponent implements OnInit { | |||
| public autoFit = true; | |||
| // public data: Observable<TypeOfLoansModel[]>; | |||
| public data: Observable<TypeOfLoansModel[]>; | |||
| public totalNumberOfLoans = 0; | |||
| constructor(private auth: AuthService, private http: HttpClient) { } | |||
| private previous: TypeOfLoansModel[] = []; | |||
| ngOnInit(): void { | |||
| this.data = this.getTypeOfLoans(); | |||
| this.data = new Observable<TypeOfLoansModel[]>( (observer) => { | |||
| this.updateTypeOfLoans(observer); | |||
| }); | |||
| } | |||
| public labelContent(e: any): string { | |||
| return e.category; | |||
| return e.dataItem.Count + ' ' + e.category + ' (' + + (e.dataItem.Share * 100).toFixed(2) + '%' + ')' ; | |||
| } | |||
| public updateTypeOfLoans(observer: Subscriber<TypeOfLoansModel[]>): void { | |||
| this.getTypeOfLoans().subscribe( | |||
| resp => { | |||
| if ( ! equalsArray(this.previous, resp ) ){ | |||
| this.previous = resp; | |||
| this.totalNumberOfLoans = 0; | |||
| resp.reduce( (acc, curr) => { | |||
| this.totalNumberOfLoans += curr.Count; | |||
| return acc; | |||
| }); | |||
| observer.next(resp); | |||
| } | |||
| }); | |||
| const refresh = randomNumber(3000, 6000); | |||
| setTimeout( () => { | |||
| // this.updateTypeOfLoans(observer); | |||
| }, refresh); | |||
| } | |||
| public getTypeOfLoans(): Observable<TypeOfLoansModel[]> { | |||
| @@ -32,7 +58,7 @@ export class ChartTypeOfLoansComponent implements OnInit { | |||
| if (Array.isArray(input)) { | |||
| input.forEach( (value, index) =>{ | |||
| ret.push({ | |||
| Kind: value.Kind + ' ' + (value.Share * 100).toFixed(2) + '%', | |||
| Kind: value.Kind, | |||
| Count: value.Count, | |||
| Share: value.Share, | |||
| Amount: value.Amount | |||
| @@ -0,0 +1,19 @@ | |||
| export const equalsArray = (a, b) => { | |||
| if (a === b) { return true; } | |||
| if (a instanceof Date && b instanceof Date) { | |||
| return a.getTime() === b.getTime(); | |||
| } | |||
| if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) { | |||
| return a === b; | |||
| } | |||
| if (a.prototype !== b.prototype) { return false; } | |||
| const keys = Object.keys(a); | |||
| if (keys.length !== Object.keys(b).length) { return false; } | |||
| return keys.every(k => equalsArray(a[k], b[k])); | |||
| }; | |||
| export function randomNumber(min: number, max: number): number { // min and max included | |||
| return Math.floor(Math.random() * (max - min + 1) + min); | |||
| } | |||
| @@ -79,8 +79,8 @@ export class ImagePopupDialogComponent implements OnInit{ | |||
| this.dialogWidth = this.scrWidth; | |||
| } | |||
| console.log(this.img.nativeElement.naturalWidth, this.img.nativeElement.naturalHeight, | |||
| this.img.nativeElement.width, this.img.nativeElement.height, this.iWidth, this.imgWidthPercent); | |||
| // console.log(this.img.nativeElement.naturalWidth, this.img.nativeElement.naturalHeight, | |||
| // this.img.nativeElement.width, this.img.nativeElement.height, this.iWidth, this.imgWidthPercent); | |||
| } | |||
| @@ -134,10 +134,6 @@ export class ListAllLoansComponent implements OnInit { | |||
| return 'url("' + url + '")'; | |||
| } | |||
| public allowEdit(): boolean { | |||
| return this.auth.allowEditLoan(); | |||
| } | |||
| public allData = (): Observable<GridDataResult> => { | |||
| return this.service.queryAll({skip: 0, take: 999999, sort: this.sort, filter: this.filter}); | |||
| } | |||
| @@ -1,32 +1,24 @@ | |||
| // tslint:disable-next-line:class-name | |||
| import {PeopleModel} from './people.model'; | |||
| import {UserExtraModel} from './user-extra.model'; | |||
| export class ApiV1LoginResponse { | |||
| constructor( | |||
| public login: boolean, | |||
| public machineId: string, | |||
| public session: string, | |||
| public sessionExpire: number, // unix timestamp | |||
| public role: string, | |||
| public User: PeopleModel, | |||
| public UserExtra?: UserExtraModel // extra user information | |||
| ) { | |||
| this.login = login; | |||
| this.machineId = machineId; | |||
| this.session = session; | |||
| this.sessionExpire = sessionExpire; | |||
| this.role = role; | |||
| this.User = User; | |||
| this.UserExtra = UserExtra; | |||
| } | |||
| public login: boolean; | |||
| public machineId: string; | |||
| public session: string; | |||
| public sessionExpire: number; // unix timestamp | |||
| public role: string; | |||
| public User: PeopleModel; | |||
| public UserExtra?: UserExtraModel; // extra user information | |||
| public static EmptyNew(): ApiV1LoginResponse{ | |||
| return new ApiV1LoginResponse( | |||
| false, '', '', 0, '', PeopleModel.EmptyNew() ); | |||
| constructor(payload: Partial<ApiV1LoginResponse>) { | |||
| this.login = payload.login || false; | |||
| this.machineId = payload.machineId || ''; | |||
| this.session = payload.session || '' ; | |||
| this.sessionExpire = payload.sessionExpire || new Date().getTime(); | |||
| this.role = payload.role || ''; | |||
| this.User = new PeopleModel(payload.User || {}); | |||
| this.UserExtra = new UserExtraModel(payload.UserExtra || {}); | |||
| } | |||
| public hasValidSession(): boolean { | |||
| @@ -37,11 +29,4 @@ export class ApiV1LoginResponse { | |||
| } | |||
| } | |||
| public hasValidMachineId(): boolean { | |||
| if (this.machineId === undefined || this.machineId === '') { | |||
| return false; | |||
| }else{ | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| import {UserExModel} from './userExModel'; | |||
| export class PayOutExModel{ | |||
| Id: number; | |||
| Ts: Date; | |||
| To: UserExModel; | |||
| Prepared: UserExModel; | |||
| Approved: UserExModel; | |||
| Paid: UserExModel; | |||
| PayDate: Date; | |||
| PayAmount: number; | |||
| Status: string; | |||
| constructor( payload: Partial<PayOutExModel>) { | |||
| this.Id = payload.Id || 0; | |||
| if (payload.Ts){ | |||
| this.Ts = new Date( payload.Ts); | |||
| }else{ | |||
| this.Ts = new Date('1800-01-01'); | |||
| } | |||
| this.To = new UserExModel(payload.To); | |||
| this.Prepared = new UserExModel(payload.Prepared); | |||
| this.Approved = new UserExModel(payload.Approved); | |||
| this.Paid = new UserExModel(payload.Paid); | |||
| if (payload.PayDate) { | |||
| this.PayDate = new Date(payload.PayDate); | |||
| }else{ | |||
| this.PayDate = new Date('1800-01-01'); | |||
| } | |||
| this.PayAmount = payload.PayAmount || 0; | |||
| this.Status = payload.Status || ''; | |||
| } | |||
| } | |||
| @@ -1,29 +0,0 @@ | |||
| import { PayoutAudit } from "./payout-audit.model"; | |||
| export class PayOutModel{ | |||
| id: string; | |||
| //parent | |||
| loan: string | |||
| //parties | |||
| id_user: string | |||
| role: string; | |||
| // | |||
| amount: number; | |||
| //paid | |||
| paid: boolean; | |||
| paid_date: Date; | |||
| paid_to: string; //bank details; | |||
| //confirmed, by receiptient | |||
| confirmed: boolean; | |||
| //source | |||
| source?: string; | |||
| //audit | |||
| audits: PayoutAudit[]; | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import {LoanModel} from './loan.model'; | |||
| import {PayOutModel} from './payout.model'; | |||
| import {PayOutModel} from './payout.ex.model'; | |||
| import {PeopleModel} from './people.model'; | |||
| import {UserExModel} from './userExModel'; | |||
| export class RewardExModel { | |||
| public Id: number; | |||
| @@ -12,9 +13,9 @@ export class RewardExModel { | |||
| public FromId: string; | |||
| public PayOutId: number; | |||
| public To?: PeopleModel; | |||
| public To?: UserExModel; | |||
| public Loan?: LoanModel; | |||
| public From?: PeopleModel; | |||
| public From?: UserExModel; | |||
| public PayOut?: PayOutModel; | |||
| constructor(payload: Partial<RewardExModel>) { | |||
| @@ -31,22 +32,21 @@ export class RewardExModel { | |||
| this.FromId = payload.FromId || ''; | |||
| this.PayOutId = payload.PayOutId || 0; | |||
| // TODO: must make reward Ex workable | |||
| // if (payload.To) { | |||
| // this.To = new PeopleModel(payload.To); | |||
| // } | |||
| // | |||
| // if (payload.Loan) { | |||
| // this.Loan = new LoanModel(payload.Loan); | |||
| // } | |||
| // | |||
| // if ( payload.From) { | |||
| // this.From = new PeopleModel(payload.From); | |||
| // } | |||
| // | |||
| // if ( payload. PayOut ) { | |||
| // this.PayOut = new PayOutModel(payload.PayOut); | |||
| // } | |||
| if (payload.To) { | |||
| this.To = new UserExModel(payload.To); | |||
| } | |||
| if (payload.Loan) { | |||
| this.Loan = new LoanModel(payload.Loan); | |||
| } | |||
| if ( payload.From) { | |||
| this.From = new UserExModel(payload.From); | |||
| } | |||
| if ( payload. PayOut ) { | |||
| this.PayOut = new PayOutModel(payload.PayOut); | |||
| } | |||
| } | |||
| } | |||
| @@ -11,11 +11,11 @@ export enum UserRoles { | |||
| Super = 'super', | |||
| } | |||
| export class UserModel extends PeopleModel{ | |||
| export class UserExModel extends PeopleModel{ | |||
| Role: UserRoles; | |||
| Broker?: BrokerModel; | |||
| Login: string; | |||
| constructor(payload: Partial<UserModel>) { | |||
| constructor(payload: Partial<UserExModel>) { | |||
| super(payload); | |||
| this.Role = payload.Role || 'People' as UserRoles; | |||
| this.Login = payload.Login || ''; | |||
| @@ -9,16 +9,16 @@ import {ChipRemoveEvent} from '@progress/kendo-angular-buttons'; | |||
| export class PayOutDetailsComponent implements OnInit { | |||
| sales = []; | |||
| background = "url('https://svr2021.lawipac.com:8080/api/v1/avatar/0')"; | |||
| background = 'url(\'https://svr2021.lawipac.com:8080/api/v1/avatar/0\')'; | |||
| constructor() { } | |||
| ngOnInit(): void { | |||
| this.sales = this.range(1, 20); | |||
| console.log(this.sales); | |||
| } | |||
| range(start, end) { | |||
| if(start === end) return [start]; | |||
| range(start: number, end: number): number[] { | |||
| if (start === end) { return [start]; } | |||
| return [start, ...this.range(start + 1, end)]; | |||
| } | |||
| @@ -16,66 +16,28 @@ export class AuthService { | |||
| private ss: SessionService, | |||
| private config: AppConfig) { } | |||
| public AutoLogin(): void { | |||
| const sfm: ApiV1LoginResponse = JSON.parse(localStorage.getItem(this.config.storageKey)); | |||
| if (!sfm) { | |||
| console.log('no auto login'); | |||
| return; | |||
| } | |||
| this.ss.loggedIn = new ApiV1LoginResponse( | |||
| sfm.login, | |||
| sfm.machineId, | |||
| sfm.session, | |||
| sfm.sessionExpire, | |||
| sfm.role, | |||
| new PeopleModel(sfm.User) | |||
| ); | |||
| if ( sfm.UserExtra !== undefined ) { | |||
| this.ss.loggedIn.UserExtra = new UserExtraModel(sfm.UserExtra); | |||
| } | |||
| this.ss.loginSuccess.emit(this.ss.loggedIn); | |||
| } | |||
| isAuthenticated(): boolean { | |||
| return this.ss.loggedIn.login; | |||
| } | |||
| allowEditLoan(): boolean{ | |||
| return true; | |||
| return this.ss.isLoggedIn(); | |||
| } | |||
| login(email: string, password: string): void { | |||
| this.http.post<ApiV1LoginResponse>(this.config.apiUrl + 'login', {u: email, p: password}).subscribe( | |||
| responseData => { | |||
| this.ss.loggedIn.session = responseData['Biukop-Session']; | |||
| this.ss.loggedIn.login = responseData.login; | |||
| this.ss.loggedIn.machineId = responseData['Biukop-Mid']; | |||
| this.ss.loggedIn.sessionExpire = responseData.sessionExpire; | |||
| this.ss.loggedIn.role = responseData.role; | |||
| if ( responseData.User !== undefined) { | |||
| this.ss.loggedIn.User = new PeopleModel(responseData.User); | |||
| if (responseData.UserExtra !== undefined ) { | |||
| this.ss.loggedIn.UserExtra = new UserExtraModel(responseData.UserExtra); | |||
| }else{ | |||
| this.ss.loggedIn.UserExtra = UserExtraModel.EmptyNew(); | |||
| } | |||
| }else{ | |||
| this.ss.loggedIn.User = PeopleModel.EmptyNew(); | |||
| } | |||
| this.ss.saveSessionInfo(); | |||
| this.ss.loginSuccess.emit(responseData); | |||
| const sfm = new ApiV1LoginResponse({ | |||
| session: responseData['Biukop-Session'], | |||
| login: responseData.login, | |||
| machineId: responseData['Biukop-Mid'], | |||
| sessionExpire: responseData.sessionExpire, | |||
| role: responseData.role, | |||
| User: responseData.User || new PeopleModel({}), | |||
| UserExtra: responseData.UserExtra || new UserExtraModel({}), | |||
| }); | |||
| this.ss.login(sfm); | |||
| this.ss.saveSessionInfo(); | |||
| }, | |||
| error => { | |||
| this.ss.loggedIn = ApiV1LoginResponse.EmptyNew(); | |||
| console.log('login error', error); | |||
| this.ss.loginSuccess.emit(this.ss.loggedIn); | |||
| this.ss.login( new ApiV1LoginResponse({}) ) ; | |||
| } | |||
| ); | |||
| } | |||
| @@ -83,12 +45,11 @@ export class AuthService { | |||
| this.ss.logout(); | |||
| } | |||
| public getUrl(key: string): string{ | |||
| return this.config.getUrl(key); | |||
| } | |||
| // whether a specific login id is available | |||
| public LoginAvailable(v: string): Observable<boolean> { | |||
| return this.http.get<boolean>(this.getUrl('login-available/' + v)); | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| import {Injectable} from '@angular/core'; | |||
| import {HttpClient} from '@angular/common/http'; | |||
| import {AuthService} from './auth.service'; | |||
| import {Observable} from 'rxjs'; | |||
| import {PayOutExModel} from '../models/payout.ex.model'; | |||
| import {CompositeFilterDescriptor} from '@progress/kendo-data-query'; | |||
| @Injectable({providedIn: 'root'}) | |||
| export class PayOutService { | |||
| constructor(private http: HttpClient, private auth: AuthService ){ } | |||
| public getPayOutEx(id: string): Observable<PayOutExModel> { | |||
| return this.http.get<PayOutExModel>(this.auth.getUrl('payout-ex/' + id)); | |||
| } | |||
| public getPayOutList(filter: CompositeFilterDescriptor): Observable<PayOutExModel[]> { | |||
| return this.http.post<PayOutExModel[]>(this.auth.getUrl('payout-ex-list/'), filter); | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| import {EventEmitter, Injectable} from '@angular/core'; | |||
| import {EventEmitter, Injectable, OnInit} from '@angular/core'; | |||
| import {AppConfig} from '../app.config'; | |||
| import {ApiV1LoginResponse} from '../models/api-v1-login-response'; | |||
| import {PeopleModel} from '../models/people.model'; | |||
| @@ -6,26 +6,59 @@ import {HttpClient} from '@angular/common/http'; | |||
| import {Router} from '@angular/router'; | |||
| import {NotificationService} from '@progress/kendo-angular-notification'; | |||
| import {debounce} from 'ts-debounce'; | |||
| import {UserExtraModel} from '../models/user-extra.model'; | |||
| import {WebSocketService} from '../websocket'; | |||
| @Injectable() | |||
| export class SessionService { | |||
| public loggedIn = ApiV1LoginResponse.EmptyNew(); | |||
| loginSuccess = new EventEmitter <ApiV1LoginResponse>(); | |||
| public loggedIn = new ApiV1LoginResponse({}); | |||
| loginResult = new EventEmitter <ApiV1LoginResponse>(); | |||
| logoutEvent = new EventEmitter <ApiV1LoginResponse>(); | |||
| private machineId = localStorage.getItem('mid') || ''; | |||
| debouncedLocalStorageMonitor = debounce( this.localStorageChange, 1000); // to avoid to frequent local storage changes | |||
| constructor(private config: AppConfig, private http: HttpClient, private router: Router, private ns: NotificationService){ | |||
| const self = this; | |||
| window.addEventListener('storage', event => { | |||
| if (event.storageArea === localStorage && event.key === this.config.storageKey) { | |||
| // It's local storage | |||
| self.debouncedLocalStorageMonitor(event).then( r => {}); | |||
| } | |||
| }, false); | |||
| constructor(private config: AppConfig, private http: HttpClient, | |||
| private router: Router, private ns: NotificationService, | |||
| private ws: WebSocketService){ | |||
| // | |||
| const self = this; | |||
| this.AutoLogin(); | |||
| window.addEventListener('storage', event => { | |||
| if (event.storageArea === localStorage && event.key === this.config.storageKey) { | |||
| // It's local storage | |||
| self.debouncedLocalStorageMonitor(event).then( r => {}); | |||
| } | |||
| }, false); | |||
| } | |||
| private AutoLogin(): void { | |||
| const sfm: ApiV1LoginResponse = JSON.parse(localStorage.getItem(this.config.storageKey)); | |||
| if (!sfm) { | |||
| return; | |||
| } | |||
| this.login(sfm); | |||
| } | |||
| public login( resp: ApiV1LoginResponse): void{ | |||
| this.loggedIn = new ApiV1LoginResponse(resp); | |||
| if ( this.MachineId !== '' && resp.machineId !== '' && this.MachineId !== resp.machineId ) { | |||
| this.MachineId = resp.machineId; // update machine id | |||
| } | |||
| if (this.loggedIn.session !== ''){ | |||
| this.ws.SessionId = this.loggedIn.session; | |||
| } | |||
| if ( resp.UserExtra !== undefined ) { | |||
| this.loggedIn.UserExtra = new UserExtraModel(resp.UserExtra); | |||
| } | |||
| this.loginResult.emit(this.loggedIn); | |||
| } | |||
| private localStorageChange(event: StorageEvent): void { | |||
| console.log(event); | |||
| console.log('local storage change', event); | |||
| const sfm: ApiV1LoginResponse = JSON.parse(localStorage.getItem(this.config.storageKey)); | |||
| if ( sfm && sfm.session && sfm.User && sfm.User.Id && this.loggedIn.User.Id) { | |||
| if ( sfm.session === this.loggedIn.session && sfm.User.Id !== this.loggedIn.User.Id){ | |||
| @@ -36,9 +69,9 @@ export class SessionService { | |||
| public saveSessionInfo(): void { | |||
| localStorage.setItem(this.config.storageKey, JSON.stringify(this.loggedIn)); | |||
| localStorage.setItem('mid', this.loggedIn.machineId); | |||
| } | |||
| public isAdmin(): boolean { | |||
| return this.loggedIn.role === 'admin'; | |||
| } | |||
| @@ -51,6 +84,14 @@ export class SessionService { | |||
| return this.loggedIn.login === true; | |||
| } | |||
| public get CurrentUser(): PeopleModel { | |||
| if (this.isLoggedIn() ) { | |||
| return this.loggedIn.User; | |||
| }else{ | |||
| return new PeopleModel({}); | |||
| } | |||
| } | |||
| public isCurrentUser(id: string): boolean { | |||
| return id !== '' && this.loggedIn.login === true && this.loggedIn.User !== undefined && this.loggedIn.User.Id === id; | |||
| } | |||
| @@ -63,54 +104,90 @@ export class SessionService { | |||
| } | |||
| logout(): void { | |||
| if ( !this.loggedIn.login ){ | |||
| if ( ! this.isLoggedIn() ){ | |||
| return; | |||
| } | |||
| this.loggedIn = ApiV1LoginResponse.EmptyNew(); | |||
| this.loginSuccess.emit(this.loggedIn); | |||
| this.router.navigate(['/login']); | |||
| localStorage.removeItem(this.config.storageKey); | |||
| this.http.post(`${this.config.apiUrl}logout`, '').subscribe( | |||
| resp => { | |||
| this.show(); | |||
| this.ToastMessage(); | |||
| }, | |||
| event => { | |||
| this.show('logout from server (with warnings)'); | |||
| this.ToastMessage('logout from server (with warnings)'); | |||
| this.reLogin(); | |||
| }, () => { | |||
| this.reLogin(); | |||
| } | |||
| ); | |||
| } | |||
| public isLoggedIn(): boolean{ | |||
| if ( this.loggedIn.login !== undefined && | |||
| this.loggedIn.login && | |||
| this.machineId !== '' && | |||
| this.loggedIn.session !== '' ) { | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| private reLogin(): void { | |||
| this.loggedIn = new ApiV1LoginResponse({}); | |||
| localStorage.removeItem(this.config.storageKey); | |||
| this.router.navigate(['/login']).then( () => { | |||
| this.logoutEvent.emit(this.loggedIn); | |||
| }); | |||
| } | |||
| logoutWithoutPersistingStorage(): void { | |||
| if ( !this.loggedIn.login ){ | |||
| if ( !this.isLoggedIn() ){ | |||
| return; | |||
| } | |||
| this.loggedIn = ApiV1LoginResponse.EmptyNew(); | |||
| this.loginSuccess.emit(this.loggedIn); | |||
| console.log('logout without storate'); | |||
| this.loggedIn = new ApiV1LoginResponse({}); | |||
| this.logoutEvent.emit(this.loggedIn); | |||
| this.router.navigate(['/login']).then(r => { | |||
| this.show(); | |||
| this.ToastMessage(); | |||
| } ); | |||
| } | |||
| logoutAndClearLocalStorage(): void { | |||
| if ( !this.loggedIn.login ){ | |||
| if ( !this.isLoggedIn() ){ | |||
| return; | |||
| } | |||
| console.log('logout ++++++++++++++ storate'); | |||
| localStorage.removeItem(this.config.storageKey); | |||
| this.loggedIn = ApiV1LoginResponse.EmptyNew(); | |||
| this.loginSuccess.emit(this.loggedIn); | |||
| this.loggedIn = new ApiV1LoginResponse({}); | |||
| this.logoutEvent.emit(this.loggedIn); | |||
| this.router.navigate(['/login']).then(r => { | |||
| this.show(); | |||
| this.ToastMessage(); | |||
| } ); | |||
| } | |||
| public show( msg: string = 'Successfully logged out'): void { | |||
| public ToastMessage(msg: string = 'Successfully logged out'): void { | |||
| this.ns.show({ | |||
| content: msg, | |||
| cssClass: 'button-notification', | |||
| animation: { type: 'slide', duration: 400 }, | |||
| position: { horizontal: 'center', vertical: 'top' }, | |||
| position: { horizontal: 'right', vertical: 'top' }, | |||
| type: { style: 'info', icon: true }, | |||
| hideAfter : 2000 | |||
| }); | |||
| } | |||
| public get MachineId(): string { | |||
| return this.machineId || ''; | |||
| } | |||
| public set MachineId(mid) { | |||
| if ( this.machineId === '' || this.machineId !== mid ){ | |||
| this.machineId = mid; | |||
| localStorage.setItem('mid', mid); | |||
| } | |||
| } | |||
| public get SessionId(): string{ | |||
| return this.loggedIn.session || ''; | |||
| } | |||
| } | |||
| @@ -20,6 +20,7 @@ export class TopBarComponent implements OnInit , OnDestroy { | |||
| Avatar = './assets/img/logout.png'; | |||
| public items: any[] = mainMenuItems; | |||
| private loginSub: Subscription; | |||
| private logoutSub: Subscription; | |||
| public showMenu = true; | |||
| public opened = false; | |||
| @@ -39,13 +40,20 @@ export class TopBarComponent implements OnInit , OnDestroy { | |||
| this.selectMenuShow(this.ss.loggedIn.role); | |||
| this.loginSub = this.ss.loginSuccess.subscribe( | |||
| this.loginSub = this.ss.loginResult.subscribe( | |||
| (rsp: ApiV1LoginResponse) => { | |||
| this.login = rsp.login; | |||
| this.LoggedInUser = this.ss.loggedIn.User; | |||
| this.LoggedInUser = this.ss.CurrentUser; | |||
| this.selectMenuShow(rsp.role); | |||
| } | |||
| ); | |||
| this.logoutSub = this.ss.logoutEvent.subscribe( | |||
| resp => { | |||
| this.login = false; | |||
| this.LoggedInUser = this.ss.CurrentUser; // always empty user | |||
| } | |||
| ); | |||
| } | |||
| private selectMenuShow(role: string): void { | |||
| @@ -89,6 +97,7 @@ export class TopBarComponent implements OnInit , OnDestroy { | |||
| ngOnDestroy(): void { | |||
| this.loginSub.unsubscribe(); | |||
| this.logoutSub.unsubscribe(); | |||
| } | |||
| public logout(): void{ | |||
| @@ -98,6 +107,7 @@ export class TopBarComponent implements OnInit , OnDestroy { | |||
| public close(action: string): void { | |||
| this.opened = false; | |||
| if ( action === 'yes') { | |||
| // this.login = false; | |||
| this.authService.logout(); | |||
| } | |||
| } | |||
| @@ -8,10 +8,13 @@ import {WsLoginEventModel} from './models/websocket/ws.login.event.model'; | |||
| @Injectable() | |||
| export class WebSocketService extends Subject<string>{ | |||
| private connected = false; | |||
| private sessionId = ''; | |||
| public ws: WebSocket; | |||
| private readonly url: string; | |||
| public LoginEvent: Subject<WsLoginEventModel>; | |||
| // cannot have session as service, as session use us as building block | |||
| constructor(private config: AppConfig) { | |||
| super(); | |||
| this.url = config.apiWsUrl; | |||
| @@ -22,22 +25,47 @@ export class WebSocketService extends Subject<string>{ | |||
| private startWebsocket(): void { | |||
| console.log('starting websocket now..', this.url); | |||
| this.ws = new WebSocket(this.url); | |||
| this.ws.onopen = this.onOpen.bind(this); | |||
| this.ws.onmessage = this.onMessage.bind(this); | |||
| this.ws.onerror = this.onError.bind(this); | |||
| this.ws.onclose = this.onClose.bind(this); | |||
| } | |||
| // public createObservableSocket(url: string): Observable<string> { | |||
| // this.url = url; | |||
| // const ret = new Observable<string> ( observer => { | |||
| // this.observer = observer; | |||
| // }); | |||
| // this.startWebsocket(); | |||
| // return ret; | |||
| // } | |||
| public onOpen(): void { | |||
| this.connected = true; | |||
| if ( this.sessionId !== '' ){ | |||
| this.registerSessionId( this.sessionId); | |||
| } | |||
| } | |||
| public set SessionId( sid: string ) { | |||
| if ( this.sessionId !== sid ){ | |||
| this.registerSessionId(sid); | |||
| } | |||
| this.sessionId = sid; | |||
| } | |||
| private registerSessionId(sid: string): void { | |||
| const msg = { | |||
| t: 'register-session', | |||
| session: sid, | |||
| }; | |||
| this.Send(JSON.stringify(msg)); | |||
| } | |||
| public Send(msg: string): void { | |||
| if ( !this.connected ) { | |||
| return; | |||
| } | |||
| try { | |||
| this.ws.send(msg); | |||
| }catch (e){ | |||
| this.connected = false; | |||
| } | |||
| } | |||
| public onMessage(event): void{ | |||
| this.connected = true; | |||
| super.next(event.data); | |||
| try { | |||
| const e = JSON.parse(event.data); | |||
| @@ -50,17 +78,14 @@ export class WebSocketService extends Subject<string>{ | |||
| } | |||
| public onError(event): void{ | |||
| this.connected = false; | |||
| console.log('on error ', event); | |||
| } | |||
| public onClose(event): void{ | |||
| this.connected = false; | |||
| console.log('websocket closed.. reconnect in 1s ..', event); | |||
| setTimeout(() => { this.startWebsocket(); } , 1000); | |||
| } | |||
| public sendMessage(message: any): void { | |||
| this.ws.send(message); | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ | |||
| "experimentalDecorators": true, | |||
| "moduleResolution": "node", | |||
| "importHelpers": true, | |||
| "target": "es2015", | |||
| "target": "es5", | |||
| "module": "es2020", | |||
| "lib": [ | |||
| "es2018", | |||