Improve logging, display additional info to superadmin
This commit is contained in:
parent
2e35f10b97
commit
350e0b22bd
|
@ -15,12 +15,28 @@ class LogsController extends Controller
|
|||
{
|
||||
User::where('id', $request->user()->id)->update(['last_access' => now()]);
|
||||
|
||||
$query = Log::join('users as changed_user', 'changed_user.id', '=', 'logs.changed_id')
|
||||
->join('users as editor_user', 'editor_user.id', '=', 'logs.editor_id')
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
$selectedCols = [
|
||||
"logs.id", "logs.action", "logs.editor_id", "logs.changed_id", "logs.created_at", "logs.source_type",
|
||||
"changed_user.name as changed", "editor_user.name as editor", "editor_user.hidden as editor_hidden"
|
||||
];
|
||||
|
||||
if($request->user()->hasPermission("logs-limited-read")) {
|
||||
$query = $query->where(function ($query) {
|
||||
$query->where('editor_user.hidden', false)
|
||||
->orWhere('editor_user.id', auth()->user()->id);
|
||||
});
|
||||
} else if($request->user()->hasPermission("logs-read")) {
|
||||
$selectedCols = array_merge($selectedCols, ["logs.ip", "logs.user_agent"]);
|
||||
} else {
|
||||
abort(401);
|
||||
}
|
||||
|
||||
return response()->json(
|
||||
Log::join('users as changed_user', 'changed_user.id', '=', 'logs.changed_id')
|
||||
->join('users as editor_user', 'editor_user.id', '=', 'logs.editor_id')
|
||||
->select("logs.id", "logs.action", "logs.editor_id", "logs.changed_id", "logs.created_at", "changed_user.name as changed", "editor_user.name as editor")
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
$query->select($selectedCols)->get()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,6 @@ class Logger {
|
|||
if(is_null($editor)) $editor = auth()->user();
|
||||
$log->editor()->associate($editor);
|
||||
|
||||
//Check if editor has attribute hidden
|
||||
if($editor->hidden) return;
|
||||
|
||||
$request = request();
|
||||
if($source_type !== "web") {
|
||||
$log->ip = null;
|
||||
|
|
|
@ -18,6 +18,7 @@ return [
|
|||
'services' => 'c,r,u,d',
|
||||
'trainings' => 'c,r,u,d',
|
||||
'alerts' => 'c,r,u',
|
||||
'logs' => 'r'
|
||||
],
|
||||
'admin' => [
|
||||
'users' => 'c,r,u,d,i,b,h,sc,sd,atc,ame',
|
||||
|
@ -25,6 +26,7 @@ return [
|
|||
'services' => 'c,r,u,d',
|
||||
'trainings' => 'c,r,u,d',
|
||||
'alerts' => 'c,r,u',
|
||||
'logs' => 'lr'
|
||||
],
|
||||
'chief' => [
|
||||
'users' => 'r,u,sc,sd,atc,ame',
|
||||
|
@ -32,6 +34,7 @@ return [
|
|||
'services' => 'c,r,u,d',
|
||||
'trainings' => 'c,r,u,d',
|
||||
'alerts' => 'c,r,u',
|
||||
'logs' => 'lr'
|
||||
],
|
||||
'user' => [
|
||||
'users' => 'lr',
|
||||
|
@ -39,6 +42,7 @@ return [
|
|||
'services' => 'c,r,u,d',
|
||||
'trainings' => 'c,r,u,d',
|
||||
'alerts' => 'r',
|
||||
'logs' => 'lr'
|
||||
]
|
||||
],
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"rxjs": "~7.4.0",
|
||||
"sweetalert2": "^11.3.4",
|
||||
"tslib": "^2.3.0",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"zone.js": "~0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -44,6 +45,7 @@
|
|||
"@types/jasmine": "~3.10.0",
|
||||
"@types/leaflet": "^1.7.8",
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"jasmine-core": "4.0.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
|
@ -4079,6 +4081,12 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ua-parser-js": {
|
||||
"version": "0.7.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
|
||||
"integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
|
||||
|
@ -5125,29 +5133,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/browser-sync/node_modules/ua-parser-js": {
|
||||
"version": "1.0.37",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
|
||||
"integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||
|
@ -8572,6 +8557,29 @@
|
|||
"node": ">=8.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/ua-parser-js": {
|
||||
"version": "0.7.37",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz",
|
||||
"integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
|
@ -12429,10 +12437,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "0.7.35",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz",
|
||||
"integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==",
|
||||
"dev": true,
|
||||
"version": "1.0.37",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
|
||||
"integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -12441,6 +12448,10 @@
|
|||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"rxjs": "~7.4.0",
|
||||
"sweetalert2": "^11.3.4",
|
||||
"tslib": "^2.3.0",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"zone.js": "~0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -49,6 +50,7 @@
|
|||
"@types/jasmine": "~3.10.0",
|
||||
"@types/leaflet": "^1.7.8",
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"jasmine-core": "4.0.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
|
|
|
@ -64,14 +64,36 @@
|
|||
<th>{{ 'changed'|translate|ftitlecase }}</th>
|
||||
<th>{{ 'editor'|translate|ftitlecase }}</th>
|
||||
<th>{{ 'datetime'|translate|ftitlecase }}</th>
|
||||
<th *ngIf="auth.profile.can('logs-read')">{{ 'device_information'|translate|ftitlecase }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table_body">
|
||||
<tr *ngFor="let row of displayedData; index as i">
|
||||
<td>{{ row.action }}</td>
|
||||
<td>
|
||||
{{ row.action }}
|
||||
<div class="float-end d-inline">
|
||||
<i *ngIf="row.source_type === 'telegram'" class="fab faxlarge fa-telegram"></i>
|
||||
<i *ngIf="row.source_type === 'web'" class="fa faxlarge fa-globe"></i>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ row.changed }}</td>
|
||||
<td>{{ row.editor }}</td>
|
||||
<td>
|
||||
<div class="float-start d-inline" *ngIf="row.editor_hidden">
|
||||
<i class="fa fa-ghost"></i>
|
||||
</div>
|
||||
{{ row.editor }}
|
||||
</td>
|
||||
<td>{{ row.created_at | date: 'dd/MM/YYYY HH:mm:ss' }}</td>
|
||||
<td *ngIf="auth.profile.can('logs-read') && row.source_type === 'web'">
|
||||
<a [href]="'https://iplocation.io/ip/'+row.ip" target="_blank" *ngIf="isPublicIp(row.ip)">
|
||||
<code>{{ row.ip }}</code>
|
||||
</a>
|
||||
<code *ngIf="!isPublicIp(row.ip)">{{ row.ip }}</code>
|
||||
<div class="mt-1" (click)="openUserAgentInfoModal(row.user_agent, useragentInfoModal)">
|
||||
<i *ngFor="let icon of userAgentToIcons(row.user_agent)" [ngClass]="'m-1 '+icon"></i>
|
||||
</div>
|
||||
</td>
|
||||
<td *ngIf="auth.profile.can('logs-read') && row.source_type !== 'web'"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -192,3 +214,48 @@
|
|||
<ng-template #firstTemplate let-disabled="disabled" let-currentPage="currentPage">
|
||||
{{ 'first'|translate|ftitlecase }}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #useragentInfoModal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title pull-left" translate>{{ 'useragent_info_modal.title' }}</h4>
|
||||
<button type="button" class="btn-close close pull-right" aria-label="Close" (click)="useragentInfoModalRef?.hide()">
|
||||
<span aria-hidden="true" class="visually-hidden">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'property'|translate|ftitlecase }}</th>
|
||||
<th>{{ 'value'|translate|ftitlecase }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'user_agent'|translate|ftitlecase }}</td>
|
||||
<td><code>{{ processedUA.ua }}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="processedUA.browser && processedUA.browser.name">
|
||||
<td>{{ 'browser'|translate|ftitlecase }}</td>
|
||||
<td><code>{{ processedUA.browser.name }} {{ processedUA.browser.version }}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="processedUA.engine && processedUA.engine.name">
|
||||
<td>{{ 'engine'|translate|ftitlecase }}</td>
|
||||
<td><code>{{ processedUA.engine.name }} {{ processedUA.engine.version }}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="processedUA.os && processedUA.os.name">
|
||||
<td>{{ 'os'|translate|ftitlecase }}</td>
|
||||
<td><code>{{ processedUA.os.name }} {{ processedUA.os.version }}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="processedUA.device && processedUA.device.name">
|
||||
<td>{{ 'device'|translate|ftitlecase }}</td>
|
||||
<td><code>{{ processedUA.device.vendor }} {{ processedUA.device.model }}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="processedUA.cpu && processedUA.cpu.architecture">
|
||||
<td>{{ 'cpu'|translate|ftitlecase }}</td>
|
||||
<td><code>{{ processedUA.cpu.architecture }}</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, TemplateRef } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||
import { AuthService } from '../../_services/auth.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { PageChangedEvent } from 'ngx-bootstrap/pagination';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import Swal from 'sweetalert2';
|
||||
import { PageChangedEvent } from 'ngx-bootstrap/pagination';
|
||||
import * as UAParser from 'ua-parser-js';
|
||||
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
|
||||
|
||||
@Component({
|
||||
selector: 'app-table',
|
||||
|
@ -78,12 +80,16 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
public searchText: string = "";
|
||||
public searchData: any = [];
|
||||
|
||||
public useragentInfoModalRef: BsModalRef | undefined;
|
||||
public processedUA: any = {};
|
||||
|
||||
constructor(
|
||||
private api: ApiClientService,
|
||||
public auth: AuthService,
|
||||
private router: Router,
|
||||
private toastr: ToastrService,
|
||||
private translate: TranslateService
|
||||
private translate: TranslateService,
|
||||
private modalService: BsModalService
|
||||
) { }
|
||||
|
||||
loadTableData() {
|
||||
|
@ -281,4 +287,94 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
extractNamesFromObject(obj: any) {
|
||||
return obj.flatMap((e: any) => e.name);
|
||||
}
|
||||
|
||||
userAgentToIcons(userAgentString: string) {
|
||||
const parser = new UAParser(userAgentString);
|
||||
let icons = [];
|
||||
|
||||
switch (parser.getBrowser().name) {
|
||||
case 'Chrome':
|
||||
case 'Chromium':
|
||||
case 'Chrome WebView':
|
||||
case 'Chrome Headless':
|
||||
icons.push('fab fa-chrome');
|
||||
break;
|
||||
case 'Mozilla':
|
||||
case 'Firefox [Focus/Reality]':
|
||||
icons.push('fab fa-firefox-browser');
|
||||
break;
|
||||
case 'Safari':
|
||||
icons.push('fab fa-safari');
|
||||
break;
|
||||
case 'IE':
|
||||
case 'IEMobile':
|
||||
icons.push('fa fa-skull-crossbones');
|
||||
break;
|
||||
case 'Edge':
|
||||
icons.push('fab fa-edge');
|
||||
break;
|
||||
case 'Android Browser':
|
||||
case 'Huawei Browser':
|
||||
case 'Samsung Browser':
|
||||
icons.push('fab fa-android');
|
||||
break;
|
||||
case 'Silk':
|
||||
icons.push('fab fa-amazon');
|
||||
break;
|
||||
case 'Instagram':
|
||||
case 'TikTok':
|
||||
case 'Snapchat':
|
||||
case 'Facebook':
|
||||
case 'WeChat':
|
||||
icons.push('fa fa-square-share-nodes');
|
||||
break;
|
||||
case 'Electron':
|
||||
case 'PhantomJS':
|
||||
icons.push('fa fa-code');
|
||||
break;
|
||||
default:
|
||||
icons.push('fa fa-question-circle');
|
||||
}
|
||||
|
||||
switch (parser.getDevice().type) {
|
||||
case 'mobile':
|
||||
icons.push('fa fa-mobile');
|
||||
break;
|
||||
case 'tablet':
|
||||
icons.push('fa fa-tablet');
|
||||
break;
|
||||
case 'smarttv':
|
||||
icons.push('fa fa-tv');
|
||||
break;
|
||||
case 'console':
|
||||
icons.push('fa fa-gamepad');
|
||||
break;
|
||||
case 'wearable':
|
||||
icons.push('fa fa-watch');
|
||||
break;
|
||||
default:
|
||||
icons.push('fa fa-desktop');
|
||||
}
|
||||
|
||||
console.log(parser.getResult(), icons);
|
||||
return icons;
|
||||
}
|
||||
|
||||
isPublicIp(ipAddress: string) {
|
||||
const parts = ipAddress.split('.');
|
||||
if (parts.length === 4) {
|
||||
return !(
|
||||
parts[0] === '10' ||
|
||||
(parts[0] === '172' && parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31) ||
|
||||
(parts[0] === '192' && parts[1] === '168') ||
|
||||
ipAddress === '127.0.0.1'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
openUserAgentInfoModal(userAgentString: string, template: TemplateRef<void>) {
|
||||
this.processedUA = new UAParser(userAgentString).getResult();
|
||||
this.useragentInfoModalRef = this.modalService.show(template);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,9 @@
|
|||
"medical_examination_modal": {
|
||||
"title": "Add medical examination"
|
||||
},
|
||||
"useragent_info_modal": {
|
||||
"title": "Client information"
|
||||
},
|
||||
"validation": {
|
||||
"place_min_length": "Place name must be at least 3 characters long",
|
||||
"type_must_be_two_characters_long": "Type must be at least 2 characters long",
|
||||
|
@ -124,6 +127,14 @@
|
|||
"document_format_not_supported": "Document format not supported",
|
||||
"file_too_big": "File too big"
|
||||
},
|
||||
"property": "property",
|
||||
"value": "value",
|
||||
"user_agent": "User Agent",
|
||||
"browser": "browser",
|
||||
"engine": "engine",
|
||||
"os": "Operating System",
|
||||
"device": "device",
|
||||
"cpu": "CPU",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"warning": "warning",
|
||||
|
@ -163,6 +174,7 @@
|
|||
"personal_information": "personal information",
|
||||
"contact_information": "contact information",
|
||||
"service_information": "service information",
|
||||
"device_information": "device information",
|
||||
"course_date": "course date",
|
||||
"documents": "documents",
|
||||
"driving_license": "driving license",
|
||||
|
|
|
@ -117,12 +117,23 @@
|
|||
"medical_examination_modal": {
|
||||
"title": "Aggiungi visita medica"
|
||||
},
|
||||
"useragent_info_modal": {
|
||||
"title": "Informazioni sul client"
|
||||
},
|
||||
"validation": {
|
||||
"place_min_length": "Il nome della località deve essere di almeno 3 caratteri",
|
||||
"type_must_be_two_characters_long": "La tipologia deve essere di almeno 2 caratteri",
|
||||
"image_format_not_supported": "Formato immagine non supportato",
|
||||
"file_too_big": "File troppo grande"
|
||||
},
|
||||
"property": "proprietà",
|
||||
"value": "valore",
|
||||
"user_agent": "User Agent",
|
||||
"browser": "browser",
|
||||
"engine": "motore",
|
||||
"os": "Sistema Operativo",
|
||||
"device": "dispositivo",
|
||||
"cpu": "CPU",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"warning": "attenzione",
|
||||
|
@ -162,6 +173,7 @@
|
|||
"personal_information": "anagrafica personale",
|
||||
"contact_information": "recapiti",
|
||||
"service_information": "informazioni di servizio",
|
||||
"device_information": "informazioni sul dispositivo",
|
||||
"course_date": "data corso",
|
||||
"documents": "documenti",
|
||||
"driving_license": "patente",
|
||||
|
|
|
@ -14,6 +14,14 @@
|
|||
font-size: 20px;
|
||||
}
|
||||
|
||||
.faxlarge {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.fa-telegram {
|
||||
color: #0088cc;
|
||||
}
|
||||
|
||||
.fa-whatsapp {
|
||||
color: green;
|
||||
font-size: 1.5em;
|
||||
|
@ -22,3 +30,11 @@
|
|||
.leaflet-pane.leaflet-shadow-pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a code {
|
||||
color: var(--bs-code-color) !important;
|
||||
}
|
||||
|
||||
a:has(code) {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue