Better License Error when pro License expired

This commit is contained in:
sepehr 2026-04-09 20:34:20 +03:00
parent 7e3469a5da
commit df1223b9e2
21 changed files with 321 additions and 8 deletions

View file

@ -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 {

View 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);
}
}
})
);
}
}

View 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;
}
}

View file

@ -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>

View file

@ -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; }
}

View file

@ -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.
}
}

View 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 { }

View file

@ -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">

View file

@ -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],

View file

@ -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>

View file

@ -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],
})

View file

@ -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;">

View file

@ -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],
})

View file

@ -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">

View file

@ -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],
})

View file

@ -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">

View file

@ -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],
})

View file

@ -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">

View file

@ -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],
})

View file

@ -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>

View file

@ -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]
})