MOBILE-4451 settings: Create error log page
parent
99c1eb5376
commit
6da2742bd3
|
@ -64,6 +64,7 @@ import { CoreSiteError } from '@classes/errors/siteerror';
|
||||||
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
|
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
|
||||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||||
import { CorePath } from '@singletons/path';
|
import { CorePath } from '@singletons/path';
|
||||||
|
import { CoreErrorLogs } from '@singletons/error-logs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QR Code type enumeration.
|
* QR Code type enumeration.
|
||||||
|
@ -1156,7 +1157,15 @@ export class CoreSite {
|
||||||
// Request not executed, enqueue again.
|
// Request not executed, enqueue again.
|
||||||
this.enqueueRequest(request);
|
this.enqueueRequest(request);
|
||||||
} else if (response.error) {
|
} else if (response.error) {
|
||||||
request.deferred.reject(CoreTextUtils.parseJSON(response.exception || ''));
|
const rejectReason = CoreTextUtils.parseJSON(response.exception || '') as Error | undefined;
|
||||||
|
request.deferred.reject(rejectReason);
|
||||||
|
CoreErrorLogs.addErrorLog({
|
||||||
|
method: request.method,
|
||||||
|
type: 'CoreSiteError',
|
||||||
|
message: response.exception ?? '',
|
||||||
|
time: new Date().getTime(),
|
||||||
|
data: request.data,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let responseData = response.data ? CoreTextUtils.parseJSON(response.data) : {};
|
let responseData = response.data ? CoreTextUtils.parseJSON(response.data) : {};
|
||||||
// Match the behaviour of CoreWSProvider.call when no response is expected.
|
// Match the behaviour of CoreWSProvider.call when no response is expected.
|
||||||
|
@ -1170,6 +1179,13 @@ export class CoreSite {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Error not specific to a single request, reject all promises.
|
// Error not specific to a single request, reject all promises.
|
||||||
requests.forEach((request) => {
|
requests.forEach((request) => {
|
||||||
|
CoreErrorLogs.addErrorLog({
|
||||||
|
method: request.method,
|
||||||
|
type: 'CoreSiteError',
|
||||||
|
message: String(error) ?? '',
|
||||||
|
time: new Date().getTime(),
|
||||||
|
data: request.data,
|
||||||
|
});
|
||||||
request.deferred.reject(error);
|
request.deferred.reject(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,9 @@
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Enable staging sites ({{stagingSitesCount}})</h2>
|
<h2>Enable staging sites ({{stagingSitesCount}})</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-toggle [(ngModel)]="enableStagingSites" (ionChange)="setEnabledStagingSites($event.detail.checked)"
|
<ion-toggle [(ngModel)]="enableStagingSites" (ionChange)="setEnabledStagingSites($event.detail.checked)" slot="end">
|
||||||
slot="end"></ion-toggle>
|
</ion-toggle>
|
||||||
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ng-container *ngIf="siteId">
|
<ng-container *ngIf="siteId">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item class="ion-text-wrap">
|
||||||
|
@ -61,6 +62,12 @@
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" (click)="openErrorLog()" [detail]="true" button>
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">Error log</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Disabled features</h2>
|
<h2>Disabled features</h2>
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { CoreSettingsHelper } from '@features/settings/services/settings-helper'
|
||||||
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
||||||
import { CoreUserTours } from '@features/usertours/services/user-tours';
|
import { CoreUserTours } from '@features/usertours/services/user-tours';
|
||||||
import { CoreConfig } from '@services/config';
|
import { CoreConfig } from '@services/config';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CorePlatform } from '@services/platform';
|
import { CorePlatform } from '@services/platform';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
@ -151,6 +152,13 @@ export class CoreSettingsDevPage implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open error log.
|
||||||
|
*/
|
||||||
|
openErrorLog(): void {
|
||||||
|
CoreNavigator.navigate('error-log');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies site info.
|
* Copies site info.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
|
||||||
|
<ion-title>
|
||||||
|
<h1>Error log</h1>
|
||||||
|
</ion-title>
|
||||||
|
|
||||||
|
<ion-buttons slot="end" *ngIf="errorLogs.length">
|
||||||
|
<ion-button fill="clear" (click)="copyInfo()" [attr.aria-label]="'core.settings.copyinfo' | translate">
|
||||||
|
<ion-icon slot="icon-only" name="fas-clipboard" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-list *ngIf="errorLogs.length; else noLogs">
|
||||||
|
<ion-item button lines="full" class="ion-text-wrap" *ngFor="let error of errorLogs">
|
||||||
|
<div class="ion-padding" [collapsible-item]="96">
|
||||||
|
<p class="item-heading">Trace</p>
|
||||||
|
<p class="ion-text-wrap">{{ error.message }}</p>
|
||||||
|
|
||||||
|
<ng-container *ngIf="error.method">
|
||||||
|
<p class="item-heading">Method</p>
|
||||||
|
<p class="ion-text-wrap">{{ error.method }}</p>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="error.type">
|
||||||
|
<p class="item-heading">Type</p>
|
||||||
|
<p class="ion-text-wrap">{{ error.type }}</p>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="error.data">
|
||||||
|
<p class="item-heading">Data</p>
|
||||||
|
<p class="ion-text-wrap">{{ error.data | json }}</p>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngIf="error.time">
|
||||||
|
<span class="ion-text-end">{{ error.time | coreFormatDate :'strftimedatetimeshort' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
<ng-template #noLogs>
|
||||||
|
<core-empty-box message="No logs available" icon="fas-clipboard-question">
|
||||||
|
</core-empty-box>
|
||||||
|
</ng-template>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,4 @@
|
||||||
|
.timestamp {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreErrorLogs, CoreSettingsErrorLog } from '@singletons/error-logs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the error logs.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-app-settings-error-log',
|
||||||
|
templateUrl: 'error-log.html',
|
||||||
|
styleUrls: ['./error-log.scss'],
|
||||||
|
})
|
||||||
|
export class CoreSettingsErrorLogPage implements OnInit {
|
||||||
|
|
||||||
|
errorLogs: CoreSettingsErrorLog[] = [];
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.errorLogs = CoreErrorLogs.getErrorLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
copyInfo(): void {
|
||||||
|
CoreUtils.copyToClipboard(JSON.stringify({ errors: this.errorLogs }));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import { CoreSettingsAboutPage } from '@features/settings/pages/about/about';
|
||||||
import { CoreSettingsLicensesPage } from '@features/settings/pages/licenses/licenses';
|
import { CoreSettingsLicensesPage } from '@features/settings/pages/licenses/licenses';
|
||||||
import { CoreSettingsDeviceInfoPage } from '@features/settings/pages/deviceinfo/deviceinfo';
|
import { CoreSettingsDeviceInfoPage } from '@features/settings/pages/deviceinfo/deviceinfo';
|
||||||
import { CoreSettingsDevPage } from '@features/settings/pages/dev/dev';
|
import { CoreSettingsDevPage } from '@features/settings/pages/dev/dev';
|
||||||
|
import { CoreSettingsErrorLogPage } from '@features/settings/pages/error-log/error-log';
|
||||||
|
|
||||||
const sectionRoutes: Routes = [
|
const sectionRoutes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -86,6 +87,10 @@ const routes: Routes = [
|
||||||
path: 'about/deviceinfo/dev',
|
path: 'about/deviceinfo/dev',
|
||||||
component: CoreSettingsDevPage,
|
component: CoreSettingsDevPage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'about/deviceinfo/dev/error-log',
|
||||||
|
component: CoreSettingsErrorLogPage,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'about/licenses',
|
path: 'about/licenses',
|
||||||
component: CoreSettingsLicensesPage,
|
component: CoreSettingsLicensesPage,
|
||||||
|
@ -106,6 +111,7 @@ const routes: Routes = [
|
||||||
CoreSettingsLicensesPage,
|
CoreSettingsLicensesPage,
|
||||||
CoreSettingsDeviceInfoPage,
|
CoreSettingsDeviceInfoPage,
|
||||||
CoreSettingsDevPage,
|
CoreSettingsDevPage,
|
||||||
|
CoreSettingsErrorLogPage,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreSettingsLazyModule {}
|
export class CoreSettingsLazyModule {}
|
||||||
|
|
|
@ -60,6 +60,7 @@ import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { CoreLang } from '@services/lang';
|
import { CoreLang } from '@services/lang';
|
||||||
import { CorePasswordModalParams, CorePasswordModalResponse } from '@components/password-modal/password-modal';
|
import { CorePasswordModalParams, CorePasswordModalResponse } from '@components/password-modal/password-modal';
|
||||||
import { CoreWSError } from '@classes/errors/wserror';
|
import { CoreWSError } from '@classes/errors/wserror';
|
||||||
|
import { CoreErrorLogs } from '@singletons/error-logs';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
||||||
|
@ -613,6 +614,7 @@ export class CoreDomUtilsProvider {
|
||||||
|
|
||||||
// We received an object instead of a string. Search for common properties.
|
// We received an object instead of a string. Search for common properties.
|
||||||
errorMessage = CoreTextUtils.getErrorMessageFromError(error);
|
errorMessage = CoreTextUtils.getErrorMessageFromError(error);
|
||||||
|
CoreErrorLogs.addErrorLog({ message: JSON.stringify(error), type: errorMessage || '', time: new Date().getTime() });
|
||||||
if (!errorMessage) {
|
if (!errorMessage) {
|
||||||
// No common properties found, just stringify it.
|
// No common properties found, just stringify it.
|
||||||
errorMessage = JSON.stringify(error);
|
errorMessage = JSON.stringify(error);
|
||||||
|
|
|
@ -43,6 +43,7 @@ import { CoreSiteError, CoreSiteErrorOptions } from '@classes/errors/siteerror';
|
||||||
import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config';
|
import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreLang, CoreLangFormat } from './lang';
|
import { CoreLang, CoreLangFormat } from './lang';
|
||||||
|
import { CoreErrorLogs } from '@singletons/error-logs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service allows performing WS calls and download/upload files.
|
* This service allows performing WS calls and download/upload files.
|
||||||
|
@ -412,9 +413,25 @@ export class CoreWSProvider {
|
||||||
let promise: Promise<HttpResponse<any>>;
|
let promise: Promise<HttpResponse<any>>;
|
||||||
|
|
||||||
if (preSets.siteUrl === undefined) {
|
if (preSets.siteUrl === undefined) {
|
||||||
throw new CoreAjaxError(Translate.instant('core.unexpectederror'));
|
const unexpectedError = new CoreAjaxError(Translate.instant('core.unexpectederror'));
|
||||||
|
CoreErrorLogs.addErrorLog({
|
||||||
|
method,
|
||||||
|
type: 'CoreAjaxError',
|
||||||
|
message: Translate.instant('core.unexpectederror'),
|
||||||
|
time: new Date().getTime(),
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
throw unexpectedError;
|
||||||
} else if (!CoreNetwork.isOnline()) {
|
} else if (!CoreNetwork.isOnline()) {
|
||||||
throw new CoreAjaxError(Translate.instant('core.networkerrormsg'));
|
const networkError = new CoreAjaxError(Translate.instant('core.networkerrormsg'));
|
||||||
|
CoreErrorLogs.addErrorLog({
|
||||||
|
method,
|
||||||
|
type: 'CoreAjaxError',
|
||||||
|
message: Translate.instant('core.networkerrormsg'),
|
||||||
|
time: new Date().getTime(),
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
throw networkError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preSets.responseExpected === undefined) {
|
if (preSets.responseExpected === undefined) {
|
||||||
|
@ -552,6 +569,10 @@ export class CoreWSProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CoreAjaxError(options, 1, data.status);
|
throw new CoreAjaxError(options, 1, data.status);
|
||||||
|
}).catch(error => {
|
||||||
|
const type = `CoreAjaxError - ${error.errorcode}`;
|
||||||
|
CoreErrorLogs.addErrorLog({ method, type, message: error, time: new Date().getTime(), data });
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,6 +803,15 @@ export class CoreWSProvider {
|
||||||
throw new CoreError(Translate.instant('core.serverconnection', {
|
throw new CoreError(Translate.instant('core.serverconnection', {
|
||||||
details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error',
|
details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error',
|
||||||
}));
|
}));
|
||||||
|
}).catch(err => {
|
||||||
|
CoreErrorLogs.addErrorLog({
|
||||||
|
method,
|
||||||
|
type: String(err),
|
||||||
|
message: String(err.exception),
|
||||||
|
time: new Date().getTime(),
|
||||||
|
data: ajaxData,
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -847,71 +877,84 @@ export class CoreWSProvider {
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
syncCall<T = unknown>(method: string, data: any, preSets: CoreWSPreSets): T {
|
syncCall<T = unknown>(method: string, data: any, preSets: CoreWSPreSets): T {
|
||||||
if (!preSets) {
|
try {
|
||||||
throw new CoreError(Translate.instant('core.unexpectederror'));
|
if (!preSets) {
|
||||||
} else if (!CoreNetwork.isOnline()) {
|
throw new CoreError(Translate.instant('core.unexpectederror'));
|
||||||
throw new CoreNetworkError();
|
} else if (!CoreNetwork.isOnline()) {
|
||||||
|
throw new CoreNetworkError();
|
||||||
|
}
|
||||||
|
|
||||||
|
preSets.typeExpected = preSets.typeExpected || 'object';
|
||||||
|
if (preSets.responseExpected === undefined) {
|
||||||
|
preSets.responseExpected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = this.convertValuesToString(data || {}, preSets.cleanUnicode);
|
||||||
|
if (data == null) {
|
||||||
|
// Empty cleaned text found.
|
||||||
|
throw new CoreError(Translate.instant('core.unicodenotsupportedcleanerror'));
|
||||||
|
}
|
||||||
|
|
||||||
|
data.wsfunction = method;
|
||||||
|
data.wstoken = preSets.wsToken;
|
||||||
|
const siteUrl = preSets.siteUrl + '/webservice/rest/server.php?moodlewsrestformat=json';
|
||||||
|
|
||||||
|
// Serialize data.
|
||||||
|
data = CoreInterceptor.serialize(data);
|
||||||
|
|
||||||
|
// Perform sync request using XMLHttpRequest.
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('post', siteUrl, false);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
|
||||||
|
|
||||||
|
xhr.send(data);
|
||||||
|
|
||||||
|
// Get response.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
data = ('response' in xhr) ? xhr.response : (<any> xhr).responseText;
|
||||||
|
|
||||||
|
// Check status.
|
||||||
|
const status = Math.max(xhr.status === 1223 ? 204 : xhr.status, 0);
|
||||||
|
if (status < 200 || status >= 300) {
|
||||||
|
// Request failed.
|
||||||
|
throw new CoreError(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat response.
|
||||||
|
data = CoreTextUtils.parseJSON(data);
|
||||||
|
|
||||||
|
// Some moodle web services return null.
|
||||||
|
// If the responseExpected value is set then so long as no data is returned, we create a blank object.
|
||||||
|
if ((!data || !data.data) && !preSets.responseExpected) {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw new CoreError(Translate.instant('core.serverconnection', {
|
||||||
|
details: Translate.instant('core.errorinvalidresponse', { method }),
|
||||||
|
}));
|
||||||
|
} else if (typeof data != preSets.typeExpected) {
|
||||||
|
this.logger.warn('Response of type "' + typeof data + '" received, expecting "' + preSets.typeExpected + '"');
|
||||||
|
throw new CoreError(Translate.instant('core.errorinvalidresponse', { method }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.exception !== undefined || data.debuginfo !== undefined) {
|
||||||
|
throw new CoreWSError(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
let errorType = '';
|
||||||
|
|
||||||
|
if (err instanceof CoreError) {
|
||||||
|
errorType = 'CoreError';
|
||||||
|
} else if (err instanceof CoreWSError) {
|
||||||
|
errorType = 'CoreWSError';
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreErrorLogs.addErrorLog({ method, type: errorType, message: String(err), time: new Date().getTime(), data });
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
preSets.typeExpected = preSets.typeExpected || 'object';
|
|
||||||
if (preSets.responseExpected === undefined) {
|
|
||||||
preSets.responseExpected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = this.convertValuesToString(data || {}, preSets.cleanUnicode);
|
|
||||||
if (data == null) {
|
|
||||||
// Empty cleaned text found.
|
|
||||||
throw new CoreError(Translate.instant('core.unicodenotsupportedcleanerror'));
|
|
||||||
}
|
|
||||||
|
|
||||||
data.wsfunction = method;
|
|
||||||
data.wstoken = preSets.wsToken;
|
|
||||||
const siteUrl = preSets.siteUrl + '/webservice/rest/server.php?moodlewsrestformat=json';
|
|
||||||
|
|
||||||
// Serialize data.
|
|
||||||
data = CoreInterceptor.serialize(data);
|
|
||||||
|
|
||||||
// Perform sync request using XMLHttpRequest.
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('post', siteUrl, false);
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
|
|
||||||
|
|
||||||
xhr.send(data);
|
|
||||||
|
|
||||||
// Get response.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
data = ('response' in xhr) ? xhr.response : (<any> xhr).responseText;
|
|
||||||
|
|
||||||
// Check status.
|
|
||||||
const status = Math.max(xhr.status === 1223 ? 204 : xhr.status, 0);
|
|
||||||
if (status < 200 || status >= 300) {
|
|
||||||
// Request failed.
|
|
||||||
throw new CoreError(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Treat response.
|
|
||||||
data = CoreTextUtils.parseJSON(data);
|
|
||||||
|
|
||||||
// Some moodle web services return null.
|
|
||||||
// If the responseExpected value is set then so long as no data is returned, we create a blank object.
|
|
||||||
if ((!data || !data.data) && !preSets.responseExpected) {
|
|
||||||
data = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
throw new CoreError(Translate.instant('core.serverconnection', {
|
|
||||||
details: Translate.instant('core.errorinvalidresponse', { method }),
|
|
||||||
}));
|
|
||||||
} else if (typeof data != preSets.typeExpected) {
|
|
||||||
this.logger.warn('Response of type "' + typeof data + '" received, expecting "' + preSets.typeExpected + '"');
|
|
||||||
throw new CoreError(Translate.instant('core.errorinvalidresponse', { method }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.exception !== undefined || data.debuginfo !== undefined) {
|
|
||||||
throw new CoreWSError(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that stores error logs in memory.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreErrorLogsService {
|
||||||
|
|
||||||
|
protected errorLogs: CoreSettingsErrorLog[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve error logs displayed in the DOM.
|
||||||
|
*
|
||||||
|
* @returns Error logs
|
||||||
|
*/
|
||||||
|
getErrorLogs(): CoreSettingsErrorLog[] {
|
||||||
|
return this.errorLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an error to error logs list.
|
||||||
|
*
|
||||||
|
* @param error Error.
|
||||||
|
*/
|
||||||
|
addErrorLog(error: CoreSettingsErrorLog): void {
|
||||||
|
this.errorLogs.push(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CoreErrorLogs = makeSingleton(CoreErrorLogsService);
|
||||||
|
|
||||||
|
export type CoreSettingsErrorLog = {
|
||||||
|
data?: unknown;
|
||||||
|
message: string;
|
||||||
|
method?: string;
|
||||||
|
time: number;
|
||||||
|
type: string;
|
||||||
|
};
|
Loading…
Reference in New Issue