diff --git a/proxy.conf.json b/proxy.conf.json index 38faa62..e290e69 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -1,6 +1,6 @@ { "/api/v1": { - "target": "http://svr2021:8080", + "target": "https://svr2021.lawipac.com:8080", "secure": false, "pathRewrite": {"^/api/v1" : "/api/v1"} } diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 6e81a47..f7bdbce 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -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 { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a3f4221..c980769 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -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 diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 802cd29..726a079 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -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(); diff --git a/src/app/auth/auth-http-interceptor.service.ts b/src/app/auth/auth-http-interceptor.service.ts index 72b27e3..9685aad 100644 --- a/src/app/auth/auth-http-interceptor.service.ts +++ b/src/app/auth/auth-http-interceptor.service.ts @@ -14,32 +14,52 @@ export class AuthHttpInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable> { 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); + } + } + } + + } diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index 2808655..d383add 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -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); diff --git a/src/app/auth/dist/auth.component.css b/src/app/auth/dist/auth.component.css deleted file mode 100644 index 7175a3d..0000000 --- a/src/app/auth/dist/auth.component.css +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/src/app/auth/dist/auth.component.js b/src/app/auth/dist/auth.component.js deleted file mode 100644 index 292be5c..0000000 --- a/src/app/auth/dist/auth.component.js +++ /dev/null @@ -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; diff --git a/src/app/chart-type-of-loans/chart-type-of-loans.component.html b/src/app/chart-type-of-loans/chart-type-of-loans.component.html index 20e8381..23a91ec 100644 --- a/src/app/chart-type-of-loans/chart-type-of-loans.component.html +++ b/src/app/chart-type-of-loans/chart-type-of-loans.component.html @@ -1,5 +1,8 @@ + +

{{totalNumberOfLoans}}

+
; public data: Observable; + public totalNumberOfLoans = 0; + constructor(private auth: AuthService, private http: HttpClient) { } + private previous: TypeOfLoansModel[] = []; ngOnInit(): void { - this.data = this.getTypeOfLoans(); + this.data = new Observable( (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): 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 { @@ -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 diff --git a/src/app/helper.function.ts b/src/app/helper.function.ts new file mode 100644 index 0000000..f8d7906 --- /dev/null +++ b/src/app/helper.function.ts @@ -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); +} diff --git a/src/app/image-popup-dialog/image-popup-dialog.component.ts b/src/app/image-popup-dialog/image-popup-dialog.component.ts index c5bac76..e69e435 100644 --- a/src/app/image-popup-dialog/image-popup-dialog.component.ts +++ b/src/app/image-popup-dialog/image-popup-dialog.component.ts @@ -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); } diff --git a/src/app/list-all-loans/list-all-loans.component.ts b/src/app/list-all-loans/list-all-loans.component.ts index 7561779..49c2d9f 100644 --- a/src/app/list-all-loans/list-all-loans.component.ts +++ b/src/app/list-all-loans/list-all-loans.component.ts @@ -134,10 +134,6 @@ export class ListAllLoansComponent implements OnInit { return 'url("' + url + '")'; } - public allowEdit(): boolean { - return this.auth.allowEditLoan(); - } - public allData = (): Observable => { return this.service.queryAll({skip: 0, take: 999999, sort: this.sort, filter: this.filter}); } diff --git a/src/app/models/api-v1-login-response.ts b/src/app/models/api-v1-login-response.ts index fa3b965..0752f9e 100644 --- a/src/app/models/api-v1-login-response.ts +++ b/src/app/models/api-v1-login-response.ts @@ -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) { + 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; - } - } } diff --git a/src/app/models/payout.ex.model.ts b/src/app/models/payout.ex.model.ts new file mode 100644 index 0000000..8c427da --- /dev/null +++ b/src/app/models/payout.ex.model.ts @@ -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) { + 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 || ''; + } +} diff --git a/src/app/models/payout.model.ts b/src/app/models/payout.model.ts deleted file mode 100644 index 44b2a46..0000000 --- a/src/app/models/payout.model.ts +++ /dev/null @@ -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[]; -} diff --git a/src/app/models/reward-ex.model.ts b/src/app/models/reward-ex.model.ts index 08e895f..18f3c76 100644 --- a/src/app/models/reward-ex.model.ts +++ b/src/app/models/reward-ex.model.ts @@ -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) { @@ -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); + } } } diff --git a/src/app/models/user.model.ts b/src/app/models/userExModel.ts similarity index 84% rename from src/app/models/user.model.ts rename to src/app/models/userExModel.ts index aae87d6..f9f4de1 100644 --- a/src/app/models/user.model.ts +++ b/src/app/models/userExModel.ts @@ -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) { + constructor(payload: Partial) { super(payload); this.Role = payload.Role || 'People' as UserRoles; this.Login = payload.Login || ''; diff --git a/src/app/pay-out-details/pay-out-details.component.ts b/src/app/pay-out-details/pay-out-details.component.ts index 46af9b1..9da6622 100644 --- a/src/app/pay-out-details/pay-out-details.component.ts +++ b/src/app/pay-out-details/pay-out-details.component.ts @@ -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)]; } diff --git a/src/app/service/auth.service.ts b/src/app/service/auth.service.ts index 22f1562..8bf54f4 100644 --- a/src/app/service/auth.service.ts +++ b/src/app/service/auth.service.ts @@ -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(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 { return this.http.get(this.getUrl('login-available/' + v)); } diff --git a/src/app/service/payout.service.ts b/src/app/service/payout.service.ts new file mode 100644 index 0000000..0e72c5e --- /dev/null +++ b/src/app/service/payout.service.ts @@ -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 { + return this.http.get(this.auth.getUrl('payout-ex/' + id)); + } + + public getPayOutList(filter: CompositeFilterDescriptor): Observable { + return this.http.post(this.auth.getUrl('payout-ex-list/'), filter); + } +} diff --git a/src/app/service/session.service.ts b/src/app/service/session.service.ts index dec0fd0..1385b07 100644 --- a/src/app/service/session.service.ts +++ b/src/app/service/session.service.ts @@ -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 (); - + public loggedIn = new ApiV1LoginResponse({}); + loginResult = new EventEmitter (); + logoutEvent = new EventEmitter (); + 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 || ''; + } + } diff --git a/src/app/top-bar/top-bar.component.ts b/src/app/top-bar/top-bar.component.ts index 00fb58f..4c0d0b5 100644 --- a/src/app/top-bar/top-bar.component.ts +++ b/src/app/top-bar/top-bar.component.ts @@ -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(); } } diff --git a/src/app/websocket.ts b/src/app/websocket.ts index 61dfd2c..a396aa0 100644 --- a/src/app/websocket.ts +++ b/src/app/websocket.ts @@ -8,10 +8,13 @@ import {WsLoginEventModel} from './models/websocket/ws.login.event.model'; @Injectable() export class WebSocketService extends Subject{ + private connected = false; + private sessionId = ''; public ws: WebSocket; private readonly url: string; public LoginEvent: Subject; + // 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{ 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 { - // this.url = url; - // const ret = new Observable ( 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{ } 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); - } } diff --git a/tsconfig.json b/tsconfig.json index f69f654..cf9f03c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, - "target": "es2015", + "target": "es5", "module": "es2020", "lib": [ "es2018",