Resolve "Entity logging service / bundle" #1208
23 changed files with 821 additions and 93 deletions
|
|
@ -14,9 +14,15 @@ APP_SECRET=b344cd6cd151ae1d61403ed55806c5ce
|
|||
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
|
||||
# Configure your db driver and server_version in config/packages/doctrine.yaml
|
||||
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
|
||||
LOGGING_DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_logging
|
||||
###< doctrine/doctrine-bundle ###
|
||||
GMAPS_API_KEY=insert_gmapsapikey_here
|
||||
|
||||
# influxdb
|
||||
INFLUXDB_HOST=127.0.0.1
|
||||
INFLUXDB_PORT=8086
|
||||
INFLUXDB_DB=logging_db
|
||||
|
||||
# rising tide sms gateway
|
||||
RT_USER=rt_user
|
||||
RT_PASS=rt_pass
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"data-dog/audit-bundle": "^0.1.10",
|
||||
"edwinhoksberg/php-fcm": "^1.0",
|
||||
"guzzlehttp/guzzle": "^6.3",
|
||||
"influxdb/influxdb-php": "^1.15",
|
||||
"predis/predis": "^1.1",
|
||||
"sensio/framework-extra-bundle": "^5.1",
|
||||
"setasign/fpdf": "^1.8",
|
||||
|
|
|
|||
65
composer.lock
generated
65
composer.lock
generated
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b101ecfbc1f6f2270f0e8ad326035b7e",
|
||||
"content-hash": "cbde0e7f1fa49277c6196a3c677c3a51",
|
||||
"packages": [
|
||||
{
|
||||
"name": "catalyst/auth-bundle",
|
||||
|
|
@ -1836,6 +1836,67 @@
|
|||
],
|
||||
"time": "2019-07-01T23:21:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "influxdb/influxdb-php",
|
||||
"version": "1.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/influxdata/influxdb-php.git",
|
||||
"reference": "bf3415f81962e1ab8c939bc1a08a85f500bead35"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/influxdata/influxdb-php/zipball/bf3415f81962e1ab8c939bc1a08a85f500bead35",
|
||||
"reference": "bf3415f81962e1ab8c939bc1a08a85f500bead35",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "^6.0",
|
||||
"php": "^5.5 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Curl extension, needed for Curl driver",
|
||||
"stefanotorresi/influxdb-php-async": "An asyncronous client for InfluxDB, implemented via ReactPHP."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"InfluxDB\\": "src/InfluxDB"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gianluca Arbezzano",
|
||||
"email": "gianarb92@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Daniel Martinez",
|
||||
"email": "danimartcas@hotmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Stephen Hoogendijk",
|
||||
"email": "stephen@tca0.nl"
|
||||
}
|
||||
],
|
||||
"description": "InfluxDB client library for PHP",
|
||||
"keywords": [
|
||||
"client",
|
||||
"influxdata",
|
||||
"influxdb",
|
||||
"influxdb class",
|
||||
"influxdb client",
|
||||
"influxdb library",
|
||||
"time series"
|
||||
],
|
||||
"time": "2019-05-30T00:15:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jdorn/sql-formatter",
|
||||
"version": "v1.2.17",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ access_keys:
|
|||
label: Super Admin Role
|
||||
- id: user.profile
|
||||
label: User Profile
|
||||
- id: user.logs
|
||||
label: User Logs
|
||||
- id: user.change.history
|
||||
label: User Change History
|
||||
- id: role
|
||||
label: Role Access
|
||||
acls:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ parameters:
|
|||
app_access_key: 'access_keys'
|
||||
cvu_brand_id: "%env(CVU_BRAND_ID)%"
|
||||
country_code: "%env(COUNTRY_CODE)%"
|
||||
log_db: "%env(INFLUXDB_DB)%"
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
|
@ -202,6 +203,12 @@ services:
|
|||
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing"
|
||||
App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet"
|
||||
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google"
|
||||
|
||||
# influxdb
|
||||
InfluxDB\Client:
|
||||
arguments: ['%env(INFLUXDB_HOST)%', '%env(INFLUXDB_PORT)%']
|
||||
InfluxDB\Database:
|
||||
arguments: ['%env(INFLUXDB_DB)%', "@InfluxDB\\Client"]
|
||||
|
||||
App\EventListener\JobOrderActiveCacheListener:
|
||||
arguments:
|
||||
|
|
@ -229,6 +236,28 @@ services:
|
|||
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
|
||||
$status_key: "%env(STATUS_RIDER_KEY)%"
|
||||
|
||||
App\EventListener\JobOrderStatusListener:
|
||||
arguments:
|
||||
$wh: "@App\\Service\\WarrantyHandler"
|
||||
tags:
|
||||
- name: 'doctrine.orm.entity_listener'
|
||||
event: 'postUpdate'
|
||||
entity: 'App\Entity\JobOrder'
|
||||
- name: 'doctrine.orm.entity_listener'
|
||||
event: 'postPersist'
|
||||
entity: 'App\Entity\JobOrder'
|
||||
|
||||
App\EventListener\EntityListener:
|
||||
arguments:
|
||||
$token_storage: "@security.token_storage"
|
||||
$log_db: "@InfluxDB\\Database"
|
||||
$entities: ['App\Entity\User', 'App\Entity\Role', 'App\Entity\Partner']
|
||||
tags:
|
||||
- name: 'doctrine.event_listener'
|
||||
event: 'onFlush'
|
||||
- name: 'doctrine.event_listener'
|
||||
event: 'postPersist'
|
||||
|
||||
# API logging
|
||||
App\EventSubscriber\LogSubscriber:
|
||||
arguments:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ doctrine:
|
|||
point: CrEOF\Spatial\DBAL\Types\Geometry\PointType
|
||||
polygon: CrEOF\Spatial\DBAL\Types\Geometry\PolygonType
|
||||
linestring: CrEOF\Spatial\DBAL\Types\Geometry\LineStringType
|
||||
multipolygon: CrEOF\Spatial\DBAL\Types\Geometry\MultiPolygonType
|
||||
orm:
|
||||
auto_generate_proxy_classes: '%kernel.debug%'
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ parameters:
|
|||
app_access_key: 'access_keys'
|
||||
cvu_brand_id: "%env(CVU_BRAND_ID)%"
|
||||
country_code: "%env(COUNTRY_CODE)%"
|
||||
log_db: "%env(INFLUXDB_DB)%"
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
|
@ -201,7 +202,13 @@ services:
|
|||
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing"
|
||||
App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet"
|
||||
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google"
|
||||
|
||||
|
||||
# influxdb
|
||||
InfluxDB\Client:
|
||||
arguments: ['%env(INFLUXDB_HOST)%', '%env(INFLUXDB_PORT)%']
|
||||
InfluxDB\Database:
|
||||
arguments: ['%env(INFLUXDB_DB)%', "@InfluxDB\\Client"]
|
||||
|
||||
App\EventListener\JobOrderActiveCacheListener:
|
||||
arguments:
|
||||
$jo_cache: "@App\\Service\\JobOrderCache"
|
||||
|
|
@ -228,6 +235,28 @@ services:
|
|||
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
|
||||
$status_key: "%env(STATUS_RIDER_KEY)%"
|
||||
|
||||
App\EventListener\JobOrderStatusListener:
|
||||
arguments:
|
||||
$wh: "@App\\Service\\WarrantyHandler"
|
||||
tags:
|
||||
- name: 'doctrine.orm.entity_listener'
|
||||
event: 'postUpdate'
|
||||
entity: 'App\Entity\JobOrder'
|
||||
- name: 'doctrine.orm.entity_listener'
|
||||
event: 'postPersist'
|
||||
entity: 'App\Entity\JobOrder'
|
||||
|
||||
App\EventListener\EntityListener:
|
||||
arguments:
|
||||
$token_storage: "@security.token_storage"
|
||||
$log_db: "@InfluxDB\\Database"
|
||||
$entities: ['App\Entity\User', 'App\Entity\Role', 'App\Entity\Partner']
|
||||
tags:
|
||||
- name: 'doctrine.event_listener'
|
||||
event: 'onFlush'
|
||||
- name: 'doctrine.event_listener'
|
||||
event: 'postPersist'
|
||||
|
||||
# inventory manager
|
||||
App\Service\InventoryManager:
|
||||
arguments:
|
||||
|
|
|
|||
|
|
@ -41,3 +41,23 @@ user_profile_submit:
|
|||
path: /profile
|
||||
controller: App\Controller\UserController::profileSubmit
|
||||
methods: [POST]
|
||||
|
||||
user_view_logs_form:
|
||||
path: /users/{id}/logs
|
||||
controller: App\Controller\UserController::viewLogsForm
|
||||
methods: [GET]
|
||||
|
||||
user_view_logs:
|
||||
path: /user/{id}/logs
|
||||
controller: App\Controller\UserController::getAuditLogs
|
||||
methods: [POST]
|
||||
|
||||
user_view_change_history_form:
|
||||
path: /users/{id}/change-history
|
||||
controller: App\Controller\UserController::viewChangeHistoryForm
|
||||
methods: [GET]
|
||||
|
||||
user_view_change_history:
|
||||
path: /user/{id}/change-history
|
||||
controller: App\Controller\UserController::getChangeHistory
|
||||
methods: [POST]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ parameters:
|
|||
app_access_key: 'access_keys'
|
||||
cvu_brand_id: "%env(CVU_BRAND_ID)%"
|
||||
country_code: "%env(COUNTRY_CODE)%"
|
||||
log_db: "%env(INFLUXDB_DB)%"
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
|
@ -21,7 +22,7 @@ services:
|
|||
public: false # Allows optimizing the container by removing unused services; this also means
|
||||
# fetching services directly from the container via $container->get() won't work.
|
||||
# The best practice is to be explicit about your dependencies anyway.
|
||||
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
|
|
@ -201,6 +202,12 @@ services:
|
|||
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Bing"
|
||||
App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet"
|
||||
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google"
|
||||
|
||||
# influxdb
|
||||
InfluxDB\Client:
|
||||
arguments: ['%env(INFLUXDB_HOST)%', '%env(INFLUXDB_PORT)%']
|
||||
InfluxDB\Database:
|
||||
arguments: ['%env(INFLUXDB_DB)%', "@InfluxDB\\Client"]
|
||||
|
||||
App\EventListener\JobOrderActiveCacheListener:
|
||||
arguments:
|
||||
|
|
@ -228,6 +235,28 @@ services:
|
|||
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
|
||||
$status_key: "%env(STATUS_RIDER_KEY)%"
|
||||
|
||||
App\EventListener\JobOrderStatusListener:
|
||||
arguments:
|
||||
$wh: "@App\\Service\\WarrantyHandler"
|
||||
tags:
|
||||
- name: 'doctrine.orm.entity_listener'
|
||||
event: 'postUpdate'
|
||||
entity: 'App\Entity\JobOrder'
|
||||
- name: 'doctrine.orm.entity_listener'
|
||||
event: 'postPersist'
|
||||
entity: 'App\Entity\JobOrder'
|
||||
|
||||
App\EventListener\EntityListener:
|
||||
arguments:
|
||||
$token_storage: "@security.token_storage"
|
||||
$log_db: "@InfluxDB\\Database"
|
||||
$entities: ['App\Entity\User', 'App\Entity\Role', 'App\Entity\Partner']
|
||||
tags:
|
||||
- name: 'doctrine.event_listener'
|
||||
event: 'onFlush'
|
||||
- name: 'doctrine.event_listener'
|
||||
event: 'postPersist'
|
||||
|
||||
# inventory manager
|
||||
App\Service\InventoryManager:
|
||||
arguments:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
|||
|
||||
use Catalyst\MenuBundle\Annotation\Menu;
|
||||
|
||||
use InfluxDB\Client;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
@ -484,4 +486,67 @@ class UserController extends Controller
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Menu(selected="user_list")
|
||||
*/
|
||||
public function viewLogsForm($id)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('user.logs', null, 'No access.');
|
||||
|
||||
$params['id'] = $id;
|
||||
|
||||
// response
|
||||
return $this->render('user/audit.log.html.twig', $params);
|
||||
}
|
||||
|
||||
public function getAuditLogs(Client $client, $id)
|
||||
{
|
||||
// fetch database
|
||||
$log_db = $this->getParameter('log_db');
|
||||
$database = $client->selectDB($log_db);
|
||||
|
||||
// query will return a resultset object
|
||||
$query_string = 'SELECT * FROM entity_log WHERE "user" = ' . $id;
|
||||
$result = $database->query($query_string);
|
||||
|
||||
// get the points from the resultset, which is an array
|
||||
$points = $result->getPoints();
|
||||
|
||||
return $this->json([
|
||||
'data' => $points,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Menu(selected="user_list")
|
||||
*/
|
||||
public function viewChangeHistoryForm($id)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('user.change.history', null, 'No access.');
|
||||
|
||||
$params['id'] = $id;
|
||||
|
||||
// response
|
||||
return $this->render('user/change.history.log.html.twig', $params);
|
||||
}
|
||||
|
||||
public function getChangeHistory(Client $client, $id)
|
||||
{
|
||||
// fetch database
|
||||
$log_db = $this->getParameter('log_db');
|
||||
$database = $client->selectDB($log_db);
|
||||
|
||||
// query will return a resultset object
|
||||
$query_string = 'SELECT * FROM entity_log WHERE entity_id = \'' . $id . '\'';
|
||||
$result = $database->query($query_string);
|
||||
|
||||
// get the points from the resultset, which is an array
|
||||
$points = $result->getPoints();
|
||||
|
||||
return $this->json([
|
||||
'data' => $points,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
8
src/Entity/AuditableEntity.php
Normal file
8
src/Entity/AuditableEntity.php
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
interface AuditableEntity
|
||||
{
|
||||
public function fieldDump();
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
|||
* @UniqueEntity("id")
|
||||
* @UniqueEntity("name")
|
||||
*/
|
||||
class Role extends BaseRole
|
||||
class Role extends BaseRole implements AuditableEntity
|
||||
{
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="User", mappedBy="roles", fetch="EXTRA_LAZY")
|
||||
|
|
@ -25,4 +25,18 @@ class Role extends BaseRole
|
|||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function fieldDump()
|
||||
{
|
||||
// get all fields and their values into an array
|
||||
$field_array = [
|
||||
'id' => $this->getID(),
|
||||
'name' => $this->getName(),
|
||||
'acl_attributes' => $this->getACLAttributes(),
|
||||
];
|
||||
|
||||
$fields = json_encode($field_array);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use Serializable;
|
|||
* @UniqueEntity("username")
|
||||
* @UniqueEntity("email")
|
||||
*/
|
||||
class User extends BaseUser implements Serializable
|
||||
class User extends BaseUser implements Serializable, AuditableEntity
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
|
|
@ -309,4 +309,24 @@ class User extends BaseUser implements Serializable
|
|||
{
|
||||
return $this->partners_created;
|
||||
}
|
||||
|
||||
public function fieldDump()
|
||||
{
|
||||
// get all fields and their values into an array
|
||||
$field_array = [
|
||||
'id' => $this->getID(),
|
||||
'username' => $this->getUsername(),
|
||||
'password' => $this->getPassword(),
|
||||
'first_name' => $this->getFirstName(),
|
||||
'last_name' => $this->getLastName(),
|
||||
'email' => $this->getEmail(),
|
||||
'contact_number' => $this->getContactNumber(),
|
||||
'roles' => $this->getRoles(),
|
||||
'hubs' => $this->getHubs(),
|
||||
];
|
||||
|
||||
$fields = json_encode($field_array);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
194
src/EventListener/EntityListener.php
Normal file
194
src/EventListener/EntityListener.php
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
|
||||
use InfluxDB\Point;
|
||||
|
||||
use App\Entity\AuditableEntity;
|
||||
|
||||
class EntityListener
|
||||
{
|
||||
protected $token_storage;
|
||||
protected $log_db;
|
||||
protected $entities;
|
||||
|
||||
public function __construct(TokenStorageInterface $token_storage, $log_db,
|
||||
$entities)
|
||||
{
|
||||
$this->token_storage = $token_storage;
|
||||
$this->log_db = $log_db;
|
||||
|
||||
// reconstruct entity array with class name as keys
|
||||
// with values of true
|
||||
foreach ($entities as $entity)
|
||||
{
|
||||
$this->entities[$entity] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function postPersist(LifecycleEventArgs $args)
|
||||
{
|
||||
$object = $args->getObject();
|
||||
|
||||
$object_class = get_class($object);
|
||||
|
||||
if (isset($this->entities[$object_class]))
|
||||
{
|
||||
if ($object instanceof AuditableEntity)
|
||||
{
|
||||
// get user id for logging
|
||||
$user = $this->token_storage->getToken()->getUser();
|
||||
$user_id = $user->getID();
|
||||
|
||||
// get entity name
|
||||
//$obj_class = preg_replace('/.*\\\\/', '', $object_class);
|
||||
|
||||
// get fields of object
|
||||
$fields = $object->fieldDump();
|
||||
|
||||
$this->writeToLogDB($object_class, $object->getID(), 'create', $fields, $user_id);
|
||||
}
|
||||
else
|
||||
error_log($object_class . ' does not implement the AuditableEntity interface.');
|
||||
}
|
||||
}
|
||||
|
||||
public function onFlush(OnFlushEventArgs $args)
|
||||
{
|
||||
$em = $args->getEntityManager();
|
||||
|
||||
// get date time of event for logging
|
||||
//$event_time = new DateTime();
|
||||
|
||||
// get user for logging
|
||||
$user = $this->token_storage->getToken()->getUser();
|
||||
$user_id = $user->getID();
|
||||
|
||||
$field_changes = [];
|
||||
|
||||
$unit_of_work = $em->getUnitOfWork();
|
||||
$created_entities = $unit_of_work->getScheduledEntityInsertions();
|
||||
$deleted_entities = $unit_of_work->getScheduledEntityDeletions();
|
||||
$updated_entities = $unit_of_work->getScheduledEntityUpdates();
|
||||
$updated_collections = $unit_of_work->getScheduledCollectionUpdates();
|
||||
$deleted_collections = $unit_of_work->getScheduledCollectionDeletions();
|
||||
|
||||
// get updated fields. This doesn't include lists of other entities.
|
||||
foreach ($updated_entities as $updated_entity)
|
||||
{
|
||||
// check if entity is in list of entities to log
|
||||
$entity_class = get_class($updated_entity);
|
||||
if (isset($this->entities[$entity_class]))
|
||||
{
|
||||
if ($updated_entity instanceof AuditableEntity)
|
||||
{
|
||||
// get entity name
|
||||
//$obj_class = preg_replace('/.*\\\\/', '', $entity_class);
|
||||
|
||||
$changeset = $unit_of_work->getEntityChangeSet($updated_entity);
|
||||
|
||||
$entity_fields = array_keys($changeset);
|
||||
foreach ($entity_fields as $field)
|
||||
{
|
||||
$values = $changeset[$field];
|
||||
|
||||
$old_value = $values[0];
|
||||
$new_value = $values[1];
|
||||
|
||||
$field_changes[$field] = [
|
||||
'old_value' => $values[0],
|
||||
'new_value' => $values[1],
|
||||
];
|
||||
}
|
||||
|
||||
$fields = json_encode($field_changes);
|
||||
|
||||
$this->writeToLogDB($entity_class, $updated_entity->getID(), 'update', $fields, $user_id);
|
||||
}
|
||||
else
|
||||
error_log($entity_class . ' does not implement the AuditableEntity interface.');
|
||||
}
|
||||
}
|
||||
|
||||
// get deleted objects
|
||||
foreach ($deleted_entities as $deleted_entity)
|
||||
{
|
||||
// check if entity is in list of entities to log
|
||||
$entity_class = get_class($deleted_entity);
|
||||
if (isset($this->entities[$entity_class]))
|
||||
{
|
||||
if ($deleted_entity instanceof AuditableEntity)
|
||||
{
|
||||
// get entity name
|
||||
//$obj_class = preg_replace('/.*\\\\/', '', get_class($deleted_entity));
|
||||
|
||||
$deleted_id = $deleted_entity->getID();
|
||||
|
||||
// get fields of deleted entity
|
||||
$fields = $deleted_entity->fieldDump();
|
||||
|
||||
$this->writeToLogDB($entity_class, $deleted_id, 'delete', $fields, $user_id);
|
||||
}
|
||||
else
|
||||
error_log($entity_class . ' does not implement the AuditableEntity interface.');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: find a way to get the list of "old" items, compare with list of updated items
|
||||
// so we can find out what was added/removed
|
||||
/*
|
||||
foreach ($updated_collections as $updated_collection)
|
||||
{
|
||||
error_log('in updated_collection snippet');
|
||||
|
||||
foreach ($updated_collection as $item)
|
||||
{
|
||||
if ($item instanceof Role)
|
||||
{
|
||||
error_log('updated_collection item ' . $item->getName());
|
||||
}
|
||||
if ($item instanceof Hub)
|
||||
{
|
||||
error_log('updated_collection item ' . $item->getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach ($deleted_collections as $deleted_collection)
|
||||
{
|
||||
error_log('in deleted_collection snippet');
|
||||
|
||||
foreach ($deleted_collection as $item)
|
||||
{
|
||||
if ($item instanceof Role)
|
||||
{
|
||||
error_log('deleted_collection item ' . $item->getName());
|
||||
}
|
||||
if ($item instanceof Hub)
|
||||
{
|
||||
error_log('deleted_collection item ' . $item->getName());
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
}
|
||||
|
||||
protected function writeToLogDB($entity_type, $entity_id, $action, $content, $user_id)
|
||||
{
|
||||
$new_point = new Point(
|
||||
'entity_log', // measurement
|
||||
null, // measurement value
|
||||
['entity_type' => $entity_type, 'entity_id' => $entity_id], // measurement tags
|
||||
['action' => $action, 'content' => $content, 'user' => $user_id], // measurement fields
|
||||
exec('date +%s%N') // timestamp in nanoseconds on Linux only
|
||||
);
|
||||
|
||||
$this->log_db->writePoints([$new_point]);
|
||||
}
|
||||
}
|
||||
91
src/EventListener/JobOrderStatusListener.php
Normal file
91
src/EventListener/JobOrderStatusListener.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
use App\Entity\JobOrder;
|
||||
use App\Entity\Warranty;
|
||||
|
||||
use App\Service\WarrantyHandler;
|
||||
use App\Service\JobOrderHandlerInterface;
|
||||
|
||||
use App\Ramcar\JOStatus;
|
||||
|
||||
class JobOrderStatusListener
|
||||
{
|
||||
protected $wh;
|
||||
protected $jo_handler;
|
||||
|
||||
public function __construct(WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler)
|
||||
{
|
||||
$this->wh = $wh;
|
||||
$this->jo_handler = $jo_handler;
|
||||
}
|
||||
|
||||
// when a JO is updated
|
||||
public function postUpdate(JobOrder $jo, LifecycleEventArgs $args)
|
||||
{
|
||||
$status = $jo->getStatus();
|
||||
|
||||
if ($status == JOStatus::FULFILLED)
|
||||
{
|
||||
$this->createWarrantyFromJO($jo);
|
||||
}
|
||||
}
|
||||
|
||||
// when a new job order comes in and status is already fulfilled
|
||||
public function postPersist(JobOrder $jo, LifecycleEventArgs $args)
|
||||
{
|
||||
$status = $jo->getStatus();
|
||||
|
||||
if ($status == JOStatus::FULFILLED)
|
||||
{
|
||||
$this->createWarrantyFromJO($jo);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createWarrantyFromJO(JobOrder $jo)
|
||||
{
|
||||
// create warranty
|
||||
if ($this->jo_handler->checkIfNewBattery($jo))
|
||||
{
|
||||
$serial = null;
|
||||
$warranty_class = $jo->getWarrantyClass();
|
||||
$first_name = $jo->getCustomer()->getFirstName();
|
||||
$last_name = $jo->getCustomer()->getLastName();
|
||||
$mobile_number = $jo->getCustomer()->getPhoneMobile();
|
||||
|
||||
// check if date fulfilled is null
|
||||
if ($jo->getDateFulfill() == null)
|
||||
$date_purchase = $jo->getDateCreate();
|
||||
else
|
||||
$date_purchase = $jo->getDateFulfill();
|
||||
|
||||
// validate plate number
|
||||
// $plate_number = $this->wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber());
|
||||
$plate_number = Warranty::cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber());
|
||||
if ($plate_number != false)
|
||||
{
|
||||
$batt_list = array();
|
||||
$invoice = $jo->getInvoice();
|
||||
if (!empty($invoice))
|
||||
{
|
||||
// get battery
|
||||
$invoice_items = $invoice->getItems();
|
||||
foreach ($invoice_items as $item)
|
||||
{
|
||||
$battery = $item->getBattery();
|
||||
if ($battery != null)
|
||||
{
|
||||
$batt_list[] = $item->getBattery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ use App\Entity\Hub;
|
|||
use App\Entity\Promo;
|
||||
use App\Entity\Rider;
|
||||
use App\Entity\JORejection;
|
||||
use App\Entity\Warranty;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\ServiceCharge;
|
||||
|
||||
|
|
@ -44,7 +43,6 @@ use App\Service\InvoiceGeneratorInterface;
|
|||
use App\Service\JobOrderHandlerInterface;
|
||||
use App\Service\RiderAssignmentHandlerInterface;
|
||||
use App\Service\CustomerHandlerInterface;
|
||||
use App\Service\WarrantyHandler;
|
||||
use App\Service\MQTTClient;
|
||||
use App\Service\APNSClient;
|
||||
use App\Service\MapTools;
|
||||
|
|
@ -67,7 +65,6 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
|
|||
protected $translator;
|
||||
protected $rah;
|
||||
protected $country_code;
|
||||
protected $wh;
|
||||
protected $cust_handler;
|
||||
|
||||
protected $template_hash;
|
||||
|
|
@ -75,8 +72,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
|
|||
public function __construct(Security $security, EntityManagerInterface $em,
|
||||
InvoiceGeneratorInterface $ic, ValidatorInterface $validator,
|
||||
TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah,
|
||||
string $country_code, WarrantyHandler $wh,
|
||||
CustomerHandlerInterface $cust_handler)
|
||||
string $country_code, CustomerHandlerInterface $cust_handler)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->ic = $ic;
|
||||
|
|
@ -85,7 +81,6 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
|
|||
$this->translator = $translator;
|
||||
$this->rah = $rah;
|
||||
$this->country_code = $country_code;
|
||||
$this->wh = $wh;
|
||||
$this->cust_handler = $cust_handler;
|
||||
|
||||
$this->loadTemplates();
|
||||
|
|
@ -977,6 +972,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
|
|||
// call rider assignment handler's fulfillJobOrder
|
||||
$this->rah->fulfillJobOrder($obj, $image_url, $rider);
|
||||
|
||||
/*
|
||||
// create the warranty if new battery only
|
||||
if ($this->checkIfNewBattery($obj))
|
||||
{
|
||||
|
|
@ -1019,7 +1015,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
|
|||
|
||||
$this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
// validated! save the entity
|
||||
$em->flush();
|
||||
|
|
@ -2658,7 +2654,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
|
|||
$cust_vehicle->setWarrantyCode($req->request->get('warranty_code'));
|
||||
|
||||
$em->persist($cust_vehicle);
|
||||
|
||||
/*
|
||||
// create the warranty if new battery only
|
||||
if ($this->checkIfNewBattery($jo))
|
||||
{
|
||||
|
|
@ -2701,7 +2697,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
|
|||
|
||||
$this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ use App\Entity\Hub;
|
|||
use App\Entity\Promo;
|
||||
use App\Entity\Rider;
|
||||
use App\Entity\JORejection;
|
||||
use App\Entity\Warranty;
|
||||
use App\Entity\Customer;
|
||||
|
||||
use App\Ramcar\InvoiceCriteria;
|
||||
|
|
@ -41,7 +40,6 @@ use App\Ramcar\JORejectionReason;
|
|||
use App\Service\InvoiceGeneratorInterface;
|
||||
use App\Service\JobOrderHandlerInterface;
|
||||
use App\Service\RiderAssignmentHandlerInterface;
|
||||
use App\Service\WarrantyHandler;
|
||||
use App\Service\MQTTClient;
|
||||
use App\Service\APNSClient;
|
||||
use App\Service\MapTools;
|
||||
|
|
@ -64,14 +62,13 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
|
|||
protected $translator;
|
||||
protected $rah;
|
||||
protected $country_code;
|
||||
protected $wh;
|
||||
|
||||
protected $template_hash;
|
||||
|
||||
public function __construct(Security $security, EntityManagerInterface $em,
|
||||
InvoiceGeneratorInterface $ic, ValidatorInterface $validator,
|
||||
TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah,
|
||||
string $country_code, WarrantyHandler $wh)
|
||||
string $country_code)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->ic = $ic;
|
||||
|
|
@ -80,7 +77,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
|
|||
$this->translator = $translator;
|
||||
$this->rah = $rah;
|
||||
$this->country_code = $country_code;
|
||||
$this->wh = $wh;
|
||||
|
||||
$this->loadTemplates();
|
||||
}
|
||||
|
|
@ -824,6 +820,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
|
|||
// call rider assignment handler's fulfillJobOrder
|
||||
$this->rah->fulfillJobOrder($obj, $image_url, $rider);
|
||||
|
||||
/*
|
||||
// create the warranty if new battery only
|
||||
if ($this->checkIfNewBattery($obj))
|
||||
{
|
||||
|
|
@ -862,7 +859,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
|
|||
|
||||
$this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class);
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ use App\Service\RiderAPIHandlerInterface;
|
|||
use App\Service\RedisClientProvider;
|
||||
use App\Service\RiderCache;
|
||||
use App\Service\MQTTClient;
|
||||
use App\Service\WarrantyHandler;
|
||||
use App\Service\JobOrderHandlerInterface;
|
||||
use App\Service\InvoiceGeneratorInterface;
|
||||
|
||||
|
|
@ -30,7 +29,6 @@ use App\Entity\Promo;
|
|||
use App\Entity\Battery;
|
||||
use App\Entity\BatteryModel;
|
||||
use App\Entity\BatterySize;
|
||||
use App\Entity\Warranty;
|
||||
|
||||
use DateTime;
|
||||
|
||||
|
|
@ -42,7 +40,6 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
protected $rcache;
|
||||
protected $country_code;
|
||||
protected $mclient;
|
||||
protected $wh;
|
||||
protected $jo_handler;
|
||||
protected $ic;
|
||||
protected $session;
|
||||
|
|
@ -50,8 +47,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis,
|
||||
EncoderFactoryInterface $ef, RiderCache $rcache,
|
||||
string $country_code, MQTTClient $mclient,
|
||||
WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler,
|
||||
InvoiceGeneratorInterface $ic)
|
||||
JobOrderHandlerInterface $jo_handler, InvoiceGeneratorInterface $ic)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->redis = $redis;
|
||||
|
|
@ -59,7 +55,6 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
$this->rcache = $rcache;
|
||||
$this->country_code = $country_code;
|
||||
$this->mclient = $mclient;
|
||||
$this->wh = $wh;
|
||||
$this->jo_handler = $jo_handler;
|
||||
$this->ic = $ic;
|
||||
|
||||
|
|
@ -523,6 +518,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
|
||||
$this->em->flush();
|
||||
|
||||
/*
|
||||
// create warranty
|
||||
if($this->jo_handler->checkIfNewBattery($jo))
|
||||
{
|
||||
|
|
@ -561,7 +557,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
|
||||
$this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
// send mqtt event (fulfilled)
|
||||
$rider = $this->session->getRider();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ use App\Service\RiderAPIHandlerInterface;
|
|||
use App\Service\RedisClientProvider;
|
||||
use App\Service\RiderCache;
|
||||
use App\Service\MQTTClient;
|
||||
use App\Service\WarrantyHandler;
|
||||
use App\Service\JobOrderHandlerInterface;
|
||||
use App\Service\InvoiceGeneratorInterface;
|
||||
|
||||
|
|
@ -41,7 +40,6 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
protected $rcache;
|
||||
protected $country_code;
|
||||
protected $mclient;
|
||||
protected $wh;
|
||||
protected $jo_handler;
|
||||
protected $ic;
|
||||
protected $session;
|
||||
|
|
@ -49,8 +47,7 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis,
|
||||
EncoderFactoryInterface $ef, RiderCache $rcache,
|
||||
string $country_code, MQTTClient $mclient,
|
||||
WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler,
|
||||
InvoiceGeneratorInterface $ic)
|
||||
JobOrderHandlerInterface $jo_handler, InvoiceGeneratorInterface $ic)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->redis = $redis;
|
||||
|
|
@ -58,7 +55,6 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
$this->rcache = $rcache;
|
||||
$this->country_code = $country_code;
|
||||
$this->mclient = $mclient;
|
||||
$this->wh = $wh;
|
||||
$this->jo_handler = $jo_handler;
|
||||
$this->ic = $ic;
|
||||
|
||||
|
|
@ -525,6 +521,7 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
|
||||
$this->em->flush();
|
||||
|
||||
/*
|
||||
// create warranty
|
||||
if($this->jo_handler->checkIfNewBattery($jo))
|
||||
{
|
||||
|
|
@ -559,7 +556,7 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
}
|
||||
|
||||
$this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class);
|
||||
}
|
||||
} */
|
||||
|
||||
// send mqtt event (fulfilled)
|
||||
$rider = $this->session->getRider();
|
||||
|
|
|
|||
|
|
@ -110,6 +110,9 @@
|
|||
"guzzlehttp/psr7": {
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"influxdb/influxdb-php": {
|
||||
"version": "1.15.0"
|
||||
},
|
||||
"jdorn/sql-formatter": {
|
||||
"version": "v1.2.17"
|
||||
},
|
||||
|
|
|
|||
79
templates/user/audit.log.html.twig
Normal file
79
templates/user/audit.log.html.twig
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<!-- BEGIN: Subheader -->
|
||||
<div class="m-subheader">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="mr-auto">
|
||||
<h3 class="m-subheader__title">Audit Logs</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Subheader -->
|
||||
<div class="m-content">
|
||||
<!--Begin::Section-->
|
||||
<div class="row">
|
||||
<div class="col-xl-12">
|
||||
<div class="m-portlet m-portlet--mobile">
|
||||
<div class="m-portlet__body">
|
||||
<!--begin: Datatable -->
|
||||
<div id="data-rows"></div>
|
||||
<!--end: Datatable -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(function() {
|
||||
console.log( {{ id }} );
|
||||
var options = {
|
||||
data: {
|
||||
type: 'remote',
|
||||
source: {
|
||||
read: {
|
||||
url: '{{ url('user_view_logs', {'id': id}) }}',
|
||||
method: 'POST'
|
||||
}
|
||||
},
|
||||
saveState: {
|
||||
cookie: false,
|
||||
webstorage: false
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'time',
|
||||
title: 'Time'
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
title: 'Action'
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
title: 'Content'
|
||||
},
|
||||
{
|
||||
field: 'entity_id',
|
||||
title: 'Entity ID'
|
||||
},
|
||||
{
|
||||
field: 'entity_type',
|
||||
title: 'Entity Type'
|
||||
},
|
||||
{
|
||||
field: 'user',
|
||||
title: 'User'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var table = $("#data-rows").mDatatable(options);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
79
templates/user/change.history.log.html.twig
Normal file
79
templates/user/change.history.log.html.twig
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<!-- BEGIN: Subheader -->
|
||||
<div class="m-subheader">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="mr-auto">
|
||||
<h3 class="m-subheader__title">Audit Logs</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Subheader -->
|
||||
<div class="m-content">
|
||||
<!--Begin::Section-->
|
||||
<div class="row">
|
||||
<div class="col-xl-12">
|
||||
<div class="m-portlet m-portlet--mobile">
|
||||
<div class="m-portlet__body">
|
||||
<!--begin: Datatable -->
|
||||
<div id="data-rows"></div>
|
||||
<!--end: Datatable -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(function() {
|
||||
console.log( {{ id }} );
|
||||
var options = {
|
||||
data: {
|
||||
type: 'remote',
|
||||
source: {
|
||||
read: {
|
||||
url: '{{ url('user_view_change_history', {'id': id}) }}',
|
||||
method: 'POST'
|
||||
}
|
||||
},
|
||||
saveState: {
|
||||
cookie: false,
|
||||
webstorage: false
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'time',
|
||||
title: 'Time'
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
title: 'Action'
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
title: 'Content'
|
||||
},
|
||||
{
|
||||
field: 'entity_id',
|
||||
title: 'Entity ID'
|
||||
},
|
||||
{
|
||||
field: 'entity_type',
|
||||
title: 'Entity Type'
|
||||
},
|
||||
{
|
||||
field: 'user',
|
||||
title: 'User'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var table = $("#data-rows").mDatatable(options);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -57,6 +57,16 @@
|
|||
<div class="form-control-feedback hide" data-field="username"></div>
|
||||
<span class="m-form__help">Unique alias for this user</span>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
{% if mode == 'update' %}
|
||||
<a href="{{ url('user_view_logs_form', {'id':obj.getID()}) }}" class="btn btn-success">View Audit Logs</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
{% if mode == 'update' %}
|
||||
<a href="{{ url('user_view_change_history_form', {'id':obj.getID()}) }}" class="btn btn-success">View Change History</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group m-form__group row">
|
||||
<div class="col-lg-6">
|
||||
|
|
@ -182,72 +192,73 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(function() {
|
||||
$("#row-form").submit(function(e) {
|
||||
var form = $(this);
|
||||
<script>
|
||||
$(function() {
|
||||
$("#row-form").submit(function(e) {
|
||||
var form = $(this);
|
||||
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: form.prop('action'),
|
||||
data: form.serialize()
|
||||
}).done(function(response) {
|
||||
// remove all error classes
|
||||
removeErrors();
|
||||
swal({
|
||||
title: 'Done!',
|
||||
text: 'Your changes have been saved!',
|
||||
type: 'success',
|
||||
onClose: function() {
|
||||
window.location.href = "{{ url(mode == 'profile' ? 'home' : 'user_list') }}";
|
||||
}
|
||||
});
|
||||
}).fail(function(response) {
|
||||
if (response.status == 422) {
|
||||
var errors = response.responseJSON.errors;
|
||||
var firstfield = false;
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: form.prop('action'),
|
||||
data: form.serialize()
|
||||
}).done(function(response) {
|
||||
// remove all error classes
|
||||
removeErrors();
|
||||
swal({
|
||||
title: 'Done!',
|
||||
text: 'Your changes have been saved!',
|
||||
type: 'success',
|
||||
onClose: function() {
|
||||
window.location.href = "{{ url(mode == 'profile' ? 'home' : 'user_list') }}";
|
||||
}
|
||||
});
|
||||
}).fail(function(response) {
|
||||
if (response.status == 422) {
|
||||
var errors = response.responseJSON.errors;
|
||||
var firstfield = false;
|
||||
|
||||
// remove all error classes first
|
||||
removeErrors();
|
||||
// remove all error classes first
|
||||
removeErrors();
|
||||
|
||||
// display errors contextually
|
||||
$.each(errors, function(field, msg) {
|
||||
var formfield = $("[name='" + field + "']");
|
||||
var label = $("label[data-field='" + field + "']");
|
||||
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
|
||||
// display errors contextually
|
||||
$.each(errors, function(field, msg) {
|
||||
var formfield = $("[name='" + field + "']");
|
||||
var label = $("label[data-field='" + field + "']");
|
||||
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
|
||||
|
||||
// add error classes to bad fields
|
||||
formfield.addClass('form-control-danger');
|
||||
label.addClass('has-danger');
|
||||
msgbox.html(msg).addClass('has-danger').removeClass('hide');
|
||||
// add error classes to bad fields
|
||||
formfield.addClass('form-control-danger');
|
||||
label.addClass('has-danger');
|
||||
msgbox.html(msg).addClass('has-danger').removeClass('hide');
|
||||
|
||||
// check if this field comes first in DOM
|
||||
var domfield = formfield.get(0);
|
||||
// check if this field comes first in DOM
|
||||
var domfield = formfield.get(0);
|
||||
|
||||
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
|
||||
firstfield = domfield;
|
||||
}
|
||||
});
|
||||
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
|
||||
firstfield = domfield;
|
||||
}
|
||||
});
|
||||
|
||||
// focus on first bad field
|
||||
firstfield.focus();
|
||||
// focus on first bad field
|
||||
firstfield.focus();
|
||||
|
||||
// scroll to above that field to make it visible
|
||||
$('html, body').animate({
|
||||
scrollTop: $(firstfield).offset().top - 200
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
// scroll to above that field to make it visible
|
||||
$('html, body').animate({
|
||||
scrollTop: $(firstfield).offset().top - 200
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// remove all error classes
|
||||
function removeErrors() {
|
||||
$(".form-control-danger").removeClass('form-control-danger');
|
||||
$("[data-field]").removeClass('has-danger');
|
||||
$(".form-control-feedback[data-field]").addClass('hide');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
// remove all error classes
|
||||
function removeErrors() {
|
||||
$(".form-control-danger").removeClass('form-control-danger');
|
||||
$("[data-field]").removeClass('has-danger');
|
||||
$(".form-control-feedback[data-field]").addClass('hide');
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue