mirror of
https://github.com/MikroWizard/mikrofront.git
synced 2026-05-09 21:39:19 +00:00
Better License Error when pro License expired
This commit is contained in:
parent
7e3469a5da
commit
df1223b9e2
21 changed files with 321 additions and 8 deletions
|
|
@ -8,7 +8,9 @@ import Aura from '@primeuix/themes/aura';
|
|||
import { ReactiveFormsModule,FormsModule } from '@angular/forms';
|
||||
|
||||
import { NgScrollbarModule } from 'ngx-scrollbar';
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { LicenseService } from './providers/license.service';
|
||||
import { LicenseInterceptor } from './providers/license-interceptor.service';
|
||||
|
||||
// Import routing module
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
|
@ -113,6 +115,12 @@ export function loginStatusProviderFactory(provider: loginChecker) {
|
|||
multi: true,
|
||||
},
|
||||
Title,
|
||||
LicenseService,
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: LicenseInterceptor,
|
||||
multi: true
|
||||
},
|
||||
provideHttpClient(withInterceptorsFromDi())
|
||||
] })
|
||||
export class AppModule {
|
||||
|
|
|
|||
55
src/app/providers/license-interceptor.service.ts
Normal file
55
src/app/providers/license-interceptor.service.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpRequest,
|
||||
HttpHandler,
|
||||
HttpEvent,
|
||||
HttpInterceptor,
|
||||
HttpResponse
|
||||
} from '@angular/common/http';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { LicenseService } from './license.service';
|
||||
|
||||
@Injectable()
|
||||
export class LicenseInterceptor implements HttpInterceptor {
|
||||
|
||||
// Pro-licensed API endpoints that should be monitored
|
||||
private readonly proEndpoints = [
|
||||
'monitoring/devs/get',
|
||||
'monitoring/events/get',
|
||||
'monitoring/eventunfixed/get',
|
||||
'/api/vpn/status',
|
||||
'networkmap/get',
|
||||
'snippet/sequence/list',
|
||||
'cloner/list',
|
||||
'/api/pssvault/get',
|
||||
'snippet/syslogregex/list'
|
||||
];
|
||||
|
||||
constructor(private licenseService: LicenseService) {}
|
||||
|
||||
private isProEndpoint(url: string): boolean {
|
||||
return this.proEndpoints.some(endpoint => url.includes(endpoint));
|
||||
}
|
||||
|
||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
return next.handle(request).pipe(
|
||||
tap((event: HttpEvent<any>) => {
|
||||
if (event instanceof HttpResponse && this.isProEndpoint(request.url)) {
|
||||
const body = event.body;
|
||||
if (
|
||||
body &&
|
||||
body.result &&
|
||||
typeof body.result === 'object' &&
|
||||
body.result.err === 'License Expired' &&
|
||||
body.result.status === 'failed'
|
||||
) {
|
||||
this.licenseService.setExpired(true);
|
||||
} else {
|
||||
// Pro endpoint responded without license error — license is valid
|
||||
this.licenseService.setExpired(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
22
src/app/providers/license.service.ts
Normal file
22
src/app/providers/license.service.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LicenseService {
|
||||
private isExpiredSubject = new BehaviorSubject<boolean>(false);
|
||||
public isExpired$: Observable<boolean> = this.isExpiredSubject.asObservable();
|
||||
|
||||
constructor() { }
|
||||
|
||||
setExpired(expired: boolean): void {
|
||||
if (this.isExpiredSubject.value !== expired) {
|
||||
this.isExpiredSubject.next(expired);
|
||||
}
|
||||
}
|
||||
|
||||
isExpired(): boolean {
|
||||
return this.isExpiredSubject.value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<div class="license-overlay" *ngIf="isExpired$ | async">
|
||||
<div class="overlay-content">
|
||||
<div class="icon-container">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i>
|
||||
</div>
|
||||
<h2>Pro Feature: License Expired</h2>
|
||||
<p>This view is restricted to pro-licensed customers. Your license has either expired or is not valid for this feature.</p>
|
||||
<div class="actions">
|
||||
<a href="https://mikrowizard.com/license-renewal" target="_blank" class="btn-renew">
|
||||
<i class="fa-solid fa-cart-shopping"></i> Renew License
|
||||
</a>
|
||||
<button class="btn-dismiss" (click)="dismiss()">Later</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
.license-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
border-radius: inherit;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
|
||||
.overlay-content {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 2.5rem;
|
||||
border-radius: 24px;
|
||||
text-align: center;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
max-width: 480px;
|
||||
width: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
transform: translateY(0);
|
||||
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, #fde8e8 0%, #f9d2d2 100%);
|
||||
color: #e55353;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #e55353;
|
||||
animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #1a1c23;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1.75rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #4b5563;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 2.5rem;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
width: 100%;
|
||||
|
||||
.btn-renew {
|
||||
flex: 3;
|
||||
background: linear-gradient(135deg, #e55353 0%, #c53030 100%);
|
||||
color: white;
|
||||
padding: 0.875rem 1.5rem;
|
||||
border-radius: 14px;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
box-shadow: 0 4px 12px rgba(229, 83, 83, 0.3);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(229, 83, 83, 0.5);
|
||||
filter: brightness(1.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-dismiss {
|
||||
flex: 1;
|
||||
background: #f3f4f6;
|
||||
color: #6b7280;
|
||||
padding: 0.875rem 1rem;
|
||||
border-radius: 14px;
|
||||
font-weight: 600;
|
||||
border: 1px solid #e5e7eb;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.95rem;
|
||||
|
||||
&:hover {
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes ping {
|
||||
0% { transform: scale(1); opacity: 0.5; }
|
||||
100% { transform: scale(2); opacity: 0; }
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { LicenseService } from '../../../providers/license.service';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-license-expired-overlay',
|
||||
templateUrl: './license-expired-overlay.component.html',
|
||||
styleUrls: ['./license-expired-overlay.component.scss']
|
||||
})
|
||||
export class LicenseExpiredOverlayComponent implements OnInit {
|
||||
public isExpired$: Observable<boolean>;
|
||||
|
||||
constructor(private licenseService: LicenseService) {
|
||||
this.isExpired$ = this.licenseService.isExpired$;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
dismiss(): void {
|
||||
// Locally hide the overlay for the current component session if needed
|
||||
// But usually, it should stay until the license is fixed.
|
||||
}
|
||||
}
|
||||
16
src/app/shared/shared.module.ts
Normal file
16
src/app/shared/shared.module.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { LicenseExpiredOverlayComponent } from './components/license-expired-overlay/license-expired-overlay.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
LicenseExpiredOverlayComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
exports: [
|
||||
LicenseExpiredOverlayComponent
|
||||
]
|
||||
})
|
||||
export class SharedModule { }
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
<div style="position: relative;">
|
||||
<c-row>
|
||||
<c-col xs>
|
||||
<c-card class="mb-4">
|
||||
|
|
@ -115,6 +116,8 @@
|
|||
</c-card>
|
||||
</c-col>
|
||||
</c-row>
|
||||
<app-license-expired-overlay></app-license-expired-overlay>
|
||||
</div>
|
||||
|
||||
<c-modal #EditClonerModal backdrop="static" size="lg" [(visible)]="EditClonerModalVisible" id="EditClonerModal">
|
||||
<c-modal-header class="bg-light">
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
|
|||
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
|
||||
|
||||
import { NgxSuperSelectModule} from "ngx-super-select";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -46,6 +47,7 @@ import { NgxSuperSelectModule} from "ngx-super-select";
|
|||
TabsModule,
|
||||
BadgeModule,
|
||||
AlertModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [ClonerComponent],
|
||||
providers: [TitleCasePipe],
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<div style="position: relative;">
|
||||
<c-row class="network-container">
|
||||
<c-col [xs]="12" class="network-col">
|
||||
<c-card class="network-card">
|
||||
|
|
@ -113,3 +114,5 @@
|
|||
<button cButton color="danger" (click)="confirmResetMap()">Reset Everything</button>
|
||||
</c-modal-footer>
|
||||
</c-modal>
|
||||
<app-license-expired-overlay></app-license-expired-overlay>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { MapsRoutingModule } from "./maps-routing.module";
|
|||
import { MapsComponent } from "./maps.component";
|
||||
import { ClipboardModule } from "@angular/cdk/clipboard";
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -53,7 +54,8 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
|||
TooltipModule,
|
||||
UtilitiesModule,
|
||||
InfiniteScrollModule,
|
||||
IconModule
|
||||
IconModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [MapsComponent],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<div style="position: relative;">
|
||||
<c-row style="height: calc(100vh - 10rem);" (click)="disableContextMenu()" >
|
||||
<c-col xs="3" style="height:100%;">
|
||||
<c-card style="height:100%">
|
||||
|
|
@ -176,6 +177,8 @@
|
|||
</c-card>
|
||||
</c-col>
|
||||
</c-row>
|
||||
<app-license-expired-overlay></app-license-expired-overlay>
|
||||
</div>
|
||||
<div *ngIf="contextmenu">
|
||||
<div class="contextmenu" [ngStyle]="{'left.px': contextmenuX, 'top.px': contextmenuY}">
|
||||
<c-card style="padding: 1px;">
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { MonitoringRoutingModule } from "./monitoring-routing.module";
|
|||
import { MonitoringComponent } from "./monitoring.component";
|
||||
import { ClipboardModule } from "@angular/cdk/clipboard";
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -49,7 +50,8 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
|||
TableModule,
|
||||
TooltipModule,
|
||||
UtilitiesModule,
|
||||
InfiniteScrollModule
|
||||
InfiniteScrollModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [MonitoringComponent],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<div style="position: relative;">
|
||||
<c-row>
|
||||
<c-col xs>
|
||||
<c-card class="mb-4">
|
||||
|
|
@ -118,6 +119,8 @@
|
|||
</c-card>
|
||||
</c-col>
|
||||
</c-row>
|
||||
<app-license-expired-overlay></app-license-expired-overlay>
|
||||
</div>
|
||||
|
||||
<c-modal #EditSequenceModal backdrop="static" size="xl" [(visible)]="EditSequenceModalVisible" id="EditSequenceModal"
|
||||
class="builder-modal">
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { TableModule } from 'primeng/table';
|
|||
import { InputTextModule } from 'primeng/inputtext';
|
||||
import { TooltipModule } from 'primeng/tooltip';
|
||||
import { NgxSuperSelectModule } from "ngx-super-select";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -47,7 +48,8 @@ import { NgxSuperSelectModule } from "ngx-super-select";
|
|||
TabsModule,
|
||||
AlertModule,
|
||||
HighlightJsModule,
|
||||
CollapseModule
|
||||
CollapseModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [SequencesComponent],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<div style="position: relative;">
|
||||
<c-row>
|
||||
<c-col xs>
|
||||
<c-card class="mb-4">
|
||||
|
|
@ -108,6 +109,8 @@
|
|||
</c-card>
|
||||
</c-col>
|
||||
</c-row>
|
||||
<app-license-expired-overlay></app-license-expired-overlay>
|
||||
</div>
|
||||
|
||||
<c-modal #EditRegexModal backdrop="static" size="xl" [fullscreen]="true" [(visible)]="EditRegexModalVisible"
|
||||
id="EditRegexModal">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { InputTextModule } from 'primeng/inputtext';
|
|||
import { MultiSelectModule } from 'primeng/multiselect';
|
||||
import { TooltipModule } from 'primeng/tooltip';
|
||||
import { DropdownModule } from 'primeng/dropdown';
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -51,7 +52,8 @@ import { DropdownModule } from 'primeng/dropdown';
|
|||
TabsModule,
|
||||
AlertModule,
|
||||
HighlightJsModule,
|
||||
CollapseModule
|
||||
CollapseModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [SyslogRegexComponent],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<div style="position: relative;">
|
||||
<c-card class="mb-4" style="margin-bottom: 0 !important;">
|
||||
<c-card-header style="padding: 0;">
|
||||
<c-row class="align-items-center">
|
||||
|
|
@ -336,6 +337,8 @@
|
|||
</c-card>
|
||||
</c-tab-pane>
|
||||
</c-tab-content>
|
||||
<app-license-expired-overlay></app-license-expired-overlay>
|
||||
</div>
|
||||
|
||||
<!-- Password Reveal Modal -->
|
||||
<c-modal #PasswordModal backdrop="static" [(visible)]="PasswordModalVisible" id="PasswordModal">
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
|
|||
|
||||
import { MatInputModule } from "@angular/material/input";
|
||||
import { MatFormFieldModule } from "@angular/material/form-field";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
@NgModule({
|
||||
imports: [
|
||||
VaultRoutingModule,
|
||||
|
|
@ -42,7 +43,8 @@ import { MatFormFieldModule } from "@angular/material/form-field";
|
|||
MatInputModule,
|
||||
MatFormFieldModule,
|
||||
CollapseModule,
|
||||
TooltipModule
|
||||
TooltipModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [VaultComponent],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<div style="position: relative;">
|
||||
<div class="fade-in">
|
||||
<!-- Communication Error Alert -->
|
||||
<c-row class="mb-4" *ngIf="isCommunicationError">
|
||||
|
|
@ -649,6 +650,7 @@
|
|||
</c-modal-footer>
|
||||
</c-modal>
|
||||
|
||||
<!-- End of File -->
|
||||
</div>
|
||||
<app-license-expired-overlay></app-license-expired-overlay>
|
||||
</div>
|
||||
<c-toaster position="fixed" placement="top-end"></c-toaster>
|
||||
|
|
@ -4,6 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|||
import { ChartjsModule } from '@coreui/angular-chartjs';
|
||||
import { HighlightJsModule } from 'ngx-highlight-js';
|
||||
import { ClipboardModule } from '@angular/cdk/clipboard';
|
||||
import { SharedModule as AppSharedModule } from "../../shared/shared.module";
|
||||
|
||||
import {
|
||||
AvatarModule,
|
||||
|
|
@ -67,7 +68,8 @@ import { InputTextModule } from 'primeng/inputtext';
|
|||
CoreUIGridModule,
|
||||
MatMenuModule,
|
||||
HighlightJsModule,
|
||||
ClipboardModule
|
||||
ClipboardModule,
|
||||
AppSharedModule
|
||||
],
|
||||
declarations: [VpnComponent]
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue