diff --git a/src/Controller/AddressTypesController.php b/src/Controller/AddressTypesController.php
index 92bfad6c8..348a8e9e6 100644
--- a/src/Controller/AddressTypesController.php
+++ b/src/Controller/AddressTypesController.php
@@ -2,6 +2,7 @@
namespace App\Controller;
use App\Controller\AppController;
+use Cake\I18n\Number;
/**
* AddressTypes Controller
diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php
index 9c43e6d69..de477a416 100644
--- a/src/Controller/AppController.php
+++ b/src/Controller/AppController.php
@@ -15,7 +15,8 @@
namespace App\Controller;
use Cake\Controller\Controller;
-use Cake\Event\Event;
+//use Cake\Event\Event;
+use Cake\ORM\TableRegistry;
/**
* Application Controller
@@ -75,6 +76,25 @@ class AppController extends Controller
* see https://book.cakephp.org/3.0/en/controllers/components/security.html
*/
//$this->loadComponent('Security');
+
+
+ // load current balance
+ $session = $this->getRequest()->getSession();
+ $state_user_id = $session->read('StateUser.id');
+ if($state_user_id) {
+ $stateBalancesTable = TableRegistry::getTableLocator()->get('stateBalances');
+ $stateBalanceEntry = $stateBalancesTable
+ ->find('all')
+ ->select('amount')
+ ->contain(false)
+ ->where(['state_user_id' => $state_user_id]);
+ if($stateBalanceEntry->count() == 1) {
+ //var_dump($stateBalanceEntry->first());
+ $session->write('StateUser.balance', $stateBalanceEntry->first()->amount);
+ //echo "stateUser.balance: " . $session->read('StateUser.balance');
+ }
+ }
+ //echo "initialize";
}
/*
public function beforeFilter(Event $event)
diff --git a/src/Controller/Component/GradidoNumberComponent.php b/src/Controller/Component/GradidoNumberComponent.php
new file mode 100644
index 000000000..417b1012a
--- /dev/null
+++ b/src/Controller/Component/GradidoNumberComponent.php
@@ -0,0 +1,33 @@
+Auth->allow(['add', 'edit']);
- $this->Auth->allow('index');
+ $this->Auth->allow(['index', 'errorHttpRequest']);
}
/**
* Index method
@@ -58,8 +58,11 @@ class DashboardController extends AppController
$loginServer = Configure::read('LoginServer');
$url = $loginServer['host'] . ':' . $loginServer['port'];
//$url = 'http://***REMOVED***';
+ $requestStart = microtime(true);
$response = $http->get($url . '/login', ['session_id' => $session_id]);
$json = $response->getJson();
+ $requestEnd = microtime(true);
+
if(isset($json) && count($json) > 0) {
@@ -70,13 +73,18 @@ class DashboardController extends AppController
if($key === 'state') { continue; }
$session->write('StateUser.' . $key, $value );
}
+
$transactionPendings = $json['Transaction.pending'];
- $session->write('Transaction.pending', $transactionPendings);
+ //echo "read transaction pending: $transactionPendings
";
+ $session->write('Transactions.pending', $transactionPendings);
$session->write('session_id', $session_id);
$stateUserTable = TableRegistry::getTableLocator()->get('StateUsers');
if($json['user']['public_hex'] != '') {
$public_key_bin = hex2bin($json['user']['public_hex']);
- $stateUserQuery = $stateUserTable->find('all')->where(['public_key' => $public_key_bin]);
+ $stateUserQuery = $stateUserTable
+ ->find('all')
+ ->where(['public_key' => $public_key_bin])
+ ->contain(['StateBalances']);
if($stateUserQuery->count() == 1) {
$stateUser = $stateUserQuery->first();
if($stateUser->first_name != $json['user']['first_name'] ||
@@ -87,7 +95,11 @@ class DashboardController extends AppController
$this->Flash->error(__('error updating state user ' . json_encode($stateUser->errors())));
}
}
- $session->write('StateUser.id', $stateUser['id']);
+ //var_dump($stateUser);
+ if(count($stateUser->state_balances) > 0) {
+ $session->write('StateUser.balance', $stateUser->state_balances[0]->amount);
+ }
+ $session->write('StateUser.id', $stateUser->id);
//echo $stateUser['id'];
} else {
$newStateUser = $stateUserTable->newEntity();
@@ -109,6 +121,7 @@ class DashboardController extends AppController
$this->set('user', $json['user']);
//$this->set('json', $json);
$this->set('timeUsed', microtime(true) - $startTime);
+ $this->set('requestTime', $requestEnd - $requestStart);
} else {
if($json['state'] === 'not found' ) {
@@ -124,7 +137,7 @@ class DashboardController extends AppController
} catch(\Exception $e) {
$msg = $e->getMessage();
$this->Flash->error(__('error http request: ') . $msg);
-
+ return $this->redirect(['controller' => 'Dashboard', 'action' => 'errorHttpRequest']);
//continue;
}
} else {
@@ -139,6 +152,13 @@ class DashboardController extends AppController
return $this->redirect(Router::url('/', true) . 'account/', 303);
}
}
+
+ public function errorHttpRequest()
+ {
+ $startTime = microtime(true);
+ $this->viewBuilder()->setLayout('frontend');
+ $this->set('timeUsed', microtime(true) - $startTime);
+ }
}
diff --git a/src/Controller/StateBalancesController.php b/src/Controller/StateBalancesController.php
index 20580fcf4..ae0f682e3 100644
--- a/src/Controller/StateBalancesController.php
+++ b/src/Controller/StateBalancesController.php
@@ -12,6 +12,13 @@ use App\Controller\AppController;
*/
class StateBalancesController extends AppController
{
+
+ public function initialize()
+ {
+ parent::initialize();
+ //$this->Auth->allow(['add', 'edit']);
+ $this->Auth->allow(['overview']);
+ }
/**
* Index method
*
@@ -26,6 +33,11 @@ class StateBalancesController extends AppController
$this->set(compact('stateBalances'));
}
+
+ public function overview()
+ {
+ $this->viewBuilder()->setLayout('frontend');
+ }
/**
* View method
diff --git a/src/Controller/StateErrorsController.php b/src/Controller/StateErrorsController.php
new file mode 100644
index 000000000..98d4dc531
--- /dev/null
+++ b/src/Controller/StateErrorsController.php
@@ -0,0 +1,113 @@
+paginate = [
+ 'contain' => ['StateUsers', 'TransactionTypes']
+ ];
+ $stateErrors = $this->paginate($this->StateErrors);
+
+ $this->set(compact('stateErrors'));
+ }
+
+ /**
+ * View method
+ *
+ * @param string|null $id State Error id.
+ * @return \Cake\Http\Response|null
+ * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
+ */
+ public function view($id = null)
+ {
+ $stateError = $this->StateErrors->get($id, [
+ 'contain' => ['StateUsers', 'TransactionTypes']
+ ]);
+
+ $this->set('stateError', $stateError);
+ }
+
+ /**
+ * Add method
+ *
+ * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise.
+ */
+ public function add()
+ {
+ $stateError = $this->StateErrors->newEntity();
+ if ($this->request->is('post')) {
+ $stateError = $this->StateErrors->patchEntity($stateError, $this->request->getData());
+ if ($this->StateErrors->save($stateError)) {
+ $this->Flash->success(__('The state error has been saved.'));
+
+ return $this->redirect(['action' => 'index']);
+ }
+ $this->Flash->error(__('The state error could not be saved. Please, try again.'));
+ }
+ $stateUsers = $this->StateErrors->StateUsers->find('list', ['limit' => 200]);
+ $transactionTypes = $this->StateErrors->TransactionTypes->find('list', ['limit' => 200]);
+ $this->set(compact('stateError', 'stateUsers', 'transactionTypes'));
+ }
+
+ /**
+ * Edit method
+ *
+ * @param string|null $id State Error id.
+ * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise.
+ * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
+ */
+ public function edit($id = null)
+ {
+ $stateError = $this->StateErrors->get($id, [
+ 'contain' => []
+ ]);
+ if ($this->request->is(['patch', 'post', 'put'])) {
+ $stateError = $this->StateErrors->patchEntity($stateError, $this->request->getData());
+ if ($this->StateErrors->save($stateError)) {
+ $this->Flash->success(__('The state error has been saved.'));
+
+ return $this->redirect(['action' => 'index']);
+ }
+ $this->Flash->error(__('The state error could not be saved. Please, try again.'));
+ }
+ $stateUsers = $this->StateErrors->StateUsers->find('list', ['limit' => 200]);
+ $transactionTypes = $this->StateErrors->TransactionTypes->find('list', ['limit' => 200]);
+ $this->set(compact('stateError', 'stateUsers', 'transactionTypes'));
+ }
+
+ /**
+ * Delete method
+ *
+ * @param string|null $id State Error id.
+ * @return \Cake\Http\Response|null Redirects to index.
+ * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
+ */
+ public function delete($id = null)
+ {
+ $this->request->allowMethod(['post', 'delete']);
+ $stateError = $this->StateErrors->get($id);
+ if ($this->StateErrors->delete($stateError)) {
+ $this->Flash->success(__('The state error has been deleted.'));
+ } else {
+ $this->Flash->error(__('The state error could not be deleted. Please, try again.'));
+ }
+
+ return $this->redirect(['action' => 'index']);
+ }
+}
diff --git a/src/Controller/TransactionCreationsController.php b/src/Controller/TransactionCreationsController.php
index 245e3eb21..62b428ae4 100644
--- a/src/Controller/TransactionCreationsController.php
+++ b/src/Controller/TransactionCreationsController.php
@@ -4,7 +4,7 @@ namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
use Cake\Routing\Router;
-use Cake\I18n\Number;
+//use Cake\I18n\Number;
use Cake\Http\Client;
use Cake\Core\Configure;
@@ -27,6 +27,7 @@ class TransactionCreationsController extends AppController
public function initialize()
{
parent::initialize();
+ $this->loadComponent('GradidoNumber');
//$this->Auth->allow(['add', 'edit']);
$this->Auth->allow('create');
}
@@ -99,12 +100,9 @@ class TransactionCreationsController extends AppController
$pubKeyHex = '';
$receiver = new ReceiverAmount();
- //echo 'amount: ' . $requestData['amount'] . '
';
- $floatAmount = floatval(Number::format($requestData['amount'], ['places' => 4, 'locale' => 'en_GB']));
- //echo 'set for receiver: ' . round($floatAmount * 10000) . '
';
- $receiver->setAmount(round($floatAmount * 10000));
- //echo 'after receiver amount set
';
-
+
+ $receiver->setAmount($this->GradidoNumber->parseInputNumberToCentNumber($requestData['amount']));
+
if(intval($requestData['receiver']) == 0) {
if(strlen($requestData['receiver_pubkey_hex']) != 64) {
$this->Flash->error(__('Invalid public Key, must contain 64 Character'));
@@ -119,7 +117,9 @@ class TransactionCreationsController extends AppController
}
}
if($pubKeyHex != '') {
- $receiver->setEd25519ReceiverPubkey(hex2bin($pubKeyHex));
+ $pubKeyBin = hex2bin($pubKeyHex);
+
+ $receiver->setEd25519ReceiverPubkey($pubKeyBin);
//var_dump($requestData);
$creationDate = new TimestampSeconds();
diff --git a/src/Controller/TransactionJsonRequestHandlerController.php b/src/Controller/TransactionJsonRequestHandlerController.php
new file mode 100644
index 000000000..eb0573f31
--- /dev/null
+++ b/src/Controller/TransactionJsonRequestHandlerController.php
@@ -0,0 +1,68 @@
+Auth->allow(['add', 'edit']);
+ $this->Auth->allow('index');
+ }
+
+
+ public function index()
+ {
+ if($this->request->is('post')) {
+ $jsonData = $this->request->input('json_decode');
+ //var_dump($jsonData);
+ if($jsonData == NULL || !isset($jsonData->method) || !isset($jsonData->transaction)) {
+ return $this->returnJson(['state' => 'error', 'msg' => 'parameter error']);
+ }
+ $method = $jsonData->method;
+ switch($method) {
+ case 'putTransaction': return $this->putTransaction($jsonData->transaction);
+ }
+ return $this->returnJson(['state' => 'error', 'msg' => 'unknown method', 'details' => $method]);
+ }
+ return $this->returnJson(['state' => 'error', 'msg' => 'no post']);
+ }
+
+ private function putTransaction($transactionBase64) {
+ $transaction = new Transaction($transactionBase64);
+ if($transaction->hasErrors()) {
+ return $this->returnJson(['state' => 'error', 'msg' => 'error parsing transaction', 'details' => $transaction->getErrors()]);
+ }
+ if(!$transaction->validate()) {
+ return $this->returnJson(['state' => 'error', 'msg' => 'error validate transaction', 'details' => $transaction->getErrors()]);
+ }
+
+ if ($transaction->save()) {
+ // success
+ return $this->returnJson(['state' => 'success']);
+ } else {
+ return $this->returnJson([
+ 'state' => 'error',
+ 'msg' => 'error saving transaction in db',
+ 'details' => json_encode($transaction->getErrors())
+ ]);
+ }
+
+ return $this->returnJson(['state' => 'success']);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/Form/CreationForm.php b/src/Form/CreationForm.php
index 1e59088ea..46363de27 100644
--- a/src/Form/CreationForm.php
+++ b/src/Form/CreationForm.php
@@ -35,7 +35,12 @@ class CreationForm extends Form
'rule' => ['maxLength', 150],
'message' => 'max 150 character'
])
- ->ascii('memo', __('Only Ascii Character allowed'))
+ //->alphaNumeric('memo', __('Only Alpha Numeric Character allowed'))
+ ->add('memo', 'custom', [
+ 'rule' => 'alphaNumeric',
+ 'provider' => 'custom',
+ 'message' => __('Only Alpha Numeric Character allowed')
+ ])
->allowEmptyString('memo', null, 'create')
/*->add('receiver_pubkey_hex', 'custom', [
'rule' => 'hexKey64',
diff --git a/src/Model/Entity/StateError.php b/src/Model/Entity/StateError.php
new file mode 100644
index 000000000..448e9ccb3
--- /dev/null
+++ b/src/Model/Entity/StateError.php
@@ -0,0 +1,37 @@
+ true,
+ 'transaction_type_id' => true,
+ 'created' => true,
+ 'message_json' => true,
+ 'state_user' => true,
+ 'transaction_type' => true
+ ];
+}
diff --git a/src/Model/Table/StateErrorsTable.php b/src/Model/Table/StateErrorsTable.php
new file mode 100644
index 000000000..62893b7bf
--- /dev/null
+++ b/src/Model/Table/StateErrorsTable.php
@@ -0,0 +1,88 @@
+setTable('state_errors');
+ $this->setDisplayField('id');
+ $this->setPrimaryKey('id');
+
+ $this->addBehavior('Timestamp');
+
+ $this->belongsTo('StateUsers', [
+ 'foreignKey' => 'state_user_id',
+ 'joinType' => 'INNER'
+ ]);
+ $this->belongsTo('TransactionTypes', [
+ 'foreignKey' => 'transaction_type_id',
+ 'joinType' => 'INNER'
+ ]);
+ }
+
+ /**
+ * Default validation rules.
+ *
+ * @param \Cake\Validation\Validator $validator Validator instance.
+ * @return \Cake\Validation\Validator
+ */
+ public function validationDefault(Validator $validator)
+ {
+ $validator
+ ->integer('id')
+ ->allowEmptyString('id', null, 'create');
+
+ $validator
+ ->scalar('message_json')
+ ->requirePresence('message_json', 'create')
+ ->notEmptyString('message_json');
+
+ return $validator;
+ }
+
+ /**
+ * Returns a rules checker object that will be used for validating
+ * application integrity.
+ *
+ * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
+ * @return \Cake\ORM\RulesChecker
+ */
+ public function buildRules(RulesChecker $rules)
+ {
+ $rules->add($rules->existsIn(['state_user_id'], 'StateUsers'));
+ $rules->add($rules->existsIn(['transaction_type_id'], 'TransactionTypes'));
+
+ return $rules;
+ }
+}
diff --git a/src/Model/Transactions/Transaction.php b/src/Model/Transactions/Transaction.php
index 1a16516cd..1ae38ffbc 100644
--- a/src/Model/Transactions/Transaction.php
+++ b/src/Model/Transactions/Transaction.php
@@ -19,11 +19,19 @@ class Transaction extends TransactionBase {
private $mTransactionBody = null;
public function __construct($base64Data) {
- $transactionBin = base64_decode($base64Data, true);
-
+ //$transactionBin = base64_decode($base64Data, true);
+ //if($transactionBin == false)
+ //sodium_base64_VARIANT_URLSAFE_NO_PADDING
+ try {
+ $transactionBin = sodium_base642bin($base64Data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
+ } catch(\SodiumException $e) {
+ $this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data);
+ return;
+ }
+ //*/
if($transactionBin == false) {
//$this->addError('base64 decode failed');
- $this->addError('Transaction', 'base64 decode error');
+ $this->addError('Transaction', 'base64 decode error: ' . $base64Data);
} else {
$this->mProtoTransaction = new \Model\Messages\Gradido\Transaction();
try {
@@ -52,7 +60,14 @@ class Transaction extends TransactionBase {
}
public function validate() {
- $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair();
+ $sigMap = $this->mProtoTransaction->getSigMap();
+ if(!$sigMap) {
+ $this->addError('Transaction', 'signature map is zero');
+ return false;
+ }
+ //var_dump($sigMap);
+ //die();
+ $sigPairs = $sigMap->getSigPair();
$bodyBytes = $this->mProtoTransaction->getBodyBytes();
@@ -98,7 +113,7 @@ class Transaction extends TransactionBase {
//signature pubkey
$sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair();
- echo "sigPairs: "; var_dump($sigPairs);
+ //echo "sigPairs: "; var_dump($sigPairs);
$signatureEntitys = [];
foreach($sigPairs as $sigPair) {
$signatureEntity = $transactionsSignaturesTable->newEntity();
diff --git a/src/Model/Transactions/TransactionBase.php b/src/Model/Transactions/TransactionBase.php
index 2eb0f6dc7..4b1ba210e 100644
--- a/src/Model/Transactions/TransactionBase.php
+++ b/src/Model/Transactions/TransactionBase.php
@@ -40,4 +40,30 @@ class TransactionBase {
return NULL;
}
+
+ protected function updateStateBalance($stateUserId, $newAmountCent) {
+ $stateBalancesTable = TableRegistry::getTableLocator()->get('stateBalances');
+ $stateBalanceQuery = $stateBalancesTable
+ ->find('all')
+ ->select(['amount'])
+ ->contain(false)
+ ->where(['state_user_id' => $stateUserId]);//->first();
+ //debug($stateBalanceQuery);
+
+ if($stateBalanceQuery->count() > 0) {
+ $stateBalanceEntry = $stateBalanceEntry->first();
+ $stateBalanceEntry->amount += $newAmountCent;
+ } else {
+ $stateBalanceEntry = $stateBalancesTable->newEntity();
+ $stateBalanceEntry->state_user_id = $stateUserId;
+ $stateBalanceEntry->amount = $newAmountCent;
+ }
+
+ if(!$stateBalancesTable->save($stateBalanceEntry)) {
+ $errors = $stateBalanceEntry->getErrors();
+ $this->addError('TransactionBase::updateStateBalance', 'error saving state balance with: ' . json_encode($errors));
+ return false;
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/src/Model/Transactions/TransactionBody.php b/src/Model/Transactions/TransactionBody.php
index 98120b28d..aeea7aff9 100644
--- a/src/Model/Transactions/TransactionBody.php
+++ b/src/Model/Transactions/TransactionBody.php
@@ -11,7 +11,15 @@ class TransactionBody extends TransactionBase {
public function __construct($bodyBytes) {
$this->mProtoTransactionBody = new \Model\Messages\Gradido\TransactionBody();
- $this->mProtoTransactionBody->mergeFromString($bodyBytes);
+ try {
+ $this->mProtoTransactionBody->mergeFromString($bodyBytes);
+ // cannot catch Exception with cakePHP, I don't know why
+ } catch(\Google\Protobuf\Internal\GPBDecodeException $e) {
+ //var_dump($e);
+ $this->addError('TransactionBody', $e->getMessage());
+ return;
+ }
+
switch($this->mProtoTransactionBody->getData()) {
case 'creation' : $this->mSpecificTransaction = new TransactionCreation($this->mProtoTransactionBody->getCreation()); break;
case 'transfer' : $this->mSpecificTransaction = new TransactionTransfer($this->mProtoTransactionBody->getTransfer()); break;
@@ -20,7 +28,7 @@ class TransactionBody extends TransactionBase {
public function validate($sigPairs) {
// check if creation time is in the past
- if($this->mProtoTransactionBody->getCreated() > time()) {
+ if($this->mProtoTransactionBody->getCreated()->getSeconds() > time()) {
$this->addError('TransactionBody::validate', 'Transaction were created in the past!');
return false;
}
diff --git a/src/Model/Transactions/TransactionCreation.php b/src/Model/Transactions/TransactionCreation.php
index 554f9ce00..1538f9366 100644
--- a/src/Model/Transactions/TransactionCreation.php
+++ b/src/Model/Transactions/TransactionCreation.php
@@ -40,22 +40,36 @@ class TransactionCreation extends TransactionBase {
}
// check if creation threshold for this month isn't reached
+
+ //$identHashBin = sprintf("%0d", $this->getIdentHash());
+ // padding with zero in case hash is smaller than 32 bytes, static length binary field in db
+ $identHashBin = pack('a32', $this->getIdentHash());
+
$existingCreations = $this->transactionCreationsTable
->find('all')
->group('ident_hash')
- ->where(['ident_hash' => $this->getIdentHash()]);
+ ->where(['ident_hash' => $identHashBin]);
$existingCreations->select(['amount_sum' => $existingCreations->func()->sum('amount')]);
- debug($existingCreations);
+ $existingCreations->matching('Transactions', function ($q) {
+ return $q->where(['EXTRACT(YEAR_MONTH FROM Transactions.received) LIKE EXTRACT(YEAR_MONTH FROM NOW())']);
+ });
+ //debug($existingCreations);
if($existingCreations->count() > 0) {
- var_dump($existingCreations->toArray());
- }
+ //var_dump($existingCreations->toArray());
+ //echo "amount sum: " . $existingCreations->first()->amount_sum . "\n";
+ if($this->getAmount() + $existingCreations->first()->amount_sum > 10000000) {
+ $this->addError('TransactionCreation::validate', 'Creation more than 1000 gr per Month not allowed');
+ return false;
+ }
+ }
+ //die("\n");
return true;
}
- public function save($transaction_id, $firstPublic) {
+ public function save($transaction_id, $firstPublic)
+ {
$transactionCreationEntity = $this->transactionCreationsTable->newEntity();
-
$transactionCreationEntity->transaction_id = $transaction_id;
// state user id
@@ -72,7 +86,12 @@ class TransactionCreation extends TransactionBase {
$this->addError('TransactionCreation::save', 'error saving transactionCreation with errors: ' . json_encode($transactionCreationEntity->getErrors()));
return false;
}
-
+ $receiverUser = $this->getStateUserId($this->getReceiverPublic());
+ // update state balance
+ if(!$this->updateStateBalance($receiverUser, $this->getAmount())) {
+ return false;
+ }
+
return true;
}
diff --git a/src/Model/Validation/TransactionValidation.php b/src/Model/Validation/TransactionValidation.php
index aba5828a1..a3463cd5b 100644
--- a/src/Model/Validation/TransactionValidation.php
+++ b/src/Model/Validation/TransactionValidation.php
@@ -30,4 +30,11 @@ class TransactionValidation
}
return false;
}
+
+ public static function alphaNumeric($value, array $context) {
+ if(preg_match('/^[a-zA-Z0-9äöüÄÖÜß _-]*$/', $value)) {
+ return true;
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/src/Template/Dashboard/error_http_request.ctp b/src/Template/Dashboard/error_http_request.ctp
new file mode 100644
index 000000000..cdbbec19b
--- /dev/null
+++ b/src/Template/Dashboard/error_http_request.ctp
@@ -0,0 +1,14 @@
+assign('title', __('Error, Please try again'));
+?>
+
+