Add ping route and maintenance mode handling

This commit is contained in:
Matteo Gheza 2024-01-10 17:30:22 +01:00
parent cc266c8e39
commit 52fd6d6d2f
10 changed files with 68 additions and 17 deletions

View File

@ -106,6 +106,12 @@ Route::get('/owner_image', function() {
);
});
Route::get('/ping', function() {
return response()->json([
'message' => 'pong'
]);
});
Route::post('/cron/execute', function(Request $request) {
//Go to app/Console/Kernel.php to view schedules
if(config('cron.external_cron_enabled') && $request->header('Cron') == config('cron.execution_code')) {

View File

@ -37,7 +37,7 @@ const genericRetryStrategy = ({
// retry after 1s, 2s, etc...
return timer(retryAttempt * scalingDuration);
}),
finalize(() => console.log('We are done!'))
//finalize(() => console.log('We are done!'))
);
};
@ -86,15 +86,26 @@ export class AuthInterceptor implements HttpInterceptor {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> {
//If maintenance mode is enabled, return 503 except for ping
if (this.api.maintenanceMode && !req.url.includes("ping")) {
return new Observable<HttpEvent<Object>>((observer) => {
observer.next(new HttpResponse({
body: { message: "Maintenance mode" },
status: 503,
statusText: "Service Unavailable",
url: req.url
}));
observer.complete();
});
}
return next.handle(this.updateRequest(req)).pipe(
retryWhen(genericRetryStrategy({
maxRetryAttempts: 3,
scalingDuration: 1,
excludedStatusCodes: [304, 400, 404, 419, 500, 503],
excludedUrls: ["login", "logout", "me", "impersonate", "stop_impersonating"]
excludedUrls: ["login", "logout", "me", "impersonate", "stop_impersonating", "ping"]
})),
catchError(error => {
console.log(error);
if (error.status === 304) {
//Return current response as successfully
return new Observable<HttpEvent<Object>>((observer) => {
@ -107,6 +118,9 @@ export class AuthInterceptor implements HttpInterceptor {
}));
observer.complete();
});
} else if (error.status === 503) {
this.api.maintenanceMode = true;
return throwError(() => error);
} else if (error.status === 419) {
return new Observable<HttpEvent<Object>>((observer) => {
this.api.get("csrf-cookie").then(() => {
@ -115,7 +129,6 @@ export class AuthInterceptor implements HttpInterceptor {
});
}
if (error instanceof HttpErrorResponse && !req.url.includes('login') && !req.url.includes('me') && !req.url.includes('logout')) {
console.log("Error: " + error.status);
if (error.status === 400) {
this.router.navigate(["logout"]);
return throwError(() => error);

View File

@ -1,36 +1,36 @@
<div class="text-center">
<h3 *ngIf="available !== undefined">{{ 'list.your_availability_is'|translate }} <b>{{ available ? ("available"|translate|uppercase) : ("unavailable"|translate|uppercase) }}{{ manual_mode ? "" : " ("+('programmed'|translate)+")" }}</b></h3>
<div id="availability-btn-group">
<button (click)="changeAvailibility(1)" type="button" id="activate-btn" class="btn btn-lg btn-success me-1">{{ 'set_available'|translate|ftitlecase }}</button>
<button (click)="changeAvailibility(0)" type="button" id="deactivate-btn" class="btn btn-lg btn-danger">{{ 'set_unavailable'|translate|ftitlecase }}</button>
<button (click)="changeAvailibility(1)" [disabled]="api.maintenanceMode" type="button" id="activate-btn" class="btn btn-lg btn-success me-1">{{ 'set_available'|translate|ftitlecase }}</button>
<button (click)="changeAvailibility(0)" [disabled]="api.maintenanceMode" type="button" id="deactivate-btn" class="btn btn-lg btn-danger">{{ 'set_unavailable'|translate|ftitlecase }}</button>
</div>
<ng-container *ngIf="manual_mode !== undefined">
<button type="button" class="btn btn-secondary" *ngIf="manual_mode" (click)="updateManualMode(0)">
<button type="button" class="btn btn-secondary" *ngIf="manual_mode" (click)="updateManualMode(0)" [disabled]="api.maintenanceMode">
{{ 'list.enable_schedules'|translate }}
</button>
<button type="button" class="btn btn-secondary" *ngIf="!manual_mode" (click)="updateManualMode(1)">
<button type="button" class="btn btn-secondary" *ngIf="!manual_mode" (click)="updateManualMode(1)" [disabled]="api.maintenanceMode">
{{ 'list.disable_schedules'|translate }}
</button>
<br>
</ng-container>
<button type="button" class="btn btn-lg" (click)="openScheduleModal()">
<button type="button" class="btn btn-lg" (click)="openScheduleModal()" [disabled]="api.maintenanceMode">
{{ 'list.update_schedules'|translate }}
</button>
</div>
<owner-image></owner-image>
<div class="text-center" *ngIf="auth.profile.can('alerts-create')">
<div class="btn-group" role="group">
<button type="button" class="btn btn-danger" (click)="addAlert('full')" [disabled]="!api.availableUsers || api.availableUsers! < 5 || alertLoading">
<button type="button" class="btn btn-danger" (click)="addAlert('full')" [disabled]="!api.availableUsers || api.availableUsers! < 5 || alertLoading || api.maintenanceMode">
🚒 Richiedi squadra completa
</button>
<button type="button" class="btn btn-warning" (click)="addAlert('support')" [disabled]="!api.availableUsers || api.availableUsers! < 2 || alertLoading">
<button type="button" class="btn btn-warning" (click)="addAlert('support')" [disabled]="!api.availableUsers || api.availableUsers! < 2 || alertLoading || api.maintenanceMode">
Richiedi squadra di supporto 🧯
</button>
</div>
</div>
<app-table [sourceType]="'list'" (changeAvailability)="changeAvailibility($event.newState, $event.user)" (moreDetails)="openUserInfoPage($event.rowId)" #table></app-table>
<div class="text-center">
<button (click)="requestTelegramToken()" class="btn btn-md btn-success mt-3">{{ 'list.connect_telegram_bot'|translate }}</button>
<button (click)="requestTelegramToken()" [disabled]="api.maintenanceMode" class="btn btn-md btn-success mt-3">{{ 'list.connect_telegram_bot'|translate }}</button>
<div class="alert alert-primary mt-4" role="alert" translate>
list.availability_minutes_updated_at_deactivation
</div>

View File

@ -10,7 +10,7 @@
<input type="password" class="form-control" (keydown.enter)="login()" [(ngModel)]="password" id="password" [placeholder]="'password'|translate" #inputPassword>
<label for="password">{{ 'password'|translate|ftitlecase }}</label>
</div>
<button class="w-100 btn btn-lg btn-primary" (click)="login()" [disabled]="loading">
<button class="w-100 btn btn-lg btn-primary" (click)="login()" [disabled]="loading || api.maintenanceMode">
<span *ngIf="!loading" translate>login.submit_btn</span>
<div class="spinner-border spinner-border-sm text-white" *ngIf="loading"></div>
</button>

View File

@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthService, LoginResponse } from 'src/app/_services/auth.service';
import { GuardLoaderIconService } from 'src/app/_services/guard-loader-icon.service';
import { ApiClientService } from 'src/app/_services/api-client.service';
@Component({
selector: 'app-login',
@ -22,7 +23,8 @@ export class LoginComponent {
public route: ActivatedRoute,
private router: Router,
private authService: AuthService,
private guardLoaderIconService: GuardLoaderIconService
private guardLoaderIconService: GuardLoaderIconService,
public api: ApiClientService
) {
this.route.params.subscribe((params) => {
if (params["redirect"]) {

View File

@ -14,6 +14,28 @@ export class ApiClientService {
public alertsChanged = new Subject<void>();
public availableUsers: undefined | number = undefined;
private _maintenanceMode = false;
private _maintenanceModeInterval: any = undefined;
public maintenanceModeChanged = new Subject<void>();
get maintenanceMode(): boolean {
return this._maintenanceMode;
}
set maintenanceMode(value: boolean) {
if(value && !this._maintenanceMode) {
//Every 5 seconds, check if maintenance mode is still active
this._maintenanceModeInterval = setInterval(() => {
this.get("ping").then(() => {
console.log("Maintenance mode disabled");
this.maintenanceMode = false;
clearInterval(this._maintenanceModeInterval);
}).catch(() => {});
}, 10000);
}
this._maintenanceMode = value;
this.maintenanceModeChanged.next();
}
constructor(private http: HttpClient) { }
public apiEndpoint(endpoint: string): string {

View File

@ -1,4 +1,4 @@
<div [className]="menuButtonClicked ? 'topnav responsive' : 'topnav'" id="topNavBar" *ngIf="auth.profile.id !== undefined">
<div [className]="menuButtonClicked ? 'topnav responsive' : 'topnav'" id="topNavBar" *ngIf="auth.profile.id !== undefined && !api.maintenanceMode">
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/list" translate>menu.list</a>
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/services" translate>menu.services</a>
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/trainings" translate>menu.trainings</a>
@ -13,7 +13,13 @@
<div class="spinner spinner-border"></div>
</div>
<alert type="danger" *ngIf="alerts.length > 0">
<div class="mt-2" *ngIf="api.maintenanceMode">
<alert type="danger">
<strong>{{ 'warning'|translate|ftitlecase }}!</strong> {{ 'maintenance_mode_warning'|translate }}<br>
</alert>
</div>
<alert type="danger" *ngIf="alerts.length > 0 && !api.maintenanceMode">
<strong>{{ 'warning'|translate|ftitlecase }}!</strong> {{ 'alert.warning_body'|translate }}<br>
<ng-container *ngIf="alerts.length == 1">
{{ 'alert.current_alert'|translate }}: <a (click)="openAlert(alerts[0]['id'])"><b>{{ alerts[0]["created_at"] | date:'dd/MM/YYYY, HH:mm:ss' }}</b> ({{ 'press_for_more_info'|translate }})</a>

View File

@ -28,7 +28,7 @@ export class AppComponent {
private locationBackService: LocationBackService,
public guardLoaderIconService: GuardLoaderIconService,
private router: Router,
private api: ApiClientService,
public api: ApiClientService,
private modalService: BsModalService,
public guard: AuthorizeGuard
) {

View File

@ -130,6 +130,7 @@
"document_format_not_supported": "Document format not supported",
"file_too_big": "File too big"
},
"maintenance_mode_warning": "The application is currently in maintenance mode. Some features may not be available.",
"property": "property",
"value": "value",
"user_agent": "User Agent",

View File

@ -129,6 +129,7 @@
"image_format_not_supported": "Formato immagine non supportato",
"file_too_big": "File troppo grande"
},
"maintenance_mode_warning": "Il gestionale è in manutenzione. Alcune funzionalità potrebbero non essere disponibili.",
"property": "proprietà",
"value": "valore",
"user_agent": "User Agent",